File indexing completed on 2024-05-12 05:06:09
0001 /* 0002 SPDX-FileCopyrightText: 2004-2017 Thomas Baumgart <tbaumgart@kde.org> 0003 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kmymoneypricedlg.h" 0008 0009 // ---------------------------------------------------------------------------- 0010 // QT Includes 0011 0012 #include <QCheckBox> 0013 #include <QPushButton> 0014 #include <QIcon> 0015 #include <QVBoxLayout> 0016 #include <QMenu> 0017 #include <QVector> 0018 0019 // ---------------------------------------------------------------------------- 0020 // KDE Includes 0021 0022 #include <KMessageBox> 0023 #include <KLocalizedString> 0024 #include <KTreeWidgetSearchLine> 0025 #include <KTreeWidgetSearchLineWidget> 0026 0027 // ---------------------------------------------------------------------------- 0028 // Project Includes 0029 0030 #include "ui_kmymoneypricedlg.h" 0031 #include "ui_kupdatestockpricedlg.h" 0032 0033 #include "icons.h" 0034 #include "kcurrencycalculator.h" 0035 #include "kequitypriceupdatedlg.h" 0036 #include "kmymoneycurrencyselector.h" 0037 #include "kmymoneyutils.h" 0038 #include "kpricetreeitem.h" 0039 #include "kupdatestockpricedlg.h" 0040 #include "mymoneyaccount.h" 0041 #include "mymoneyexception.h" 0042 #include "mymoneyfile.h" 0043 #include "mymoneymoney.h" 0044 #include "mymoneyprice.h" 0045 #include "mymoneysecurity.h" 0046 #include "mymoneyutils.h" 0047 0048 #include "kmmyesno.h" 0049 0050 using namespace Icons; 0051 0052 // duplicated eMenu namespace from menuenums.h for consistency 0053 // there shouldn't be any clash, because we don't need menuenums.h here 0054 namespace eMenu { 0055 enum class Action { 0056 // ************* 0057 // The price menu 0058 // ************* 0059 NewPrice, DeletePrice, 0060 UpdatePrice, EditPrice, 0061 }; 0062 inline qHashSeedType qHash(const Action key, qHashSeedType seed) 0063 { 0064 return ::qHash(static_cast<uint>(key), seed); 0065 } 0066 } 0067 0068 class KMyMoneyPriceDlgPrivate 0069 { 0070 Q_DISABLE_COPY(KMyMoneyPriceDlgPrivate) 0071 Q_DECLARE_PUBLIC(KMyMoneyPriceDlg) 0072 0073 public: 0074 explicit KMyMoneyPriceDlgPrivate(KMyMoneyPriceDlg *qq) : 0075 q_ptr(qq), 0076 ui(new Ui::KMyMoneyPriceDlg), 0077 m_currentItem(nullptr), 0078 m_searchWidget(nullptr) 0079 { 0080 } 0081 0082 ~KMyMoneyPriceDlgPrivate() 0083 { 0084 delete ui; 0085 } 0086 0087 int editPrice() 0088 { 0089 Q_Q(KMyMoneyPriceDlg); 0090 int rc = QDialog::Rejected; 0091 auto item = ui->m_priceList->currentItem(); 0092 if (item) { 0093 MyMoneySecurity from(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().from())); 0094 MyMoneySecurity to(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().to())); 0095 signed64 fract = MyMoneyMoney::precToDenom(from.pricePrecision()); 0096 0097 QPointer<KCurrencyCalculator> calc = 0098 new KCurrencyCalculator(from, 0099 to, 0100 MyMoneyMoney::ONE, 0101 item->data(0, Qt::UserRole).value<MyMoneyPrice>().rate(to.id()), 0102 item->data(0, Qt::UserRole).value<MyMoneyPrice>().date(), 0103 fract, 0104 q); 0105 calc->setupPriceEditor(); 0106 0107 rc = calc->exec(); 0108 delete calc; 0109 } 0110 return rc; 0111 } 0112 0113 KMyMoneyPriceDlg *q_ptr; 0114 Ui::KMyMoneyPriceDlg *ui; 0115 QTreeWidgetItem* m_currentItem; 0116 /** 0117 * Search widget for the list 0118 */ 0119 KTreeWidgetSearchLineWidget* m_searchWidget; 0120 QMap<QString, QString> m_stockNameMap; 0121 }; 0122 0123 KMyMoneyPriceDlg::KMyMoneyPriceDlg(QWidget* parent) : 0124 QDialog(parent), 0125 d_ptr(new KMyMoneyPriceDlgPrivate(this)) 0126 { 0127 Q_D(KMyMoneyPriceDlg); 0128 d->ui->setupUi(this); 0129 0130 // create the searchline widget 0131 // and insert it into the existing layout 0132 d->m_searchWidget = new KTreeWidgetSearchLineWidget(this, d->ui->m_priceList); 0133 d->m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); 0134 0135 d->ui->m_listLayout->insertWidget(0, d->m_searchWidget); 0136 0137 d->ui->m_priceList->header()->setSortIndicator(0, Qt::AscendingOrder); 0138 d->ui->m_priceList->header()->setStretchLastSection(true); 0139 d->ui->m_priceList->setContextMenuPolicy(Qt::CustomContextMenu); 0140 0141 d->ui->m_deleteButton->setIcon(Icons::get(Icon::EditRemove)); 0142 d->ui->m_newButton->setIcon(Icons::get(Icon::DocumentNew)); 0143 d->ui->m_editButton->setIcon(Icons::get(Icon::DocumentEdit)); 0144 0145 d->ui->m_onlineQuoteButton->setIcon(Icons::get(Icon::OnlinePriceUpdate)); 0146 0147 connect(d->ui->m_editButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotEditPrice); 0148 connect(d->ui->m_deleteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotDeletePrice); 0149 connect(d->ui->m_newButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotNewPrice); 0150 connect(d->ui->m_priceList, &QTreeWidget::itemSelectionChanged, this, &KMyMoneyPriceDlg::slotSelectPrice); 0151 connect(d->ui->m_onlineQuoteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotOnlinePriceUpdate); 0152 connect(d->ui->m_priceList, &QWidget::customContextMenuRequested, this, &KMyMoneyPriceDlg::slotShowPriceMenu); 0153 0154 connect(d->ui->m_showAllPrices, &QAbstractButton::toggled, this, &KMyMoneyPriceDlg::slotLoadWidgets); 0155 connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneyPriceDlg::slotLoadWidgets); 0156 0157 slotLoadWidgets(); 0158 slotSelectPrice(); 0159 } 0160 0161 KMyMoneyPriceDlg::~KMyMoneyPriceDlg() 0162 { 0163 Q_D(KMyMoneyPriceDlg); 0164 delete d; 0165 } 0166 0167 void KMyMoneyPriceDlg::slotLoadWidgets() 0168 { 0169 Q_D(KMyMoneyPriceDlg); 0170 auto file = MyMoneyFile::instance(); 0171 0172 //clear the list and disable the sorting while it loads the widgets, for performance 0173 d->ui->m_priceList->setSortingEnabled(false); 0174 d->ui->m_priceList->clear(); 0175 d->m_stockNameMap.clear(); 0176 0177 //load the currencies for investments, which we'll need later 0178 QList<MyMoneyAccount> accList; 0179 file->accountList(accList); 0180 QList<MyMoneyAccount>::const_iterator acc_it; 0181 for (acc_it = accList.constBegin(); acc_it != accList.constEnd(); ++acc_it) { 0182 if ((*acc_it).isInvest()) { 0183 if (d->m_stockNameMap.contains((*acc_it).currencyId())) { 0184 d->m_stockNameMap[(*acc_it).currencyId()] = QString(d->m_stockNameMap.value((*acc_it).currencyId()) + ", " + (*acc_it).name()); 0185 } else { 0186 d->m_stockNameMap[(*acc_it).currencyId()] = (*acc_it).name(); 0187 } 0188 } 0189 } 0190 0191 //get the price list 0192 MyMoneyPriceList list = file->priceList(); 0193 MyMoneyPriceList::ConstIterator it_allPrices; 0194 for (it_allPrices = list.constBegin(); it_allPrices != list.constEnd(); ++it_allPrices) { 0195 const auto& pair = it_allPrices.key(); 0196 try { 0197 if (file->security(pair.first).isCurrency() && !file->security(pair.second).isCurrency()) { 0198 qDebug() << "A currency pair" << pair << "is invalid (from currency to equity). Omitting from listing."; 0199 continue; 0200 } 0201 } catch (MyMoneyException&) { 0202 qDebug() << "A currency pair" << pair << "is invalid. Omitting from listing."; 0203 continue; 0204 } 0205 0206 MyMoneyPriceEntries::ConstIterator it_priceItem; 0207 if (d->ui->m_showAllPrices->isChecked()) { 0208 for (it_priceItem = (*it_allPrices).constBegin(); it_priceItem != (*it_allPrices).constEnd(); ++it_priceItem) { 0209 loadPriceItem(*it_priceItem); 0210 } 0211 } else { 0212 //if it doesn't show all prices, it only shows the most recent occurrence for each price 0213 if ((*it_allPrices).count() > 0) { 0214 //the prices for each currency are ordered by date in ascending order 0215 //it gets the last item of the item, which is supposed to be the most recent price 0216 it_priceItem = (*it_allPrices).constEnd(); 0217 --it_priceItem; 0218 loadPriceItem(*it_priceItem); 0219 } 0220 } 0221 } 0222 //reenable sorting and sort by the commodity column 0223 d->ui->m_priceList->setSortingEnabled(true); 0224 d->ui->m_priceList->sortByColumn(KPriceTreeItem::ePriceCommodity, Qt::SortOrder::AscendingOrder); 0225 0226 //update the search widget so the list gets refreshed correctly if it was being filtered 0227 if (!d->m_searchWidget->searchLine()->text().isEmpty()) 0228 d->m_searchWidget->searchLine()->updateSearch(d->m_searchWidget->searchLine()->text()); 0229 } 0230 0231 QTreeWidgetItem* KMyMoneyPriceDlg::loadPriceItem(const MyMoneyPrice& basePrice) 0232 { 0233 Q_D(KMyMoneyPriceDlg); 0234 MyMoneySecurity from, to; 0235 auto price = MyMoneyPrice(basePrice); 0236 0237 auto priceTreeItem = new KPriceTreeItem(d->ui->m_priceList); 0238 0239 if (!price.isValid()) 0240 price = MyMoneyFile::instance()->price(price.from(), price.to(), price.date()); 0241 0242 if (price.isValid()) { 0243 QString priceBase = price.to(); 0244 from = MyMoneyFile::instance()->security(price.from()); 0245 to = MyMoneyFile::instance()->security(price.to()); 0246 if (!to.isCurrency()) { 0247 from = MyMoneyFile::instance()->security(price.to()); 0248 to = MyMoneyFile::instance()->security(price.from()); 0249 priceBase = price.from(); 0250 } 0251 0252 priceTreeItem->setData(KPriceTreeItem::ePriceCommodity, Qt::UserRole, QVariant::fromValue(price)); 0253 priceTreeItem->setText(KPriceTreeItem::ePriceCommodity, (from.isCurrency()) ? from.id() : from.tradingSymbol()); 0254 priceTreeItem->setText(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id())); 0255 priceTreeItem->setToolTip(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id())); 0256 priceTreeItem->setText(KPriceTreeItem::ePriceCurrency, to.id()); 0257 priceTreeItem->setText(KPriceTreeItem::ePriceDate, MyMoneyUtils::formatDate(price.date())); 0258 priceTreeItem->setData(KPriceTreeItem::ePriceDate, KPriceTreeItem::OrderRole, QVariant(price.date())); 0259 priceTreeItem->setText(KPriceTreeItem::ePricePrice, price.rate(priceBase).formatMoney("", from.pricePrecision())); 0260 priceTreeItem->setTextAlignment(KPriceTreeItem::ePricePrice, Qt::AlignRight | Qt::AlignVCenter); 0261 priceTreeItem->setData(KPriceTreeItem::ePricePrice, KPriceTreeItem::OrderRole, QVariant::fromValue(price.rate(priceBase))); 0262 priceTreeItem->setText(KPriceTreeItem::ePriceSource, price.source()); 0263 } 0264 return priceTreeItem; 0265 } 0266 0267 void KMyMoneyPriceDlg::slotSelectPrice() 0268 { 0269 Q_D(KMyMoneyPriceDlg); 0270 QTreeWidgetItem* item = 0; 0271 if (d->ui->m_priceList->selectedItems().count() > 0) { 0272 item = d->ui->m_priceList->selectedItems().at(0); 0273 } 0274 d->m_currentItem = item; 0275 d->ui->m_editButton->setEnabled(item != 0); 0276 bool deleteEnabled = (item != 0); 0277 0278 //if one of the selected entries is a default, then deleting is disabled 0279 QList<QTreeWidgetItem*> itemsList = d->ui->m_priceList->selectedItems(); 0280 QList<QTreeWidgetItem*>::const_iterator item_it; 0281 for (item_it = itemsList.constBegin(); item_it != itemsList.constEnd(); ++item_it) { 0282 MyMoneyPrice price = (*item_it)->data(0, Qt::UserRole).value<MyMoneyPrice>(); 0283 if (price.source() == "KMyMoney") 0284 deleteEnabled = false; 0285 } 0286 d->ui->m_deleteButton->setEnabled(deleteEnabled); 0287 0288 // Modification of automatically added entries is not allowed 0289 // Multiple entries cannot be edited at once 0290 if (item) { 0291 MyMoneyPrice price = item->data(0, Qt::UserRole).value<MyMoneyPrice>(); 0292 if (price.source() == "KMyMoney" || itemsList.count() > 1) 0293 d->ui->m_editButton->setEnabled(false); 0294 // Q_EMIT selectObject(price); 0295 } 0296 } 0297 0298 void KMyMoneyPriceDlg::slotNewPrice() 0299 { 0300 Q_D(KMyMoneyPriceDlg); 0301 QPointer<KUpdateStockPriceDlg> dlg = new KUpdateStockPriceDlg(this); 0302 try { 0303 auto item = d->ui->m_priceList->currentItem(); 0304 if (item) { 0305 MyMoneySecurity security; 0306 security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().from()); 0307 dlg->ui->m_security->setSecurity(security); 0308 security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().to()); 0309 dlg->ui->m_currency->setSecurity(security); 0310 } 0311 0312 if (dlg->exec()) { 0313 MyMoneyPrice price(dlg->ui->m_security->security().id(), dlg->ui->m_currency->security().id(), dlg->date(), MyMoneyMoney::ONE, QString()); 0314 QTreeWidgetItem* p = loadPriceItem(price); 0315 d->ui->m_priceList->setCurrentItem(p, true); 0316 // If the user cancels the following operation, we delete the new item 0317 // and re-select any previously selected one 0318 if (d->editPrice() == Rejected) { 0319 delete p; 0320 if (item) 0321 d->ui->m_priceList->setCurrentItem(item, true); 0322 } 0323 } 0324 } catch (...) { 0325 delete dlg; 0326 throw; 0327 } 0328 delete dlg; 0329 } 0330 0331 void KMyMoneyPriceDlg::slotEditPrice() 0332 { 0333 Q_D(KMyMoneyPriceDlg); 0334 d->editPrice(); 0335 } 0336 0337 void KMyMoneyPriceDlg::slotDeletePrice() 0338 { 0339 Q_D(KMyMoneyPriceDlg); 0340 QList<QTreeWidgetItem*> listItems = d->ui->m_priceList->selectedItems(); 0341 if (listItems.count() > 0) { 0342 if (KMessageBox::questionTwoActions( 0343 this, 0344 i18np("Do you really want to delete the selected price entry?", "Do you really want to delete the selected price entries?", listItems.count()), 0345 i18n("Delete price information"), 0346 KMMYesNo::yes(), 0347 KMMYesNo::no(), 0348 "DeletePrice") 0349 == KMessageBox::PrimaryAction) { 0350 MyMoneyFileTransaction ft; 0351 try { 0352 QList<QTreeWidgetItem*>::const_iterator price_it; 0353 for (price_it = listItems.constBegin(); price_it != listItems.constEnd(); ++price_it) { 0354 MyMoneyFile::instance()->removePrice((*price_it)->data(0, Qt::UserRole).value<MyMoneyPrice>()); 0355 } 0356 ft.commit(); 0357 } catch (const MyMoneyException &) { 0358 qDebug("Cannot delete price"); 0359 } 0360 } 0361 } 0362 } 0363 0364 void KMyMoneyPriceDlg::slotOnlinePriceUpdate() 0365 { 0366 QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(this); 0367 if (dlg->exec() == Accepted && dlg) 0368 dlg->storePrices(); 0369 delete dlg; 0370 } 0371 0372 void KMyMoneyPriceDlg::slotShowPriceMenu(const QPoint& p) 0373 { 0374 Q_D(KMyMoneyPriceDlg); 0375 auto item = d->ui->m_priceList->itemAt(p); 0376 if (item) { 0377 d->ui->m_priceList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); 0378 const auto price = item->data(0, Qt::UserRole).value<MyMoneyPrice>(); 0379 const auto cond1 = !price.from().isEmpty() && price.source() != QLatin1String("KMyMoney"); 0380 const auto cond2 = cond1 && MyMoneyFile::instance()->security(price.from()).isCurrency(); 0381 auto menu = new QMenu; 0382 typedef void(KMyMoneyPriceDlg::*KMyMoneyPriceDlgFunc)(); 0383 struct actionInfo { 0384 eMenu::Action action; 0385 KMyMoneyPriceDlgFunc callback; 0386 QString text; 0387 Icon icon; 0388 bool enabled; 0389 }; 0390 0391 const QVector<actionInfo> actionInfos { 0392 {eMenu::Action::NewPrice, &KMyMoneyPriceDlg::slotNewPrice, i18n("New price..."), Icon::DocumentNew, true}, 0393 {eMenu::Action::EditPrice, &KMyMoneyPriceDlg::slotEditPrice, i18n("Edit price..."), Icon::DocumentEdit, cond1}, 0394 {eMenu::Action::UpdatePrice, &KMyMoneyPriceDlg::slotOnlinePriceUpdate, i18n("Online Price Update..."), Icon::OnlinePriceUpdate, cond2}, 0395 {eMenu::Action::DeletePrice, &KMyMoneyPriceDlg::slotDeletePrice, i18n("Delete price..."), Icon::EditRemove, cond1}, 0396 }; 0397 0398 QList<QAction*> LUTActions; 0399 for (const auto& info : actionInfos) { 0400 auto a = new QAction(Icons::get(info.icon), info.text, nullptr); // WARNING: no empty Icon::Empty here 0401 a->setEnabled(info.enabled); 0402 connect(a, &QAction::triggered, this, info.callback); 0403 LUTActions.append(a); 0404 } 0405 menu->addSection(i18nc("@title:menu", "Price options")); 0406 menu->addActions(LUTActions); 0407 menu->exec(QCursor::pos()); 0408 } 0409 }