Alkimia API

alkonlinequoteswidget.cpp
1/*
2 SPDX-FileCopyrightText: 2004-2019 Thomas Baumgart tbaumgart @kde.org
3
4 This file is part of libalkimia.
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "alkonlinequoteswidget.h"
10
11#include "alkdebug.h"
12#include "alknewstuffwidget.h"
13#include "alkonlinequote.h"
14#include "alkonlinequotesmodel.h"
15#include "alkonlinequotesource.h"
16#include "alkonlinequotesprofile.h"
17#include "alkonlinequotesprofilemanager.h"
18#include "alkonlinequoteuploaddialog.h"
19#include "alkwebpage.h"
20#include "alkwebview.h"
21
22#include <QCheckBox>
23#include <QClipboard>
24#include <QDesktopServices>
25#include <QKeyEvent>
26#include <QSortFilterProxyModel>
27#include <QTreeView>
28#include <QTreeWidget>
29#include <QTreeWidgetItem>
30
31#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
32#include <KIconLoader>
33#include <KMessageWidget>
34#include <QIcon>
35#define KIcon QIcon
36#else
37#include <KComponentData>
38#include <KIcon>
39#include <KIconLoader>
40#endif
41
42#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
43#include <QLocale>
44#define initLocale() QLocale()
45#else
46#include <KGlobal>
47#define initLocale() KGlobal::locale()
48#endif
49
50#include <KMessageBox>
51
52#include <ui_alkonlinequotedetails.h>
53#include <ui_alkonlinequoteslist.h>
54#include <ui_alkonlinequotesdebug.h>
55#include <ui_alkonlinequotesprofiledetails.h>
56#include <ui_alkonlinequotesprofiles.h>
57
58#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
59#include <klocale.h>
60static KLocale _locale(TRANSLATION_DOMAIN);
61#define i18nc(context, text) ki18nc(context, text).toString(&_locale)
62#define i18n(text) ki18n(text).toString(&_locale)
63#define tr2i18n(text, context) ki18nc(context, text).toString(&_locale)
64#endif
65
66class AlkOnlineQuotesWidget::Private
67 : public QWidget
68 , public Ui::AlkOnlineQuoteDetailsWidget
69 , public Ui::AlkOnlineQuotesDebugWidget
70 , public Ui::AlkOnlineQuotesProfileDetailsWidget
71 , public Ui::AlkOnlineQuotesProfilesWidget
72 , public Ui::AlkOnlineQuotesListWidget
73{
75public:
76 QString m_acceptLanguage;
78 AlkOnlineQuoteSource m_currentItem;
79 bool m_quoteInEditing;
80 AlkOnlineQuotesProfile *m_profile;
81 AlkWebView *m_webView;
82 bool m_showProfiles;
83 bool m_showUpload;
84 bool m_ghnsEditable;
85 bool m_disableUpdate;
86 QPixmap m_emptyIcon;
87 QPixmap m_inWorkIcon;
88 QPixmap m_okIcon;
89 QPixmap m_failIcon;
90 QPixmap m_unknownIcon;
91 QDialog *m_webPageDialog;
92#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
93 KMessageWidget* m_infoMessage;
94#endif
95 AlkOnlineQuotesModel *m_model;
96 AlkOnlineQuotesWidget *m_p;
97
98 Private(bool showProfiles, bool showUpload, AlkOnlineQuotesWidget *parent);
99 ~Private();
100
101public Q_SLOTS:
102 void slotNewProfile();
103 void slotDeleteProfile();
104 void slotSelectProfile();
105 void slotLoadProfile();
106
107 void slotDeleteEntry();
108 void slotDuplicateEntry();
109 void slotAcceptEntry();
110 void slotCopySettingsToClipboard();
111 void slotAddReferenceButton();
112 void slotLoadQuoteSource(const QModelIndex &index = QModelIndex());
113 void slotEntryChanged();
114 void slotNewEntry();
115 void slotCheckEntry();
116 void slotLogStatus(const QString &s);
117 void slotLogError(const QString &s);
118 void slotLogFailed(const QString &id, const QString &symbol);
119 void slotLogQuote(const QString &id, const QString &symbol, const QDate &date, double price);
120 void slotLogQuotes(const QString &id, const QString &symbol, const AlkDatePriceMap &prices);
121 void slotInstallEntries();
122 void slotResetQuotesList();
123 void slotUploadEntry();
124 void slotShowButton();
125
126public:
127 void loadProfiles();
128 void loadQuotesList(const bool updateResetList = false);
129 void clearIcons();
130 void initIcons();
131 void setupIcons(const AlkOnlineQuote::Errors &errors);
132 QString singleSymbol() const;
133 QStringList doubleSymbol() const;
134 QString expandedUrl() const;
135 void updateButtonState();
136 void setDefaultSource(QLineEdit* editWidget, const QString& sourceDefaultValue, const QString& defaultValue);
137};
138
139AlkOnlineQuotesWidget::Private::Private(bool showProfiles, bool showUpload, AlkOnlineQuotesWidget *parent)
140 : QWidget(parent)
141 , m_quoteInEditing(false)
142 , m_profile(nullptr)
143 , m_showProfiles(showProfiles)
144 , m_showUpload(showUpload)
145 , m_ghnsEditable(false)
146 , m_disableUpdate(false)
147#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
148 , m_inWorkIcon(BarIcon("view-refresh"))
149 , m_okIcon(BarIcon("dialog-ok-apply"))
150 , m_failIcon(BarIcon("dialog-cancel"))
151#else
152 , m_inWorkIcon(QIcon::fromTheme("view-refresh").pixmap(16))
153 , m_okIcon(QIcon::fromTheme("dialog-ok-apply").pixmap(16))
154 , m_failIcon(QIcon::fromTheme("dialog-cancel").pixmap(16))
155#endif
156 , m_webPageDialog(nullptr)
157
158#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
159 , m_infoMessage(nullptr)
160#endif
161 , m_model(nullptr)
162 , m_p(parent)
163{
164#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
165 static KComponentData alk(TRANSLATION_DOMAIN);
166#endif
167 Ui::AlkOnlineQuoteDetailsWidget::setupUi(parent);
168 Ui::AlkOnlineQuotesDebugWidget::setupUi(parent);
169 Ui::AlkOnlineQuotesProfileDetailsWidget::setupUi(parent);
170 Ui::AlkOnlineQuotesProfilesWidget::setupUi(parent);
171 Ui::AlkOnlineQuotesListWidget::setupUi(parent);
172
173 if (!QString(BUILD_KEY).isEmpty())
174 m_buildKey->setText(QString("<small>alkimia version: %1</small>").arg(BUILD_KEY));
175 else
176 m_buildKey->setText(QString());
177
178#ifdef BUILD_WITH_WEBENGINE
179 AlkWebView::setWebInspectorEnabled(true);
180#endif
181 initLocale();
182 m_webView = new AlkWebView;
183 m_webView->setWebPage(new AlkWebPage);
184#ifdef BUILD_WITH_WEBKIT
185 m_webView->setWebInspectorEnabled(true);
186#endif
187 AlkOnlineQuotesProfileManager::instance().setWebView(m_webView);
188 AlkOnlineQuotesProfileManager::instance().setWebPage(m_webView->webPage());
189
190 profilesGroupBox->setVisible(showProfiles);
191 profileDetailsBox->setVisible(showProfiles);
192 m_showButton->setVisible(!showProfiles && AlkOnlineQuotesProfileManager::instance().webViewEnabled());
193 m_ghnsSource->setVisible(false);
194 m_urlCheckLabel->setMinimumWidth(m_okIcon.width());
195
196 loadProfiles();
197
198#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
199 m_infoMessage = new KMessageWidget(onlineQuotesGroupBox);
200 groupBoxLayout->insertWidget(0, m_infoMessage);
201 m_infoMessage->hide();
202#endif
203
204 connect(m_newProfile, SIGNAL(clicked()), this, SLOT(slotNewProfile()));
205 connect(m_deleteProfile, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
206 connect(m_profileList, SIGNAL(itemSelectionChanged()), this, SLOT(slotLoadProfile()));
207
208 connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(slotLoadQuoteSource()));
209 connect(m_acceptButton, SIGNAL(clicked()), this, SLOT(slotAcceptEntry()));
210 connect(m_copyButton, SIGNAL(clicked()), this, SLOT(slotCopySettingsToClipboard()));
211 connect(m_addReferenceButton, SIGNAL(clicked()), this, SLOT(slotAddReferenceButton()));
212 connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotNewEntry()));
213 connect(m_resetButton, SIGNAL(clicked()), this, SLOT(slotResetQuotesList()));
214 connect(m_checkButton, SIGNAL(clicked()), this, SLOT(slotCheckEntry()));
215 connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteEntry()));
216 connect(m_duplicateButton, SIGNAL(clicked()), this, SLOT(slotDuplicateEntry()));
217 connect(m_installButton, SIGNAL(clicked()), this, SLOT(slotInstallEntries()));
218 connect(m_uploadButton, SIGNAL(clicked()), this, SLOT(slotUploadEntry()));
219
221 const int rowHeight = fm.height();
222#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
223 m_quoteSourceList->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
224 m_quoteSourceList->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
225 m_quoteSourceList->verticalHeader()->setDefaultSectionSize(rowHeight);
226#else
227 m_quoteSourceList->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
228 m_quoteSourceList->verticalHeader()->setResizeMode(QHeaderView::Fixed);
229 m_quoteSourceList->verticalHeader()->setDefaultSectionSize(rowHeight);
230#endif
231 m_quoteSourceList->verticalHeader()->setVisible(false);
232 m_quoteSourceList->setShowGrid(false);
233 m_quoteSourceList->horizontalHeader()->setVisible(true);
234 m_quoteSourceList->setSortingEnabled(true);
235 m_quoteSourceList->setSelectionMode(QAbstractItemView::SingleSelection);
236 m_quoteSourceList->setSelectionBehavior(QAbstractItemView::SelectRows);
237
238 connect(m_quoteSourceList, SIGNAL(clicked(QModelIndex)), this, SLOT(slotLoadQuoteSource(QModelIndex)));
239
240 connect(m_editURL, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
241 connect(m_editIdentifier, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
242
243 m_editIdSelector->addItem(i18nc("@item:inlistbox Stock", "Symbol"), AlkOnlineQuoteSource::IdSelector::Symbol);
244 m_editIdSelector->addItem(i18nc("@item:inlistbox Stock", "Identification number"), AlkOnlineQuoteSource::IdSelector::IdentificationNumber);
245 m_editIdSelector->addItem(i18nc("@item:inlistbox Stock", "Name"), AlkOnlineQuoteSource::IdSelector::Name);
246 connect(m_editIdSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(slotEntryChanged()));
247
248 connect(m_editDate, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
249 connect(m_editDateFormat, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
250 connect(m_editDefaultId, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
251
252 // TODO
253 // During the string freeze, these combo box entries cannot be moved from the UI file.
254 // However, to ensure that the list box entries are always defined in the source code,
255 // this should be done afterwards.
256#if 1
257 m_editPriceDecimalSeparator->setItemData(0, AlkOnlineQuoteSource::DecimalSeparator::Period);
258 m_editPriceDecimalSeparator->setItemData(1, AlkOnlineQuoteSource::DecimalSeparator::Comma);
259 m_editPriceDecimalSeparator->setItemData(2, AlkOnlineQuoteSource::DecimalSeparator::Legacy);
260#else
261 // m_editPriceDecimalSeparator->addItem(i18nc("@item:inlistbox Stock", "Legacy"), AlkOnlineQuoteSource::DecimalSeparator::Legacy);
262 // m_editPriceDecimalSeparator->addItem(i18nc("@item:inlistbox Stock", "Period (.)"), AlkOnlineQuoteSource::DecimalSeparator::Period);
263 // m_editPriceDecimalSeparator->addItem(i18nc("@item:inlistbox Stock", "Comma (,)"), AlkOnlineQuoteSource::DecimalSeparator::Comma);
264#endif
265 connect(m_editPriceDecimalSeparator, SIGNAL(currentIndexChanged(int)), this, SLOT(slotEntryChanged()));
266 connect(m_editPrice, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
267
268 m_editDataFormat->addItem(toString(AlkOnlineQuoteSource::DataFormat::StrippedHTML), AlkOnlineQuoteSource::DataFormat::StrippedHTML);
269 m_editDataFormat->addItem(toString(AlkOnlineQuoteSource::DataFormat::HTML), AlkOnlineQuoteSource::DataFormat::HTML);
270 m_editDataFormat->addItem(toString(AlkOnlineQuoteSource::DataFormat::CSV), AlkOnlineQuoteSource::DataFormat::CSV);
271#ifdef BUILD_WITH_WEBKIT
272 m_editDataFormat->addItem(toString(AlkOnlineQuoteSource::DataFormat::CSS), AlkOnlineQuoteSource::DataFormat::CSS);
273#endif
274 m_editDataFormat->addItem(toString(AlkOnlineQuoteSource::DataFormat::JSON), AlkOnlineQuoteSource::DataFormat::JSON);
275 connect(m_editDataFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(slotEntryChanged()));
276
277 m_editDownloadType->addItem(i18nc("@item:inlistbox Stock", "Default"), AlkOnlineQuoteSource::DownloadType::Default);
278 m_editDownloadType->addItem(i18nc("@item:inlistbox Stock", "Javascript"), AlkOnlineQuoteSource::DownloadType::Javascript);
279 connect(m_editDownloadType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotEntryChanged()));
280
281 connect(m_ghnsSource, SIGNAL(toggled(bool)), this, SLOT(slotEntryChanged()));
282 connect(m_showButton, SIGNAL(clicked()), this, SLOT(slotShowButton()));
283
284 m_uploadButton->setVisible(false);
285 m_acceptButton->setEnabled(false);
286 m_resetButton->setVisible(m_showProfiles);
287 slotLoadProfile();
288}
289
290AlkOnlineQuotesWidget::Private::~Private()
291{
292 m_webPageDialog->deleteLater();
293 delete m_webView->webPage();
294 delete m_webView;
295 delete m_model;
296}
297
298void AlkOnlineQuotesWidget::Private::loadProfiles()
299{
300 AlkOnlineQuotesProfileList list = AlkOnlineQuotesProfileManager::instance().profiles();
301 if (list.isEmpty())
302 return;
303 for (AlkOnlineQuotesProfile *profile : list) {
304 QListWidgetItem *item = new QListWidgetItem(dynamic_cast<QListWidget *>(m_profileList));
305 item->setText(profile->name());
306 item->setFlags(item->flags() | Qt::ItemIsEditable);
307 }
308 m_profileList->setCurrentRow(0);
309 m_profile = AlkOnlineQuotesProfileManager::instance().profiles().first();
310 loadQuotesList(true);
311}
312
313void AlkOnlineQuotesWidget::Private::loadQuotesList(const bool updateResetList)
314{
315 // create or update model stack
316 if (!m_model) {
317 m_model = new AlkOnlineQuotesModel(m_profile);
318 auto proxyModel = new QSortFilterProxyModel(this);
319 proxyModel->setSourceModel(m_model);
320 m_quoteSourceList->setModel(proxyModel);
321 } else {
322 m_model->setProfile(m_profile);
323 }
324
325 if (updateResetList) {
326 m_resetList.clear();
327 const QStringList groups = m_profile->quoteSources();
328
329 // Keep a copy of all loaded local entries.
330 // GHNS sources can only be maintained through the
331 // external dialog because otherwise resetConfig() would
332 // remove the GHNS file behind the scenes.
333 for (const auto& quoteSourceName : groups) {
334 const auto quoteSource = AlkOnlineQuoteSource(quoteSourceName, m_profile);
335 if (!quoteSource.isGHNS()) {
336 m_resetList.append(quoteSource);
337 }
338 }
339 }
340
341 const auto indexes = m_quoteSourceList->model()->match(m_model->index(0, 0), Qt::DisplayRole, m_currentItem.name(), 1, Qt::MatchExactly);
342 const auto index = !indexes.isEmpty() ? indexes.at(0) : QModelIndex();
343
344 m_quoteSourceList->setCurrentIndex(index);
345 m_quoteSourceList->selectRow(index.row());
346 m_quoteSourceList->scrollTo(index, QAbstractItemView::EnsureVisible);
347 slotLoadQuoteSource(m_quoteSourceList->currentIndex());
348 updateButtonState();
349}
350
351void AlkOnlineQuotesWidget::Private::slotNewProfile()
352{
353 QTreeWidgetItem *item = new QTreeWidgetItem(dynamic_cast<QTreeWidget *>(m_profileList));
354 item->setText(0, QLatin1String("new profile"));
355 item->setFlags(item->flags() | Qt::ItemIsEditable);
356}
357
358void AlkOnlineQuotesWidget::Private::slotDeleteProfile()
359{
360 delete m_profileList->currentItem();
361}
362
363void AlkOnlineQuotesWidget::Private::slotSelectProfile()
364{
365 slotLoadProfile();
366}
367
368void AlkOnlineQuotesWidget::Private::slotLoadProfile()
369{
370 m_uploadButton->setEnabled(false);
371 const AlkOnlineQuotesProfileList list = AlkOnlineQuotesProfileManager::instance().profiles();
372 if (!m_showProfiles) {
373 if (list.isEmpty())
374 return;
375 m_profile = list.first();
376 m_installButton->setVisible(m_profile->hasGHNSSupport());
377 m_uploadButton->setVisible(m_profile->hasGHNSSupport());
378 loadQuotesList(true);
379 return;
380 }
381
382 for (AlkOnlineQuotesProfile *profile : list) {
383 if (m_profileList->currentItem() && m_profileList->currentItem()->text() == profile->name()) {
384 m_profile = profile;
385 loadQuotesList(true);
386 m_installButton->setVisible(profile->hasGHNSSupport());
387 m_uploadButton->setVisible(profile->hasGHNSSupport());
388 break;
389 }
390 }
391
392 bool visible = m_profile->type() != AlkOnlineQuotesProfile::Type::None;
393 m_configFilePath->setText(m_profile->kConfigFile());
394 m_configFilePath->setVisible(visible);
395 m_configLabel->setEnabled(visible);
396
397 visible = m_profile->hasGHNSSupport();
398 m_GHNSConfigFilePath->setText(m_profile->hotNewStuffConfigFile());
399 m_GHNSConfigFilePath->setVisible(visible);
400 m_GHNSConfigLabel->setEnabled(visible);
401
402 m_GHNSDataPath->setText(m_profile->hotNewStuffReadPath().join(" "));
403 m_GHNSDataPath->setVisible(visible);
404 m_GHNSDataLabel->setEnabled(visible);
405}
406
407void AlkOnlineQuotesWidget::Private::slotLoadQuoteSource(const QModelIndex &index)
408{
409 Q_UNUSED(index)
410
411 m_quoteInEditing = false;
412
413 m_disableUpdate = true;
414 m_editURL->clear();
415 m_editIdentifier->clear();
416 m_editIdSelector->setCurrentIndex(AlkOnlineQuoteSource::IdSelector::Symbol);
417 m_editPriceDecimalSeparator->setCurrentIndex(AlkOnlineQuoteSource::DecimalSeparator::Legacy);
418 m_editPrice->clear();
419 m_editDate->clear();
420 m_editDateFormat->clear();
421 m_editDefaultId->clear();
422
424 if (m_quoteSourceList->currentIndex().isValid()) {
425 name = m_quoteSourceList->model()->data(m_quoteSourceList->currentIndex(), AlkOnlineQuotesModel::NameRole).toString();
426 AlkOnlineQuoteSource source = AlkOnlineQuoteSource(name, m_profile);
427 m_currentItem = source;
428 if (source.isReference())
429 source = source.asReference();
430 m_editURL->setText(source.url());
431 m_editIdentifier->setText(source.idRegex());
432 m_editIdSelector->setData<AlkOnlineQuoteSource::IdSelector>(source.idSelector());
433 m_editPriceDecimalSeparator->setData<AlkOnlineQuoteSource::DecimalSeparator>(source.priceDecimalSeparator());
434 m_editPrice->setText(source.priceRegex());
435 m_editDataFormat->setData<AlkOnlineQuoteSource::DataFormat>(source.dataFormat());
436 m_editDate->setText(source.dateRegex());
437 m_editDateFormat->setText(source.dateFormat());
438 m_editDefaultId->setText(source.defaultId());
439 m_editDownloadType->setData<AlkOnlineQuoteSource::DownloadType>(source.downloadType());
440 m_ghnsSource->setChecked(source.isGHNS());
441 }
442
443 bool enabled = !name.isEmpty() && !m_currentItem.isReference();
444 bool isFinanceQuoteSource = (enabled && AlkOnlineQuoteSource::isFinanceQuote(name)) ||
445 m_profile->type() == AlkOnlineQuotesProfile::Type::Script;
446
447 if (isFinanceQuoteSource || (m_currentItem.isGHNS() && !m_ghnsEditable))
448 enabled = false;
449
450 m_editURL->setEnabled(enabled);
451 m_editIdentifier->setEnabled(enabled);
452 m_editIdSelector->setEnabled(enabled);
453 m_editPriceDecimalSeparator->setEnabled(enabled);
454 m_editPrice->setEnabled(enabled);
455 m_editDate->setEnabled(enabled);
456 m_editDateFormat->setEnabled(enabled);
457 m_editDefaultId->setEnabled(enabled);
458 m_editDownloadType->setEnabled(enabled);
459 m_ghnsSource->setVisible(m_profile && m_profile->hasGHNSSupport());
460 m_ghnsSource->setEnabled(m_showUpload && m_profile && m_profile->hasGHNSSupport() && enabled);
461 m_uploadButton->setEnabled(m_showUpload && m_profile && m_profile->hasGHNSSupport());
462 m_addReferenceButton->setEnabled(m_currentItem.isGHNS());
463 m_editDataFormat->setEnabled(enabled);
464
465 // tab order seems to get messed up when enabling widgets
466 // easy solution: setup the tab order again
467 QWidget::setTabOrder(m_editURL, m_editDownloadType);
468 QWidget::setTabOrder(m_editDownloadType, m_editDataFormat);
469 QWidget::setTabOrder(m_editDataFormat, m_editIdentifier);
470 QWidget::setTabOrder(m_editIdentifier, m_editIdSelector);
471 QWidget::setTabOrder(m_editIdSelector, m_editPrice);
472 QWidget::setTabOrder(m_editPrice, m_editPriceDecimalSeparator);
473 QWidget::setTabOrder(m_editPriceDecimalSeparator, m_editDate);
474 QWidget::setTabOrder(m_editDate, m_editDateFormat);
475 QWidget::setTabOrder(m_editDateFormat, m_editDefaultId);
476 QWidget::setTabOrder(m_editDefaultId, m_ghnsSource);
477 QWidget::setTabOrder(m_ghnsSource, m_acceptButton);
478 QWidget::setTabOrder(m_acceptButton, m_cancelButton);
479 QWidget::setTabOrder(m_cancelButton, m_copyButton);
480
481 m_disableUpdate = false;
482
483 updateButtonState();
484}
485
486void AlkOnlineQuotesWidget::Private::slotEntryChanged()
487{
488 if (!m_disableUpdate)
489 updateButtonState();
490}
491
492void AlkOnlineQuotesWidget::Private::updateButtonState()
493{
494 clearIcons();
495 bool modified = !m_currentItem.isReference() &&
496 (m_editURL->text() != m_currentItem.url()
497 || m_editIdentifier->text() != m_currentItem.idRegex()
498 || m_editIdSelector->currentIndex() != m_editIdSelector->findData(m_currentItem.idSelector())
499 || m_editDataFormat->currentIndex() != m_editDataFormat->findData(m_currentItem.dataFormat())
500 || m_editDate->text() != m_currentItem.dateRegex()
501 || m_editDateFormat->text() != m_currentItem.dateFormat()
502 || m_editDefaultId->text() != m_currentItem.defaultId()
503 || m_editPriceDecimalSeparator->currentIndex() != m_editPriceDecimalSeparator->findData(m_currentItem.priceDecimalSeparator())
504 || m_editDownloadType->currentIndex() != m_editDownloadType->findData(m_currentItem.downloadType())
505 || m_editPrice->text() != m_currentItem.priceRegex()
506 || m_ghnsSource->isChecked() != m_currentItem.isGHNS());
507
508 bool isFinanceQuote = m_currentItem.isFinanceQuote() || m_profile->type() == AlkOnlineQuotesProfile::Type::Script;
509 bool hasWriteSupport = (m_profile->type() != AlkOnlineQuotesProfile::Type::None && !isFinanceQuote) || m_profile->hasGHNSSupport();
510 bool isRemoteUnpublished = m_currentItem.isGHNS() && m_currentItem.profile()->GHNSFilePath(m_currentItem.name()).isEmpty();
511 bool isDefaultSource = m_profile->defaultQuoteSources().contains(m_currentItem.name());
512 m_newButton->setEnabled(hasWriteSupport);
513 m_cancelButton->setEnabled(modified);
514 m_duplicateButton->setEnabled(hasWriteSupport);
515 m_deleteButton->setEnabled((!m_currentItem.isReadOnly() && !m_currentItem.isGHNS() && !isDefaultSource) || isRemoteUnpublished);
516 m_uploadButton->setEnabled(m_profile->hasGHNSSupport() && m_currentItem.isGHNS() && AlkOnlineQuoteUploadDialog::isSupported());
517 m_acceptButton->setEnabled(modified);
518 m_checkButton->setEnabled(isFinanceQuote || !modified);
519 m_editIdSelector->setVisible(m_profile->type() == AlkOnlineQuotesProfile::Type::KMyMoney5);
520 m_editIdSelectorLabel->setVisible(m_profile->type() == AlkOnlineQuotesProfile::Type::KMyMoney5);
521
522 // debug dock widget
523 AlkOnlineQuoteSource source(m_currentItem);
524 if (source.isReference())
525 source = source.asReference();
526
527 bool hasDateRange = source.dataFormat() == AlkOnlineQuoteSource::CSV || source.dataFormat() == AlkOnlineQuoteSource::JSON;
528 m_startDateLabel->setVisible(hasDateRange);
529 m_endDateLabel->setVisible(hasDateRange);
530 m_startDateEdit->setVisible(hasDateRange);
531 m_endDateEdit->setVisible(hasDateRange);
532
533 if (source.requiresTwoIdentifier()) {
534 m_checkSymbol->setEnabled(false);
535 m_checkSymbol->setText(QString());
536 m_checkSymbol2->setEnabled(true);
537 setDefaultSource(m_checkSymbol2, source.defaultId(), "BTC GBP");
538 } else {
539 m_checkSymbol->setEnabled(true);
540 setDefaultSource(m_checkSymbol, source.defaultId(), "ORCL");
541 m_checkSymbol2->setEnabled(false);
542 m_checkSymbol2->setText(QString());
543 }
544}
545
546void AlkOnlineQuotesWidget::Private::setDefaultSource(QLineEdit* editWidget, const QString& sourceDefaultValue, const QString& defaultValue)
547{
548 QString currentValue = editWidget->text();
549 if (currentValue.isEmpty()) {
550 editWidget->setText(!sourceDefaultValue.isEmpty() ? sourceDefaultValue : defaultValue);
551 }
552}
553void AlkOnlineQuotesWidget::Private::slotDeleteEntry()
554{
555 if (!m_quoteSourceList->currentIndex().isValid())
556 return;
557
559 i18n("Are you sure to delete this online quote ?"),
560 i18n("Delete online quote"),
563 QString("DeletingOnlineQuote"));
564 if (ret == KMessageBox::Cancel) {
565 return;
566 }
567
568 // keep this order to avoid deleting the wrong current item
569 m_quoteSourceList->model()->removeRow(m_quoteSourceList->currentIndex().row());
570 updateButtonState();
571}
572
573void AlkOnlineQuotesWidget::Private::slotDuplicateEntry()
574{
575 if (!m_quoteSourceList->currentIndex().isValid())
576 return;
577
578 AlkOnlineQuoteSource copy(m_currentItem);
579 copy.setName(copy.name() + i18n(".copy"));
580 copy.setGHNS(false);
581 copy.write();
582 m_currentItem = copy;
583 loadQuotesList();
584}
585
586void AlkOnlineQuotesWidget::Private::slotAcceptEntry()
587{
588 m_currentItem.setUrl(m_editURL->text());
589 m_currentItem.setIdRegex(m_editIdentifier->text());
590 m_currentItem.setIdSelector(m_editIdSelector->currentData().value<AlkOnlineQuoteSource::IdSelector>());
591 m_currentItem.setDataFormat(m_editDataFormat->currentData().value<AlkOnlineQuoteSource::DataFormat>());
592 m_currentItem.setDateRegex(m_editDate->text());
593 m_currentItem.setDateFormat(m_editDateFormat->text());
594 m_currentItem.setDefaultId(m_editDefaultId->text());
595 m_currentItem.setPriceDecimalSeparator(m_editPriceDecimalSeparator->currentData().value<AlkOnlineQuoteSource::DecimalSeparator>());
596 m_currentItem.setDownloadType(m_editDownloadType->currentData().value<AlkOnlineQuoteSource::DownloadType>());
597 m_currentItem.setPriceRegex(m_editPrice->text());
598 m_currentItem.setGHNS(m_ghnsSource->isChecked());
599 m_currentItem.write();
600 m_checkButton->setEnabled(true);
601 loadQuotesList();
602 updateButtonState();
603}
604
605void AlkOnlineQuotesWidget::Private::slotCopySettingsToClipboard()
606{
607 QClipboard* clipboard = QApplication::clipboard();
608
609 QStringList settings;
610 settings << i18nc("@title %1 is version info", "Online quote settings generated with Alkimia %1").arg(BUILD_KEY);
611 settings << QString();
612
613 settings << i18nc("@info online quote setting", "URL: %1").arg(m_editURL->text());
614 settings << i18nc("@info online quote setting", "Download mode: %1").arg(m_editDownloadType->currentText());
615 settings << i18nc("@info online quote setting", "Data format: %1").arg(m_editDataFormat->currentText());
616 settings << i18nc("@info online quote setting", "Identifier: %1").arg(m_editIdentifier->text());
617 settings << i18nc("@info online quote setting", "Select by: %1").arg(m_editIdSelector->currentText());
618 settings << i18nc("@info online quote setting", "Price: %1").arg(m_editPrice->text());
619 settings << i18nc("@info online quote setting", "Price decimal separator: %1").arg(m_editPriceDecimalSeparator->currentText());
620 settings << i18nc("@info online quote setting", "Date: %1").arg(m_editDate->text());
621 settings << i18nc("@info online quote setting", "Date format: %1").arg(m_editDateFormat->text());
622 settings << i18nc("@info online quote setting", "Default identifier: %1").arg(m_editDefaultId->text());
623 settings << i18nc("@info online quote setting", "Remote source: %1")
624 .arg(m_ghnsSource->isChecked() ? i18nc("@item:intext checkbox setting", "checked") : i18nc("@item:intext checkbox setting", "not checked"));
625
626 // force a final NL character on the last line
627 settings << QString();
628
629 clipboard->setText(settings.join(QLatin1String("\n")));
630}
631
632void AlkOnlineQuotesWidget::Private::slotAddReferenceButton()
633{
634 if (!m_quoteSourceList->currentIndex().isValid())
635 return;
636
637 QString newNameBase = m_currentItem.name() + i18nc("@item:valuesuffix to name for a quote source reference", ".reference");
638 int index = 1;
639 QString newName = newNameBase;
640 while(m_profile->quoteSources().contains(newName)) {
641 newName = QString("%1%2").arg(newNameBase).arg(index++);
642 }
643 AlkOnlineQuoteSource copy(newName, m_profile);
644 copy.setGHNS(false);
645 copy.setReferenceName(m_currentItem.name());
646 copy.write();
647 m_currentItem = copy;
648 loadQuotesList();
649}
650
651void AlkOnlineQuotesWidget::Private::slotNewEntry()
652{
653 const bool newEntries = m_profile->quoteSources().contains(i18n("New Quote Source"));
654 if (!newEntries) {
655 AlkOnlineQuoteSource newSource(i18n("New Quote Source"), m_profile);
656 newSource.write();
657 m_currentItem = newSource;
658 loadQuotesList();
659
660 } else {
661#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
662 if (!m_infoMessage->isVisible() && !m_infoMessage->isShowAnimationRunning()) {
663 m_infoMessage->resize(width(), m_infoMessage->heightForWidth(width()));
664 m_infoMessage->setText(
665 i18nc("@info Detail that only one new entry can exist at any time", "<b>New Quote Source</b> already exists."));
667 m_infoMessage->animatedShow();
668 }
669#endif
670 }
671}
672
673void AlkOnlineQuotesWidget::Private::clearIcons()
674{
675 m_urlCheckLabel->setPixmap(m_emptyIcon);
676 m_dateCheckLabel->setPixmap(m_emptyIcon);
677 m_priceCheckLabel->setPixmap(m_emptyIcon);
678 m_symbolCheckLabel->setPixmap(m_emptyIcon);
679 m_dateFormatCheckLabel->setPixmap(m_emptyIcon);
680}
681
682void AlkOnlineQuotesWidget::Private::initIcons()
683{
684 m_urlCheckLabel->setPixmap(m_inWorkIcon);
685 m_dateCheckLabel->setPixmap(m_inWorkIcon);
686 m_priceCheckLabel->setPixmap(m_inWorkIcon);
687 m_symbolCheckLabel->setPixmap(m_inWorkIcon);
688 m_dateFormatCheckLabel->setPixmap(m_inWorkIcon);
689}
690
691void AlkOnlineQuotesWidget::Private::setupIcons(const AlkOnlineQuote::Errors &errors)
692{
693 clearIcons();
694 if (errors & AlkOnlineQuote::Errors::URL) {
695 m_urlCheckLabel->setPixmap(m_failIcon);
696 } else {
697 m_urlCheckLabel->setPixmap(m_okIcon);
698 m_symbolCheckLabel->setPixmap((errors & AlkOnlineQuote::Errors::Symbol) ? m_failIcon : m_okIcon);
699 m_priceCheckLabel->setPixmap((errors & AlkOnlineQuote::Errors::Price) ? m_failIcon : m_okIcon);
700 if (errors & AlkOnlineQuote::Errors::Date) {
701 m_dateCheckLabel->setPixmap(m_failIcon);
702 } else {
703 if (m_currentItem.dateRegex().isEmpty()) {
704 m_dateCheckLabel->setPixmap(m_emptyIcon);
705 m_dateFormatCheckLabel->setPixmap(m_emptyIcon);
706 } else {
707 m_dateCheckLabel->setPixmap(m_okIcon);
708 m_dateFormatCheckLabel->setPixmap(
709 (errors & AlkOnlineQuote::Errors::DateFormat) ? m_failIcon : m_okIcon);
710 }
711 }
712 }
713}
714
715void AlkOnlineQuotesWidget::Private::slotCheckEntry()
716{
717 AlkOnlineQuote quote(m_profile);
718 m_logWindow->setVisible(true);
719 m_logWindow->clear();
720 clearIcons();
721 quote.setAcceptLanguage(m_acceptLanguage);
722
723 connect(&quote, SIGNAL(status(QString)), this, SLOT(slotLogStatus(QString)));
724 connect(&quote, SIGNAL(error(QString)), this, SLOT(slotLogError(QString)));
725 connect(&quote, SIGNAL(failed(QString,QString)), this, SLOT(slotLogFailed(QString,QString)));
726 connect(&quote, SIGNAL(quote(QString,QString,QDate,double)), this,
727 SLOT(slotLogQuote(QString,QString,QDate,double)));
728 connect(&quote, SIGNAL(quotes(QString,QString,AlkDatePriceMap)), this,
729 SLOT(slotLogQuotes(QString,QString,AlkDatePriceMap)));
730 initIcons();
731
732 AlkOnlineQuoteSource source(m_currentItem);
733 if (source.isReference())
734 source = source.asReference();
735 if (source.dataFormat() == AlkOnlineQuoteSource::CSV || source.dataFormat() == AlkOnlineQuoteSource::JSON) {
736 quote.setDateRange(m_startDateEdit->date(), m_endDateEdit->date());
737 } else {
738 quote.setDateRange(QDate(), QDate());
739 }
740 quote.setReturnLastPriceState(static_cast<AlkOnlineQuote::LastPriceState>(m_returnLastPriceStateComboBox->currentIndex()));
741 if (source.requiresTwoIdentifier()) {
742 quote.launch(m_checkSymbol2->text(), m_checkSymbol2->text(), source.name());
743 } else {
744 quote.launch(m_checkSymbol->text(), m_checkSymbol->text(), source.name());
745 }
746 setupIcons(quote.errors());
747}
748
749void AlkOnlineQuotesWidget::Private::slotLogStatus(const QString &s)
750{
751 m_logWindow->append(s);
752}
753
754void AlkOnlineQuotesWidget::Private::slotLogError(const QString &s)
755{
756 slotLogStatus(QString("<font color=\"red\"><b>") + s + QString("</b></font>"));
757}
758
759void AlkOnlineQuotesWidget::Private::slotLogFailed(const QString &id, const QString &symbol)
760{
761 slotLogStatus(QString("%1 %2").arg(id, symbol));
762}
763
764void AlkOnlineQuotesWidget::Private::slotLogQuote(const QString &id, const QString &symbol,
765 const QDate &date, double price)
766{
767 slotLogStatus(QString("<font color=\"green\">%1 %2 %3 %4</font>").arg(id, symbol,
768 date.toString()).arg(
769 price));
770}
771
772void AlkOnlineQuotesWidget::Private::slotLogQuotes(const QString &id, const QString &symbol,
773 const AlkDatePriceMap &prices)
774{
775 slotLogStatus(QString("<font color=\"green\">%1 %2</font>").arg(id, symbol));
776
777 slotLogStatus(QString("<font color=\"green\">date price</font>"));
778 for (auto i = prices.constBegin(), end = prices.constEnd(); i != end; ++i) {
779 slotLogStatus(QString("<font color=\"green\">%1 %2</font>")
780 .arg(i.key().toString(Qt::ISODate)).arg(i.value().toDouble()));
781 }
782}
783
784void AlkOnlineQuotesWidget::Private::slotInstallEntries()
785{
786 QString configFile = m_profile->hotNewStuffConfigFile();
787
788 AlkNewStuffWidget widget;
789 widget.init(configFile);
790 if (widget.showInstallDialog()) {
791 m_profile->reload();
792 loadQuotesList();
793 }
794}
795
796void AlkOnlineQuotesWidget::Private::slotResetQuotesList()
797{
798 m_p->resetConfig();
799}
800
801void AlkOnlineQuotesWidget::Private::slotUploadEntry()
802{
803 QPointer<AlkOnlineQuoteUploadDialog> dialog = new AlkOnlineQuoteUploadDialog(m_currentItem, false, this);
804 dialog->exec();
805 delete dialog;
806}
807
808void AlkOnlineQuotesWidget::Private::slotShowButton()
809{
810 if (!m_webPageDialog) {
811 m_webPageDialog = new QDialog;
812 m_webPageDialog->setWindowTitle(i18n("Online Quote HTML Result Window"));
814 layout->addWidget(m_webView);
815 m_webPageDialog->setLayout(layout);
816 }
817 m_webPageDialog->show();
818}
819
820QString AlkOnlineQuotesWidget::Private::expandedUrl() const
821{
822 AlkOnlineQuoteSource source(m_currentItem);
823 if (source.isReference())
824 source = source.asReference();
825 if (source.requiresTwoIdentifier()) {
826 return source.url().arg(m_checkSymbol2->text());
827 } else {
828 return source.url().arg(m_checkSymbol->text());
829 }
830}
831
832AlkOnlineQuotesWidget::AlkOnlineQuotesWidget(bool showProfiles, bool showUpload, QWidget *parent)
833 : QWidget(parent)
834 , d(new Private(showProfiles, showUpload, this))
835{
836}
837
838AlkOnlineQuotesWidget::~AlkOnlineQuotesWidget()
839{
840 delete d;
841}
842
843QWidget *AlkOnlineQuotesWidget::profilesWidget()
844{
845 QFrame *frame = new QFrame;
846 frame->setLayout(d->profilesGroupBox->layout());
847 return frame;
848}
849
850QWidget *AlkOnlineQuotesWidget::profileDetailsWidget()
851{
852 QFrame *frame = new QFrame;
853 frame->setLayout(d->profileDetailsBox->layout());
854 return frame;
855}
856
857QWidget *AlkOnlineQuotesWidget::onlineQuotesWidget()
858{
859 QFrame *frame = new QFrame;
860 frame->setLayout(d->onlineQuotesGroupBox->layout());
861 return frame;
862}
863
864QWidget *AlkOnlineQuotesWidget::quoteDetailsWidget()
865{
866 QFrame *frame = new QFrame;
867 frame->setLayout(d->detailsGroupBox->layout());
868 return frame;
869}
870
871QWidget *AlkOnlineQuotesWidget::debugWidget()
872{
873 QFrame *frame = new QFrame;
874 frame->setLayout(d->debugGroupBox->layout());
875 return frame;
876}
877
878void AlkOnlineQuotesWidget::readConfig()
879{
880}
881
882void AlkOnlineQuotesWidget::writeConfig()
883{
884}
885
886void AlkOnlineQuotesWidget::resetConfig()
887{
889 QStringList groups = d->m_profile->quoteSources();
890
891 // delete all currently defined entries
892 for (it = groups.constBegin(); it != groups.constEnd(); ++it) {
893 AlkOnlineQuoteSource quoteSource(*it, d->m_profile);
894 // Only remove when not a GHNS source. Those can only
895 // be removed via the external dialog. If removed here
896 // only the GHNS file will be removed and the entry
897 // shows up as empty local one when added by the logic
898 // below. Hence, the GHNS entries are not collected
899 // in the m_resetList.
900 if (!quoteSource.isGHNS()) {
901 quoteSource.remove();
902 }
903 }
904
905 // and write back the one's from the reset list
907 for (itr = d->m_resetList.begin(); itr != d->m_resetList.end(); ++itr) {
908 (*itr).write();
909 }
910
911 d->loadQuotesList();
912}
913
914QString AlkOnlineQuotesWidget::acceptLanguage() const
915{
916 return d->m_acceptLanguage;
917}
918
919void AlkOnlineQuotesWidget::setAcceptLanguage(const QString &text)
920{
921 d->m_acceptLanguage = text;
922}
923
924bool AlkOnlineQuotesWidget::GHNSSourceEditable()
925{
926 return d->m_ghnsEditable;
927
928}
929
930void AlkOnlineQuotesWidget::setGHNSSourceEditable(bool state)
931{
932 d->m_ghnsEditable = state;
933}
934
935#include "alkonlinequoteswidget.moc"
Wrapper for debug output.
Wrapper for new stuff widgets.
const QString & defaultId() const
Return the default identifier known to work.
void setDefaultId(const QString &defaultId)
Set the default identifier, which is known to work.
bool requiresTwoIdentifier() const
Return state if this source requires two identifier.
DataFormat
Supported formats of downloaded data.
bool isReference() const
Return state if this source is a reference.
DecimalSeparator
Type of decimal separator.
AlkOnlineQuoteSource asReference() const
Return referenced quote source.
DataFormat dataFormat() const
Return the format of the downloaded data.
bool isFinanceQuote() const
Checks whether the current source is of type "Finance::Quote".
void setDataFormat(DataFormat dataFormat)
Set the format of the downloaded data.
void setDateRegex(const QString &dateRegex)
Set regular expression for parsing dates.
Hold errors reported from price quote fetching and parsing.
Retrieves a price quote from a web-based quote source.
LastPriceState
Supported values for returning prices in special cases.
The AlkWebPage class provides an object to load and view web documents to provide functionality like ...
Definition alkwebpage.h:100
The AlkWebView class provides a widget that is used to load and display web documents.
Definition alkwebview.h:87
bool isShowAnimationRunning() const
void animatedShow()
int heightForWidth(int width) const override
void setMessageType(KMessageWidget::MessageType type)
void setText(const QString &text)
Q_SCRIPTABLE CaptureState status()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
void initIcons()
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardAction id)
KGuiItem cont()
KGuiItem cancel()
const QList< QKeySequence > & copy()
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
QFont font()
void setText(const QString &text, Mode mode)
QString toString(QStringView format, QCalendar cal) const const
QClipboard * clipboard()
void addWidget(QWidget *w)
void append(QList< T > &&value)
iterator begin()
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator end()
T & first()
bool isEmpty() const const
Qt::ItemFlags flags() const const
void setFlags(Qt::ItemFlags flags)
void setText(const QString &text)
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const Key &key) const const
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QObject * parent() const const
int width() const const
QString arg(Args &&... args) const const
QChar * data()
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
DisplayRole
ItemIsEditable
MatchExactly
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Qt::ItemFlags flags() const const
void setFlags(Qt::ItemFlags flags)
void setText(int column, const QString &text)
void hide()
QLayout * layout() const const
void setLayout(QLayout *layout)
void setTabOrder(QWidget *first, QWidget *second)
void show()
void resize(const QSize &)
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:13 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.