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