File indexing completed on 2024-05-12 16:44:02
0001 /* 0002 SPDX-FileCopyrightText: 2001 Felix Rodriguez <frodriguez@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2002-2011 Thomas Baumgart <tbaumgart@kde.org> 0004 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kmymoneycombo.h" 0009 #include "kmymoneycombo_p.h" 0010 0011 // ---------------------------------------------------------------------------- 0012 // QT Includes 0013 0014 #include <QRect> 0015 #include <QStyle> 0016 #include <QPainter> 0017 #include <QKeyEvent> 0018 #include <QList> 0019 #include <QFocusEvent> 0020 #include <QMouseEvent> 0021 #include <QMetaMethod> 0022 0023 // ---------------------------------------------------------------------------- 0024 // KDE Includes 0025 0026 #include <KConfig> 0027 #include <KConfigGroup> 0028 0029 // ---------------------------------------------------------------------------- 0030 // Project Includes 0031 0032 #include "kmymoneyselector.h" 0033 #include "kmymoneycompletion.h" 0034 #include "kmymoneylineedit.h" 0035 0036 KMyMoneyCombo::KMyMoneyCombo(QWidget *parent) : 0037 KComboBox(parent), 0038 d_ptr(new KMyMoneyComboPrivate) 0039 { 0040 } 0041 0042 KMyMoneyCombo::KMyMoneyCombo(bool rw, QWidget *parent) : 0043 KComboBox(rw, parent), 0044 d_ptr(new KMyMoneyComboPrivate) 0045 { 0046 Q_D(KMyMoneyCombo); 0047 if (rw) { 0048 d->m_edit = new KMyMoneyLineEdit(this, true); 0049 setLineEdit(d->m_edit); 0050 } 0051 } 0052 0053 KMyMoneyCombo::KMyMoneyCombo(KMyMoneyComboPrivate &dd, bool rw, QWidget *parent) : 0054 KComboBox(rw, parent), 0055 d_ptr(&dd) 0056 { 0057 Q_D(KMyMoneyCombo); 0058 if (rw) { 0059 d->m_edit = new KMyMoneyLineEdit(this, true); 0060 setLineEdit(d->m_edit); 0061 } 0062 } 0063 0064 KMyMoneyCombo::~KMyMoneyCombo() 0065 { 0066 Q_D(KMyMoneyCombo); 0067 delete d; 0068 } 0069 0070 void KMyMoneyCombo::setCurrentTextById(const QString& id) 0071 { 0072 clearEditText(); 0073 if (!id.isEmpty()) { 0074 QTreeWidgetItem* item = selector()->item(id); 0075 if (item) { 0076 setCompletedText(item->text(0)); 0077 setEditText(item->text(0)); 0078 } 0079 } 0080 } 0081 0082 void KMyMoneyCombo::slotItemSelected(const QString& id) 0083 { 0084 Q_D(KMyMoneyCombo); 0085 if (isEditable()) { 0086 bool blocked = signalsBlocked(); 0087 blockSignals(true); 0088 setCurrentTextById(id); 0089 blockSignals(blocked); 0090 } 0091 0092 d->m_completion->hide(); 0093 0094 if (d->m_id != id) { 0095 d->m_id = id; 0096 emit itemSelected(id); 0097 } 0098 } 0099 0100 void KMyMoneyCombo::setEditable(bool y) 0101 { 0102 Q_D(KMyMoneyCombo); 0103 if (y == isEditable()) 0104 return; 0105 0106 KComboBox::setEditable(y); 0107 0108 // make sure we use our own line edit style 0109 if (y) { 0110 d->m_edit = new KMyMoneyLineEdit(this, true); 0111 setLineEdit(d->m_edit); 0112 d->m_edit->setPalette(palette()); 0113 } else { 0114 d->m_edit = 0; 0115 } 0116 } 0117 0118 void KMyMoneyCombo::setPlaceholderText(const QString& hint) const 0119 { 0120 Q_D(const KMyMoneyCombo); 0121 if (d->m_edit) 0122 d->m_edit->setPlaceholderText(hint); 0123 } 0124 0125 void KMyMoneyCombo::paintEvent(QPaintEvent* ev) 0126 { 0127 Q_D(KMyMoneyCombo); 0128 KComboBox::paintEvent(ev); 0129 // if we don't have an edit field, we need to paint the text onto the button 0130 if (!d->m_edit) { 0131 if (d->m_completion) { 0132 QStringList list; 0133 selector()->selectedItems(list); 0134 if (!list.isEmpty()) { 0135 QString str = selector()->item(list[0])->text(0); 0136 // we only paint, if the text is longer than 1 char. Assumption 0137 // is that length 1 is the blank case so no need to do painting 0138 if (str.length() > 1) { 0139 QPainter p(this); 0140 p.setPen(palette().text().color()); 0141 QStyleOptionComboBox opt; 0142 initStyleOption(&opt); 0143 QRect re = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this); 0144 p.setClipRect(re); 0145 p.save(); 0146 p.setFont(font()); 0147 QFontMetrics fm(font()); 0148 int x = re.x(), y = re.y() + fm.ascent(); 0149 p.drawText(x, y, str); 0150 p.restore(); 0151 } 0152 } 0153 } 0154 } 0155 } 0156 0157 void KMyMoneyCombo::mousePressEvent(QMouseEvent *e) 0158 { 0159 Q_D(KMyMoneyCombo); 0160 // mostly copied from QCombo::mousePressEvent() and adjusted for our needs 0161 if (e->button() != Qt::LeftButton) 0162 return; 0163 0164 if (((!isEditable() || isInArrowArea(e->globalPos())) && selector()->itemList().count()) && !d->m_completion->isVisible()) { 0165 d->m_completion->setVisible(true); 0166 } 0167 0168 if (d->m_timer.isActive()) { 0169 d->m_timer.stop(); 0170 d->m_completion->slotMakeCompletion(QString()); 0171 // the above call clears the selection in the selector but maintains the current index, use that index to restore the selection 0172 QTreeWidget* listView = selector()->listView(); 0173 QModelIndex currentIndex = listView->currentIndex(); 0174 if (currentIndex.isValid()) { 0175 listView->selectionModel()->select(currentIndex, QItemSelectionModel::Select); 0176 listView->scrollToItem(listView->currentItem()); 0177 } 0178 } else { 0179 KConfig config("kcminputrc"); 0180 KConfigGroup grp = config.group("KDE"); 0181 d->m_timer.setSingleShot(true); 0182 d->m_timer.start(grp.readEntry("DoubleClickInterval", 400)); 0183 } 0184 } 0185 0186 bool KMyMoneyCombo::isInArrowArea(const QPoint& pos) const 0187 { 0188 QStyleOptionComboBox opt; 0189 initStyleOption(&opt); 0190 QRect arrowRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxArrow, this); 0191 0192 // Correction for motif style, where arrow is smaller 0193 // and thus has a rect that doesn't fit the button. 0194 arrowRect.setHeight(qMax(height() - (2 * arrowRect.y()), arrowRect.height())); 0195 0196 // if the button is not isEditable, it covers the whole widget 0197 if (!isEditable()) 0198 arrowRect = rect(); 0199 0200 return arrowRect.contains(mapFromGlobal(pos)); 0201 } 0202 0203 0204 void KMyMoneyCombo::setSuppressObjectCreation(bool suppress) 0205 { 0206 Q_D(KMyMoneyCombo); 0207 d->m_canCreateObjects = !suppress; 0208 } 0209 0210 void KMyMoneyCombo::setCurrentText(const QString& txt) 0211 { 0212 KComboBox::setItemText(KComboBox::currentIndex(), txt); 0213 } 0214 0215 void KMyMoneyCombo::setCurrentText() 0216 { 0217 KComboBox::setItemText(KComboBox::currentIndex(), QString()); 0218 } 0219 0220 void KMyMoneyCombo::keyPressEvent(QKeyEvent* e) 0221 { 0222 Q_D(KMyMoneyCombo); 0223 if ((e->key() == Qt::Key_F4 && e->modifiers() == 0) || 0224 (e->key() == Qt::Key_Down && (e->modifiers() & Qt::AltModifier)) || 0225 (!isEditable() && e->key() == Qt::Key_Space)) { 0226 // if we have at least one item in the list, we open the dropdown 0227 if (selector()->listView()->itemAt(0, 0)) 0228 d->m_completion->setVisible(true); 0229 e->ignore(); 0230 return; 0231 } 0232 KComboBox::keyPressEvent(e); 0233 } 0234 0235 void KMyMoneyCombo::connectNotify(const QMetaMethod & signal) 0236 { 0237 Q_D(KMyMoneyCombo); 0238 if (signal != QMetaMethod::fromSignal(&KMyMoneyCombo::createItem)) { 0239 d->m_canCreateObjects = true; 0240 } 0241 } 0242 0243 void KMyMoneyCombo::disconnectNotify(const QMetaMethod & signal) 0244 { 0245 Q_D(KMyMoneyCombo); 0246 if (signal != QMetaMethod::fromSignal(&KMyMoneyCombo::createItem)) { 0247 d->m_canCreateObjects = false; 0248 } 0249 } 0250 0251 void KMyMoneyCombo::focusOutEvent(QFocusEvent* e) 0252 { 0253 Q_D(KMyMoneyCombo); 0254 // don't do anything if the focus is lost due to window activation, this way switching 0255 // windows while typing a category will not popup the category creation dialog 0256 // also ignore the fact that the focus is lost because of Qt::PopupFocusReason (context menu) 0257 if (e->reason() == Qt::ActiveWindowFocusReason || e->reason() == Qt::PopupFocusReason) 0258 return; 0259 0260 if (d->m_inFocusOutEvent) { 0261 KComboBox::focusOutEvent(e); 0262 return; 0263 } 0264 0265 d->m_inFocusOutEvent = true; 0266 if (isEditable() && !currentText().isEmpty()) { 0267 if (d->m_canCreateObjects) { 0268 if (!d->m_completion->selector()->contains(currentText())) { 0269 QString id; 0270 // announce that we go into a possible dialog to create an object 0271 // This can be used by upstream widgets to disable filters etc. 0272 emit objectCreation(true); 0273 0274 emit createItem(currentText(), id); 0275 0276 // Announce that we return from object creation 0277 emit objectCreation(false); 0278 0279 // update the field to a possibly created object 0280 d->m_id = id; 0281 setCurrentTextById(id); 0282 0283 // make sure the completion does not show through 0284 d->m_completion->hide(); 0285 } 0286 0287 // else if we cannot create objects, and the current text is not 0288 // in the list, then we clear the text and the selection. 0289 } else if (!d->m_completion->selector()->contains(currentText())) { 0290 clearEditText(); 0291 } 0292 } 0293 0294 KComboBox::focusOutEvent(e); 0295 0296 // force update of hint and id if there is no text in the widget 0297 if (isEditable() && currentText().isEmpty()) { 0298 QString id = d->m_id; 0299 d->m_id.clear(); 0300 if (!id.isEmpty()) 0301 emit itemSelected(d->m_id); 0302 update(); 0303 } 0304 d->m_inFocusOutEvent = false; 0305 } 0306 0307 KMyMoneySelector* KMyMoneyCombo::selector() const 0308 { 0309 Q_D(const KMyMoneyCombo); 0310 return d->m_completion->selector(); 0311 } 0312 0313 KMyMoneyCompletion* KMyMoneyCombo::completion() const 0314 { 0315 Q_D(const KMyMoneyCombo); 0316 return d->m_completion; 0317 } 0318 0319 void KMyMoneyCombo::selectedItems(QStringList& list) const 0320 { 0321 Q_D(const KMyMoneyCombo); 0322 if (lineEdit() && lineEdit()->text().length() == 0) { 0323 list.clear(); 0324 } else { 0325 d->m_completion->selector()->selectedItems(list); 0326 } 0327 } 0328 0329 QString KMyMoneyCombo::selectedItem() const 0330 { 0331 Q_D(const KMyMoneyCombo); 0332 return d->m_id; 0333 } 0334 0335 void KMyMoneyCombo::setSelectedItem(const QString& id) 0336 { 0337 Q_D(KMyMoneyCombo); 0338 d->m_completion->selector()->setSelected(id, true); 0339 blockSignals(true); 0340 slotItemSelected(id); 0341 blockSignals(false); 0342 update(); 0343 } 0344 0345 QSize KMyMoneyCombo::sizeHint() const 0346 { 0347 return KComboBox::sizeHint(); 0348 0349 // I wanted to use the code below to adjust the size of the combo box 0350 // according to the largest item in the selector list. Apparently that 0351 // does not work too well in the enter and edit schedule dialog for 0352 // the category combo box. So we just use the standard implementation for now. 0353 #if 0 0354 constPolish(); 0355 int i, w; 0356 QFontMetrics fm = fontMetrics(); 0357 0358 int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18; 0359 int maxH = qMax(fm.lineSpacing(), 14) + 2; 0360 0361 w = selector()->optimizedWidth(); 0362 if (w > maxW) 0363 maxW = w; 0364 0365 QSize sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, this, 0366 QSize(maxW, maxH)). 0367 expandedTo(QApplication::globalStrut())); 0368 0369 return sizeHint; 0370 #endif 0371 }