diff options
Diffstat (limited to 'kalarm/lib')
42 files changed, 6633 insertions, 0 deletions
diff --git a/kalarm/lib/Makefile.am b/kalarm/lib/Makefile.am new file mode 100644 index 000000000..7d68a252b --- /dev/null +++ b/kalarm/lib/Makefile.am @@ -0,0 +1,22 @@ +INCLUDES = -I$(top_srcdir)/kalarm -I$(top_srcdir) $(all_includes) + +noinst_LTLIBRARIES = libkalarm.la + +libkalarm_la_METASOURCES = AUTO + +libkalarm_la_SOURCES = \ + buttongroup.cpp checkbox.cpp colourcombo.cpp colourlist.cpp \ + combobox.cpp dateedit.cpp datetime.cpp label.cpp messagebox.cpp \ + pushbutton.cpp radiobutton.cpp timeedit.cpp timespinbox.cpp \ + timeperiod.cpp shellprocess.cpp slider.cpp spinbox.cpp spinbox2.cpp \ + lineedit.cpp synchtimer.cpp + +noinst_HEADERS = \ + buttongroup.h checkbox.h colourcombo.h colourlist.h \ + combobox.h dateedit.h datetime.h label.h lineedit.h messagebox.h \ + pushbutton.h radiobutton.h timeedit.h timespinbox.h \ + timeperiod.h shellprocess.h slider.h spinbox.h spinbox2.h \ + synchtimer.h spinbox2private.h + +DOXYGEN_REFERENCES=kdecore kdeui libkdepim +include $(top_srcdir)/admin/Doxyfile.am diff --git a/kalarm/lib/buttongroup.cpp b/kalarm/lib/buttongroup.cpp new file mode 100644 index 000000000..b448df48a --- /dev/null +++ b/kalarm/lib/buttongroup.cpp @@ -0,0 +1,70 @@ +/* + * buttongroup.cpp - QButtonGroup with an extra signal and KDE 2 compatibility + * Program: kalarm + * Copyright (c) 2002, 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "kalarm.h" + +#include <qlayout.h> +#include <qbutton.h> +#include <kdialog.h> + +#include "buttongroup.moc" + + +ButtonGroup::ButtonGroup(QWidget* parent, const char* name) + : QButtonGroup(parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +ButtonGroup::ButtonGroup(const QString& title, QWidget* parent, const char* name) + : QButtonGroup(title, parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +ButtonGroup::ButtonGroup(int strips, Qt::Orientation orient, QWidget* parent, const char* name) + : QButtonGroup(strips, orient, parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +ButtonGroup::ButtonGroup(int strips, Qt::Orientation orient, const QString& title, QWidget* parent, const char* name) + : QButtonGroup(strips, orient, title, parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +/****************************************************************************** + * Inserts a button in the group. + * This should really be a virtual method... + */ +int ButtonGroup::insert(QButton* button, int id) +{ + id = QButtonGroup::insert(button, id); + connect(button, SIGNAL(toggled(bool)), SLOT(slotButtonToggled(bool))); + return id; +} + +/****************************************************************************** + * Called when one of the member buttons is toggled. + */ +void ButtonGroup::slotButtonToggled(bool) +{ + emit buttonSet(selectedId()); +} diff --git a/kalarm/lib/buttongroup.h b/kalarm/lib/buttongroup.h new file mode 100644 index 000000000..1d647b420 --- /dev/null +++ b/kalarm/lib/buttongroup.h @@ -0,0 +1,90 @@ +/* + * buttongroup.h - QButtonGroup with an extra signal and Qt 2 compatibility + * Program: kalarm + * Copyright © 2002,2004,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef BUTTONGROUP_H +#define BUTTONGROUP_H + +#include <qbuttongroup.h> + + +/** + * @short A QButtonGroup with signal on new selection, plus Qt 2 compatibility. + * + * The ButtonGroup class provides an enhanced version of the QButtonGroup class. + * + * It emits an additional signal, buttonSet(int), whenever any of its buttons + * changes state, for whatever reason, including programmatic control. (The + * QButtonGroup class only emits signals when buttons are clicked on by the user.) + * The class also provides Qt 2 compatibility. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class ButtonGroup : public QButtonGroup +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit ButtonGroup(QWidget* parent, const char* name = 0); + /** Constructor. + * @param title The title displayed for this button group. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + ButtonGroup(const QString& title, QWidget* parent, const char* name = 0); + /** Constructor. + * @param strips The number of rows or columns of buttons. + * @param orient The orientation (Qt::Horizontal or Qt::Vertical) of the button group. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + ButtonGroup(int strips, Qt::Orientation orient, QWidget* parent, const char* name = 0); + /** Constructor. + * @param strips The number of rows or columns of buttons. + * @param orient The orientation (Qt::Horizontal or Qt::Vertical) of the button group. + * @param title The title displayed for this button group. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + ButtonGroup(int strips, Qt::Orientation orient, const QString& title, QWidget* parent, const char* name = 0); + /** Inserts a button in the group. + * This overrides the insert() method of QButtonGroup, which should really be a virtual method... + * @param button The button to insert. + * @param id The identifier for the button. + * @return The identifier of the inserted button. + */ + int insert(QButton* button, int id = -1); + /** Sets the button with the specified identifier to be on. If this is an exclusive group, + * all other buttons in the group will be set off. The buttonSet() signal is emitted. + * @param id The identifier of the button to set on. + */ + virtual void setButton(int id) { QButtonGroup::setButton(id); emit buttonSet(id); } + private slots: + void slotButtonToggled(bool); + signals: + /** Signal emitted whenever whenever any button in the group changes state, + * for whatever reason. + * @param id The identifier of the button which is now selected. + */ + void buttonSet(int id); +}; + +#endif // BUTTONGROUP_H diff --git a/kalarm/lib/checkbox.cpp b/kalarm/lib/checkbox.cpp new file mode 100644 index 000000000..c600a4950 --- /dev/null +++ b/kalarm/lib/checkbox.cpp @@ -0,0 +1,133 @@ +/* + * checkbox.cpp - check box with read-only option + * Program: kalarm + * Copyright (c) 2002, 2003 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "checkbox.moc" + + +CheckBox::CheckBox(QWidget* parent, const char* name) + : QCheckBox(parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +CheckBox::CheckBox(const QString& text, QWidget* parent, const char* name) + : QCheckBox(text, parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +/****************************************************************************** +* Set the read-only status. If read-only, the checkbox can be toggled by the +* application, but not by the user. +*/ +void CheckBox::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + setFocusPolicy(ro ? QWidget::NoFocus : mFocusPolicy); + if (ro) + clearFocus(); + } +} + +/****************************************************************************** +* Specify a widget to receive focus when the checkbox is clicked on. +*/ +void CheckBox::setFocusWidget(QWidget* w, bool enable) +{ + mFocusWidget = w; + mFocusWidgetEnable = enable; + if (w) + connect(this, SIGNAL(clicked()), SLOT(slotClicked())); + else + disconnect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); +} + +/****************************************************************************** +* Called when the checkbox is clicked. +* If it is now checked, focus is transferred to any specified focus widget. +*/ +void CheckBox::slotClicked() +{ + if (mFocusWidget && isChecked()) + { + if (mFocusWidgetEnable) + mFocusWidget->setEnabled(true); + mFocusWidget->setFocus(); + } +} + +/****************************************************************************** +* Event handlers to intercept events if in read-only mode. +* Any events which could change the checkbox state are discarded. +*/ +void CheckBox::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QCheckBox::mousePressEvent(e); +} + +void CheckBox::mouseReleaseEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QCheckBox::mouseReleaseEvent(e); +} + +void CheckBox::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QCheckBox::mouseMoveEvent(e); +} + +void CheckBox::keyPressEvent(QKeyEvent* e) +{ + if (mReadOnly) + switch (e->key()) + { + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Down: + // Process keys which shift the focus + break; + default: + return; + } + QCheckBox::keyPressEvent(e); +} + +void CheckBox::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QCheckBox::keyReleaseEvent(e); +} diff --git a/kalarm/lib/checkbox.h b/kalarm/lib/checkbox.h new file mode 100644 index 000000000..72ad4aee3 --- /dev/null +++ b/kalarm/lib/checkbox.h @@ -0,0 +1,88 @@ +/* + * checkbox.h - check box with focus widget and read-only options + * Program: kalarm + * Copyright © 2002,2003,2005,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef CHECKBOX_H +#define CHECKBOX_H + +#include <qcheckbox.h> + + +/** + * @short A QCheckBox with focus widget and read-only options. + * + * The CheckBox class is a QCheckBox with the ability to transfer focus to another + * widget when checked, and with a read-only option. + * + * Another widget may be specified as the focus widget for the check box. Whenever + * the user clicks on the check box so as to set its state to checked, focus is + * automatically transferred to the focus widget. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class CheckBox : public QCheckBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit CheckBox(QWidget* parent, const char* name = 0); + /** Constructor. + * @param text Text to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + CheckBox(const QString& text, QWidget* parent, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the check box is read-only for the user. If read-only, + * its state cannot be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns the widget which receives focus when the user selects the check box by clicking on it. */ + QWidget* focusWidget() const { return mFocusWidget; } + /** Specifies a widget to receive focus when the user selects the check box by clicking on it. + * @param widget Widget to receive focus. + * @param enable If true, @p widget will be enabled before receiving focus. If + * false, the enabled state of @p widget will be left unchanged when + * the check box is clicked. + */ + void setFocusWidget(QWidget* widget, bool enable = true); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + protected slots: + void slotClicked(); + private: + QWidget::FocusPolicy mFocusPolicy; // default focus policy for the QCheckBox + QWidget* mFocusWidget; // widget to receive focus when button is clicked on + bool mFocusWidgetEnable; // enable focus widget before setting focus + bool mReadOnly; // value cannot be changed +}; + +#endif // CHECKBOX_H diff --git a/kalarm/lib/colourcombo.cpp b/kalarm/lib/colourcombo.cpp new file mode 100644 index 000000000..d5fa052ac --- /dev/null +++ b/kalarm/lib/colourcombo.cpp @@ -0,0 +1,239 @@ +/* + * colourcombo.cpp - colour selection combo box + * Program: kalarm + * Copyright (c) 2001 - 2003, 2005 by David Jarvie <software@astrojar.org.uk> + * + * Some code taken from kdelibs/kdeui/kcolorcombo.cpp in the KDE libraries: + * Copyright (C) 1997 Martin Jones (mjones@kde.org) + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <qpainter.h> + +#include <klocale.h> +#include <kcolordialog.h> + +#include "preferences.h" +#include "colourcombo.moc" + + +ColourCombo::ColourCombo(QWidget* parent, const char* name, const QColor& defaultColour) + : QComboBox(parent, name), + mColourList(Preferences::messageColours()), + mSelectedColour(defaultColour), + mCustomColour(255, 255, 255), + mReadOnly(false), + mDisabled(false) +{ + addColours(); + connect(this, SIGNAL(activated(int)), SLOT(slotActivated(int))); + connect(this, SIGNAL(highlighted(int)), SLOT(slotHighlighted(int))); + Preferences::connect(SIGNAL(preferencesChanged()), this, SLOT(slotPreferencesChanged())); +} + +void ColourCombo::setColour(const QColor& colour) +{ + mSelectedColour = colour; + addColours(); +} + +/****************************************************************************** +* Set a new colour selection. +*/ +void ColourCombo::setColours(const ColourList& colours) +{ + mColourList = colours; + if (mSelectedColour != mCustomColour + && !mColourList.contains(mSelectedColour)) + { + // The current colour has been deleted + mSelectedColour = mColourList.count() ? mColourList.first() : mCustomColour; + } + addColours(); +} + +/****************************************************************************** +* Called when the user changes the preference settings. +* If the colour list has changed, update the colours displayed. +*/ +void ColourCombo::slotPreferencesChanged() +{ + const ColourList& prefColours = Preferences::messageColours(); + if (prefColours != mColourList) + setColours(prefColours); // update the display with the new colours +} + +/****************************************************************************** +* Enable or disable the control. +* If it is disabled, its colour is set to the dialog background colour. +*/ +void ColourCombo::setEnabled(bool enable) +{ + if (enable && mDisabled) + { + mDisabled = false; + setColour(mSelectedColour); + } + else if (!enable && !mDisabled) + { + mSelectedColour = color(); + int end = count(); + if (end > 1) + { + // Add a dialog background colour item + QPixmap pm = *pixmap(1); + pm.fill(paletteBackgroundColor()); + insertItem(pm); + setCurrentItem(end); + } + mDisabled = true; + } + QComboBox::setEnabled(enable); +} + +void ColourCombo::slotActivated(int index) +{ + if (index) + mSelectedColour = mColourList[index - 1]; + else + { + if (KColorDialog::getColor(mCustomColour, this) == QDialog::Accepted) + { + QRect rect; + drawCustomItem(rect, false); + } + mSelectedColour = mCustomColour; + } + emit activated(mSelectedColour); +} + +void ColourCombo::slotHighlighted(int index) +{ + mSelectedColour = index ? mColourList[index - 1] : mCustomColour; + emit highlighted(mSelectedColour); +} + +/****************************************************************************** +* Initialise the items in the combo box to one for each colour in the list. +*/ +void ColourCombo::addColours() +{ + clear(); + + for (ColourList::const_iterator it = mColourList.begin(); ; ++it) + { + if (it == mColourList.end()) + { + mCustomColour = mSelectedColour; + break; + } + if (mSelectedColour == *it) + break; + } + + QRect rect; + drawCustomItem(rect, true); + + QPainter painter; + QPixmap pixmap(rect.width(), rect.height()); + int i = 1; + for (ColourList::const_iterator it = mColourList.begin(); it != mColourList.end(); ++i, ++it) + { + painter.begin(&pixmap); + QBrush brush(*it); + painter.fillRect(rect, brush); + painter.end(); + + insertItem(pixmap); + pixmap.detach(); + + if (*it == mSelectedColour.rgb()) + setCurrentItem(i); + } +} + +void ColourCombo::drawCustomItem(QRect& rect, bool insert) +{ + QPen pen; + if (qGray(mCustomColour.rgb()) < 128) + pen.setColor(Qt::white); + else + pen.setColor(Qt::black); + + QPainter painter; + QFontMetrics fm = QFontMetrics(painter.font()); + rect.setRect(0, 0, width(), fm.height() + 4); + QPixmap pixmap(rect.width(), rect.height()); + + painter.begin(&pixmap); + QBrush brush(mCustomColour); + painter.fillRect(rect, brush); + painter.setPen(pen); + painter.drawText(2, fm.ascent() + 2, i18n("Custom...")); + painter.end(); + + if (insert) + insertItem(pixmap); + else + changeItem(pixmap, 0); + pixmap.detach(); +} + +void ColourCombo::setReadOnly(bool ro) +{ + mReadOnly = ro; +} + +void ColourCombo::resizeEvent(QResizeEvent* re) +{ + QComboBox::resizeEvent(re); + addColours(); +} + +void ColourCombo::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QComboBox::mousePressEvent(e); +} + +void ColourCombo::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseReleaseEvent(e); +} + +void ColourCombo::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseMoveEvent(e); +} + +void ColourCombo::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + QComboBox::keyPressEvent(e); +} + +void ColourCombo::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QComboBox::keyReleaseEvent(e); +} diff --git a/kalarm/lib/colourcombo.h b/kalarm/lib/colourcombo.h new file mode 100644 index 000000000..f3e4ebca6 --- /dev/null +++ b/kalarm/lib/colourcombo.h @@ -0,0 +1,102 @@ +/* + * colourcombo.h - colour selection combo box + * Program: kalarm + * Copyright © 2001-2003,2005,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef COLOURCOMBO_H +#define COLOURCOMBO_H + +#include <qcombobox.h> +#include "colourlist.h" + + +/** + * @short A colour selection combo box whose colour list can be specified. + * + * The ColourCombo class is a combo box allowing the user to select a colour. + * + * It is similar to KColorCombo but allows the list of colours to be restricted to those + * which are specified. The first item in the list is a custom colour entry, which allows + * the user to define an arbitrary colour. The remaining entries in the list are preset + * by the program. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class ColourCombo : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param defaultColour The colour which is selected by default. + */ + explicit ColourCombo(QWidget* parent = 0, const char* name = 0, const QColor& defaultColour = 0xFFFFFF); + /** Returns the selected colour. */ + QColor color() const { return mSelectedColour; } + /** Returns the selected colour. */ + QColor colour() const { return mSelectedColour; } + /** Sets the selected colour to @p c. */ + void setColor(const QColor& c) { setColour(c); } + /** Sets the selected colour to @p c. */ + void setColour(const QColor& c); + /** Initialises the list of colours to @p list. */ + void setColours(const ColourList& list); + /** Returns true if the first entry in the list, i.e. the custom colour, is selected. */ + bool isCustomColour() const { return !currentItem(); } + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the combo box can be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + signals: + /** Signal emitted when a new colour has been selected. */ + void activated(const QColor&); // a new colour box has been selected + /** Signal emitted when a new colour has been highlighted. */ + void highlighted(const QColor&); // a new item has been highlighted + public slots: + /** Enables or disables the widget. */ + virtual void setEnabled(bool enabled); + protected: + virtual void resizeEvent(QResizeEvent*); + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private slots: + void slotActivated(int index); + void slotHighlighted(int index); + void slotPreferencesChanged(); + private: + void addColours(); + void drawCustomItem(QRect&, bool insert); + + ColourList mColourList; // the sorted colours to display + QColor mSelectedColour; // currently selected colour + QColor mCustomColour; // current colour of the Custom item + bool mReadOnly; // value cannot be changed + bool mDisabled; +}; + +#endif // COLOURCOMBO_H diff --git a/kalarm/lib/colourlist.cpp b/kalarm/lib/colourlist.cpp new file mode 100644 index 000000000..e02a64466 --- /dev/null +++ b/kalarm/lib/colourlist.cpp @@ -0,0 +1,43 @@ +/* + * colourlist.cpp - an ordered list of colours + * Program: kalarm + * Copyright (C) 2003 by David Jarvie software@astrojar.org.uk + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "colourlist.h" + + +ColourList::ColourList(const QColor* colours) +{ + while (colours->isValid()) + mList.append((*colours++).rgb()); +} + +void ColourList::insert(const QColor& colour) +{ + QRgb rgb = colour.rgb(); + for (QValueListIterator<QRgb> it = mList.begin(); it != mList.end(); ++it) + { + if (rgb <= *it) + { + if (rgb != *it) // don't insert duplicates + mList.insert(it, rgb); + return; + } + } + mList.append(rgb); +} diff --git a/kalarm/lib/colourlist.h b/kalarm/lib/colourlist.h new file mode 100644 index 000000000..ef641c04a --- /dev/null +++ b/kalarm/lib/colourlist.h @@ -0,0 +1,110 @@ +/* + * colourlist.h - an ordered list of colours + * Program: kalarm + * Copyright (C) 2003, 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef COLOURLIST_H +#define COLOURLIST_H + +#include <qtl.h> +#include <qcolor.h> +#include <qvaluelist.h> + + +/** + * @short Represents a sorted list of colours. + * + * The ColourList class holds a list of colours, sorted in RGB value order. + * + * It provides a sorted QValueList of colours in RGB value order, with iterators + * and other access methods which return either QRgb or QColor objects. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class ColourList +{ + public: + typedef size_t size_type; + typedef QValueListConstIterator<QRgb> const_iterator; + + /** Constructs an empty list. */ + ColourList() { } + /** Copy constructor. */ + ColourList(const ColourList& l) : mList(l.mList) { } + /** Constructs a list whose values are preset to the colours in @p list. */ + ColourList(const QValueList<QRgb>& list) : mList(list) { qHeapSort(mList); } + /** Constructs a list whose values are preset to the colours in the @p list. + * Terminate @p list by an invalid colour. + */ + ColourList(const QColor* list); + /** Assignment operator. */ + ColourList& operator=(const ColourList& l) { mList = l.mList; return *this; } + /** Sets the list to comprise the colours in @p list. */ + ColourList& operator=(const QValueList<QRgb>& list) { mList = list; qHeapSort(mList); return *this; } + /** Removes all values from the list. */ + void clear() { mList.clear(); } + /** Adds the specified colour @p c to the list. */ + void insert(const QColor& c); + /** Removes the colour @p c from the list. */ + void remove(const QColor& c) { mList.remove(c.rgb()); } + /** Adds the specified colour @p c to the list. */ + ColourList& operator+=(const QColor& c) { insert(c); return *this; } + /** Adds the colours in @p list to this list. */ + ColourList& operator+=(const ColourList& list) { mList += list.mList; qHeapSort(mList); return *this; } + /** Returns true if the colours in the two lists are the same. */ + bool operator==(const ColourList& l) const { return mList == l.mList; } + /** Returns true if the colours in the two lists differ. */ + bool operator!=(const ColourList& l) const { return mList != l.mList; } + /** Returns the number of colours in the list. */ + size_type count() const { return mList.count(); } + /** Returns true if the list is empty. */ + bool isEmpty() const { return mList.isEmpty(); } + /** Returns an iterator pointing to the first colour in the list. */ + const_iterator begin() const { return mList.begin(); } + /** Returns an iterator pointing past the last colour in the list. */ + const_iterator end() const { return mList.end(); } + /** Returns an iterator pointing to the last colour in the list, or end() if the list is empty. */ + const_iterator fromLast() const { return mList.fromLast(); } + /** Returns an iterator pointing to the colour at position @p i in the list. */ + const_iterator at(size_type i) const { return mList.at(i); } + /** Returns true if the list contains the colour @p c. */ + size_type contains(const QColor& c) const { return mList.contains(c.rgb()); } + /** Returns an iterator pointing to the first occurrence of colour @p c in the list. + * Returns end() if colour @p c is not in the list. + */ + const_iterator find(const QColor& c) const { return mList.find(c.rgb()); } + /** Returns an iterator pointing to the first occurrence of colour @p c in the list, starting. + * from position @p it. Returns end() if colour @p c is not in the list. + */ + const_iterator find(const_iterator it, const QColor& c) const { return mList.find(it, c.rgb()); } + /** Returns the index to the first occurrence of colour @p c in the list. + * Returns -1 if colour @p c is not in the list. + */ + int findIndex(const QColor& c) const { return mList.findIndex(c.rgb()); } + /** Returns the first colour in the list. If the list is empty, the result is undefined. */ + QColor first() const { return QColor(mList.first()); } + /** Returns the last colour in the list. If the list is empty, the result is undefined. */ + QColor last() const { return QColor(mList.last()); } + /** Returns the colour at position @p i in the list. If the item does not exist, the result is undefined. */ + QColor operator[](size_type i) const { return QColor(mList[i]); } + private: + void sort(); + QValueList<QRgb> mList; +}; + +#endif // COLOURLIST_H diff --git a/kalarm/lib/combobox.cpp b/kalarm/lib/combobox.cpp new file mode 100644 index 000000000..7e0bea4bd --- /dev/null +++ b/kalarm/lib/combobox.cpp @@ -0,0 +1,78 @@ +/* + * combobox.cpp - combo box with read-only option + * Program: kalarm + * Copyright (c) 2002 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <qlineedit.h> +#include "combobox.moc" + + +ComboBox::ComboBox(QWidget* parent, const char* name) + : QComboBox(parent, name), + mReadOnly(false) +{ } + +ComboBox::ComboBox(bool rw, QWidget* parent, const char* name) + : QComboBox(rw, parent, name), + mReadOnly(false) +{ } + +void ComboBox::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + if (lineEdit()) + lineEdit()->setReadOnly(ro); + } +} + +void ComboBox::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QComboBox::mousePressEvent(e); +} + +void ComboBox::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseReleaseEvent(e); +} + +void ComboBox::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseMoveEvent(e); +} + +void ComboBox::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + QComboBox::keyPressEvent(e); +} + +void ComboBox::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QComboBox::keyReleaseEvent(e); +} diff --git a/kalarm/lib/combobox.h b/kalarm/lib/combobox.h new file mode 100644 index 000000000..d33ac147e --- /dev/null +++ b/kalarm/lib/combobox.h @@ -0,0 +1,69 @@ +/* + * combobox.h - combo box with read-only option + * Program: kalarm + * Copyright © 2002,2005,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef COMBOBOX_H +#define COMBOBOX_H + +#include <qcombobox.h> + + +/** + * @short A QComboBox with read-only option. + * + * The ComboBox class is a QComboBox with a read-only option. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class ComboBox : public QComboBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit ComboBox(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param rw True for a read-write combo box, false for a read-only combo box. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit ComboBox(bool rw, QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the combo box is read-only for the user. If read-only, + * its state cannot be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + bool mReadOnly; // value cannot be changed +}; + +#endif // COMBOBOX_H diff --git a/kalarm/lib/dateedit.cpp b/kalarm/lib/dateedit.cpp new file mode 100644 index 000000000..71553d704 --- /dev/null +++ b/kalarm/lib/dateedit.cpp @@ -0,0 +1,122 @@ +/* + * dateedit.cpp - date entry widget + * Program: kalarm + * Copyright © 2002-2007 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <kglobal.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "dateedit.moc" + + +DateEdit::DateEdit(QWidget* parent, const char* name) + : KDateEdit(parent, name) +{ + connect(this, SIGNAL(dateEntered(const QDate&)), SLOT(newDateEntered(const QDate&))); +} + +void DateEdit::setMinDate(const QDate& d, const QString& errorDate) +{ + mMinDate = d; + if (mMinDate.isValid() && date().isValid() && date() < mMinDate) + setDate(mMinDate); + mMinDateErrString = errorDate; +} + +void DateEdit::setMaxDate(const QDate& d, const QString& errorDate) +{ + mMaxDate = d; + if (mMaxDate.isValid() && date().isValid() && date() > mMaxDate) + setDate(mMaxDate); + mMaxDateErrString = errorDate; +} + +void DateEdit::setInvalid() +{ + setDate(QDate()); +} + +// Check a new date against any minimum or maximum date. +void DateEdit::newDateEntered(const QDate& newDate) +{ + if (newDate.isValid()) + { + if (mMinDate.isValid() && newDate < mMinDate) + { + pastLimitMessage(mMinDate, mMinDateErrString, + i18n("Date cannot be earlier than %1")); + setDate(mMinDate); + } + else if (mMaxDate.isValid() && newDate > mMaxDate) + { + pastLimitMessage(mMaxDate, mMaxDateErrString, + i18n("Date cannot be later than %1")); + setDate(mMaxDate); + } + } +} + +void DateEdit::pastLimitMessage(const QDate& limit, const QString& error, const QString& defaultError) +{ + QString errString = error; + if (errString.isNull()) + { + if (limit == QDate::currentDate()) + errString = i18n("today"); + else + errString = KGlobal::locale()->formatDate(limit, true); + errString = defaultError.arg(errString); + } + KMessageBox::sorry(this, errString); +} + +void DateEdit::mousePressEvent(QMouseEvent *e) +{ + if (isReadOnly()) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + KDateEdit::mousePressEvent(e); +} + +void DateEdit::mouseReleaseEvent(QMouseEvent* e) +{ + if (!isReadOnly()) + KDateEdit::mouseReleaseEvent(e); +} + +void DateEdit::mouseMoveEvent(QMouseEvent* e) +{ + if (!isReadOnly()) + KDateEdit::mouseMoveEvent(e); +} + +void DateEdit::keyPressEvent(QKeyEvent* e) +{ + if (!isReadOnly()) + KDateEdit::keyPressEvent(e); +} + +void DateEdit::keyReleaseEvent(QKeyEvent* e) +{ + if (!isReadOnly()) + KDateEdit::keyReleaseEvent(e); +} diff --git a/kalarm/lib/dateedit.h b/kalarm/lib/dateedit.h new file mode 100644 index 000000000..031a96741 --- /dev/null +++ b/kalarm/lib/dateedit.h @@ -0,0 +1,90 @@ +/* + * dateedit.h - date entry widget + * Program: kalarm + * Copyright © 2002-2007 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DATEEDIT_H +#define DATEEDIT_H + +#include <libkdepim/kdateedit.h> + +/** + * @short Date edit widget with range limits. + * + * The DateEdit class provides a date editor with the ability to set limits on the + * dates which can be entered. + * + * Minimum and/or maximum permissible dates may be set, together with corresponding + * error messages. If the user tries to enter a date outside the allowed range, the + * appropriate error message (if any) is output using KMessageBox::sorry(). + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class DateEdit : public KDateEdit +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit DateEdit(QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget contains a valid date. */ + bool isValid() const { return date().isValid(); } + /** Returns the earliest date which can be entered. + * If there is no minimum date, returns an invalid date. + */ + const QDate& minDate() const { return mMinDate; } + /** Returns the latest date which can be entered. + * If there is no maximum date, returns an invalid date. + */ + const QDate& maxDate() const { return mMaxDate; } + /** Sets the earliest date which can be entered. + * @param date Earliest date allowed. If invalid, any minimum limit is removed. + * @param errorDate Error message to be displayed when a date earlier than + * @p date is entered. Set to QString::null to use the default error message. + */ + void setMinDate(const QDate& date, const QString& errorDate = QString::null); + /** Sets the latest date which can be entered. + * @param date Latest date allowed. If invalid, any maximum limit is removed. + * @param errorDate Error message to be displayed when a date later than + * @p date is entered. Set to QString::null to use the default error message. + */ + void setMaxDate(const QDate& date, const QString& errorDate = QString::null); + /** Sets the date held in the widget to an invalid date. */ + void setInvalid(); + + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + + private slots: + void newDateEntered(const QDate&); + + private: + void pastLimitMessage(const QDate& limit, const QString& error, const QString& defaultError); + + QDate mMinDate; // minimum allowed date, or invalid for no minimum + QDate mMaxDate; // maximum allowed date, or invalid for no maximum + QString mMinDateErrString; // error message when entered date < mMinDate + QString mMaxDateErrString; // error message when entered date > mMaxDate +}; + +#endif // DATEEDIT_H diff --git a/kalarm/lib/datetime.cpp b/kalarm/lib/datetime.cpp new file mode 100644 index 000000000..d7f5ee862 --- /dev/null +++ b/kalarm/lib/datetime.cpp @@ -0,0 +1,80 @@ +/* + * datetime.cpp - date/time representation with optional date-only value + * Program: kalarm + * Copyright (C) 2003, 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "kalarm.h" + +#include <kglobal.h> +#include <klocale.h> + +#include "datetime.h" + +QTime DateTime::mStartOfDay; + +QTime DateTime::time() const +{ + return mDateOnly ? mStartOfDay : mDateTime.time(); +} + +QDateTime DateTime::dateTime() const +{ + return mDateOnly ? QDateTime(mDateTime.date(), mStartOfDay) : mDateTime; +} + +QString DateTime::formatLocale(bool shortFormat) const +{ + if (mDateOnly) + return KGlobal::locale()->formatDate(mDateTime.date(), shortFormat); + else if (mTimeValid) + return KGlobal::locale()->formatDateTime(mDateTime, shortFormat); + else + return QString::null; +} + +bool operator==(const DateTime& dt1, const DateTime& dt2) +{ + if (dt1.mDateTime.date() != dt2.mDateTime.date()) + return false; + if (dt1.mDateOnly && dt2.mDateOnly) + return true; + if (!dt1.mDateOnly && !dt2.mDateOnly) + { + bool valid1 = dt1.mTimeValid && dt1.mDateTime.time().isValid(); + bool valid2 = dt2.mTimeValid && dt2.mDateTime.time().isValid(); + if (!valid1 && !valid2) + return true; + if (!valid1 || !valid2) + return false; + return dt1.mDateTime.time() == dt2.mDateTime.time(); + } + return (dt1.mDateOnly ? dt2.mDateTime.time() : dt1.mDateTime.time()) == DateTime::startOfDay(); +} + +bool operator<(const DateTime& dt1, const DateTime& dt2) +{ + if (dt1.mDateTime.date() != dt2.mDateTime.date()) + return dt1.mDateTime.date() < dt2.mDateTime.date(); + if (dt1.mDateOnly && dt2.mDateOnly) + return false; + if (!dt1.mDateOnly && !dt2.mDateOnly) + return dt1.mDateTime.time() < dt2.mDateTime.time(); + QTime t = DateTime::startOfDay(); + if (dt1.mDateOnly) + return t < dt2.mDateTime.time(); + return dt1.mDateTime.time() < t; +} diff --git a/kalarm/lib/datetime.h b/kalarm/lib/datetime.h new file mode 100644 index 000000000..3b0831918 --- /dev/null +++ b/kalarm/lib/datetime.h @@ -0,0 +1,241 @@ +/* + * datetime.h - date/time representation with optional date-only value + * Program: kalarm + * Copyright © 2003,2005,2007 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef DATETIME_H +#define DATETIME_H + +#include <qdatetime.h> + + +/** + * @short A QDateTime with date-only option. + * + * The DateTime class holds a date, with or without a time. + * + * DateTime is very similar to the QDateTime class, with the additional option to + * hold a date-only value. This allows a single date-time representation to be used + * for both an event having a specific date and time, and an all-day event. + * + * The time assumed for date-only values is the start-of-day time set by setStartOfDay(). + * + * @author David Jarvie <software@astrojar.org.uk> +*/ +class DateTime +{ + public: + /** Default constructor. + * Constructs an invalid date-time. + */ + DateTime() : mDateOnly(false), mTimeValid(false) { } + /** Constructor for a date-only value. */ + DateTime(const QDate& d) : mDateTime(d), mDateOnly(true) { } + /** Constructor for a date-time value. */ + DateTime(const QDate& d, const QTime& t) + : mDateTime(d, t), mDateOnly(false), mTimeValid(true) { } + /** Constructor for a date-time or date-only value. + * @param dt the date and time to use. + * @param dateOnly True to construct a date-only value; false to construct a date-time value. + */ + DateTime(const QDateTime& dt, bool dateOnly = false) + : mDateTime(dt), mDateOnly(dateOnly), mTimeValid(true) + { if (dateOnly) mDateTime.setTime(QTime()); } + /** Assignment operator. + * Sets the value to a specified date-time or date-only value. + */ + DateTime& operator=(const DateTime& dt) + { mDateTime = dt.mDateTime; mDateOnly = dt.mDateOnly; mTimeValid = dt.mTimeValid; return *this; } + /** Assignment operator. + * Sets the value to a specified date-time. + */ + DateTime& operator=(const QDateTime& dt) + { mDateTime = dt; mDateOnly = false; mTimeValid = true; return *this; } + /** Assignment operator. + * Sets the value to a specified date-only value. + */ + DateTime& operator=(const QDate& d) + { mDateTime.setDate(d); mDateTime.setTime(QTime()); mDateOnly = true; return *this; } + /** Returns true if the date is null and, if it is a date-time value, the time is also null. */ + bool isNull() const { return mDateTime.date().isNull() && (mDateOnly || mDateTime.time().isNull()); } + /** Returns true if the date is valid and, if it is a date-time value, the time is also valid. */ + bool isValid() const { return mDateTime.date().isValid() && (mDateOnly || mTimeValid && mDateTime.time().isValid()); } + /** Returns true if it is date-only value. */ + bool isDateOnly() const { return mDateOnly; } + /** Sets the value to be either date-only or date-time. + * @param d True to set the value to be date-only; false to set it to a date-time value. + */ + void setDateOnly(bool d) { if (d) mDateTime.setTime(QTime()); + else if (mDateOnly) mTimeValid = false; + mDateOnly = d; + } + /** Returns the date part of the value. */ + QDate date() const { return mDateTime.date(); } + /** Returns the time part of the value. + * If the value is date-only, the time returned is the start-of-day time set by setStartOfDay(). + */ + QTime time() const; + /** Returns the date and time of the value. + * If the value is date-only, the time part returned is equal to the start-of-day time set + * by setStartOfDay(). + */ + QDateTime dateTime() const; + /** Returns the date and time of the value. + * if the value is date-only, the time part returned is 00:00:00. + */ + QDateTime rawDateTime() const { return mDateTime; } + /** Sets a date-time or date-only value. + * @param dt the date-time to use. + * @param dateOnly True to set a date-only value; false to set a date-time value. + */ + void set(const QDateTime& dt, bool dateOnly = false) + { + mDateTime = dt; + mDateOnly = dateOnly; + if (dateOnly) + mDateTime.setTime(QTime()); + mTimeValid = true; + } + /** Sets a date-time value. */ + void set(const QDate& d, const QTime& t) + { mDateTime.setDate(d); mDateTime.setTime(t); mDateOnly = false; mTimeValid = true; } + /** Sets the time component of the value. + * The value is converted if necessary to be a date-time value. + */ + void setTime(const QTime& t) { mDateTime.setTime(t); mDateOnly = false; mTimeValid = true; } + /** Sets the value to a specified date-time value. + * @param secs The time_t date-time value, expressed as the number of seconds elapsed + * since 1970-01-01 00:00:00 UTC. + */ + void setTime_t(uint secs) { mDateTime.setTime_t(secs); mDateOnly = false; mTimeValid = true; } + /** Returns a DateTime value @p secs seconds later than the value of this object. + * If this object is date-only, @p secs is first rounded down to a whole number of days + * before adding the value. + */ + DateTime addSecs(int n) const + { + if (mDateOnly) + return DateTime(mDateTime.date().addDays(n / (3600*24)), true); + else + return DateTime(mDateTime.addSecs(n), false); + } + /** Returns a DateTime value @p mins minutes later than the value of this object. + * If this object is date-only, @p mins is first rounded down to a whole number of days + * before adding the value. + */ + DateTime addMins(int n) const + { + if (mDateOnly) + return DateTime(mDateTime.addDays(n / (60*24)), true); + else + return DateTime(mDateTime.addSecs(n * 60), false); + } + /** Returns a DateTime value @p n days later than the value of this object. */ + DateTime addDays(int n) const { return DateTime(mDateTime.addDays(n), mDateOnly); } + /** Returns a DateTime value @p n months later than the value of this object. */ + DateTime addMonths(int n) const { return DateTime(mDateTime.addMonths(n), mDateOnly); } + /** Returns a DateTime value @p n years later than the value of this object. */ + DateTime addYears(int n) const { return DateTime(mDateTime.addYears(n), mDateOnly); } + /** Returns the number of days from this date or date-time to @p dt. */ + int daysTo(const DateTime& dt) const + { return (mDateOnly || dt.mDateOnly) ? mDateTime.date().daysTo(dt.date()) : mDateTime.daysTo(dt.mDateTime); } + /** Returns the number of minutes from this date or date-time to @p dt. + * If either of the values is date-only, the result is calculated by simply + * taking the difference in dates and ignoring the times. + */ + int minsTo(const DateTime& dt) const + { return (mDateOnly || dt.mDateOnly) ? mDateTime.date().daysTo(dt.date()) * 24*60 : mDateTime.secsTo(dt.mDateTime) / 60; } + /** Returns the number of seconds from this date or date-time to @p dt. + * If either of the values is date-only, the result is calculated by simply + * taking the difference in dates and ignoring the times. + */ + int secsTo(const DateTime& dt) const + { return (mDateOnly || dt.mDateOnly) ? mDateTime.date().daysTo(dt.date()) * 24*3600 : mDateTime.secsTo(dt.mDateTime); } + /** Returns the value as a string. + * If it is a date-time, both time and date are included in the output. + * If it is date-only, only the date is included in the output. + */ + QString toString(Qt::DateFormat f = Qt::TextDate) const + { + if (mDateOnly) + return mDateTime.date().toString(f); + else if (mTimeValid) + return mDateTime.toString(f); + else + return QString::null; + } + /** Returns the value as a string. + * If it is a date-time, both time and date are included in the output. + * If it is date-only, only the date is included in the output. + */ + QString toString(const QString& format) const + { + if (mDateOnly) + return mDateTime.date().toString(format); + else if (mTimeValid) + return mDateTime.toString(format); + else + return QString::null; + } + /** Returns the value as a string, formatted according to the user's locale. + * If it is a date-time, both time and date are included in the output. + * If it is date-only, only the date is included in the output. + */ + QString formatLocale(bool shortFormat = true) const; + /** Sets the start-of-day time. + * The default value is midnight (0000 hrs). + */ + static void setStartOfDay(const QTime& sod) { mStartOfDay = sod; } + /** Returns the start-of-day time. */ + static QTime startOfDay() { return mStartOfDay; } + + friend bool operator==(const DateTime& dt1, const DateTime& dt2); + friend bool operator<(const DateTime& dt1, const DateTime& dt2); + + private: + static QTime mStartOfDay; + QDateTime mDateTime; + bool mDateOnly; + bool mTimeValid; // whether the time is potentially valid - applicable only if mDateOnly false +}; + +/** Returns true if the two values are equal. */ +bool operator==(const DateTime& dt1, const DateTime& dt2); +/** Returns true if the @p dt1 is earlier than @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +bool operator<(const DateTime& dt1, const DateTime& dt2); +/** Returns true if the two values are not equal. */ +inline bool operator!=(const DateTime& dt1, const DateTime& dt2) { return !operator==(dt1, dt2); } +/** Returns true if the @p dt1 is later than @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +inline bool operator>(const DateTime& dt1, const DateTime& dt2) { return operator<(dt2, dt1); } +/** Returns true if the @p dt1 is later than or equal to @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +inline bool operator>=(const DateTime& dt1, const DateTime& dt2) { return !operator<(dt1, dt2); } +/** Returns true if the @p dt1 is earlier than or equal to @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +inline bool operator<=(const DateTime& dt1, const DateTime& dt2) { return !operator<(dt2, dt1); } + +#endif // DATETIME_H diff --git a/kalarm/lib/label.cpp b/kalarm/lib/label.cpp new file mode 100644 index 000000000..c61ce76ad --- /dev/null +++ b/kalarm/lib/label.cpp @@ -0,0 +1,118 @@ +/* + * label.cpp - label with radiobutton buddy option + * Program: kalarm + * Copyright (C) 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" +#include <qradiobutton.h> +#include "label.moc" + + +Label::Label(QWidget* parent, const char* name, WFlags f) + : QLabel(parent, name, f), + mRadioButton(0), + mFocusWidget(0) +{ } + +Label::Label(const QString& text, QWidget* parent, const char* name, WFlags f) + : QLabel(text, parent, name, f), + mRadioButton(0), + mFocusWidget(0) +{ } + +Label::Label(QWidget* buddy, const QString& text, QWidget* parent, const char* name, WFlags f) + : QLabel(buddy, text, parent, name, f), + mRadioButton(0), + mFocusWidget(0) +{ } + +/****************************************************************************** +* Set a buddy widget. +* If it (or its focus proxy) is a radio button, create a focus widget. +* When the accelerator key is pressed, the focus widget then receives focus. +* That event triggers the selection of the radio button. +*/ +void Label::setBuddy(QWidget* bud) +{ + if (mRadioButton) + disconnect(mRadioButton, SIGNAL(destroyed()), this, SLOT(buddyDead())); + QWidget* w = bud; + if (w) + { + while (w->focusProxy()) + w = w->focusProxy(); + if (!w->inherits("QRadioButton")) + w = 0; + } + if (!w) + { + // The buddy widget isn't a radio button + QLabel::setBuddy(bud); + delete mFocusWidget; + mFocusWidget = 0; + mRadioButton = 0; + } + else + { + // The buddy widget is a radio button, so set a different buddy + if (!mFocusWidget) + mFocusWidget = new LabelFocusWidget(this); + QLabel::setBuddy(mFocusWidget); + mRadioButton = (QRadioButton*)bud; + connect(mRadioButton, SIGNAL(destroyed()), this, SLOT(buddyDead())); + } +} + +void Label::buddyDead() +{ + delete mFocusWidget; + mFocusWidget = 0; + mRadioButton = 0; +} + +/****************************************************************************** +* Called when focus is transferred to the label's special focus widget. +* Transfer focus to the radio button and select it. +*/ +void Label::activated() +{ + if (mFocusWidget && mRadioButton) + { + mRadioButton->setFocus(); + mRadioButton->setChecked(true); + } +} + + +/*============================================================================= +* Class: LabelFocusWidget +=============================================================================*/ + +LabelFocusWidget::LabelFocusWidget(QWidget* parent, const char* name) + : QWidget(parent, name) +{ + setFocusPolicy(ClickFocus); + setFixedSize(QSize(1,1)); +} + +void LabelFocusWidget::focusInEvent(QFocusEvent*) +{ + Label* parent = (Label*)parentWidget(); + parent->activated(); + +} diff --git a/kalarm/lib/label.h b/kalarm/lib/label.h new file mode 100644 index 000000000..c65a7fcd6 --- /dev/null +++ b/kalarm/lib/label.h @@ -0,0 +1,96 @@ +/* + * label.h - label with radiobutton buddy option + * Program: kalarm + * Copyright © 2004-2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef LABEL_H +#define LABEL_H + +#include <qlabel.h> +class QRadioButton; +class LabelFocusWidget; + +/** + * @short A QLabel with option for a buddy radio button. + * + * The Label class provides a text display, with special behaviour when a radio + * button is set as a buddy. + * + * The Label object in effect acts as if it were part of the buddy radio button, + * in that when the label's accelerator key is pressed, the radio button receives + * focus and is switched on. When a non-radio button is specified as a buddy, the + * behaviour is the same as for QLabel. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class Label : public QLabel +{ + Q_OBJECT + friend class LabelFocusWidget; + public: + /** Constructs an empty label. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param f Flags. See QWidget constructor for details. + */ + explicit Label(QWidget* parent, const char* name = 0, WFlags f = 0); + /** Constructs a label that displays @p text. + * @param text Text string to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param f Flags. See QWidget constructor for details. + */ + Label(const QString& text, QWidget* parent, const char* name = 0, WFlags f = 0); + /** Constructs a label, with a buddy widget, that displays @p text. + * @param buddy Buddy widget which receives the keyboard focus when the + * label's accelerator key is pressed. If @p buddy is a radio + * button, @p buddy is in addition selected when the + * accelerator key is pressed. + * @param text Text string to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param f Flags. See QWidget constructor for details. + */ + Label(QWidget* buddy, const QString& text, QWidget* parent, const char* name = 0, WFlags f = 0); + /** Sets the label's buddy widget which receives the keyboard focus when the + * label's accelerator key is pressed. If @p buddy is a radio button, + * @p buddy is in addition selected when the accelerator key is pressed. + */ + virtual void setBuddy(QWidget* buddy); + protected: + virtual void drawContents(QPainter* p) { QLabel::drawContents(p); } + private slots: + void buddyDead(); + private: + void activated(); + QRadioButton* mRadioButton; // buddy widget if it's a radio button, else 0 + LabelFocusWidget* mFocusWidget; +}; + + +// Private class for use by Label +class LabelFocusWidget : public QWidget +{ + Q_OBJECT + public: + LabelFocusWidget(QWidget* parent, const char* name = 0); + protected: + virtual void focusInEvent(QFocusEvent*); +}; + +#endif // LABEL_H diff --git a/kalarm/lib/lineedit.cpp b/kalarm/lib/lineedit.cpp new file mode 100644 index 000000000..943d7b2d0 --- /dev/null +++ b/kalarm/lib/lineedit.cpp @@ -0,0 +1,200 @@ +/* + * lineedit.cpp - Line edit widget with extra drag and drop options + * Program: kalarm + * Copyright (C) 2003 - 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" + +#include <qregexp.h> +#include <qdragobject.h> + +#include <kurldrag.h> +#include <kurlcompletion.h> + +#include <libkdepim/maillistdrag.h> +#include <libkdepim/kvcarddrag.h> +#include <libkcal/icaldrag.h> + +#include "lineedit.moc" + + +/*============================================================================= += Class LineEdit += Line edit which accepts drag and drop of text, URLs and/or email addresses. +* It has an option to prevent its contents being selected when it receives += focus. +=============================================================================*/ +LineEdit::LineEdit(Type type, QWidget* parent, const char* name) + : KLineEdit(parent, name), + mType(type), + mNoSelect(false), + mSetCursorAtEnd(false) +{ + init(); +} + +LineEdit::LineEdit(QWidget* parent, const char* name) + : KLineEdit(parent, name), + mType(Text), + mNoSelect(false), + mSetCursorAtEnd(false) +{ + init(); +} + +void LineEdit::init() +{ + if (mType == Url) + { + setCompletionMode(KGlobalSettings::CompletionShell); + KURLCompletion* comp = new KURLCompletion(KURLCompletion::FileCompletion); + comp->setReplaceHome(true); + setCompletionObject(comp); + setAutoDeleteCompletionObject(true); + } + else + setCompletionMode(KGlobalSettings::CompletionNone); +} + +/****************************************************************************** +* Called when the line edit receives focus. +* If 'noSelect' is true, prevent the contents being selected. +*/ +void LineEdit::focusInEvent(QFocusEvent* e) +{ + if (mNoSelect) + QFocusEvent::setReason(QFocusEvent::Other); + KLineEdit::focusInEvent(e); + if (mNoSelect) + { + QFocusEvent::resetReason(); + mNoSelect = false; + } +} + +void LineEdit::setText(const QString& text) +{ + KLineEdit::setText(text); + setCursorPosition(mSetCursorAtEnd ? text.length() : 0); +} + +void LineEdit::dragEnterEvent(QDragEnterEvent* e) +{ + if (KCal::ICalDrag::canDecode(e)) + e->accept(false); // don't accept "text/calendar" objects + e->accept(QTextDrag::canDecode(e) + || KURLDrag::canDecode(e) + || mType != Url && KPIM::MailListDrag::canDecode(e) + || mType == Emails && KVCardDrag::canDecode(e)); +} + +void LineEdit::dropEvent(QDropEvent* e) +{ + QString newText; + QStringList newEmails; + QString txt; + KPIM::MailList mailList; + KURL::List files; + KABC::Addressee::List addrList; + + if (mType != Url + && e->provides(KPIM::MailListDrag::format()) + && KPIM::MailListDrag::decode(e, mailList)) + { + // KMail message(s) - ignore all but the first + if (mailList.count()) + { + if (mType == Emails) + newText = mailList.first().from(); + else + setText(mailList.first().subject()); // replace any existing text + } + } + // This must come before KURLDrag + else if (mType == Emails + && KVCardDrag::canDecode(e) && KVCardDrag::decode(e, addrList)) + { + // KAddressBook entries + for (KABC::Addressee::List::Iterator it = addrList.begin(); it != addrList.end(); ++it) + { + QString em((*it).fullEmail()); + if (!em.isEmpty()) + newEmails.append(em); + } + } + else if (KURLDrag::decode(e, files) && files.count()) + { + // URL(s) + switch (mType) + { + case Url: + // URL entry field - ignore all but the first dropped URL + setText(files.first().prettyURL()); // replace any existing text + break; + case Emails: + { + // Email entry field - ignore all but mailto: URLs + QString mailto = QString::fromLatin1("mailto"); + for (KURL::List::Iterator it = files.begin(); it != files.end(); ++it) + { + if ((*it).protocol() == mailto) + newEmails.append((*it).path()); + } + break; + } + case Text: + newText = files.first().prettyURL(); + break; + } + } + else if (QTextDrag::decode(e, txt)) + { + // Plain text + if (mType == Emails) + { + // Remove newlines from a list of email addresses, and allow an eventual mailto: protocol + QString mailto = QString::fromLatin1("mailto:"); + newEmails = QStringList::split(QRegExp("[\r\n]+"), txt); + for (QStringList::Iterator it = newEmails.begin(); it != newEmails.end(); ++it) + { + if ((*it).startsWith(mailto)) + { + KURL url(*it); + *it = url.path(); + } + } + } + else + { + int newline = txt.find('\n'); + newText = (newline >= 0) ? txt.left(newline) : txt; + } + } + + if (newEmails.count()) + { + newText = newEmails.join(","); + int c = cursorPosition(); + if (c > 0) + newText.prepend(","); + if (c < static_cast<int>(text().length())) + newText.append(","); + } + if (!newText.isEmpty()) + insert(newText); +} diff --git a/kalarm/lib/lineedit.h b/kalarm/lib/lineedit.h new file mode 100644 index 000000000..57f8378ee --- /dev/null +++ b/kalarm/lib/lineedit.h @@ -0,0 +1,94 @@ +/* + * lineedit.h - line edit widget with extra drag and drop options + * Program: kalarm + * Copyright (C) 2003 - 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef LINEEDIT_H +#define LINEEDIT_H + +#include <klineedit.h> + + +/** + * @short Line edit widget with extra drag and drop options. + * + * The LineEdit class is a line edit widget which accepts specified types of drag and + * drop content. + * + * The widget will always accept drag and drop of text, except text/calendar mime type, + * and of URLs. It will accept additional mime types depending on its configuration: + * Text type accepts email address lists. + * Email type accepts email address lists and VCard data (e.g. from KAddressBook). + * + * The class also provides an option to prevent its contents being selected when the + * widget receives focus. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class LineEdit : public KLineEdit +{ + Q_OBJECT + public: + /** Types of drag and drop content which will be accepted. + * @li Text - the line edit contains general text. It accepts text, a URL + * or an email from KMail (the subject line is used). If multiple + * URLs or emails are dropped, only the first is used; the + * rest are ignored. + * @li Url - the line edit contains a URL. It accepts text or a URL. If + * multiple URLs are dropped, only the first URL is used; the + * rest are ignored. + * @li Emails - the line edit contains email addresses. It accepts text, + * mailto: URLs, emails from KMail (the From address is used) + * or vcard data (e.g. from KAddressBook). If multiple emails + * are dropped, only the first is used; the rest are ignored. + * + */ + enum Type { Text, Url, Emails }; + /** Constructor. + * @param type The content type for the line edit. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit LineEdit(Type type, QWidget* parent = 0, const char* name = 0); + /** Constructs a line edit whose content type is Text. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit LineEdit(QWidget* parent = 0, const char* name = 0); + /** Prevents the line edit's contents being selected when the widget receives focus. */ + void setNoSelect() { mNoSelect = true; } + /** Sets whether the cursor should be set at the beginning or end of the text when + * setText() is called. + */ + void setCursorAtEnd(bool end = true) { mSetCursorAtEnd = end; } + public slots: + /** Sets the contents of the line edit to be @p str. */ + virtual void setText(const QString& str); + protected: + virtual void focusInEvent(QFocusEvent*); + virtual void dragEnterEvent(QDragEnterEvent*); + virtual void dropEvent(QDropEvent*); + private: + void init(); + + Type mType; + bool mNoSelect; + bool mSetCursorAtEnd; // setText() should position cursor at end +}; + +#endif // LINEEDIT_H diff --git a/kalarm/lib/messagebox.cpp b/kalarm/lib/messagebox.cpp new file mode 100644 index 000000000..514b45bc2 --- /dev/null +++ b/kalarm/lib/messagebox.cpp @@ -0,0 +1,178 @@ +/* + * messagebox.cpp - enhanced KMessageBox class + * Program: kalarm + * Copyright (C) 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" +#include <kconfig.h> +#include "messagebox.h" + + +KConfig* MessageBox::mConfig = 0; +QMap<QString, KMessageBox::ButtonCode> MessageBox::mContinueDefaults; + + +/****************************************************************************** +* Set the default button for continue/cancel message boxes with the specified +* 'dontAskAgainName'. +*/ +void MessageBox::setContinueDefault(const QString& dontAskAgainName, ButtonCode defaultButton) +{ + mContinueDefaults[dontAskAgainName] = (defaultButton == Cancel ? Cancel : Continue); +} + +/****************************************************************************** +* Get the default button for continue/cancel message boxes with the specified +* 'dontAskAgainName'. +*/ +KMessageBox::ButtonCode MessageBox::getContinueDefault(const QString& dontAskAgainName) +{ + ButtonCode defaultButton = Continue; + if (!dontAskAgainName.isEmpty()) + { + QMap<QString, ButtonCode>::ConstIterator it = mContinueDefaults.find(dontAskAgainName); + if (it != mContinueDefaults.end()) + defaultButton = it.data(); + } + return defaultButton; +} + +/****************************************************************************** +* Continue/cancel message box. +* If 'dontAskAgainName' is specified: +* 1) The message box will only be suppressed if the user chose Continue last time. +* 2) The default button is that last set with either setContinueDefault() or +* warningContinueCancel() for that 'dontAskAgainName' value. If neither method +* has set a default button, Continue is the default. +*/ +int MessageBox::warningContinueCancel(QWidget* parent, const QString& text, const QString& caption, + const KGuiItem& buttonContinue, const QString& dontAskAgainName) +{ + ButtonCode defaultButton = getContinueDefault(dontAskAgainName); + return warningContinueCancel(parent, defaultButton, text, caption, buttonContinue, dontAskAgainName); +} + +/****************************************************************************** +* Continue/cancel message box with the option as to which button is the default. +* If 'dontAskAgainName' is specified, the message box will only be suppressed +* if the user chose Continue last time. +*/ +int MessageBox::warningContinueCancel(QWidget* parent, ButtonCode defaultButton, const QString& text, + const QString& caption, const KGuiItem& buttonContinue, + const QString& dontAskAgainName) +{ + setContinueDefault(dontAskAgainName, defaultButton); + if (defaultButton != Cancel) + return KMessageBox::warningContinueCancel(parent, text, caption, buttonContinue, dontAskAgainName); + + // Cancel is the default button, so we have to use KMessageBox::warningYesNo() + if (!dontAskAgainName.isEmpty()) + { + ButtonCode b; + if (!shouldBeShownYesNo(dontAskAgainName, b) + && b != KMessageBox::Yes) + { + // Notification has been suppressed, but No (alias Cancel) is the default, + // so unsuppress notification. + saveDontShowAgain(dontAskAgainName, true, false); + } + } + return warningYesNo(parent, text, caption, buttonContinue, KStdGuiItem::cancel(), dontAskAgainName); +} + +/****************************************************************************** +* If there is no current setting for whether a non-yes/no message box should be +* shown, set it to 'defaultShow'. +* If a continue/cancel message box has Cancel as the default button, either +* setContinueDefault() or warningContinueCancel() must have been called +* previously to set this for this 'dontShowAgainName' value. +* Reply = true if 'defaultShow' was written. +*/ +bool MessageBox::setDefaultShouldBeShownContinue(const QString& dontShowAgainName, bool defaultShow) +{ + if (dontShowAgainName.isEmpty()) + return false; + // First check whether there is an existing setting + KConfig* config = mConfig ? mConfig : KGlobal::config(); + config->setGroup(QString::fromLatin1("Notification Messages")); + if (config->hasKey(dontShowAgainName)) + return false; + + // There is no current setting, so write one + saveDontShowAgainContinue(dontShowAgainName, !defaultShow); + return true; +} + +/****************************************************************************** +* Return whether a non-yes/no message box should be shown. +* If the message box has Cancel as the default button, either setContinueDefault() +* or warningContinueCancel() must have been called previously to set this for this +* 'dontShowAgainName' value. +*/ +bool MessageBox::shouldBeShownContinue(const QString& dontShowAgainName) +{ + if (getContinueDefault(dontShowAgainName) != Cancel) + return KMessageBox::shouldBeShownContinue(dontShowAgainName); + // Cancel is the default button, so we have to use a yes/no message box + ButtonCode b; + return shouldBeShownYesNo(dontShowAgainName, b); +} + + +/****************************************************************************** +* Save whether the yes/no message box should not be shown again. +* If 'dontShow' is true, the message box will be suppressed and it will return +* 'result'. +*/ +void MessageBox::saveDontShowAgainYesNo(const QString& dontShowAgainName, bool dontShow, ButtonCode result) +{ + saveDontShowAgain(dontShowAgainName, true, dontShow, (result == Yes ? "yes" : "no")); +} + +/****************************************************************************** +* Save whether a non-yes/no message box should not be shown again. +* If 'dontShow' is true, the message box will be suppressed and it will return +* Continue. +* If the message box has Cancel as the default button, either setContinueDefault() +* or warningContinueCancel() must have been called previously to set this for this +* 'dontShowAgainName' value. +*/ +void MessageBox::saveDontShowAgainContinue(const QString& dontShowAgainName, bool dontShow) +{ + if (getContinueDefault(dontShowAgainName) == Cancel) + saveDontShowAgainYesNo(dontShowAgainName, dontShow, Yes); + else + saveDontShowAgain(dontShowAgainName, false, dontShow); +} + +/****************************************************************************** +* Save whether the message box should not be shown again. +*/ +void MessageBox::saveDontShowAgain(const QString& dontShowAgainName, bool yesno, bool dontShow, const char* yesnoResult) +{ + if (dontShowAgainName.isEmpty()) + return; + KConfig* config = mConfig ? mConfig : KGlobal::config(); + config->setGroup(QString::fromLatin1("Notification Messages")); + bool global = (dontShowAgainName[0] == ':'); + if (yesno) + config->writeEntry(dontShowAgainName, QString::fromLatin1(dontShow ? yesnoResult : ""), true, global); + else + config->writeEntry(dontShowAgainName, !dontShow, true, global); + config->sync(); +} diff --git a/kalarm/lib/messagebox.h b/kalarm/lib/messagebox.h new file mode 100644 index 000000000..32e0d7325 --- /dev/null +++ b/kalarm/lib/messagebox.h @@ -0,0 +1,125 @@ +/* + * messagebox.h - enhanced KMessageBox class + * Program: kalarm + * Copyright (C) 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MESSAGEBOX_H +#define MESSAGEBOX_H + +#include <kstdguiitem.h> +#include <kmessagebox.h> + + +/** + * @short Enhanced KMessageBox. + * + * The MessageBox class provides an extension to KMessageBox, including the option for + * Continue/Cancel message boxes to have a default button of Cancel. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class MessageBox : public KMessageBox +{ + public: + /** MessageBox types. + * @li CONT_CANCEL_DEF_CONT - Continue/Cancel, with Continue as the default button. + * @li CONT_CANCEL_DEF_CANCEL - Continue/Cancel, with Cancel as the default button. + * @li YES_NO_DEF_NO - Yes/No, with No as the default button. + */ + enum AskType { // MessageBox types + CONT_CANCEL_DEF_CONT, // Continue/Cancel, with default = Continue + CONT_CANCEL_DEF_CANCEL, // Continue/Cancel, with default = Cancel + YES_NO_DEF_NO // Yes/No, with default = No + }; + /** Gets the default button for the Continue/Cancel message box with the specified + * "don't ask again" name. + * @param dontAskAgainName The identifier controlling whether the message box is suppressed. + */ + static ButtonCode getContinueDefault(const QString& dontAskAgainName); + /** Sets the default button for the Continue/Cancel message box with the specified + * "don't ask again" name. + * @param dontAskAgainName The identifier controlling whether the message box is suppressed. + * @param defaultButton The default button for the message box. Valid values are Continue or Cancel. + */ + static void setContinueDefault(const QString& dontAskAgainName, ButtonCode defaultButton); + /** Displays a Continue/Cancel message box with the option as to which button is the default. + * @param parent Parent widget + * @param defaultButton The default button for the message box. Valid values are Continue or Cancel. + * @param text Message string + * @param caption Caption (window title) of the message box + * @param buttonContinue The text for the first button (default = i18n("Continue")) + * @param dontAskAgainName If specified, the message box will only be suppressed + * if the user chose Continue last time. + */ + static int warningContinueCancel(QWidget* parent, ButtonCode defaultButton, const QString& text, + const QString& caption = QString::null, + const KGuiItem& buttonContinue = KStdGuiItem::cont(), + const QString& dontAskAgainName = QString::null); + /** Displays a Continue/Cancel message box. + * @param parent Parent widget + * @param text Message string + * @param caption Caption (window title) of the message box + * @param buttonContinue The text for the first button (default = i18n("Continue")) + * @param dontAskAgainName If specified, (1) The message box will only be suppressed + * if the user chose Continue last time, and (2) The default button is that last set + * with either setContinueDefault() or warningContinueCancel() for the same + * @p dontAskAgainName value. If neither method has been used to set a default button, + * Continue is the default. + */ + static int warningContinueCancel(QWidget* parent, const QString& text, const QString& caption = QString::null, + const KGuiItem& buttonContinue = KStdGuiItem::cont(), + const QString& dontAskAgainName = QString::null); + /** If there is no current setting for whether a non-Yes/No message box should be + * shown, sets it to @p defaultShow. + * If a Continue/Cancel message box has Cancel as the default button, either + * setContinueDefault() or warningContinueCancel() must have been called + * previously to set this for the specified @p dontShowAgainName value. + * @return true if @p defaultShow was written. + */ + static bool setDefaultShouldBeShownContinue(const QString& dontShowAgainName, bool defaultShow); + /** Returns whether a non-Yes/No message box should be shown. + * If the message box has Cancel as the default button, either setContinueDefault() + * or warningContinueCancel() must have been called previously to set this for the + * specified @p dontShowAgainName value. + * @param dontShowAgainName The identifier controlling whether the message box is suppressed. + */ + static bool shouldBeShownContinue(const QString& dontShowAgainName); + /** Stores whether the Yes/No message box should or should not be shown again. + * @param dontShowAgainName The identifier controlling whether the message box is suppressed. + * @param dontShow If true, the message box will be suppressed and will return @p result. + * @param result The button code to return if the message box is suppressed. + */ + static void saveDontShowAgainYesNo(const QString& dontShowAgainName, bool dontShow = true, ButtonCode result = No); + /** Stores whether a non-Yes/No message box should or should not be shown again. + * If the message box has Cancel as the default button, either setContinueDefault() + * or warningContinueCancel() must have been called previously to set this for the + * specified @p dontShowAgainName value. + * @param dontShowAgainName The identifier controlling whether the message box is suppressed. + * @param dontShow If true, the message box will be suppressed and will return Continue. + */ + static void saveDontShowAgainContinue(const QString& dontShowAgainName, bool dontShow = true); + /** Sets the KConfig object to be used by the MessageBox class. */ + static void setDontShowAskAgainConfig(KConfig* cfg) { mConfig = cfg; } + + private: + static void saveDontShowAgain(const QString& dontShowAgainName, bool yesno, bool dontShow, const char* yesnoResult = 0); + static KConfig* mConfig; + static QMap<QString, ButtonCode> mContinueDefaults; +}; + +#endif diff --git a/kalarm/lib/pushbutton.cpp b/kalarm/lib/pushbutton.cpp new file mode 100644 index 000000000..8b279082c --- /dev/null +++ b/kalarm/lib/pushbutton.cpp @@ -0,0 +1,102 @@ +/* + * pushbutton.cpp - push button with read-only option + * Program: kalarm + * Copyright (c) 2002 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "pushbutton.moc" + + +PushButton::PushButton(QWidget* parent, const char* name) + : QPushButton(parent, name), + mFocusPolicy(focusPolicy()), + mReadOnly(false) +{ } + +PushButton::PushButton(const QString& text, QWidget* parent, const char* name) + : QPushButton(text, parent, name), + mFocusPolicy(focusPolicy()), + mReadOnly(false) +{ } + +PushButton::PushButton(const QIconSet& icon, const QString& text, QWidget* parent, const char* name) + : QPushButton(icon, text, parent, name), + mFocusPolicy(focusPolicy()), + mReadOnly(false) +{ } + +void PushButton::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + setFocusPolicy(ro ? QWidget::NoFocus : mFocusPolicy); + if (ro) + clearFocus(); + } +} + +void PushButton::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QPushButton::mousePressEvent(e); +} + +void PushButton::mouseReleaseEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QPushButton::mouseReleaseEvent(e); +} + +void PushButton::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QPushButton::mouseMoveEvent(e); +} + +void PushButton::keyPressEvent(QKeyEvent* e) +{ + if (mReadOnly) + switch (e->key()) + { + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Down: + // Process keys which shift the focus + break; + default: + return; + } + QPushButton::keyPressEvent(e); +} + +void PushButton::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QPushButton::keyReleaseEvent(e); +} diff --git a/kalarm/lib/pushbutton.h b/kalarm/lib/pushbutton.h new file mode 100644 index 000000000..6ef0f3a2a --- /dev/null +++ b/kalarm/lib/pushbutton.h @@ -0,0 +1,77 @@ +/* + * pushbutton.h - push button with read-only option + * Program: kalarm + * Copyright © 2002,2003,2005,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef PUSHBUTTON_H +#define PUSHBUTTON_H + +#include <qpushbutton.h> + + +/** + * @short A QPushButton with read-only option. + * + * The PushButton class is a QPushButton with a read-only option. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class PushButton : public QPushButton +{ + Q_OBJECT + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit PushButton(QWidget* parent, const char* name = 0); + /** Constructor for a push button which displays a text. + * @param text The text to show on the button. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + PushButton(const QString& text, QWidget* parent, const char* name = 0); + /** Constructor for a push button which displays an icon and a text. + * @param icon The icon to show on the button. + * @param text The text to show on the button. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + PushButton(const QIconSet& icon, const QString& text, QWidget* parent, const char* name = 0); + /** Sets whether the push button is read-only for the user. + * @param readOnly True to set the widget read-only, false to enable its action. + */ + virtual void setReadOnly(bool readOnly); + /** Returns true if the widget is read only. */ + virtual bool isReadOnly() const { return mReadOnly; } + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + QWidget::FocusPolicy mFocusPolicy; // default focus policy for the QPushButton + bool mReadOnly; // value cannot be changed +}; + +#endif // PUSHBUTTON_H diff --git a/kalarm/lib/radiobutton.cpp b/kalarm/lib/radiobutton.cpp new file mode 100644 index 000000000..d66911eeb --- /dev/null +++ b/kalarm/lib/radiobutton.cpp @@ -0,0 +1,134 @@ +/* + * radiobutton.cpp - radio button with read-only option + * Program: kalarm + * Copyright (c) 2002, 2003 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "radiobutton.moc" + + +RadioButton::RadioButton(QWidget* parent, const char* name) + : QRadioButton(parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +RadioButton::RadioButton(const QString& text, QWidget* parent, const char* name) + : QRadioButton(text, parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +/****************************************************************************** +* Set the read-only status. If read-only, the button can be toggled by the +* application, but not by the user. +*/ +void RadioButton::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + setFocusPolicy(ro ? QWidget::NoFocus : mFocusPolicy); + if (ro) + clearFocus(); + } +} + +/****************************************************************************** +* Specify a widget to receive focus when the button is clicked on. +*/ +void RadioButton::setFocusWidget(QWidget* w, bool enable) +{ + mFocusWidget = w; + mFocusWidgetEnable = enable; + if (w) + connect(this, SIGNAL(clicked()), SLOT(slotClicked())); + else + disconnect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); +} + +/****************************************************************************** +* Called when the button is clicked. +* If it is now checked, focus is transferred to any specified focus widget. +*/ +void RadioButton::slotClicked() +{ + if (mFocusWidget && isChecked()) + { + if (mFocusWidgetEnable) + mFocusWidget->setEnabled(true); + mFocusWidget->setFocus(); + } +} + +/****************************************************************************** +* Event handlers to intercept events if in read-only mode. +* Any events which could change the button state are discarded. +*/ +void RadioButton::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QRadioButton::mousePressEvent(e); +} + +void RadioButton::mouseReleaseEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QRadioButton::mouseReleaseEvent(e); +} + +void RadioButton::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QRadioButton::mouseMoveEvent(e); +} + +void RadioButton::keyPressEvent(QKeyEvent* e) +{ + if (mReadOnly) + switch (e->key()) + { + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Down: + // Process keys which shift the focus + case Qt::Key_Escape: + break; + default: + return; + } + QRadioButton::keyPressEvent(e); +} + +void RadioButton::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QRadioButton::keyReleaseEvent(e); +} diff --git a/kalarm/lib/radiobutton.h b/kalarm/lib/radiobutton.h new file mode 100644 index 000000000..96bca04dd --- /dev/null +++ b/kalarm/lib/radiobutton.h @@ -0,0 +1,88 @@ +/* + * radiobutton.h - radio button with focus widget and read-only options + * Program: kalarm + * Copyright © 2002,2003,2005,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef RADIOBUTTON_H +#define RADIOBUTTON_H + +#include <qradiobutton.h> + + +/** + * @short A QRadioButton with focus widget and read-only options. + * + * The RadioButton class is a QRadioButton with the ability to transfer focus to + * another widget when checked, and with a read-only option. + * + * Another widget may be specified as the focus widget for the radio button. Whenever + * the user clicks on the radio button so as to set its state to checked, focus is + * automatically transferred to the focus widget. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class RadioButton : public QRadioButton +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit RadioButton(QWidget* parent, const char* name = 0); + /** Constructor. + * @param text Text to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + RadioButton(const QString& text, QWidget* parent, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the radio button is read-only for the user. If read-only, + * its state cannot be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns the widget which receives focus when the button is clicked. */ + QWidget* focusWidget() const { return mFocusWidget; } + /** Specifies a widget to receive focus when the button is clicked. + * @param widget Widget to receive focus. + * @param enable If true, @p widget will be enabled before receiving focus. If + * false, the enabled state of @p widget will be left unchanged when + * the radio button is clicked. + */ + void setFocusWidget(QWidget* widget, bool enable = true); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + protected slots: + void slotClicked(); + private: + QWidget::FocusPolicy mFocusPolicy; // default focus policy for the QRadioButton + QWidget* mFocusWidget; // widget to receive focus when button is clicked on + bool mFocusWidgetEnable; // enable focus widget before setting focus + bool mReadOnly; // value cannot be changed +}; + +#endif // RADIOBUTTON_H diff --git a/kalarm/lib/shellprocess.cpp b/kalarm/lib/shellprocess.cpp new file mode 100644 index 000000000..1e37d2e74 --- /dev/null +++ b/kalarm/lib/shellprocess.cpp @@ -0,0 +1,208 @@ +/* + * shellprocess.cpp - execute a shell process + * Program: kalarm + * Copyright (c) 2004, 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <sys/stat.h> +#include <kapplication.h> +#include <klocale.h> +#include <kdebug.h> + +#include "shellprocess.moc" + + +QCString ShellProcess::mShellName; +QCString ShellProcess::mShellPath; +bool ShellProcess::mInitialised = false; +bool ShellProcess::mAuthorised = false; + + +ShellProcess::ShellProcess(const QString& command) + : KShellProcess(shellName()), + mCommand(command), + mStatus(INACTIVE), + mStdinExit(false) +{ +} + +/****************************************************************************** +* Execute a command. +*/ +bool ShellProcess::start(Communication comm) +{ + if (!authorised()) + { + mStatus = UNAUTHORISED; + return false; + } + KShellProcess::operator<<(mCommand); + connect(this, SIGNAL(wroteStdin(KProcess*)), SLOT(writtenStdin(KProcess*))); + connect(this, SIGNAL(processExited(KProcess*)), SLOT(slotExited(KProcess*))); + if (!KShellProcess::start(KProcess::NotifyOnExit, comm)) + { + mStatus = START_FAIL; + return false; + } + mStatus = RUNNING; + return true; +} + +/****************************************************************************** +* Called when a shell process execution completes. +* Interprets the exit status according to which shell was called, and emits +* a shellExited() signal. +*/ +void ShellProcess::slotExited(KProcess* proc) +{ + kdDebug(5950) << "ShellProcess::slotExited()\n"; + mStdinQueue.clear(); + mStatus = SUCCESS; + if (!proc->normalExit()) + { + kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": died/killed\n"; + mStatus = DIED; + } + else + { + // Some shells report if the command couldn't be found, or is not executable + int status = proc->exitStatus(); + if (mShellName == "bash" && (status == 126 || status == 127) + || mShellName == "ksh" && status == 127) + { + kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": not found or not executable\n"; + mStatus = NOT_FOUND; + } + } + emit shellExited(this); +} + +/****************************************************************************** +* Write a string to STDIN. +*/ +void ShellProcess::writeStdin(const char* buffer, int bufflen) +{ + QCString scopy(buffer, bufflen+1); // construct a deep copy + bool write = mStdinQueue.isEmpty(); + mStdinQueue.append(scopy); + if (write) + KProcess::writeStdin(mStdinQueue.first(), mStdinQueue.first().length()); +} + +/****************************************************************************** +* Called when output to STDIN completes. +* Send the next queued output, if any. +* Note that buffers written to STDIN must not be freed until the writtenStdin() +* signal has been processed. +*/ +void ShellProcess::writtenStdin(KProcess* proc) +{ + mStdinQueue.pop_front(); // free the buffer which has now been written + if (!mStdinQueue.isEmpty()) + proc->writeStdin(mStdinQueue.first(), mStdinQueue.first().length()); + else if (mStdinExit) + kill(); +} + +/****************************************************************************** +* Tell the process to exit once all STDIN strings have been written. +*/ +void ShellProcess::stdinExit() +{ + if (mStdinQueue.isEmpty()) + kill(); + else + mStdinExit = true; +} + +/****************************************************************************** +* Return the error message corresponding to the command exit status. +* Reply = null string if not yet exited, or if command successful. +*/ +QString ShellProcess::errorMessage() const +{ + switch (mStatus) + { + case UNAUTHORISED: + return i18n("Failed to execute command (shell access not authorized):"); + case START_FAIL: + case NOT_FOUND: + return i18n("Failed to execute command:"); + case DIED: + return i18n("Command execution error:"); + case INACTIVE: + case RUNNING: + case SUCCESS: + default: + return QString::null; + } +} + +/****************************************************************************** +* Determine which shell to use. +* This is a duplication of what KShellProcess does, but we need to know +* which shell is used in order to decide what its exit code means. +*/ +const QCString& ShellProcess::shellPath() +{ + if (mShellPath.isEmpty()) + { + // Get the path to the shell + mShellPath = "/bin/sh"; + QCString envshell = QCString(getenv("SHELL")).stripWhiteSpace(); + if (!envshell.isEmpty()) + { + struct stat fileinfo; + if (stat(envshell.data(), &fileinfo) != -1 // ensure file exists + && !S_ISDIR(fileinfo.st_mode) // and it's not a directory + && !S_ISCHR(fileinfo.st_mode) // and it's not a character device + && !S_ISBLK(fileinfo.st_mode) // and it's not a block device +#ifdef S_ISSOCK + && !S_ISSOCK(fileinfo.st_mode) // and it's not a socket +#endif + && !S_ISFIFO(fileinfo.st_mode) // and it's not a fifo + && !access(envshell.data(), X_OK)) // and it's executable + mShellPath = envshell; + } + + // Get the shell filename with the path stripped off + int i = mShellPath.findRev('/'); + if (i >= 0) + mShellName = mShellPath.mid(i + 1); + else + mShellName = mShellPath; + } + return mShellPath; +} + +/****************************************************************************** +* Check whether shell commands are allowed at all. +*/ +bool ShellProcess::authorised() +{ + if (!mInitialised) + { + mAuthorised = kapp->authorize("shell_access"); + mInitialised = true; + } + return mAuthorised; +} diff --git a/kalarm/lib/shellprocess.h b/kalarm/lib/shellprocess.h new file mode 100644 index 000000000..06a262a8d --- /dev/null +++ b/kalarm/lib/shellprocess.h @@ -0,0 +1,138 @@ +/* + * shellprocess.h - execute a process through the shell + * Program: kalarm + * Copyright © 2004-2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SHELLPROCESS_H +#define SHELLPROCESS_H + +/** @file shellprocess.h - execute a process through the shell */ + +#include <kprocess.h> + + +/** + * @short Enhanced KShellProcess. + * + * The ShellProcess class runs a shell command and interprets the shell exit status + * as far as possible. It blocks execution if shell access is prohibited. It buffers + * data written to the process's stdin. + * + * Before executing any command, ShellProcess checks whether shell commands are + * allowed at all. If not (e.g. if the user is running in kiosk mode), it blocks + * execution. + * + * Derived from KShellProcess, this class additionally tries to interpret the shell + * exit status. Different shells use different exit codes. Currently, if bash or ksh + * report that the command could not be found or could not be executed, the NOT_FOUND + * status is returned. + * + * Writes to the process's stdin are buffered, so that unlike with KShellProcess, there + * is no need to wait for the write to complete before writing again. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class ShellProcess : public KShellProcess +{ + Q_OBJECT + public: + /** Current status of the shell process. + * @li INACTIVE - start() has not yet been called to run the command. + * @li RUNNING - the command is currently running. + * @li SUCCESS - the command appears to have exited successfully. + * @li UNAUTHORISED - shell commands are not authorised for this user. + * @li DIED - the command didn't exit cleanly, i.e. was killed or died. + * @li NOT_FOUND - the command was either not found or not executable. + * @li START_FAIL - the command couldn't be started for other reasons. + */ + enum Status { + INACTIVE, // start() has not yet been called to run the command + RUNNING, // command is currently running + SUCCESS, // command appears to have exited successfully + UNAUTHORISED, // shell commands are not authorised for this user + DIED, // command didn't exit cleanly, i.e. was killed or died + NOT_FOUND, // command either not found or not executable + START_FAIL // command couldn't be started for other reasons + }; + /** Constructor. + * @param command The command line to be run when start() is called. + */ + explicit ShellProcess(const QString& command); + /** Executes the configured command. + * @param comm Which communication links should be established to the child process + * (stdin/stdout/stderr). + */ + bool start(Communication comm = NoCommunication); + /** Returns the current status of the shell process. */ + Status status() const { return mStatus; } + /** Returns whether the command was run successfully. + * @return True if the command has been run and appears to have exited successfully. + */ + bool normalExit() const { return mStatus == SUCCESS; } + /** Returns the command configured to be run. */ + const QString& command() const { return mCommand; } + /** Returns the error message corresponding to the command exit status. + * @return Error message if an error occurred. Null string if the command has not yet + * exited, or if the command ran successfully. + */ + QString errorMessage() const; + /** Writes a string to the process's STDIN. */ + void writeStdin(const char* buffer, int bufflen); + /** Tell the process to exit once any outstanding STDIN strings have been written. */ + void stdinExit(); + /** Returns whether the user is authorised to run shell commands. Shell commands may + * be prohibited in kiosk mode, for example. + */ + static bool authorised(); + /** Determines which shell to use. + * @return file name of shell, excluding path. + */ + static const QCString& shellName() { shellPath(); return mShellName; } + /** Determines which shell to use. + * @return path name of shell. + */ + static const QCString& shellPath(); + + signals: + /** Signal emitted when the shell process execution completes. It is not emitted + * if start() did not attempt to start the command execution, e.g. in kiosk mode. + */ + void shellExited(ShellProcess*); + + private slots: + void writtenStdin(KProcess*); + void slotExited(KProcess*); + + private: + // Prohibit the following inherited methods + ShellProcess& operator<<(const QString&); + ShellProcess& operator<<(const QCString&); + ShellProcess& operator<<(const QStringList&); + ShellProcess& operator<<(const char*); + + static QCString mShellName; // name of shell to be used + static QCString mShellPath; // path of shell to be used + static bool mInitialised; // true once static data has been initialised + static bool mAuthorised; // true if shell commands are authorised + QString mCommand; // copy of command to be executed + QValueList<QCString> mStdinQueue; // queued strings to send to STDIN + Status mStatus; // current execution status + bool mStdinExit; // exit once STDIN queue has been written +}; + +#endif // SHELLPROCESS_H diff --git a/kalarm/lib/slider.cpp b/kalarm/lib/slider.cpp new file mode 100644 index 000000000..1a0999645 --- /dev/null +++ b/kalarm/lib/slider.cpp @@ -0,0 +1,85 @@ +/* + * slider.cpp - slider control with read-only option + * Program: kalarm + * Copyright (c) 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "slider.moc" + + +Slider::Slider(QWidget* parent, const char* name) + : QSlider(parent, name), + mReadOnly(false) +{ } + +Slider::Slider(Orientation o, QWidget* parent, const char* name) + : QSlider(o, parent, name), + mReadOnly(false) +{ } + +Slider::Slider(int minval, int maxval, int pageStep, int value, Orientation o, QWidget* parent, const char* name) + : QSlider(minval, maxval, pageStep, value, o, parent, name), + mReadOnly(false) +{ } + +/****************************************************************************** +* Set the read-only status. If read-only, the slider can be moved by the +* application, but not by the user. +*/ +void Slider::setReadOnly(bool ro) +{ + mReadOnly = ro; +} + +/****************************************************************************** +* Event handlers to intercept events if in read-only mode. +* Any events which could change the slider value are discarded. +*/ +void Slider::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QSlider::mousePressEvent(e); +} + +void Slider::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QSlider::mouseReleaseEvent(e); +} + +void Slider::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QSlider::mouseMoveEvent(e); +} + +void Slider::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + QSlider::keyPressEvent(e); +} + +void Slider::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QSlider::keyReleaseEvent(e); +} diff --git a/kalarm/lib/slider.h b/kalarm/lib/slider.h new file mode 100644 index 000000000..17635e68a --- /dev/null +++ b/kalarm/lib/slider.h @@ -0,0 +1,80 @@ +/* + * slider.h - slider control with read-only option + * Program: kalarm + * Copyright © 2004,2006 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SLIDER_H +#define SLIDER_H + +#include <qslider.h> + + +/** + * @short A QSlider with read-only option. + * + * The Slider class is a QSlider with a read-only option. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class Slider : public QSlider +{ + Q_OBJECT + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit Slider(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param orient The orientation of the slider, either Qt::Horizonal or Qt::Vertical. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit Slider(Orientation orient, QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param minValue The minimum value which the slider can have. + * @param maxValue The maximum value which the slider can have. + * @param pageStep The page step increment. + * @param value The initial value for the slider. + * @param orient The orientation of the slider, either Qt::Horizonal or Qt::Vertical. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + Slider(int minValue, int maxValue, int pageStep, int value, Orientation orient, + QWidget* parent = 0, const char* name = 0); + /** Returns true if the slider is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the slider is read-only for the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + bool mReadOnly; // value cannot be changed by the user +}; + +#endif // SLIDER_H diff --git a/kalarm/lib/spinbox.cpp b/kalarm/lib/spinbox.cpp new file mode 100644 index 000000000..bb9ef2441 --- /dev/null +++ b/kalarm/lib/spinbox.cpp @@ -0,0 +1,476 @@ +/* + * spinbox.cpp - spin box with read-only option and shift-click step value + * Program: kalarm + * Copyright © 2002,2004,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <kdeversion.h> +#include <qlineedit.h> +#include <qobjectlist.h> +#include "spinbox.moc" + + +SpinBox::SpinBox(QWidget* parent, const char* name) + : QSpinBox(0, 99999, 1, parent, name), + mMinValue(QSpinBox::minValue()), + mMaxValue(QSpinBox::maxValue()) +{ + init(); +} + +SpinBox::SpinBox(int minValue, int maxValue, int step, QWidget* parent, const char* name) + : QSpinBox(minValue, maxValue, step, parent, name), + mMinValue(minValue), + mMaxValue(maxValue) +{ + init(); +} + +void SpinBox::init() +{ + int step = QSpinBox::lineStep(); + mLineStep = step; + mLineShiftStep = step; + mCurrentButton = NO_BUTTON; + mShiftMouse = false; + mShiftMinBound = false; + mShiftMaxBound = false; + mSelectOnStep = true; + mReadOnly = false; + mSuppressSignals = false; + mEdited = false; + + // Find the spin widgets which are part of the spin boxes, in order to + // handle their shift-button presses. + QObjectList* spinwidgets = queryList("QSpinWidget", 0, false, true); + QSpinWidget* spin = (QSpinWidget*)spinwidgets->getFirst(); + if (spin) + spin->installEventFilter(this); // handle shift-button presses + delete spinwidgets; + editor()->installEventFilter(this); // handle shift-up/down arrow presses + +#if KDE_IS_VERSION(3,1,90) + // Detect when the text field is edited + connect(editor(), SIGNAL(textChanged(const QString&)), SLOT(textEdited())); +#endif +} + +void SpinBox::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + editor()->setReadOnly(ro); + if (ro) + setShiftStepping(false, mCurrentButton); + } +} + +int SpinBox::bound(int val) const +{ + return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val; +} + +void SpinBox::setMinValue(int val) +{ + mMinValue = val; + QSpinBox::setMinValue(val); + mShiftMinBound = false; +} + +void SpinBox::setMaxValue(int val) +{ + mMaxValue = val; + QSpinBox::setMaxValue(val); + mShiftMaxBound = false; +} + +void SpinBox::setLineStep(int step) +{ + mLineStep = step; + if (!mShiftMouse) + QSpinBox::setLineStep(step); +} + +void SpinBox::setLineShiftStep(int step) +{ + mLineShiftStep = step; + if (mShiftMouse) + QSpinBox::setLineStep(step); +} + +void SpinBox::stepUp() +{ + int step = QSpinBox::lineStep(); + addValue(step); + emit stepped(step); +} + +void SpinBox::stepDown() +{ + int step = -QSpinBox::lineStep(); + addValue(step); + emit stepped(step); +} + +/****************************************************************************** +* Adds a positive or negative increment to the current value, wrapping as appropriate. +* If 'current' is true, any temporary 'shift' values for the range are used instead +* of the real maximum and minimum values. +*/ +void SpinBox::addValue(int change, bool current) +{ + int newval = value() + change; + int maxval = current ? QSpinBox::maxValue() : mMaxValue; + int minval = current ? QSpinBox::minValue() : mMinValue; + if (wrapping()) + { + int range = maxval - minval + 1; + if (newval > maxval) + newval = minval + (newval - maxval - 1) % range; + else if (newval < minval) + newval = maxval - (minval - 1 - newval) % range; + } + else + { + if (newval > maxval) + newval = maxval; + else if (newval < minval) + newval = minval; + } + setValue(newval); +} + +void SpinBox::valueChange() +{ + if (!mSuppressSignals) + { + int val = value(); + if (mShiftMinBound && val >= mMinValue) + { + // Reinstate the minimum bound now that the value has returned to the normal range. + QSpinBox::setMinValue(mMinValue); + mShiftMinBound = false; + } + if (mShiftMaxBound && val <= mMaxValue) + { + // Reinstate the maximum bound now that the value has returned to the normal range. + QSpinBox::setMaxValue(mMaxValue); + mShiftMaxBound = false; + } + + bool focus = !mSelectOnStep && hasFocus(); + if (focus) + clearFocus(); // prevent selection of the spin box text + QSpinBox::valueChange(); + if (focus) + setFocus(); + } +} + +/****************************************************************************** +* Called whenever the line edit text is changed. +*/ +void SpinBox::textEdited() +{ + mEdited = true; +} + +void SpinBox::updateDisplay() +{ + mEdited = false; + QSpinBox::updateDisplay(); +} + +/****************************************************************************** +* Receives events destined for the spin widget or for the edit field. +*/ +bool SpinBox::eventFilter(QObject* obj, QEvent* e) +{ + if (obj == editor()) + { + int step = 0; + bool shift = false; + switch (e->type()) + { + case QEvent::KeyPress: + { + // Up and down arrow keys step the value + QKeyEvent* ke = (QKeyEvent*)e; + int key = ke->key(); + if (key == Qt::Key_Up) + step = 1; + else if (key == Qt::Key_Down) + step = -1; + shift = ((ke->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton); + break; + } + case QEvent::Wheel: + { + QWheelEvent* we = (QWheelEvent*)e; + step = (we->delta() > 0) ? 1 : -1; + shift = ((we->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton); + break; + } +#if KDE_IS_VERSION(3,1,90) + case QEvent::Leave: + if (mEdited) + interpretText(); + break; +#endif + default: + break; + } + if (step) + { + if (mReadOnly) + return true; // discard up/down arrow keys or wheel + if (shift) + { + // Shift stepping + int val = value(); + if (step > 0) + step = mLineShiftStep - val % mLineShiftStep; + else + step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1); + } + else + step = (step > 0) ? mLineStep : -mLineStep; + addValue(step, false); + return true; + } + } + else + { + int etype = e->type(); // avoid switch compile warnings + switch (etype) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + { + QMouseEvent* me = (QMouseEvent*)e; + if (me->button() == Qt::LeftButton) + { + // It's a left button press. Set normal or shift stepping as appropriate. + if (mReadOnly) + return true; // discard the event + mCurrentButton = whichButton(me->pos()); + if (mCurrentButton == NO_BUTTON) + return true; + bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if (setShiftStepping(shift, mCurrentButton)) + return true; // hide the event from the spin widget + return false; // forward event to the destination widget + } + break; + } + case QEvent::MouseButtonRelease: + { + QMouseEvent* me = (QMouseEvent*)e; + if (me->button() == Qt::LeftButton && mShiftMouse) + { + setShiftStepping(false, mCurrentButton); // cancel shift stepping + return false; // forward event to the destination widget + } + break; + } + case QEvent::MouseMove: + { + QMouseEvent* me = (QMouseEvent*)e; + if (me->state() & Qt::LeftButton) + { + // The left button is down. Track which spin button it's in. + if (mReadOnly) + return true; // discard the event + int newButton = whichButton(me->pos()); + if (newButton != mCurrentButton) + { + // The mouse has moved to a new spin button. + // Set normal or shift stepping as appropriate. + mCurrentButton = newButton; + bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if (setShiftStepping(shift, mCurrentButton)) + return true; // hide the event from the spin widget + } + return false; // forward event to the destination widget + } + break; + } + case QEvent::Wheel: + { + QWheelEvent* we = (QWheelEvent*)e; + bool shift = (we->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if (setShiftStepping(shift, (we->delta() > 0 ? UP : DOWN))) + return true; // hide the event from the spin widget + return false; // forward event to the destination widget + } + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::AccelOverride: // this is needed to receive Shift presses! + { + QKeyEvent* ke = (QKeyEvent*)e; + int key = ke->key(); + int state = ke->state(); + if ((state & Qt::LeftButton) + && (key == Qt::Key_Shift || key == Qt::Key_Alt)) + { + // The left mouse button is down, and the Shift or Alt key has changed + if (mReadOnly) + return true; // discard the event + state ^= (key == Qt::Key_Shift) ? Qt::ShiftButton : Qt::AltButton; // new state + bool shift = (state & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if ((!shift && mShiftMouse) || (shift && !mShiftMouse)) + { + // The effective shift state has changed. + // Set normal or shift stepping as appropriate. + if (setShiftStepping(shift, mCurrentButton)) + return true; // hide the event from the spin widget + } + } + break; + } + } + } + return QSpinBox::eventFilter(obj, e); +} + +/****************************************************************************** +* Set spin widget stepping to the normal or shift increment. +*/ +bool SpinBox::setShiftStepping(bool shift, int currentButton) +{ + if (currentButton == NO_BUTTON) + shift = false; + if (shift && !mShiftMouse) + { + /* The value is to be stepped to a multiple of the shift increment. + * Adjust the value so that after the spin widget steps it, it will be correct. + * Then, if the mouse button is held down, the spin widget will continue to + * step by the shift amount. + */ + int val = value(); + int step = (currentButton == UP) ? mLineShiftStep : (currentButton == DOWN) ? -mLineShiftStep : 0; + int adjust = shiftStepAdjustment(val, step); + mShiftMouse = true; + if (adjust) + { + /* The value is to be stepped by other than the shift increment, + * presumably because it is being set to a multiple of the shift + * increment. Achieve this by making the adjustment here, and then + * allowing the normal step processing to complete the job by + * adding/subtracting the normal shift increment. + */ + if (!wrapping()) + { + // Prevent the step from going past the spinbox's range, or + // to the minimum value if that has a special text unless it is + // already at the minimum value + 1. + int newval = val + adjust + step; + int svt = specialValueText().isEmpty() ? 0 : 1; + int minval = mMinValue + svt; + if (newval <= minval || newval >= mMaxValue) + { + // Stepping to the minimum or maximum value + if (svt && newval <= mMinValue && val == mMinValue) + newval = mMinValue; + else + newval = (newval <= minval) ? minval : mMaxValue; + QSpinBox::setValue(newval); + emit stepped(step); + return true; + } + + // If the interim value will lie outside the spinbox's range, + // temporarily adjust the range to allow the value to be set. + int tempval = val + adjust; + if (tempval < mMinValue) + { + QSpinBox::setMinValue(tempval); + mShiftMinBound = true; + } + else if (tempval > mMaxValue) + { + QSpinBox::setMaxValue(tempval); + mShiftMaxBound = true; + } + } + + // Don't process changes since this new value will be stepped immediately + mSuppressSignals = true; + bool blocked = signalsBlocked(); + blockSignals(true); + addValue(adjust, true); + blockSignals(blocked); + mSuppressSignals = false; + } + QSpinBox::setLineStep(mLineShiftStep); + } + else if (!shift && mShiftMouse) + { + // Reinstate to normal (non-shift) stepping + QSpinBox::setLineStep(mLineStep); + QSpinBox::setMinValue(mMinValue); + QSpinBox::setMaxValue(mMaxValue); + mShiftMinBound = mShiftMaxBound = false; + mShiftMouse = false; + } + return false; +} + +/****************************************************************************** +* Return the initial adjustment to the value for a shift step up or down. +* The default is to step up or down to the nearest multiple of the shift +* increment, so the adjustment returned is for stepping up the decrement +* required to round down to a multiple of the shift increment <= current value, +* or for stepping down the increment required to round up to a multiple of the +* shift increment >= current value. +* This method's caller then adjusts the resultant value if necessary to cater +* for the widget's minimum/maximum value, and wrapping. +* This should really be a static method, but it needs to be virtual... +*/ +int SpinBox::shiftStepAdjustment(int oldValue, int shiftStep) +{ + if (oldValue == 0 || shiftStep == 0) + return 0; + if (shiftStep > 0) + { + if (oldValue >= 0) + return -(oldValue % shiftStep); + else + return (-oldValue - 1) % shiftStep + 1 - shiftStep; + } + else + { + shiftStep = -shiftStep; + if (oldValue >= 0) + return shiftStep - ((oldValue - 1) % shiftStep + 1); + else + return (-oldValue) % shiftStep; + } +} + +/****************************************************************************** +* Find which spin widget button a mouse event is in. +*/ +int SpinBox::whichButton(const QPoint& pos) +{ + if (upRect().contains(pos)) + return UP; + if (downRect().contains(pos)) + return DOWN; + return NO_BUTTON; +} diff --git a/kalarm/lib/spinbox.h b/kalarm/lib/spinbox.h new file mode 100644 index 000000000..0df910233 --- /dev/null +++ b/kalarm/lib/spinbox.h @@ -0,0 +1,156 @@ +/* + * spinbox.h - spin box with shift-click step value and read-only option + * Program: kalarm + * Copyright © 2002-2006,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SPINBOX_H +#define SPINBOX_H + +#include <qspinbox.h> + + +/** + * @short Spin box with accelerated shift key stepping and read-only option. + * + * The SpinBox class provides a QSpinBox with accelerated stepping using the shift key. + * + * A separate step increment may optionally be specified for use when the shift key is + * held down. Typically this would be larger than the normal step. Then, when the user + * clicks the spin buttons, he/she can increment or decrement the value faster by holding + * the shift key down. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class SpinBox : public QSpinBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit SpinBox(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param minValue The minimum value which the spin box can have. + * @param maxValue The maximum value which the spin box can have. + * @param step The (unshifted) step interval. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + SpinBox(int minValue, int maxValue, int step = 1, QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the spin box can be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns whether the spin box value text is selected when its value is stepped. */ + bool selectOnStep() const { return mSelectOnStep; } + /** Sets whether the spin box value text should be selected when its value is stepped. */ + void setSelectOnStep(bool sel) { mSelectOnStep = sel; } + /** Adds a value to the current value of the spin box. */ + void addValue(int change) { addValue(change, false); } + /** Returns the minimum value of the spin box. */ + int minValue() const { return mMinValue; } + /** Returns the maximum value of the spin box. */ + int maxValue() const { return mMaxValue; } + /** Sets the minimum value of the spin box. */ + void setMinValue(int val); + /** Sets the maximum value of the spin box. */ + void setMaxValue(int val); + /** Sets the minimum and maximum values of the spin box. */ + void setRange(int minValue, int maxValue) { setMinValue(minValue); setMaxValue(maxValue); } + /** Returns the specified value clamped to the range of the spin box. */ + int bound(int val) const; + /** Returns the unshifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked without the shift key being pressed. + */ + int lineStep() const { return mLineStep; } + /** Sets the unshifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked without the shift key being pressed. + */ + void setLineStep(int step); + /** Returns the shifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked while the shift key is pressed. + */ + int lineShiftStep() const { return mLineShiftStep; } + /** Sets the shifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked while the shift key is pressed. + */ + void setLineShiftStep(int step); + public slots: + /** Increments the value of the spin box by the unshifted step increment. */ + virtual void stepUp(); + /** Decrements the value of the spin box by the unshifted step increment. */ + virtual void stepDown(); + signals: + /** Signal emitted when the spin box's value is stepped (by the shifted or unshifted increment). + * @param step The requested step in the spin box's value. Note that the actual change in value + * may have been less than this. + */ + void stepped(int step); + + protected: + /** A virtual method called whenever the value of the spin box has changed. */ + virtual void valueChange(); + /** Returns the initial adjustment to the value for a shift step up or down. + * The default is to step up or down to the nearest multiple of the shift + * increment, so the adjustment returned is for stepping up the decrement + * required to round down to a multiple of the shift increment <= current value, + * or for stepping down the increment required to round up to a multiple of the + * shift increment >= current value. + * This method's caller then adjusts the resultant value if necessary to cater + * for the widget's minimum/maximum value, and wrapping. + * This should really be a static method, but it needs to be virtual... + */ + virtual int shiftStepAdjustment(int oldValue, int shiftStep); + /** Receives events destined for the spin widget or for the edit field. */ + virtual bool eventFilter(QObject*, QEvent*); + /** Updates the contents of the embedded QLineEdit to reflect the current value + * using mapValueToText(). Also enables/disables the up/down push buttons accordingly. + */ + virtual void updateDisplay(); + + private slots: + void textEdited(); + private: + void init(); + void addValue(int change, bool current); + int whichButton(const QPoint&); + bool setShiftStepping(bool, int currentButton); + + enum { NO_BUTTON, UP, DOWN }; + + int mMinValue; + int mMaxValue; + int mLineStep; // step when spin arrows are pressed + int mLineShiftStep; // step when spin arrows are pressed with shift key + int mCurrentButton; // current spin widget button + bool mShiftMouse; // true while left button is being held down with shift key + bool mShiftMinBound; // true if a temporary minimum bound has been set during shift stepping + bool mShiftMaxBound; // true if a temporary maximum bound has been set during shift stepping + bool mSelectOnStep; // select the editor text whenever spin buttons are clicked (default) + bool mReadOnly; // value cannot be changed + bool mSuppressSignals; + bool mEdited; // text field has been edited +}; + +#endif // SPINBOX_H diff --git a/kalarm/lib/spinbox2.cpp b/kalarm/lib/spinbox2.cpp new file mode 100644 index 000000000..313d3eb56 --- /dev/null +++ b/kalarm/lib/spinbox2.cpp @@ -0,0 +1,511 @@ +/* + * spinbox2.cpp - spin box with extra pair of spin buttons (for Qt 3) + * Program: kalarm + * Copyright © 2001-2005,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <qglobal.h> + +#include <stdlib.h> + +#include <qstyle.h> +#include <qobjectlist.h> +#include <qapplication.h> +#include <qpixmap.h> +#include <qcursor.h> +#include <qtimer.h> +#include <qwmatrix.h> + +#include "spinbox2.moc" +#include "spinbox2private.moc" + + +/* List of styles which need to display the extra pair of spin buttons as a + * left-to-right mirror image. This is only necessary when, for example, the + * corners of widgets are rounded. For most styles, it is better not to mirror + * the spin widgets so as to keep the normal lighting/shading on either side. + */ +static const char* mirrorStyles[] = { + "PlastikStyle", + 0 // list terminator +}; +static bool mirrorStyle(const QStyle&); + + +int SpinBox2::mReverseLayout = -1; + +SpinBox2::SpinBox2(QWidget* parent, const char* name) + : QFrame(parent, name), + mReverseWithLayout(true) +{ + mUpdown2Frame = new QFrame(this); + mSpinboxFrame = new QFrame(this); + mUpdown2 = new ExtraSpinBox(mUpdown2Frame, "updown2"); +// mSpinbox = new MainSpinBox(0, 1, 1, this, mSpinboxFrame); + mSpinbox = new MainSpinBox(this, mSpinboxFrame); + init(); +} + +SpinBox2::SpinBox2(int minValue, int maxValue, int step, int step2, QWidget* parent, const char* name) + : QFrame(parent, name), + mReverseWithLayout(true) +{ + mUpdown2Frame = new QFrame(this); + mSpinboxFrame = new QFrame(this); + mUpdown2 = new ExtraSpinBox(minValue, maxValue, step2, mUpdown2Frame, "updown2"); + mSpinbox = new MainSpinBox(minValue, maxValue, step, this, mSpinboxFrame); + setSteps(step, step2); + init(); +} + +void SpinBox2::init() +{ + if (mReverseLayout < 0) + mReverseLayout = QApplication::reverseLayout() ? 1 : 0; + mMinValue = mSpinbox->minValue(); + mMaxValue = mSpinbox->maxValue(); + mLineStep = mSpinbox->lineStep(); + mLineShiftStep = mSpinbox->lineShiftStep(); + mPageStep = mUpdown2->lineStep(); + mPageShiftStep = mUpdown2->lineShiftStep(); + mSpinbox->setSelectOnStep(false); // default + mUpdown2->setSelectOnStep(false); // always false + setFocusProxy(mSpinbox); + mUpdown2->setFocusPolicy(QWidget::NoFocus); + mSpinMirror = new SpinMirror(mUpdown2, mUpdown2Frame, this); + if (!mirrorStyle(style())) + mSpinMirror->hide(); // hide mirrored spin buttons when they are inappropriate + connect(mSpinbox, SIGNAL(valueChanged(int)), SLOT(valueChange())); + connect(mSpinbox, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int))); + connect(mSpinbox, SIGNAL(valueChanged(const QString&)), SIGNAL(valueChanged(const QString&))); + connect(mUpdown2, SIGNAL(stepped(int)), SLOT(stepPage(int))); + connect(mUpdown2, SIGNAL(styleUpdated()), SLOT(updateMirror())); +} + +void SpinBox2::setReadOnly(bool ro) +{ + if (static_cast<int>(ro) != static_cast<int>(mSpinbox->isReadOnly())) + { + mSpinbox->setReadOnly(ro); + mUpdown2->setReadOnly(ro); + mSpinMirror->setReadOnly(ro); + } +} + +void SpinBox2::setReverseWithLayout(bool reverse) +{ + if (reverse != mReverseWithLayout) + { + mReverseWithLayout = reverse; + setSteps(mLineStep, mPageStep); + setShiftSteps(mLineShiftStep, mPageShiftStep); + } +} + +void SpinBox2::setEnabled(bool enabled) +{ + QFrame::setEnabled(enabled); + updateMirror(); +} + +void SpinBox2::setWrapping(bool on) +{ + mSpinbox->setWrapping(on); + mUpdown2->setWrapping(on); +} + +QRect SpinBox2::up2Rect() const +{ + return mUpdown2->upRect(); +} + +QRect SpinBox2::down2Rect() const +{ + return mUpdown2->downRect(); +} + +void SpinBox2::setLineStep(int step) +{ + mLineStep = step; + if (reverseButtons()) + mUpdown2->setLineStep(step); // reverse layout, but still set the right buttons + else + mSpinbox->setLineStep(step); +} + +void SpinBox2::setSteps(int line, int page) +{ + mLineStep = line; + mPageStep = page; + if (reverseButtons()) + { + mUpdown2->setLineStep(line); // reverse layout, but still set the right buttons + mSpinbox->setLineStep(page); + } + else + { + mSpinbox->setLineStep(line); + mUpdown2->setLineStep(page); + } +} + +void SpinBox2::setShiftSteps(int line, int page) +{ + mLineShiftStep = line; + mPageShiftStep = page; + if (reverseButtons()) + { + mUpdown2->setLineShiftStep(line); // reverse layout, but still set the right buttons + mSpinbox->setLineShiftStep(page); + } + else + { + mSpinbox->setLineShiftStep(line); + mUpdown2->setLineShiftStep(page); + } +} + +void SpinBox2::setButtonSymbols(QSpinBox::ButtonSymbols newSymbols) +{ + if (mSpinbox->buttonSymbols() == newSymbols) + return; + mSpinbox->setButtonSymbols(newSymbols); + mUpdown2->setButtonSymbols(newSymbols); +} + +int SpinBox2::bound(int val) const +{ + return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val; +} + +void SpinBox2::setMinValue(int val) +{ + mMinValue = val; + mSpinbox->setMinValue(val); + mUpdown2->setMinValue(val); +} + +void SpinBox2::setMaxValue(int val) +{ + mMaxValue = val; + mSpinbox->setMaxValue(val); + mUpdown2->setMaxValue(val); +} + +void SpinBox2::valueChange() +{ + int val = mSpinbox->value(); + bool blocked = mUpdown2->signalsBlocked(); + mUpdown2->blockSignals(true); + mUpdown2->setValue(val); + mUpdown2->blockSignals(blocked); +} + +/****************************************************************************** +* Called when the widget is about to be displayed. +* (At construction time, the spin button widths cannot be determined correctly, +* so we need to wait until now to definitively rearrange the widget.) +*/ +void SpinBox2::showEvent(QShowEvent*) +{ + arrange(); +} + +QSize SpinBox2::sizeHint() const +{ + getMetrics(); + QSize size = mSpinbox->sizeHint(); + size.setWidth(size.width() - xSpinbox + wUpdown2 + wGap); + return size; +} + +QSize SpinBox2::minimumSizeHint() const +{ + getMetrics(); + QSize size = mSpinbox->minimumSizeHint(); + size.setWidth(size.width() - xSpinbox + wUpdown2 + wGap); + return size; +} + +void SpinBox2::styleChange(QStyle&) +{ + if (mirrorStyle(style())) + mSpinMirror->show(); // show rounded corners with Plastik etc. + else + mSpinMirror->hide(); // keep normal shading with other styles + arrange(); +} + +/****************************************************************************** +* Called when the extra pair of spin buttons has repainted after a style change. +* Updates the mirror image of the spin buttons. +*/ +void SpinBox2::updateMirror() +{ + mSpinMirror->setNormalButtons(QPixmap::grabWidget(mUpdown2Frame, 0, 0)); +} + +/****************************************************************************** +* Set the positions and sizes of all the child widgets. +*/ +void SpinBox2::arrange() +{ + getMetrics(); + QRect arrowRect = QStyle::visualRect(QRect(0, 0, wUpdown2, height()), this); + mUpdown2Frame->setGeometry(arrowRect); + mUpdown2->setGeometry(-xUpdown2, 0, mUpdown2->width(), height()); + mSpinboxFrame->setGeometry(QStyle::visualRect(QRect(wUpdown2 + wGap, 0, width() - wUpdown2 - wGap, height()), this)); + mSpinbox->setGeometry(-xSpinbox, 0, mSpinboxFrame->width() + xSpinbox, height()); + mSpinMirror->resize(wUpdown2, mUpdown2->height()); + mSpinMirror->setGeometry(arrowRect); +//mSpinMirror->setGeometry(QStyle::visualRect(QRect(0, 11, wUpdown2, height()), this)); + mSpinMirror->setNormalButtons(QPixmap::grabWidget(mUpdown2Frame, 0, 0)); +} + +/****************************************************************************** +* Calculate the width and position of the extra pair of spin buttons. +* Style-specific adjustments are made for a better appearance. +*/ +void SpinBox2::getMetrics() const +{ + QRect rect = mUpdown2->style().querySubControlMetrics(QStyle::CC_SpinWidget, mUpdown2, QStyle::SC_SpinWidgetButtonField); + if (style().inherits("PlastikStyle")) + rect.setLeft(rect.left() - 1); // Plastik excludes left border from spin widget rectangle + xUpdown2 = mReverseLayout ? 0 : rect.left(); + wUpdown2 = mUpdown2->width() - rect.left(); + xSpinbox = mSpinbox->style().querySubControlMetrics(QStyle::CC_SpinWidget, mSpinbox, QStyle::SC_SpinWidgetEditField).left(); + wGap = 0; + + // Make style-specific adjustments for a better appearance + if (style().inherits("QMotifPlusStyle")) + { + xSpinbox = 0; // show the edit control left border + wGap = 2; // leave a space to the right of the left-hand pair of spin buttons + } +} + +/****************************************************************************** +* Called when the extra pair of spin buttons is clicked to step the value. +* Normally this is a page step, but with a right-to-left language where the +* button functions are reversed, this is a line step. +*/ +void SpinBox2::stepPage(int step) +{ + if (abs(step) == mUpdown2->lineStep()) + mSpinbox->setValue(mUpdown2->value()); + else + { + // It's a shift step + int oldValue = mSpinbox->value(); + if (!reverseButtons()) + { + // The button pairs have the normal function. + // Page shift stepping - step up or down to a multiple of the + // shift page increment, leaving unchanged the part of the value + // which is the remainder from the page increment. + if (oldValue >= 0) + oldValue -= oldValue % mUpdown2->lineStep(); + else + oldValue += (-oldValue) % mUpdown2->lineStep(); + } + int adjust = mSpinbox->shiftStepAdjustment(oldValue, step); + if (adjust == -step + && ((step > 0 && oldValue + step >= mSpinbox->maxValue()) + || (step < 0 && oldValue + step <= mSpinbox->minValue()))) + adjust = 0; // allow stepping to the minimum or maximum value + mSpinbox->addValue(adjust + step); + } + bool focus = mSpinbox->selectOnStep() && mUpdown2->hasFocus(); + if (focus) + mSpinbox->selectAll(); + + // Make the covering arrows image show the pressed arrow + mSpinMirror->redraw(QPixmap::grabWidget(mUpdown2Frame, 0, 0)); +} + + +/*============================================================================= += Class SpinBox2::MainSpinBox +=============================================================================*/ + +/****************************************************************************** +* Return the initial adjustment to the value for a shift step up or down, for +* the main (visible) spin box. +* Normally this is a line step, but with a right-to-left language where the +* button functions are reversed, this is a page step. +*/ +int SpinBox2::MainSpinBox::shiftStepAdjustment(int oldValue, int shiftStep) +{ + if (owner->reverseButtons()) + { + // The button pairs have the opposite function from normal. + // Page shift stepping - step up or down to a multiple of the + // shift page increment, leaving unchanged the part of the value + // which is the remainder from the page increment. + if (oldValue >= 0) + oldValue -= oldValue % lineStep(); + else + oldValue += (-oldValue) % lineStep(); + } + return SpinBox::shiftStepAdjustment(oldValue, shiftStep); +} + + +/*============================================================================= += Class ExtraSpinBox +=============================================================================*/ + +/****************************************************************************** +* Repaint the widget. +* If it's the first time since a style change, tell the parent SpinBox2 to +* update the SpinMirror with the new unpressed button image. We make the +* presumably reasonable assumption that when a style change occurs, the spin +* buttons are unpressed. +*/ +void ExtraSpinBox::paintEvent(QPaintEvent* e) +{ + SpinBox::paintEvent(e); + if (mNewStylePending) + { + mNewStylePending = false; + emit styleUpdated(); + } +} + + +/*============================================================================= += Class SpinMirror +=============================================================================*/ + +SpinMirror::SpinMirror(SpinBox* spinbox, QFrame* spinFrame, QWidget* parent, const char* name) + : QCanvasView(new QCanvas, parent, name), + mSpinbox(spinbox), + mSpinFrame(spinFrame), + mReadOnly(false) +{ + setVScrollBarMode(QScrollView::AlwaysOff); + setHScrollBarMode(QScrollView::AlwaysOff); + setFrameStyle(QFrame::NoFrame); + + // Find the spin widget which is part of the spin box, in order to + // pass on its shift-button presses. + QObjectList* spinwidgets = spinbox->queryList("QSpinWidget", 0, false, true); + mSpinWidget = (SpinBox*)spinwidgets->getFirst(); + delete spinwidgets; +} + +void SpinMirror::setNormalButtons(const QPixmap& px) +{ + mNormalButtons = px; + redraw(mNormalButtons); +} + +void SpinMirror::redraw() +{ + redraw(QPixmap::grabWidget(mSpinFrame, 0, 0)); +} + +void SpinMirror::redraw(const QPixmap& px) +{ + QCanvas* c = canvas(); + c->setBackgroundPixmap(px); + c->setAllChanged(); + c->update(); +} + +void SpinMirror::resize(int w, int h) +{ + canvas()->resize(w, h); + QCanvasView::resize(w, h); + resizeContents(w, h); + setWorldMatrix(QWMatrix(-1, 0, 0, 1, w - 1, 0)); // mirror left to right +} + +/****************************************************************************** +* Pass on all mouse events to the spinbox which we're covering up. +*/ +void SpinMirror::contentsMouseEvent(QMouseEvent* e) +{ + if (mReadOnly) + return; + QPoint pt = contentsToViewport(e->pos()); + pt.setX(pt.x() + mSpinbox->upRect().left()); + QApplication::postEvent(mSpinWidget, new QMouseEvent(e->type(), pt, e->button(), e->state())); + + // If the mouse button has been pressed or released, refresh the spin buttons + switch (e->type()) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + QTimer::singleShot(0, this, SLOT(redraw())); + break; + default: + break; + } +} + +/****************************************************************************** +* Pass on all mouse events to the spinbox which we're covering up. +*/ +void SpinMirror::contentsWheelEvent(QWheelEvent* e) +{ + if (mReadOnly) + return; + QPoint pt = contentsToViewport(e->pos()); + pt.setX(pt.x() + mSpinbox->upRect().left()); + QApplication::postEvent(mSpinWidget, new QWheelEvent(pt, e->delta(), e->state(), e->orientation())); +} + +/****************************************************************************** +* Pass on to the main spinbox events which are needed to activate mouseover and +* other graphic effects when the mouse cursor enters and leaves the widget. +*/ +bool SpinMirror::event(QEvent* e) +{ + switch (e->type()) + { + case QEvent::Leave: + case QEvent::Enter: + QApplication::postEvent(mSpinWidget, new QEvent(e->type())); + QTimer::singleShot(0, this, SLOT(redraw())); + break; + case QEvent::FocusIn: + mSpinbox->setFocus(); + QTimer::singleShot(0, this, SLOT(redraw())); + break; + default: + break; + } + return QCanvasView::event(e); +} + + +/*============================================================================= += Local functions +=============================================================================*/ + +/****************************************************************************** +* Determine whether the extra pair of spin buttons needs to be mirrored +* left-to-right in the specified style. +*/ +static bool mirrorStyle(const QStyle& style) +{ + for (const char** s = mirrorStyles; *s; ++s) + if (style.inherits(*s)) + return true; + return false; +} diff --git a/kalarm/lib/spinbox2.h b/kalarm/lib/spinbox2.h new file mode 100644 index 000000000..772eaee70 --- /dev/null +++ b/kalarm/lib/spinbox2.h @@ -0,0 +1,317 @@ +/* + * spinbox2.h - spin box with extra pair of spin buttons (for Qt 3) + * Program: kalarm + * Copyright © 2001-2007 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SPINBOX2_H +#define SPINBOX2_H + +#include <qglobal.h> +#include <qlineedit.h> + +class SpinMirror; +class ExtraSpinBox; +#include "spinbox.h" + + +/** + * @short Spin box with a pair of spin buttons on either side. + * + * The SpinBox2 class provides a spin box with two pairs of spin buttons, one on either side. + * + * It is designed as a base class for implementing such facilities as time spin boxes, where + * the hours and minutes values are separately displayed in the edit field. When the + * appropriate step increments are configured, the left spin arrows can then be used to + * change the hours value, while the right spin arrows can be used to change the minutes + * value. + * + * Rather than using SpinBox2 directly for time entry, use in preference TimeSpinBox or + * TimeEdit classes which are tailored from SpinBox2 for this purpose. + * + * Separate step increments may optionally be specified for use when the shift key is + * held down. Typically these would be larger than the normal steps. Then, when the user + * clicks the spin buttons, he/she can increment or decrement the value faster by holding + * the shift key down. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class SpinBox2 : public QFrame +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit SpinBox2(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param minValue The minimum value which the spin box can have. + * @param maxValue The maximum value which the spin box can have. + * @param step The (unshifted) step interval for the right-hand spin buttons. + * @param step2 The (unshifted) step interval for the left-hand spin buttons. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + SpinBox2(int minValue, int maxValue, int step = 1, int step2 = 1, + QWidget* parent = 0, const char* name = 0); + /** Sets whether the spin box can be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mSpinbox->isReadOnly(); } + /** Sets whether the spin box value text should be selected when its value is stepped. */ + void setSelectOnStep(bool sel) { mSpinbox->setSelectOnStep(sel); } + /** Sets whether the spin button pairs should be reversed for a right-to-left language. + * The default is for them to be reversed. + */ + void setReverseWithLayout(bool reverse); + /** Returns whether the spin button pairs will be reversed for a right-to-left language. */ + bool reverseButtons() const { return mReverseLayout && !mReverseWithLayout; } + + /** Returns the spin box's text, including any prefix() and suffix(). */ + QString text() const { return mSpinbox->text(); } + /** Returns the prefix for the spin box's text. */ + virtual QString prefix() const { return mSpinbox->prefix(); } + /** Returns the suffix for the spin box's text. */ + virtual QString suffix() const { return mSpinbox->suffix(); } + /** Returns the spin box's text with no prefix(), suffix() or leading or trailing whitespace. */ + virtual QString cleanText() const { return mSpinbox->cleanText(); } + + /** Sets the special-value text which, if non-null, is displayed instead of a numeric + * value when the current value is equal to minValue(). + */ + virtual void setSpecialValueText(const QString& text) { mSpinbox->setSpecialValueText(text); } + /** Returns the special-value text which, if non-null, is displayed instead of a numeric + * value when the current value is equal to minValue(). + */ + QString specialValueText() const { return mSpinbox->specialValueText(); } + + /** Sets whether it is possible to step the value from the highest value to the + * lowest value and vice versa. + */ + virtual void setWrapping(bool on); + /** Returns whether it is possible to step the value from the highest value to the + * lowest value and vice versa. + */ + bool wrapping() const { return mSpinbox->wrapping(); } + + /** Set the text alignment of the widget */ + void setAlignment(int a) { mSpinbox->setAlignment(a); } + /** Sets the button symbols to use (arrows or plus/minus). */ + virtual void setButtonSymbols(QSpinBox::ButtonSymbols); + /** Returns the button symbols currently in use (arrows or plus/minus). */ + QSpinBox::ButtonSymbols buttonSymbols() const { return mSpinbox->buttonSymbols(); } + + /** Sets the validator to @p v. The validator controls what keyboard input is accepted + * when the user is editing the value field. + */ + virtual void setValidator(const QValidator* v) { mSpinbox->setValidator(v); } + /** Returns the current validator. The validator controls what keyboard input is accepted + * when the user is editing the value field. + */ + const QValidator* validator() const { return mSpinbox->validator(); } + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + /** Returns the minimum value of the spin box. */ + int minValue() const { return mMinValue; } + /** Returns the maximum value of the spin box. */ + int maxValue() const { return mMaxValue; } + /** Sets the minimum value of the spin box. */ + virtual void setMinValue(int val); + /** Sets the maximum value of the spin box. */ + virtual void setMaxValue(int val); + /** Sets the minimum and maximum values of the spin box. */ + void setRange(int minValue, int maxValue) { setMinValue(minValue); setMaxValue(maxValue); } + /** Returns the current value of the spin box. */ + int value() const { return mSpinbox->value(); } + /** Returns the specified value clamped to the range of the spin box. */ + int bound(int val) const; + + /** Returns the geometry of the right-hand "up" button. */ + QRect upRect() const { return mSpinbox->upRect(); } + /** Returns the geometry of the right-hand "down" button. */ + QRect downRect() const { return mSpinbox->downRect(); } + /** Returns the geometry of the left-hand "up" button. */ + QRect up2Rect() const; + /** Returns the geometry of the left-hand "down" button. */ + QRect down2Rect() const; + + /** Returns the unshifted step increment for the right-hand spin buttons, + * i.e. the amount by which the spin box value changes when a right-hand + * spin button is clicked without the shift key being pressed. + */ + int lineStep() const { return mLineStep; } + /** Returns the shifted step increment for the right-hand spin buttons, + * i.e. the amount by which the spin box value changes when a right-hand + * spin button is clicked while the shift key is pressed. + */ + int lineShiftStep() const { return mLineShiftStep; } + /** Returns the unshifted step increment for the left-hand spin buttons, + * i.e. the amount by which the spin box value changes when a left-hand + * spin button is clicked without the shift key being pressed. + */ + int pageStep() const { return mPageStep; } + /** Returns the shifted step increment for the left-hand spin buttons, + * i.e. the amount by which the spin box value changes when a left-hand + * spin button is clicked while the shift key is pressed. + */ + int pageShiftStep() const { return mPageShiftStep; } + /** Sets the unshifted step increment for the right-hand spin buttons, + * i.e. the amount by which the spin box value changes when a right-hand + * spin button is clicked without the shift key being pressed. + */ + void setLineStep(int step); + /** Sets the unshifted step increments for the two pairs of spin buttons, + * i.e. the amount by which the spin box value changes when a spin button + * is clicked without the shift key being pressed. + * @param line The step increment for the right-hand spin buttons. + * @param page The step increment for the left-hand spin buttons. + */ + void setSteps(int line, int page); + /** Sets the shifted step increments for the two pairs of spin buttons, + * i.e. the amount by which the spin box value changes when a spin button + * is clicked while the shift key is pressed. + * @param line The shift step increment for the right-hand spin buttons. + * @param page The shift step increment for the left-hand spin buttons. + */ + void setShiftSteps(int line, int page); + + /** Increments the current value by adding the unshifted step increment for + * the left-hand spin buttons. + */ + void addPage() { addValue(mPageStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the left-hand spin buttons. + */ + void subtractPage() { addValue(-mPageStep); } + /** Increments the current value by adding the unshifted step increment for + * the right-hand spin buttons. + */ + void addLine() { addValue(mLineStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the right-hand spin buttons. + */ + void subtractLine() { addValue(-mLineStep); } + /** Adjusts the current value by adding @p change. */ + void addValue(int change) { mSpinbox->addValue(change); } + + public slots: + /** Sets the current value to @p val. */ + virtual void setValue(int val) { mSpinbox->setValue(val); } + /** Sets the prefix which is prepended to the start of the displayed text. */ + virtual void setPrefix(const QString& text) { mSpinbox->setPrefix(text); } + /** Sets the suffix which is prepended to the start of the displayed text. */ + virtual void setSuffix(const QString& text) { mSpinbox->setSuffix(text); } + /** Increments the current value by adding the unshifted step increment for + * the right-hand spin buttons. + */ + virtual void stepUp() { addValue(mLineStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the right-hand spin buttons. + */ + virtual void stepDown() { addValue(-mLineStep); } + /** Increments the current value by adding the unshifted step increment for + * the left-hand spin buttons. + */ + virtual void pageUp() { addValue(mPageStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the left-hand spin buttons. + */ + virtual void pageDown() { addValue(-mPageStep); } + /** Selects all the text in the spin box's editor. */ + virtual void selectAll() { mSpinbox->selectAll(); } + /** Sets whether the widget is enabled. */ + virtual void setEnabled(bool enabled); + + signals: + /** Signal which is emitted whenever the value of the spin box changes. */ + void valueChanged(int value); + /** Signal which is emitted whenever the value of the spin box changes. */ + void valueChanged(const QString& valueText); + + protected: + virtual QString mapValueToText(int v) { return mSpinbox->mapValToText(v); } + virtual int mapTextToValue(bool* ok) { return mSpinbox->mapTextToVal(ok); } + virtual void resizeEvent(QResizeEvent*) { arrange(); } + virtual void showEvent(QShowEvent*); + virtual void styleChange(QStyle&); + virtual void getMetrics() const; + + mutable int wUpdown2; // width of second spin widget + mutable int xUpdown2; // x offset of visible area in 'mUpdown2' + mutable int xSpinbox; // x offset of visible area in 'mSpinbox' + mutable int wGap; // gap between mUpdown2Frame and mSpinboxFrame + + protected slots: + virtual void valueChange(); + virtual void stepPage(int); + + private slots: + void updateMirror(); + + private: + void init(); + void arrange(); + int whichButton(QObject* spinWidget, const QPoint&); + void setShiftStepping(bool on); + + // Visible spin box class. + // Declared here to allow use of mSpinBox in inline methods. + class MainSpinBox : public SpinBox + { + public: + MainSpinBox(SpinBox2* sb2, QWidget* parent, const char* name = 0) + : SpinBox(parent, name), owner(sb2) { } + MainSpinBox(int minValue, int maxValue, int step, SpinBox2* sb2, QWidget* parent, const char* name = 0) + : SpinBox(minValue, maxValue, step, parent, name), owner(sb2) { } + void setAlignment(int a) { editor()->setAlignment(a); } + virtual QString mapValueToText(int v) { return owner->mapValueToText(v); } + virtual int mapTextToValue(bool* ok) { return owner->mapTextToValue(ok); } + QString mapValToText(int v) { return SpinBox::mapValueToText(v); } + int mapTextToVal(bool* ok) { return SpinBox::mapTextToValue(ok); } + virtual int shiftStepAdjustment(int oldValue, int shiftStep); + private: + SpinBox2* owner; // owner SpinBox2 + }; + + enum { NO_BUTTON = -1, UP, DOWN, UP2, DOWN2 }; + + static int mReverseLayout; // widgets are mirrored right to left + QFrame* mUpdown2Frame; // contains visible part of the extra pair of spin buttons + QFrame* mSpinboxFrame; // contains the main spin box + ExtraSpinBox* mUpdown2; // the extra pair of spin buttons + MainSpinBox* mSpinbox; // the visible spin box + SpinMirror* mSpinMirror; // image of the extra pair of spin buttons + int mMinValue; + int mMaxValue; + int mLineStep; // right button increment + int mLineShiftStep; // right button increment with shift pressed + int mPageStep; // left button increment + int mPageShiftStep; // left button increment with shift pressed + bool mReverseWithLayout; // reverse button positions if reverse layout (default = true) + + friend class MainSpinBox; +}; + +#endif // SPINBOX2_H diff --git a/kalarm/lib/spinbox2private.h b/kalarm/lib/spinbox2private.h new file mode 100644 index 000000000..74fbeb66c --- /dev/null +++ b/kalarm/lib/spinbox2private.h @@ -0,0 +1,92 @@ +/* + * spinbox2private.h - private classes for SpinBox2 (for Qt 3) + * Program: kalarm + * Copyright © 2005,2006,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SPINBOX2PRIVATE_H +#define SPINBOX2PRIVATE_H + +#include <qcanvas.h> +#include "spinbox.h" + + +/*============================================================================= += Class ExtraSpinBox +* Extra pair of spin buttons for SpinBox2. +* The widget is actually a whole spin box, but only the buttons are displayed. +=============================================================================*/ + +class ExtraSpinBox : public SpinBox +{ + Q_OBJECT + public: + explicit ExtraSpinBox(QWidget* parent, const char* name = 0) + : SpinBox(parent, name), mNewStylePending(false) { } + ExtraSpinBox(int minValue, int maxValue, int step, QWidget* parent, const char* name = 0) + : SpinBox(minValue, maxValue, step, parent, name), mNewStylePending(false) { } + signals: + void styleUpdated(); + protected: + virtual void paintEvent(QPaintEvent*); + virtual void styleChange(QStyle&) { mNewStylePending = true; } + private: + bool mNewStylePending; // style has changed, but not yet repainted +}; + + +/*============================================================================= += Class SpinMirror +* Displays the left-to-right mirror image of a pair of spin buttons, for use +* as the extra spin buttons in a SpinBox2. All mouse clicks are passed on to +* the real extra pair of spin buttons for processing. +* Mirroring in this way allows styles with rounded corners to display correctly. +=============================================================================*/ + +class SpinMirror : public QCanvasView +{ + Q_OBJECT + public: + explicit SpinMirror(SpinBox*, QFrame* spinFrame, QWidget* parent = 0, const char* name = 0); + void setReadOnly(bool ro) { mReadOnly = ro; } + bool isReadOnly() const { return mReadOnly; } + void setNormalButtons(const QPixmap&); + void redraw(const QPixmap&); + + public slots: + virtual void resize(int w, int h); + void redraw(); + + protected: + virtual void contentsMousePressEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsMouseReleaseEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsMouseMoveEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsMouseDoubleClickEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsWheelEvent(QWheelEvent*); + virtual bool event(QEvent*); + + private: + void contentsMouseEvent(QMouseEvent*); + + SpinBox* mSpinbox; // spinbox whose spin buttons are being mirrored + QFrame* mSpinFrame; // widget containing mSpinbox's spin buttons + QWidget* mSpinWidget; // spin buttons in mSpinbox + QPixmap mNormalButtons; // image of spin buttons in unpressed state + bool mReadOnly; // value cannot be changed +}; + +#endif // SPINBOX2PRIVATE_H diff --git a/kalarm/lib/synchtimer.cpp b/kalarm/lib/synchtimer.cpp new file mode 100644 index 000000000..1b681d6eb --- /dev/null +++ b/kalarm/lib/synchtimer.cpp @@ -0,0 +1,233 @@ +/* + * synchtimer.cpp - timers which synchronise to time boundaries + * Program: kalarm + * Copyright (C) 2004, 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" +#include <qtimer.h> +#include <kdebug.h> +#include "synchtimer.moc" + + +/*============================================================================= += Class: SynchTimer += Virtual base class for application-wide timer synchronised to a time boundary. +=============================================================================*/ + +SynchTimer::SynchTimer() +{ + mTimer = new QTimer(this, "mTimer"); +} + +SynchTimer::~SynchTimer() +{ + delete mTimer; + mTimer = 0; +} + +/****************************************************************************** +* Connect to the timer. The timer is started if necessary. +*/ +void SynchTimer::connecT(QObject* receiver, const char* member) +{ + Connection connection(receiver, member); + if (mConnections.find(connection) != mConnections.end()) + return; // the slot is already connected, so ignore request + connect(mTimer, SIGNAL(timeout()), receiver, member); + mConnections.append(connection); + if (!mTimer->isActive()) + { + connect(mTimer, SIGNAL(timeout()), this, SLOT(slotTimer())); + start(); + } +} + +/****************************************************************************** +* Disconnect from the timer. The timer is stopped if no longer needed. +*/ +void SynchTimer::disconnecT(QObject* receiver, const char* member) +{ + if (mTimer) + { + mTimer->disconnect(receiver, member); + if (member) + mConnections.remove(Connection(receiver, member)); + else + { + for (QValueList<Connection>::Iterator it = mConnections.begin(); it != mConnections.end(); ) + { + if ((*it).receiver == receiver) + it = mConnections.remove(it); + else + ++it; + } + } + if (mConnections.isEmpty()) + { + mTimer->disconnect(); + mTimer->stop(); + } + } +} + + +/*============================================================================= += Class: MinuteTimer += Application-wide timer synchronised to the minute boundary. +=============================================================================*/ + +MinuteTimer* MinuteTimer::mInstance = 0; + +MinuteTimer* MinuteTimer::instance() +{ + if (!mInstance) + mInstance = new MinuteTimer; + return mInstance; +} + +/****************************************************************************** +* Called when the timer triggers, or to start the timer. +* Timers can under some circumstances wander off from the correct trigger time, +* so rather than setting a 1 minute interval, calculate the correct next +* interval each time it triggers. +*/ +void MinuteTimer::slotTimer() +{ + kdDebug(5950) << "MinuteTimer::slotTimer()" << endl; + int interval = 62 - QTime::currentTime().second(); + mTimer->start(interval * 1000, true); // execute a single shot +} + + +/*============================================================================= += Class: DailyTimer += Application-wide timer synchronised to midnight. +=============================================================================*/ + +QValueList<DailyTimer*> DailyTimer::mFixedTimers; + +DailyTimer::DailyTimer(const QTime& timeOfDay, bool fixed) + : mTime(timeOfDay), + mFixed(fixed) +{ + if (fixed) + mFixedTimers.append(this); +} + +DailyTimer::~DailyTimer() +{ + if (mFixed) + mFixedTimers.remove(this); +} + +DailyTimer* DailyTimer::fixedInstance(const QTime& timeOfDay, bool create) +{ + for (QValueList<DailyTimer*>::Iterator it = mFixedTimers.begin(); it != mFixedTimers.end(); ++it) + if ((*it)->mTime == timeOfDay) + return *it; + return create ? new DailyTimer(timeOfDay, true) : 0; +} + +/****************************************************************************** +* Disconnect from the timer signal which triggers at the given fixed time of day. +* If there are no remaining connections to that timer, it is destroyed. +*/ +void DailyTimer::disconnect(const QTime& timeOfDay, QObject* receiver, const char* member) +{ + DailyTimer* timer = fixedInstance(timeOfDay, false); + if (!timer) + return; + timer->disconnecT(receiver, member); + if (!timer->hasConnections()) + delete timer; +} + +/****************************************************************************** +* Change the time at which the variable timer triggers. +*/ +void DailyTimer::changeTime(const QTime& newTimeOfDay, bool triggerMissed) +{ + if (mFixed) + return; + if (mTimer->isActive()) + { + mTimer->stop(); + bool triggerNow = false; + if (triggerMissed) + { + QTime now = QTime::currentTime(); + if (now >= newTimeOfDay && now < mTime) + { + // The trigger time is now earlier and it has already arrived today. + // Trigger a timer event immediately. + triggerNow = true; + } + } + mTime = newTimeOfDay; + if (triggerNow) + mTimer->start(0, true); // trigger immediately + else + start(); + } + else + mTime = newTimeOfDay; +} + +/****************************************************************************** +* Initialise the timer to trigger at the specified time. +* This will either be today or tomorrow, depending on whether the trigger time +* has already passed. +*/ +void DailyTimer::start() +{ + // TIMEZONE = local time + QDateTime now = QDateTime::currentDateTime(); + // Find out whether to trigger today or tomorrow. + // In preference, use the last trigger date to determine this, since + // that will avoid possible errors due to daylight savings time changes. + bool today; + if (mLastDate.isValid()) + today = (mLastDate < now.date()); + else + today = (now.time() < mTime); + QDateTime next; + if (today) + next = QDateTime(now.date(), mTime); + else + next = QDateTime(now.date().addDays(1), mTime); + uint interval = next.toTime_t() - now.toTime_t(); + mTimer->start(interval * 1000, true); // execute a single shot + kdDebug(5950) << "DailyTimer::start(at " << mTime.hour() << ":" << mTime.minute() << "): interval = " << interval/3600 << ":" << (interval/60)%60 << ":" << interval%60 << endl; +} + +/****************************************************************************** +* Called when the timer triggers. +* Set the timer to trigger again tomorrow at the specified time. +* Note that if daylight savings time changes occur, this will not be 24 hours +* from now. +*/ +void DailyTimer::slotTimer() +{ + // TIMEZONE = local time + QDateTime now = QDateTime::currentDateTime(); + mLastDate = now.date(); + QDateTime next = QDateTime(mLastDate.addDays(1), mTime); + uint interval = next.toTime_t() - now.toTime_t(); + mTimer->start(interval * 1000, true); // execute a single shot + kdDebug(5950) << "DailyTimer::slotTimer(at " << mTime.hour() << ":" << mTime.minute() << "): interval = " << interval/3600 << ":" << (interval/60)%60 << ":" << interval%60 << endl; +} diff --git a/kalarm/lib/synchtimer.h b/kalarm/lib/synchtimer.h new file mode 100644 index 000000000..551fd1687 --- /dev/null +++ b/kalarm/lib/synchtimer.h @@ -0,0 +1,198 @@ +/* + * synchtimer.h - timers which synchronise to time boundaries + * Program: kalarm + * Copyright (C) 2004, 2005 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SYNCHTIMER_H +#define SYNCHTIMER_H + +/* @file synchtimer.h - timers which synchronise to time boundaries */ + +#include <qobject.h> +#include <qvaluelist.h> +#include <qcstring.h> +#include <qdatetime.h> +class QTimer; + +/** SynchTimer is a virtual base class for application-wide timers synchronised + * to a time boundary. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class SynchTimer : public QObject +{ + Q_OBJECT + public: + virtual ~SynchTimer(); + + struct Connection + { + Connection() { } + Connection(QObject* r, const char* s) : receiver(r), slot(s) { } + bool operator==(const Connection& c) const { return receiver == c.receiver && slot == c.slot; } + QObject* receiver; + const QCString slot; + }; + protected: + SynchTimer(); + virtual void start() = 0; + void connecT(QObject* receiver, const char* member); + void disconnecT(QObject* receiver, const char* member = 0); + bool hasConnections() const { return !mConnections.isEmpty(); } + + QTimer* mTimer; + + protected slots: + virtual void slotTimer() = 0; + + private slots: + void slotReceiverGone(QObject* r) { disconnecT(r); } + + private: + SynchTimer(const SynchTimer&); // prohibit copying + QValueList<Connection> mConnections; // list of current clients +}; + + +/** MinuteTimer is an application-wide timer synchronised to the minute boundary. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class MinuteTimer : public SynchTimer +{ + Q_OBJECT + public: + virtual ~MinuteTimer() { mInstance = 0; } + /** Connect to the timer signal. + * @param receiver Receiving object. + * @param member Slot to activate. + */ + static void connect(QObject* receiver, const char* member) + { instance()->connecT(receiver, member); } + /** Disconnect from the timer signal. + * @param receiver Receiving object. + * @param member Slot to disconnect. If null, all slots belonging to + * @p receiver will be disconnected. + */ + static void disconnect(QObject* receiver, const char* member = 0) + { if (mInstance) mInstance->disconnecT(receiver, member); } + + protected: + MinuteTimer() : SynchTimer() { } + static MinuteTimer* instance(); + virtual void start() { slotTimer(); } + + protected slots: + virtual void slotTimer(); + + private: + static MinuteTimer* mInstance; // the one and only instance +}; + + +/** DailyTimer is an application-wide timer synchronised to a specified time of day, local time. + * + * Daily timers come in two flavours: fixed, which can only be accessed through static methods, + * and variable, whose time can be adjusted and which are accessed through non-static methods. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class DailyTimer : public SynchTimer +{ + Q_OBJECT + public: + virtual ~DailyTimer(); + /** Connect to the timer signal which triggers at the given fixed time of day. + * A new timer is created if necessary. + * @param timeOfDay Time at which the timer is to trigger. + * @param receiver Receiving object. + * @param member Slot to activate. + */ + static void connect(const QTime& timeOfDay, QObject* receiver, const char* member) + { fixedInstance(timeOfDay)->connecT(receiver, member); } + /** Disconnect from the timer signal which triggers at the given fixed time of day. + * If there are no remaining connections to that timer, it is destroyed. + * @param timeOfDay Time at which the timer triggers. + * @param receiver Receiving object. + * @param member Slot to disconnect. If null, all slots belonging to + * @p receiver will be disconnected. + */ + static void disconnect(const QTime& timeOfDay, QObject* receiver, const char* member = 0); + /** Change the time at which this variable timer triggers. + * @param newTimeOfDay New time at which the timer should trigger. + * @param triggerMissed If true, and if @p newTimeOfDay < @p oldTimeOfDay, and if the current + * time is between @p newTimeOfDay and @p oldTimeOfDay, the timer will be + * triggered immediately so as to avoid missing today's trigger. + */ + void changeTime(const QTime& newTimeOfDay, bool triggerMissed = true); + /** Return the current time of day at which this variable timer triggers. */ + QTime timeOfDay() const { return mTime; } + + protected: + /** Construct an instance. + * The constructor is protected to ensure that for variable timers, only derived classes + * can construct instances. This ensures that multiple timers are not created for the same + * use. + */ + DailyTimer(const QTime&, bool fixed); + /** Return the instance which triggers at the specified fixed time of day, + * optionally creating a new instance if necessary. + * @param timeOfDay Time at which the timer triggers. + * @param create If true, create a new instance if none already exists + * for @p timeOfDay. + * @return The instance for @p timeOfDay, or 0 if it does not exist. + */ + static DailyTimer* fixedInstance(const QTime& timeOfDay, bool create = true); + virtual void start(); + + protected slots: + virtual void slotTimer(); + + private: + static QValueList<DailyTimer*> mFixedTimers; // list of timers whose trigger time is fixed + QTime mTime; + QDate mLastDate; // the date on which the timer was last triggered + bool mFixed; // the time at which the timer triggers cannot be changed +}; + + +/** MidnightTimer is an application-wide timer synchronised to midnight, local time. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class MidnightTimer +{ + public: + /** Connect to the timer signal. + * @param receiver Receiving object. + * @param member Slot to activate. + */ + static void connect(QObject* receiver, const char* member) + { DailyTimer::connect(QTime(0,0), receiver, member); } + /** Disconnect from the timer signal. + * @param receiver Receiving object. + * @param member Slot to disconnect. If null, all slots belonging to + * @p receiver will be disconnected. + */ + static void disconnect(QObject* receiver, const char* member = 0) + { DailyTimer::disconnect(QTime(0,0), receiver, member); } + +}; + +#endif // SYNCHTIMER_H + diff --git a/kalarm/lib/timeedit.cpp b/kalarm/lib/timeedit.cpp new file mode 100644 index 000000000..8aabb90b6 --- /dev/null +++ b/kalarm/lib/timeedit.cpp @@ -0,0 +1,207 @@ +/* + * timeedit.cpp - time-of-day edit widget, with AM/PM shown depending on locale + * Program: kalarm + * Copyright (C) 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" + +#include <kglobal.h> +#include <klocale.h> + +#include "combobox.h" +#include "timespinbox.h" +#include "timeedit.moc" + + +TimeEdit::TimeEdit(QWidget* parent, const char* name) + : QHBox(parent, name), + mAmPm(0), + mAmIndex(-1), + mPmIndex(-1), + mReadOnly(false) +{ + bool use12hour = KGlobal::locale()->use12Clock(); + mSpinBox = new TimeSpinBox(!use12hour, this); + mSpinBox->setFixedSize(mSpinBox->sizeHint()); + connect(mSpinBox, SIGNAL(valueChanged(int)), SLOT(slotValueChanged(int))); + if (use12hour) + { + mAmPm = new ComboBox(this); + setAmPmCombo(1, 1); // add "am" and "pm" options to the combo box + mAmPm->setFixedSize(mAmPm->sizeHint()); + connect(mAmPm, SIGNAL(highlighted(int)), SLOT(slotAmPmChanged(int))); + } +} + +void TimeEdit::setReadOnly(bool ro) +{ + if (ro != mReadOnly) + { + mReadOnly = ro; + mSpinBox->setReadOnly(ro); + if (mAmPm) + mAmPm->setReadOnly(ro); + } +} + +int TimeEdit::value() const +{ + return mSpinBox->value(); +} + +bool TimeEdit::isValid() const +{ + return mSpinBox->isValid(); +} + +/****************************************************************************** + * Set the edit value as valid or invalid. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + */ +void TimeEdit::setValid(bool valid) +{ + bool oldValid = mSpinBox->isValid(); + if (valid && !oldValid + || !valid && oldValid) + { + mSpinBox->setValid(valid); + if (mAmPm) + mAmPm->setCurrentItem(0); + } +} + +/****************************************************************************** + * Set the widget's value. + */ +void TimeEdit::setValue(int minutes) +{ + if (mAmPm) + { + int i = (minutes >= 720) ? mPmIndex : mAmIndex; + mAmPm->setCurrentItem(i >= 0 ? i : 0); + } + mSpinBox->setValue(minutes); +} + +bool TimeEdit::wrapping() const +{ + return mSpinBox->wrapping(); +} + +void TimeEdit::setWrapping(bool on) +{ + mSpinBox->setWrapping(on); +} + +int TimeEdit::minValue() const +{ + return mSpinBox->minValue(); +} + +int TimeEdit::maxValue() const +{ + return mSpinBox->maxValue(); +} + +void TimeEdit::setMinValue(int minutes) +{ + if (mAmPm) + setAmPmCombo((minutes < 720 ? 1 : 0), -1); // insert/remove "am" in combo box + mSpinBox->setMinValue(minutes); +} + +void TimeEdit::setMaxValue(int minutes) +{ + if (mAmPm) + setAmPmCombo(-1, (minutes < 720 ? 0 : 1)); // insert/remove "pm" in combo box + mSpinBox->setMaxValue(minutes); +} + +/****************************************************************************** + * Called when the spin box value has changed. + */ +void TimeEdit::slotValueChanged(int value) +{ + if (mAmPm) + { + bool pm = (mAmPm->currentItem() == mPmIndex); + if (pm && value < 720) + mAmPm->setCurrentItem(mAmIndex); + else if (!pm && value >= 720) + mAmPm->setCurrentItem(mPmIndex); + } + emit valueChanged(value); +} + +/****************************************************************************** + * Called when a new selection has been made by the user in the AM/PM combo box. + * Adjust the current time value by 12 hours. + */ +void TimeEdit::slotAmPmChanged(int item) +{ + if (mAmPm) + { + int value = mSpinBox->value(); + if (item == mPmIndex && value < 720) + mSpinBox->setValue(value + 720); + else if (item != mPmIndex && value >= 720) + mSpinBox->setValue(value - 720); + } +} + +/****************************************************************************** + * Set up the AM/PM combo box to contain the specified items. + */ +void TimeEdit::setAmPmCombo(int am, int pm) +{ + if (am > 0 && mAmIndex < 0) + { + // Insert "am" + mAmIndex = 0; + mAmPm->insertItem(KGlobal::locale()->translate("am"), mAmIndex); + if (mPmIndex >= 0) + mPmIndex = 1; + mAmPm->setCurrentItem(mPmIndex >= 0 ? mPmIndex : mAmIndex); + } + else if (am == 0 && mAmIndex >= 0) + { + // Remove "am" + mAmPm->removeItem(mAmIndex); + mAmIndex = -1; + if (mPmIndex >= 0) + mPmIndex = 0; + mAmPm->setCurrentItem(mPmIndex); + } + + if (pm > 0 && mPmIndex < 0) + { + // Insert "pm" + mPmIndex = mAmIndex + 1; + mAmPm->insertItem(KGlobal::locale()->translate("pm"), mPmIndex); + if (mAmIndex < 0) + mAmPm->setCurrentItem(mPmIndex); + } + else if (pm == 0 && mPmIndex >= 0) + { + // Remove "pm" + mAmPm->removeItem(mPmIndex); + mPmIndex = -1; + mAmPm->setCurrentItem(mAmIndex); + } +} diff --git a/kalarm/lib/timeedit.h b/kalarm/lib/timeedit.h new file mode 100644 index 000000000..5888c7665 --- /dev/null +++ b/kalarm/lib/timeedit.h @@ -0,0 +1,122 @@ +/* + * timeedit.h - time-of-day edit widget, with AM/PM shown depending on locale + * Program: kalarm + * Copyright (C) 2004 by David Jarvie <software@astrojar.org.uk> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TIMEEDIT_H +#define TIMEEDIT_H + +#include <qdatetime.h> +#include <qhbox.h> + +class ComboBox; +class TimeSpinBox; + + +/** + * @short Widget to enter a time of day. + * + * The TimeEdit class provides a widget to enter a time of day in hours and minutes, + * using a 12- or 24-hour clock according to the user's locale settings. + * + * It displays a TimeSpinBox widget to enter hours and minutes. If a 12-hour clock is + * being used, it also displays a combo box to select am or pm. + * + * TimeSpinBox displays a spin box with two pairs of spin buttons, one for hours and + * one for minutes. It provides accelerated stepping using the spin buttons, when the + * shift key is held down (inherited from SpinBox2). The default shift steps are 5 + * minutes and 6 hours. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class TimeEdit : public QHBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit TimeEdit(QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the widget is read-only for the user. If read-only, + * the time cannot be edited and the spin buttons and am/pm combo box + * are inactive. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns true if the widget contains a valid value. */ + bool isValid() const; + /** Sets whether the edit value is valid. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + * @param valid True to set the value valid, false to set it invalid. + */ + void setValid(bool valid); + /** Returns the entered time as a value in minutes. */ + int value() const; + /** Returns the entered time as a QTime value. */ + QTime time() const { int m = value(); return QTime(m/60, m%60); } + /** Returns true if it is possible to step the value from the highest value to the lowest value and vice versa. */ + bool wrapping() const; + /** Sets whether it is possible to step the value from the highest value to the lowest value and vice versa. + * @param on True to enable wrapping, else false. + */ + void setWrapping(bool on); + /** Returns the minimum value of the widget in minutes. */ + int minValue() const; + /** Returns the maximum value of the widget in minutes. */ + int maxValue() const; + /** Returns the maximum value of the widget as a QTime value. */ + QTime maxTime() const { int mv = maxValue(); return QTime(mv/60, mv%60); } + /** Sets the minimum value of the widget. */ + void setMinValue(int minutes); + /** Sets the maximum value of the widget. */ + void setMaxValue(int minutes); + /** Sets the maximum value of the widget. */ + void setMaxValue(const QTime& time) { setMaxValue(time.hour()*60 + time.minute()); } + public slots: + /** Sets the value of the widget. */ + virtual void setValue(int minutes); + /** Sets the value of the widget. */ + void setValue(const QTime& t) { setValue(t.hour()*60 + t.minute()); } + signals: + /** This signal is emitted every time the value of the widget changes + * (for whatever reason). + * @param minutes The new value. + */ + void valueChanged(int minutes); + + private slots: + void slotValueChanged(int); + void slotAmPmChanged(int item); + private: + void setAmPmCombo(int am, int pm); + + TimeSpinBox* mSpinBox; // always holds the 24-hour time + ComboBox* mAmPm; + int mAmIndex; // mAmPm index to "am", or -1 if none + int mPmIndex; // mAmPm index to "pm", or -1 if none + bool mReadOnly; // the widget is read only +}; + +#endif // TIMEEDIT_H diff --git a/kalarm/lib/timeperiod.cpp b/kalarm/lib/timeperiod.cpp new file mode 100644 index 000000000..22a7c3179 --- /dev/null +++ b/kalarm/lib/timeperiod.cpp @@ -0,0 +1,384 @@ +/* + * timeperiod.h - time period data entry widget + * Program: kalarm + * Copyright © 2003,2004,2007,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" + +#include <qwidgetstack.h> +#include <qwhatsthis.h> + +#include <klocale.h> +#include <kdialog.h> + +#include "combobox.h" +#include "spinbox.h" +#include "timespinbox.h" +#include "timeperiod.moc" + + +// Collect these widget labels together to ensure consistent wording and +// translations across different modules. +QString TimePeriod::i18n_minutes() { return i18n("minutes"); } +QString TimePeriod::i18n_Minutes() { return i18n("Minutes"); } +QString TimePeriod::i18n_hours_mins() { return i18n("hours/minutes"); } +QString TimePeriod::i18n_Hours_Mins() { return i18n("Hours/Minutes"); } +QString TimePeriod::i18n_days() { return i18n("days"); } +QString TimePeriod::i18n_Days() { return i18n("Days"); } +QString TimePeriod::i18n_weeks() { return i18n("weeks"); } +QString TimePeriod::i18n_Weeks() { return i18n("Weeks"); } + +static const int maxMinutes = 1000*60-1; // absolute maximum value for hours:minutes = 999H59M + +/*============================================================================= += Class TimePeriod += Contains a time unit combo box, plus a time spinbox, to select a time period. +=============================================================================*/ + +TimePeriod::TimePeriod(bool allowHourMinute, QWidget* parent, const char* name) + : QHBox(parent, name), + mMaxDays(9999), + mNoHourMinute(!allowHourMinute), + mReadOnly(false) +{ + setSpacing(KDialog::spacingHint()); + + mSpinStack = new QWidgetStack(this); + mSpinBox = new SpinBox(mSpinStack); + mSpinBox->setLineStep(1); + mSpinBox->setLineShiftStep(10); + mSpinBox->setRange(1, mMaxDays); + connect(mSpinBox, SIGNAL(valueChanged(int)), SLOT(slotDaysChanged(int))); + mSpinStack->addWidget(mSpinBox, 0); + + mTimeSpinBox = new TimeSpinBox(0, 99999, mSpinStack); + mTimeSpinBox->setRange(1, maxMinutes); // max 999H59M + connect(mTimeSpinBox, SIGNAL(valueChanged(int)), SLOT(slotTimeChanged(int))); + mSpinStack->addWidget(mTimeSpinBox, 1); + + mSpinStack->setFixedSize(mSpinBox->sizeHint().expandedTo(mTimeSpinBox->sizeHint())); + mHourMinuteRaised = mNoHourMinute; + showHourMin(!mNoHourMinute); + + mUnitsCombo = new ComboBox(false, this); + if (mNoHourMinute) + mDateOnlyOffset = 2; + else + { + mDateOnlyOffset = 0; + mUnitsCombo->insertItem(i18n_minutes()); + mUnitsCombo->insertItem(i18n_hours_mins()); + } + mUnitsCombo->insertItem(i18n_days()); + mUnitsCombo->insertItem(i18n_weeks()); + mMaxUnitShown = WEEKS; + mUnitsCombo->setFixedSize(mUnitsCombo->sizeHint()); + connect(mUnitsCombo, SIGNAL(activated(int)), SLOT(slotUnitsSelected(int))); + + setFocusProxy(mUnitsCombo); + setTabOrder(mUnitsCombo, mSpinStack); +} + +void TimePeriod::setReadOnly(bool ro) +{ + if (ro != mReadOnly) + { + mReadOnly = ro; + mSpinBox->setReadOnly(ro); + mTimeSpinBox->setReadOnly(ro); + mUnitsCombo->setReadOnly(ro); + } +} + +/****************************************************************************** +* Set whether the editor text is to be selected whenever spin buttons are +* clicked. Default is to select them. +*/ +void TimePeriod::setSelectOnStep(bool sel) +{ + mSpinBox->setSelectOnStep(sel); + mTimeSpinBox->setSelectOnStep(sel); +} + +/****************************************************************************** +* Set the input focus on the count field. +*/ +void TimePeriod::setFocusOnCount() +{ + mSpinStack->setFocus(); +} + +/****************************************************************************** +* Set the maximum values for the hours:minutes and days/weeks spinboxes. +* If 'hourmin' = 0, the hours:minutes maximum is left unchanged. +*/ +void TimePeriod::setMaximum(int hourmin, int days) +{ + int oldmins = minutes(); + if (hourmin > 0) + { + if (hourmin > maxMinutes) + hourmin = maxMinutes; + mTimeSpinBox->setRange(1, hourmin); + } + mMaxDays = (days >= 0) ? days : 0; + adjustDayWeekShown(); + setUnitRange(); + int mins = minutes(); + if (mins != oldmins) + emit valueChanged(mins); +} + +/****************************************************************************** + * Get the specified number of minutes. + * Reply = 0 if error. + */ +int TimePeriod::minutes() const +{ + int factor = 0; + switch (mUnitsCombo->currentItem() + mDateOnlyOffset) + { + case HOURS_MINUTES: + return mTimeSpinBox->value(); + case MINUTES: factor = 1; break; + case DAYS: factor = 24*60; break; + case WEEKS: factor = 7*24*60; break; + } + return mSpinBox->value() * factor; +} + +/****************************************************************************** +* Initialise the controls with a specified time period. +* The time unit combo-box is initialised to 'defaultUnits', but if 'dateOnly' +* is true, it will never be initialised to minutes or hours/minutes. +*/ +void TimePeriod::setMinutes(int mins, bool dateOnly, TimePeriod::Units defaultUnits) +{ + int oldmins = minutes(); + if (!dateOnly && mNoHourMinute) + dateOnly = true; + int item; + if (mins) + { + int count = mins; + if (mins % (24*60)) + item = (defaultUnits == MINUTES && count <= mSpinBox->maxValue()) ? MINUTES : HOURS_MINUTES; + else if (mins % (7*24*60)) + { + item = DAYS; + count = mins / (24*60); + } + else + { + item = WEEKS; + count = mins / (7*24*60); + } + if (item < mDateOnlyOffset) + item = mDateOnlyOffset; + else if (item > mMaxUnitShown) + item = mMaxUnitShown; + mUnitsCombo->setCurrentItem(item - mDateOnlyOffset); + if (item == HOURS_MINUTES) + mTimeSpinBox->setValue(count); + else + mSpinBox->setValue(count); + item = setDateOnly(mins, dateOnly, false); + } + else + { + item = defaultUnits; + if (item < mDateOnlyOffset) + item = mDateOnlyOffset; + else if (item > mMaxUnitShown) + item = mMaxUnitShown; + mUnitsCombo->setCurrentItem(item - mDateOnlyOffset); + if (dateOnly && !mDateOnlyOffset || !dateOnly && mDateOnlyOffset) + item = setDateOnly(mins, dateOnly, false); + } + showHourMin(item == HOURS_MINUTES && !mNoHourMinute); + + int newmins = minutes(); + if (newmins != oldmins) + emit valueChanged(newmins); +} + +/****************************************************************************** +* Enable/disable hours/minutes units (if hours/minutes were permitted in the +* constructor). +*/ +TimePeriod::Units TimePeriod::setDateOnly(int mins, bool dateOnly, bool signal) +{ + int oldmins = 0; + if (signal) + oldmins = minutes(); + int index = mUnitsCombo->currentItem(); + Units units = static_cast<Units>(index + mDateOnlyOffset); + if (!mNoHourMinute) + { + if (!dateOnly && mDateOnlyOffset) + { + // Change from date-only to allow hours/minutes + mUnitsCombo->insertItem(i18n_minutes(), 0); + mUnitsCombo->insertItem(i18n_hours_mins(), 1); + mDateOnlyOffset = 0; + adjustDayWeekShown(); + mUnitsCombo->setCurrentItem(index += 2); + } + else if (dateOnly && !mDateOnlyOffset) + { + // Change from allowing hours/minutes to date-only + mUnitsCombo->removeItem(0); + mUnitsCombo->removeItem(0); + mDateOnlyOffset = 2; + if (index > 2) + index -= 2; + else + index = 0; + adjustDayWeekShown(); + mUnitsCombo->setCurrentItem(index); + if (units == HOURS_MINUTES || units == MINUTES) + { + // Set units to days and round up the warning period + units = DAYS; + mUnitsCombo->setCurrentItem(DAYS - mDateOnlyOffset); + mSpinBox->setValue((mins + 1439) / 1440); + } + showHourMin(false); + } + } + + if (signal) + { + int newmins = minutes(); + if (newmins != oldmins) + emit valueChanged(newmins); + } + return units; +} + +/****************************************************************************** +* Adjust the days/weeks units shown to suit the maximum days limit. +*/ +void TimePeriod::adjustDayWeekShown() +{ + Units newMaxUnitShown = (mMaxDays >= 7) ? WEEKS : (mMaxDays || mDateOnlyOffset) ? DAYS : HOURS_MINUTES; + if (newMaxUnitShown > mMaxUnitShown) + { + if (mMaxUnitShown < DAYS) + mUnitsCombo->insertItem(i18n_days()); + if (newMaxUnitShown == WEEKS) + mUnitsCombo->insertItem(i18n_weeks()); + } + else if (newMaxUnitShown < mMaxUnitShown) + { + if (mMaxUnitShown == WEEKS) + mUnitsCombo->removeItem(WEEKS - mDateOnlyOffset); + if (newMaxUnitShown < DAYS) + mUnitsCombo->removeItem(DAYS - mDateOnlyOffset); + } + mMaxUnitShown = newMaxUnitShown; +} + +/****************************************************************************** +* Set the maximum value which may be entered into the day/week count field, +* depending on the current unit selection. +*/ +void TimePeriod::setUnitRange() +{ + int maxval; + switch (static_cast<Units>(mUnitsCombo->currentItem() + mDateOnlyOffset)) + { + case WEEKS: + maxval = mMaxDays / 7; + if (maxval) + break; + mUnitsCombo->setCurrentItem(DAYS - mDateOnlyOffset); + // fall through to DAYS + case DAYS: + maxval = mMaxDays ? mMaxDays : 1; + break; + case MINUTES: + maxval = mTimeSpinBox->maxValue(); + break; + case HOURS_MINUTES: + default: + return; + } + mSpinBox->setRange(1, maxval); +} + +/****************************************************************************** +* Called when a new item is made current in the time units combo box. +*/ +void TimePeriod::slotUnitsSelected(int index) +{ + setUnitRange(); + showHourMin(index + mDateOnlyOffset == HOURS_MINUTES); + emit valueChanged(minutes()); +} + +/****************************************************************************** +* Called when the value of the days/weeks spin box changes. +*/ +void TimePeriod::slotDaysChanged(int) +{ + if (!mHourMinuteRaised) + emit valueChanged(minutes()); +} + +/****************************************************************************** +* Called when the value of the time spin box changes. +*/ +void TimePeriod::slotTimeChanged(int value) +{ + if (mHourMinuteRaised) + emit valueChanged(value); +} + +/****************************************************************************** + * Set the currently displayed count widget. + */ +void TimePeriod::showHourMin(bool hourMinute) +{ + if (hourMinute != mHourMinuteRaised) + { + mHourMinuteRaised = hourMinute; + if (hourMinute) + { + mSpinStack->raiseWidget(mTimeSpinBox); + mSpinStack->setFocusProxy(mTimeSpinBox); + } + else + { + mSpinStack->raiseWidget(mSpinBox); + mSpinStack->setFocusProxy(mSpinBox); + } + } +} + +/****************************************************************************** + * Set separate WhatsThis texts for the count spinboxes and the units combobox. + * If the hours:minutes text is omitted, both spinboxes are set to the same + * WhatsThis text. + */ +void TimePeriod::setWhatsThis(const QString& units, const QString& dayWeek, const QString& hourMin) +{ + QWhatsThis::add(mUnitsCombo, units); + QWhatsThis::add(mSpinBox, dayWeek); + QWhatsThis::add(mTimeSpinBox, (hourMin.isNull() ? dayWeek : hourMin)); +} diff --git a/kalarm/lib/timeperiod.h b/kalarm/lib/timeperiod.h new file mode 100644 index 000000000..ef1a3b5c3 --- /dev/null +++ b/kalarm/lib/timeperiod.h @@ -0,0 +1,146 @@ +/* + * timeperiod.cpp - time period data entry widget + * Program: kalarm + * Copyright © 2003,2004,2007,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TIMEPERIOD_H +#define TIMEPERIOD_H + +#include <qhbox.h> +#include <qstring.h> + +class QWidgetStack; +class ComboBox; +class SpinBox; +class TimeSpinBox; + + +/** + * @short Time period entry widget. + * + * The TimePeriod class provides a widget for entering a time period as a number of + * weeks, days, hours and minutes, or minutes. + * + * It displays a combo box to select the time units (weeks, days, hours and minutes, or + * minutes) alongside a spin box to enter the number of units. The type of spin box + * displayed alters according to the units selection: day, week and minute values are + * entered in a normal spin box, while hours and minutes are entered in a time spin box + * (with two pairs of spin buttons, one for hours and one for minutes). + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class TimePeriod : public QHBox +{ + Q_OBJECT + public: + /** Units for the time period. + * @li MINUTES - the time period is entered as a number of minutes. + * @li HOURS_MINUTES - the time period is entered as an hours/minutes value. + * @li DAYS - the time period is entered as a number of days. + * @li WEEKS - the time period is entered as a number of weeks. + */ + enum Units { MINUTES, HOURS_MINUTES, DAYS, WEEKS }; + + /** Constructor. + * @param allowMinute Set false to prevent hours/minutes or minutes from + * being allowed as units; only days and weeks can ever be used, + * regardless of other method calls. Set true to allow minutes, + * hours/minutes, days or weeks as units. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + TimePeriod(bool allowMinute, QWidget* parent, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the widget is read-only for the user. If read-only, + * the time period cannot be edited and the units combo box is inactive. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Gets the entered time period expressed in minutes. */ + int minutes() const; + /** Initialises the time period value. + * @param minutes The value of the time period to set, expressed as a number of minutes. + * @param dateOnly True to restrict the units available in the combo box to days or weeks. + * @param defaultUnits The units to display initially in the combo box. + */ + void setMinutes(int minutes, bool dateOnly, Units defaultUnits); + /** Enables or disables minutes and hours/minutes units in the combo box. To + * disable minutes and hours/minutes, set @p dateOnly true; to enable minutes + * and hours/minutes, set @p dateOnly false. But note that minutes and + * hours/minutes cannot be enabled if it was disallowed in the constructor. + */ + void setDateOnly(bool dateOnly) { setDateOnly(minutes(), dateOnly, true); } + /** Sets the maximum values for the minutes and hours/minutes, and days/weeks + * spin boxes. + * Set @p hourmin = 0 to leave the minutes and hours/minutes maximum unchanged. + */ + void setMaximum(int hourmin, int days); + /** Sets whether the editor text is to be selected whenever spin buttons are + * clicked. The default is to select it. + */ + void setSelectOnStep(bool select); + /** Sets the input focus to the count field. */ + void setFocusOnCount(); + /** Sets separate WhatsThis texts for the count spin boxes and the units combo box. + * If @p hourMin is omitted, both spin boxes are set to the same WhatsThis text. + */ + void setWhatsThis(const QString& units, const QString& dayWeek, const QString& hourMin = QString::null); + + static QString i18n_minutes(); // text of 'minutes' units, lower case + static QString i18n_Minutes(); // text of 'Minutes' units, initial capitals + static QString i18n_hours_mins(); // text of 'hours/minutes' units, lower case + static QString i18n_Hours_Mins(); // text of 'Hours/Minutes' units, initial capitals + static QString i18n_days(); // text of 'days' units, lower case + static QString i18n_Days(); // text of 'Days' units, initial capital + static QString i18n_weeks(); // text of 'weeks' units, lower case + static QString i18n_Weeks(); // text of 'Weeks' units, initial capital + + signals: + /** This signal is emitted whenever the value held in the widget changes. + * @param minutes The current value of the time period, expressed in minutes. + */ + void valueChanged(int minutes); // value has changed + + private slots: + void slotUnitsSelected(int index); + void slotDaysChanged(int); + void slotTimeChanged(int minutes); + + private: + Units setDateOnly(int minutes, bool dateOnly, bool signal); + void setUnitRange(); + void showHourMin(bool hourMin); + void adjustDayWeekShown(); + + QWidgetStack* mSpinStack; // displays either the days/weeks or hours:minutes spinbox + SpinBox* mSpinBox; // the minutes/days/weeks value spinbox + TimeSpinBox* mTimeSpinBox; // the hours:minutes value spinbox + ComboBox* mUnitsCombo; + int mMaxDays; // maximum day count + int mDateOnlyOffset; // for mUnitsCombo: 2 if minutes & hours/minutes are disabled, else 0 + Units mMaxUnitShown; // for mUnitsCombo: maximum units shown + bool mNoHourMinute; // hours/minutes cannot be displayed, ever + bool mReadOnly; // the widget is read only + bool mHourMinuteRaised; // hours:minutes spinbox is currently displayed +}; + +#endif // TIMEPERIOD_H diff --git a/kalarm/lib/timespinbox.cpp b/kalarm/lib/timespinbox.cpp new file mode 100644 index 000000000..7a22cd230 --- /dev/null +++ b/kalarm/lib/timespinbox.cpp @@ -0,0 +1,364 @@ +/* + * timespinbox.cpp - time spinbox widget + * Program: kalarm + * Copyright © 2001-2004,2007,2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kalarm.h" + +#include <qvalidator.h> +#include <qlineedit.h> +#include <klocale.h> + +#include "timespinbox.moc" + + +class TimeSpinBox::TimeValidator : public QValidator +{ + public: + TimeValidator(int minMin, int maxMin, QWidget* parent, const char* name = 0) + : QValidator(parent, name), + minMinute(minMin), maxMinute(maxMin), m12Hour(false), mPm(false) { } + virtual State validate(QString&, int&) const; + int minMinute, maxMinute; + bool m12Hour; + bool mPm; +}; + + +/*============================================================================= += Class TimeSpinBox += This is a spin box displaying a time in the format hh:mm, with a pair of += spin buttons for each of the hour and minute values. += It can operate in 3 modes: += 1) a time of day using the 24-hour clock. += 2) a time of day using the 12-hour clock. The value is held as 0:00 - 23:59, += but is displayed as 12:00 - 11:59. This is for use in a TimeEdit widget. += 3) a length of time, not restricted to the length of a day. +=============================================================================*/ + +/****************************************************************************** + * Construct a wrapping 00:00 - 23:59, or 12:00 - 11:59 time spin box. + */ +TimeSpinBox::TimeSpinBox(bool use24hour, QWidget* parent, const char* name) + : SpinBox2(0, 1439, 1, 60, parent, name), + mMinimumValue(0), + m12Hour(!use24hour), + mPm(false), + mInvalid(false), + mEnteredSetValue(false) +{ + mValidator = new TimeValidator(0, 1439, this, "TimeSpinBox validator"); + mValidator->m12Hour = m12Hour; + setValidator(mValidator); + setWrapping(true); + setReverseWithLayout(false); // keep buttons the same way round even if right-to-left language + setShiftSteps(5, 360); // shift-left button increments 5 min / 6 hours + setSelectOnStep(false); + setAlignment(Qt::AlignHCenter); + connect(this, SIGNAL(valueChanged(int)), SLOT(slotValueChanged(int))); +} + +/****************************************************************************** + * Construct a non-wrapping time spin box. + */ +TimeSpinBox::TimeSpinBox(int minMinute, int maxMinute, QWidget* parent, const char* name) + : SpinBox2(minMinute, maxMinute, 1, 60, parent, name), + mMinimumValue(minMinute), + m12Hour(false), + mInvalid(false), + mEnteredSetValue(false) +{ + mValidator = new TimeValidator(minMinute, maxMinute, this, "TimeSpinBox validator"); + setValidator(mValidator); + setReverseWithLayout(false); // keep buttons the same way round even if right-to-left language + setShiftSteps(5, 300); // shift-left button increments 5 min / 5 hours + setSelectOnStep(false); + setAlignment(QApplication::reverseLayout() ? Qt::AlignLeft : Qt::AlignRight); +} + +QString TimeSpinBox::shiftWhatsThis() +{ + return i18n("Press the Shift key while clicking the spin buttons to adjust the time by a larger step (6 hours / 5 minutes)."); +} + +QTime TimeSpinBox::time() const +{ + return QTime(value() / 60, value() % 60); +} + +QString TimeSpinBox::mapValueToText(int v) +{ + if (m12Hour) + { + if (v < 60) + v += 720; // convert 0:nn to 12:nn + else if (v >= 780) + v -= 720; // convert 13 - 23 hours to 1 - 11 + } + QString s; + s.sprintf((wrapping() ? "%02d:%02d" : "%d:%02d"), v/60, v%60); + return s; +} + +/****************************************************************************** + * Convert the user-entered text to a value in minutes. + * The allowed formats are: + * [hour]:[minute], where minute must be non-blank, or + * hhmm, 4 digits, where hour < 24. + */ +int TimeSpinBox::mapTextToValue(bool* ok) +{ + QString text = cleanText(); + int colon = text.find(':'); + if (colon >= 0) + { + // [h]:m format for any time value + QString hour = text.left(colon).stripWhiteSpace(); + QString minute = text.mid(colon + 1).stripWhiteSpace(); + if (!minute.isEmpty()) + { + bool okmin; + bool okhour = true; + int m = minute.toUInt(&okmin); + int h = 0; + if (!hour.isEmpty()) + h = hour.toUInt(&okhour); + if (okhour && okmin && m < 60) + { + if (m12Hour) + { + if (h == 0 || h > 12) + h = 100; // error + else if (h == 12) + h = 0; // convert 12:nn to 0:nn + if (mPm) + h += 12; // convert to PM + } + int t = h * 60 + m; + if (t >= mMinimumValue && t <= maxValue()) + { + if (ok) + *ok = true; + return t; + } + } + } + } + else if (text.length() == 4) + { + // hhmm format for time of day + bool okn; + int mins = text.toUInt(&okn); + if (okn) + { + int m = mins % 100; + int h = mins / 100; + if (m12Hour) + { + if (h == 0 || h > 12) + h = 100; // error + else if (h == 12) + h = 0; // convert 12:nn to 0:nn + if (mPm) + h += 12; // convert to PM + } + int t = h * 60 + m; + if (h < 24 && m < 60 && t >= mMinimumValue && t <= maxValue()) + { + if (ok) + *ok = true; + return t; + } + } + + } + if (ok) + *ok = false; + return 0; +} + +/****************************************************************************** + * Set the spin box as valid or invalid. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + */ +void TimeSpinBox::setValid(bool valid) +{ + if (valid && mInvalid) + { + mInvalid = false; + if (value() < mMinimumValue) + SpinBox2::setValue(mMinimumValue); + setSpecialValueText(QString()); + SpinBox2::setMinValue(mMinimumValue); + } + else if (!valid && !mInvalid) + { + mInvalid = true; + SpinBox2::setMinValue(mMinimumValue - 1); + setSpecialValueText(QString::fromLatin1("**:**")); + SpinBox2::setValue(mMinimumValue - 1); + } +} + +/****************************************************************************** +* Set the spin box's minimum value. +*/ +void TimeSpinBox::setMinValue(int minutes) +{ + mMinimumValue = minutes; + SpinBox2::setMinValue(mMinimumValue - (mInvalid ? 1 : 0)); +} + +/****************************************************************************** + * Set the spin box's value. + */ +void TimeSpinBox::setValue(int minutes) +{ + if (!mEnteredSetValue) + { + mEnteredSetValue = true; + mPm = (minutes >= 720); + if (minutes > maxValue()) + setValid(false); + else + { + if (mInvalid) + { + mInvalid = false; + setSpecialValueText(QString()); + SpinBox2::setMinValue(mMinimumValue); + } + SpinBox2::setValue(minutes); + mEnteredSetValue = false; + } + } +} + +/****************************************************************************** + * Step the spin box value. + * If it was invalid, set it valid and set the value to the minimum. + */ +void TimeSpinBox::stepUp() +{ + if (mInvalid) + setValid(true); + else + SpinBox2::stepUp(); +} + +void TimeSpinBox::stepDown() +{ + if (mInvalid) + setValid(true); + else + SpinBox2::stepDown(); +} + +bool TimeSpinBox::isValid() const +{ + return value() >= mMinimumValue; +} + +void TimeSpinBox::slotValueChanged(int value) +{ + mPm = mValidator->mPm = (value >= 720); +} + +QSize TimeSpinBox::sizeHint() const +{ + QSize sz = SpinBox2::sizeHint(); + QFontMetrics fm(font()); + return QSize(sz.width() + fm.width(":"), sz.height()); +} + +QSize TimeSpinBox::minimumSizeHint() const +{ + QSize sz = SpinBox2::minimumSizeHint(); + QFontMetrics fm(font()); + return QSize(sz.width() + fm.width(":"), sz.height()); +} + +/****************************************************************************** + * Validate the time spin box input. + * The entered time must either be 4 digits, or it must contain a colon, but + * hours may be blank. + */ +QValidator::State TimeSpinBox::TimeValidator::validate(QString& text, int& /*cursorPos*/) const +{ + QString cleanText = text.stripWhiteSpace(); + if (cleanText.isEmpty()) + return QValidator::Intermediate; + QValidator::State state = QValidator::Acceptable; + QString hour; + bool ok; + int hr = 0; + int mn = 0; + int colon = cleanText.find(':'); + if (colon >= 0) + { + QString minute = cleanText.mid(colon + 1); + if (minute.isEmpty()) + state = QValidator::Intermediate; + else if ((mn = minute.toUInt(&ok)) >= 60 || !ok) + return QValidator::Invalid; + + hour = cleanText.left(colon); + } + else if (maxMinute >= 1440) + { + // The hhmm form of entry is only allowed for time-of-day, i.e. <= 2359 + hour = cleanText; + state = QValidator::Intermediate; + } + else + { + if (cleanText.length() > 4) + return QValidator::Invalid; + if (cleanText.length() < 4) + state = QValidator::Intermediate; + hour = cleanText.left(2); + QString minute = cleanText.mid(2); + if (!minute.isEmpty() + && ((mn = minute.toUInt(&ok)) >= 60 || !ok)) + return QValidator::Invalid; + } + + if (!hour.isEmpty()) + { + hr = hour.toUInt(&ok); + if (m12Hour) + { + if (hr == 0 || hr > 12) + hr = 100; // error; + else if (hr == 12) + hr = 0; // convert 12:nn to 0:nn + if (mPm) + hr += 12; // convert to PM + } + if (!ok || hr > maxMinute/60) + return QValidator::Invalid; + } + if (state == QValidator::Acceptable) + { + int t = hr * 60 + mn; + if (t < minMinute || t > maxMinute) + return QValidator::Invalid; + } + return state; +} diff --git a/kalarm/lib/timespinbox.h b/kalarm/lib/timespinbox.h new file mode 100644 index 000000000..275fdf309 --- /dev/null +++ b/kalarm/lib/timespinbox.h @@ -0,0 +1,127 @@ +/* + * timespinbox.h - time spinbox widget + * Program: kalarm + * Copyright © 2001-2008 by David Jarvie <djarvie@kde.org> + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TIMESPINBOX_H +#define TIMESPINBOX_H + +#include <qdatetime.h> +#include "spinbox2.h" + + +/** + * @short Hours/minutes time entry widget. + * + * The TimeSpinBox class provides a widget to enter a time consisting of an hours/minutes + * value. It can hold a time in any of 3 modes: a time of day using the 24-hour clock; a + * time of day using the 12-hour clock; or a length of time not restricted to 24 hours. + * + * Derived from SpinBox2, it displays a spin box with two pairs of spin buttons, one + * for hours and one for minutes. It provides accelerated stepping using the spin + * buttons, when the shift key is held down (inherited from SpinBox2). The default + * shift steps are 5 minutes and 6 hours. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie <software@astrojar.org.uk> + */ +class TimeSpinBox : public SpinBox2 +{ + Q_OBJECT + public: + /** Constructor for a wrapping time spin box which can be used to enter a time of day. + * @param use24hour True for entry of 24-hour clock times (range 00:00 to 23:59). + * False for entry of 12-hour clock times (range 12:00 to 11:59). + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit TimeSpinBox(bool use24hour, QWidget* parent = 0, const char* name = 0); + /** Constructor for a non-wrapping time spin box which can be used to enter a length of time. + * @param minMinute The minimum value which the spin box can hold, in minutes. + * @param maxMinute The maximum value which the spin box can hold, in minutes. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + TimeSpinBox(int minMinute, int maxMinute, QWidget* parent = 0, const char* name = 0); + /** Returns true if the spin box holds a valid value. + * An invalid value is displayed as asterisks. + */ + bool isValid() const; + /** Sets the spin box as holding a valid or invalid value. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + */ + void setValid(bool); + /** Returns the current value held in the spin box. + * If an invalid value is displayed, returns a value lower than the minimum value. + */ + QTime time() const; + /** Sets the maximum value which can be held in the spin box. + * @param minutes The maximum value expressed in minutes. + */ + virtual void setMinValue(int minutes); + /** Sets the maximum value which can be held in the spin box. + * @param minutes The maximum value expressed in minutes. + */ + virtual void setMaxValue(int minutes) { SpinBox2::setMaxValue(minutes); } + /** Sets the maximum value which can be held in the spin box. */ + void setMaxValue(const QTime& t) { SpinBox2::setMaxValue(t.hour()*60 + t.minute()); } + /** Returns the maximum value which can be held in the spin box. */ + QTime maxTime() const { int mv = maxValue(); return QTime(mv/60, mv%60); } + /** Returns a text describing use of the shift key as an accelerator for + * the spin buttons, designed for incorporation into WhatsThis texts. + */ + static QString shiftWhatsThis(); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + public slots: + /** Sets the value of the spin box. + * @param minutes The new value of the spin box, expressed in minutes. + */ + virtual void setValue(int minutes); + /** Sets the value of the spin box. */ + void setValue(const QTime& t) { setValue(t.hour()*60 + t.minute()); } + /** Increments the spin box value. + * If the value was previously invalid, the spin box is set to the minimum value. + */ + virtual void stepUp(); + /** Decrements the spin box value. + * If the value was previously invalid, the spin box is set to the minimum value. + */ + virtual void stepDown(); + + protected: + virtual QString mapValueToText(int v); + virtual int mapTextToValue(bool* ok); + private slots: + void slotValueChanged(int value); + private: + class TimeValidator; + TimeValidator* mValidator; + int mMinimumValue; // real minimum value, excluding special value for "**:**" + bool m12Hour; // use 12-hour clock + bool mPm; // use PM for manually entered values (with 12-hour clock) + bool mInvalid; // value is currently invalid (asterisks) + bool mEnteredSetValue; // to prevent infinite recursion in setValue() +}; + +#endif // TIMESPINBOX_H |