Changeset View
Changeset View
Standalone View
Standalone View
kmymoney/views/kmymoneyview.cpp
Show First 20 Lines • Show All 100 Lines • ▼ Show 20 Line(s) | |||||
101 | #include "mymoneysecurity.h" | 101 | #include "mymoneysecurity.h" | ||
102 | #include "mymoneyreport.h" | 102 | #include "mymoneyreport.h" | ||
103 | #include "kmymoneyplugin.h" | 103 | #include "kmymoneyplugin.h" | ||
104 | #include "mymoneyenums.h" | 104 | #include "mymoneyenums.h" | ||
105 | 105 | | |||
106 | using namespace Icons; | 106 | using namespace Icons; | ||
107 | using namespace eMyMoney; | 107 | using namespace eMyMoney; | ||
108 | 108 | | |||
109 | static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; | | |||
110 | static constexpr char recoveryKeyId[] = "0xD2B08440"; | | |||
111 | | ||||
112 | typedef void(KMyMoneyView::*KMyMoneyViewFunc)(); | 109 | typedef void(KMyMoneyView::*KMyMoneyViewFunc)(); | ||
113 | 110 | | |||
114 | KMyMoneyView::KMyMoneyView(KMyMoneyApp *kmymoney) | 111 | KMyMoneyView::KMyMoneyView(KMyMoneyApp *kmymoney) | ||
115 | : KPageWidget(nullptr), | 112 | : KPageWidget(nullptr), | ||
116 | m_header(0), | 113 | m_header(0), | ||
117 | m_inConstructor(true), | 114 | m_inConstructor(true), | ||
118 | m_fileOpen(false), | | |||
119 | m_fmode(QFileDevice::ReadUser | QFileDevice::WriteUser), | | |||
120 | m_lastViewSelected(0), | 115 | m_lastViewSelected(0), | ||
121 | m_storagePlugins(nullptr) | 116 | m_storagePlugins(nullptr) | ||
122 | #ifdef KF5Activities_FOUND | 117 | #ifdef KF5Activities_FOUND | ||
123 | , m_activityResourceInstance(0) | 118 | , m_activityResourceInstance(0) | ||
124 | #endif | 119 | #endif | ||
125 | { | 120 | { | ||
126 | // this is a workaround for the bug in KPageWidget that causes the header to be shown | 121 | // this is a workaround for the bug in KPageWidget that causes the header to be shown | ||
127 | // for a short while during page switch which causes a kind of bouncing of the page's | 122 | // for a short while during page switch which causes a kind of bouncing of the page's | ||
Show All 12 Lines | 131 | if (headerItem && qobject_cast<KTitleWidget*>(headerItem->widget()) != NULL) { | |||
140 | m_header->setObjectName("titleLabel"); | 135 | m_header->setObjectName("titleLabel"); | ||
141 | m_header->setMinimumSize(QSize(100, 30)); | 136 | m_header->setMinimumSize(QSize(100, 30)); | ||
142 | m_header->setRightImageFile("pics/titlelabel_background.png"); | 137 | m_header->setRightImageFile("pics/titlelabel_background.png"); | ||
143 | m_header->setVisible(KMyMoneySettings::showTitleBar()); | 138 | m_header->setVisible(KMyMoneySettings::showTitleBar()); | ||
144 | gridLayout->addWidget(m_header, 1, 1); | 139 | gridLayout->addWidget(m_header, 1, 1); | ||
145 | } | 140 | } | ||
146 | } | 141 | } | ||
147 | 142 | | |||
148 | newStorage(); | 143 | // newStorage(); | ||
149 | m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit | 144 | m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit | ||
150 | 145 | | |||
151 | connect(kmymoney, &KMyMoneyApp::fileLoaded, this, &KMyMoneyView::slotRefreshViews); | 146 | connect(kmymoney, &KMyMoneyApp::fileLoaded, this, &KMyMoneyView::slotRefreshViews); | ||
152 | 147 | | |||
153 | // Page 0 | 148 | // Page 0 | ||
154 | m_homeView = new KHomeView; | 149 | m_homeView = new KHomeView; | ||
155 | viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home")); | 150 | viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home")); | ||
156 | viewFrames[View::Home]->setIcon(Icons::get(Icon::ViewHome)); | 151 | viewFrames[View::Home]->setIcon(Icons::get(Icon::ViewHome)); | ||
▲ Show 20 Lines • Show All 80 Lines • ▼ Show 20 Line(s) | 171 | //this is to solve the way long strings are handled differently among versions of KPageWidget | |||
237 | connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); | 232 | connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); | ||
238 | connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); | 233 | connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); | ||
239 | 234 | | |||
240 | connect(m_reportsView, &KReportsView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); | 235 | connect(m_reportsView, &KReportsView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); | ||
241 | connect(m_ledgerView, &KGlobalLedgerView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); | 236 | connect(m_ledgerView, &KGlobalLedgerView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); | ||
242 | connect(m_homeView, &KHomeView::ledgerSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); | 237 | connect(m_homeView, &KHomeView::ledgerSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); | ||
243 | 238 | | |||
244 | #ifdef ENABLE_UNFINISHEDFEATURES | 239 | #ifdef ENABLE_UNFINISHEDFEATURES | ||
245 | SimpleLedgerView* view = new SimpleLedgerView(kmymoney, this); | 240 | m_simpleLedgerView = new SimpleLedgerView(kmymoney, this); | ||
246 | KPageWidgetItem* frame = m_model->addPage(view, i18n("New ledger")); | 241 | KPageWidgetItem* frame = m_model->addPage(m_simpleLedgerView, i18n("New ledger")); | ||
247 | frame->setIcon(Icons::get(Icon::DocumentProperties)); | 242 | frame->setIcon(Icons::get(Icon::DocumentProperties)); | ||
248 | #endif | 243 | #endif | ||
249 | 244 | | |||
250 | 245 | | |||
251 | //set the model | 246 | //set the model | ||
252 | setModel(m_model); | 247 | setModel(m_model); | ||
253 | setCurrentPage(viewFrames[View::Home]); | 248 | setCurrentPage(viewFrames[View::Home]); | ||
254 | connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); | 249 | connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); | ||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Line(s) | |||||
306 | } | 301 | } | ||
307 | 302 | | |||
308 | KMyMoneyView::~KMyMoneyView() | 303 | KMyMoneyView::~KMyMoneyView() | ||
309 | { | 304 | { | ||
310 | KMyMoneySettings::setLastViewSelected(m_lastViewSelected); | 305 | KMyMoneySettings::setLastViewSelected(m_lastViewSelected); | ||
311 | #ifdef KF5Activities_FOUND | 306 | #ifdef KF5Activities_FOUND | ||
312 | delete m_activityResourceInstance; | 307 | delete m_activityResourceInstance; | ||
313 | #endif | 308 | #endif | ||
314 | removeStorage(); | 309 | // removeStorage(); | ||
310 | } | ||||
311 | | ||||
312 | void KMyMoneyView::slotFileOpened() | ||||
313 | { | ||||
314 | #ifdef ENABLE_UNFINISHEDFEATURES | ||||
315 | m_simpleLedgerView->openFavoriteLedgers(); | ||||
316 | #endif | ||||
317 | switchToDefaultView(); | ||||
318 | } | ||||
319 | | ||||
320 | void KMyMoneyView::slotFileClosed() | ||||
321 | { | ||||
322 | if (m_reportsView) | ||||
323 | m_reportsView->slotCloseAll(); | ||||
324 | #ifdef ENABLE_UNFINISHEDFEATURES | ||||
325 | m_simpleLedgerView->closeLedgers(); | ||||
326 | #endif | ||||
327 | slotShowHomePage(); | ||||
315 | } | 328 | } | ||
316 | 329 | | |||
317 | void KMyMoneyView::slotShowHomePage() | 330 | void KMyMoneyView::slotShowHomePage() | ||
318 | { | 331 | { | ||
319 | showPage(viewFrames[View::Home]); | 332 | showPage(viewFrames[View::Home]); | ||
320 | } | 333 | } | ||
321 | 334 | | |||
322 | void KMyMoneyView::slotShowInstitutionsPage() | 335 | void KMyMoneyView::slotShowInstitutionsPage() | ||
▲ Show 20 Lines • Show All 247 Lines • ▼ Show 20 Line(s) | |||||
570 | { | 583 | { | ||
571 | bool rc = ( | 584 | bool rc = ( | ||
572 | viewFrames[View::Reports] == currentPage() | 585 | viewFrames[View::Reports] == currentPage() | ||
573 | || viewFrames[View::Home] == currentPage() | 586 | || viewFrames[View::Home] == currentPage() | ||
574 | ); | 587 | ); | ||
575 | return rc; | 588 | return rc; | ||
576 | } | 589 | } | ||
577 | 590 | | |||
578 | void KMyMoneyView::newStorage() | 591 | void KMyMoneyView::enableViewsIfFileOpen(bool fileOpen) | ||
579 | { | | |||
580 | removeStorage(); | | |||
581 | auto file = MyMoneyFile::instance(); | | |||
582 | file->attachStorage(new MyMoneyStorageMgr); | | |||
583 | } | | |||
584 | | ||||
585 | void KMyMoneyView::removeStorage() | | |||
586 | { | | |||
587 | auto file = MyMoneyFile::instance(); | | |||
588 | auto p = file->storage(); | | |||
589 | if (p) { | | |||
590 | file->detachStorage(p); | | |||
591 | delete p; | | |||
592 | } | | |||
593 | } | | |||
594 | | ||||
595 | void KMyMoneyView::enableViewsIfFileOpen() | | |||
596 | { | 592 | { | ||
597 | // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this | 593 | // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this | ||
598 | for (auto i = (int)View::Home; i < (int)View::None; ++i) | 594 | for (auto i = (int)View::Home; i < (int)View::None; ++i) | ||
599 | if (viewFrames.contains(View(i))) | 595 | if (viewFrames.contains(View(i))) | ||
600 | if (viewFrames[View(i)]->isEnabled() != m_fileOpen) | 596 | if (viewFrames[View(i)]->isEnabled() != fileOpen) | ||
601 | viewFrames[View(i)]->setEnabled(m_fileOpen); | 597 | viewFrames[View(i)]->setEnabled(fileOpen); | ||
602 | | ||||
603 | emit viewStateChanged(m_fileOpen); | | |||
604 | } | | |||
605 | | ||||
606 | void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) | | |||
607 | { | | |||
608 | showPage(viewFrames[View::Payees]); | | |||
609 | m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); | | |||
610 | } | | |||
611 | | ||||
612 | void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) | | |||
613 | { | | |||
614 | showPage(viewFrames[View::Tags]); | | |||
615 | m_tagsView->slotSelectTagAndTransaction(tag, account, transaction); | | |||
616 | } | | |||
617 | | ||||
618 | bool KMyMoneyView::fileOpen() | | |||
619 | { | | |||
620 | return m_fileOpen; | | |||
621 | } | | |||
622 | | ||||
623 | void KMyMoneyView::closeFile() | | |||
624 | { | | |||
625 | if (m_reportsView) | | |||
626 | m_reportsView->slotCloseAll(); | | |||
627 | | ||||
628 | // disconnect the signals | | |||
629 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
630 | Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); | | |||
631 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
632 | Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); | | |||
633 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
634 | Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); | | |||
635 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, | | |||
636 | Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
637 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, | | |||
638 | Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
639 | | ||||
640 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
641 | Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); | | |||
642 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
643 | Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); | | |||
644 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
645 | Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); | | |||
646 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, | | |||
647 | Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
648 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, | | |||
649 | Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
650 | | ||||
651 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
652 | Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); | | |||
653 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
654 | Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); | | |||
655 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
656 | Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); | | |||
657 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, | | |||
658 | Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); | | |||
659 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, | | |||
660 | Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); | | |||
661 | | ||||
662 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
663 | Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); | | |||
664 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
665 | Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); | | |||
666 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
667 | Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); | | |||
668 | | ||||
669 | disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); | | |||
670 | | ||||
671 | // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) | | |||
672 | Models::instance()->fileClosed(); | | |||
673 | | ||||
674 | emit kmmFilePlugin(preClose); | | |||
675 | if (isDatabase()) | | |||
676 | MyMoneyFile::instance()->storage()->close(); // to log off a database user | | |||
677 | newStorage(); | | |||
678 | | ||||
679 | slotShowHomePage(); | | |||
680 | | ||||
681 | emit kmmFilePlugin(postClose); | | |||
682 | m_fileOpen = false; | | |||
683 | | ||||
684 | emit fileClosed(); | | |||
685 | } | | |||
686 | | ||||
687 | void KMyMoneyView::ungetString(QIODevice *qfile, char *buf, int len) | | |||
688 | { | | |||
689 | buf = &buf[len-1]; | | |||
690 | while (len--) { | | |||
691 | qfile->ungetChar(*buf--); | | |||
692 | } | | |||
693 | } | | |||
694 | | ||||
695 | bool KMyMoneyView::readFile(const QUrl &url, IMyMoneyOperationsFormat* pExtReader) | | |||
696 | { | | |||
697 | QString filename; | | |||
698 | bool downloadedFile = false; | | |||
699 | m_fileOpen = false; | | |||
700 | bool isEncrypted = false; | | |||
701 | | ||||
702 | IMyMoneyOperationsFormat* pReader = 0; | | |||
703 | | ||||
704 | if (!url.isValid()) { | | |||
705 | qDebug("Invalid URL '%s'", qPrintable(url.url())); | | |||
706 | return false; | | |||
707 | } | | |||
708 | | ||||
709 | // disconnect the current storga manager from the engine | | |||
710 | MyMoneyFile::instance()->detachStorage(); | | |||
711 | | ||||
712 | if (url.scheme() == QLatin1String("sql")) { // handle reading of database | | |||
713 | m_fileType = KmmDb; | | |||
714 | // get rid of the mode parameter which is now redundant | | |||
715 | QUrl newUrl(url); | | |||
716 | QUrlQuery query(url); | | |||
717 | query.removeQueryItem("mode"); | | |||
718 | newUrl.setQuery(query); | | |||
719 | auto rc = openDatabase(newUrl); // on error, any message will have been displayed | | |||
720 | if (!rc) | | |||
721 | MyMoneyFile::instance()->attachStorage(new MyMoneyStorageMgr); | | |||
722 | return rc; | | |||
723 | } | | |||
724 | | ||||
725 | auto storage = new MyMoneyStorageMgr; | | |||
726 | | ||||
727 | if (url.isLocalFile()) { | | |||
728 | filename = url.toLocalFile(); | | |||
729 | } else { | | |||
730 | downloadedFile = true; | | |||
731 | KIO::StoredTransferJob *transferjob = KIO::storedGet (url); | | |||
732 | KJobWidgets::setWindow(transferjob, this); | | |||
733 | if (! transferjob->exec()) { | | |||
734 | KMessageBox::detailedError(this, | | |||
735 | i18n("Error while loading file '%1'.", url.url()), | | |||
736 | transferjob->errorString(), | | |||
737 | i18n("File access error")); | | |||
738 | return false; | | |||
739 | } | | |||
740 | QTemporaryFile file; | | |||
741 | file.setAutoRemove(false); | | |||
742 | file.open(); | | |||
743 | file.write(transferjob->data()); | | |||
744 | filename = file.fileName(); | | |||
745 | file.close(); | | |||
746 | } | | |||
747 | | ||||
748 | // let's glimps into the file to figure out, if it's one | | |||
749 | // of the old (uncompressed) or new (compressed) files. | | |||
750 | QFile file(filename); | | |||
751 | QFileInfo info(file); | | |||
752 | if (!info.isFile()) { | | |||
753 | QString msg = i18n("<p><b>%1</b> is not a KMyMoney file.</p>", filename); | | |||
754 | KMessageBox::error(this, msg, i18n("Filetype Error")); | | |||
755 | return false; | | |||
756 | } | | |||
757 | m_fmode = QFileDevice::ReadUser | QFileDevice::WriteUser; | | |||
758 | m_fmode |= info.permissions(); | | |||
759 | | ||||
760 | bool rc = true; | | |||
761 | | ||||
762 | // There's a problem with the KFilterDev and KGPGFile classes: | | |||
763 | // One supports the at(n) member but not ungetch() together with | | |||
764 | // read() and the other does not provide an at(n) method but | | |||
765 | // supports read() that considers the ungetch() buffer. QFile | | |||
766 | // supports everything so this is not a problem. We solve the problem | | |||
767 | // for now by keeping track of which method can be used. | | |||
768 | bool haveAt = true; | | |||
769 | | ||||
770 | emit kmmFilePlugin(preOpen); | | |||
771 | if (file.open(QIODevice::ReadOnly)) { | | |||
772 | QByteArray hdr(2, '\0'); | | |||
773 | int cnt; | | |||
774 | cnt = file.read(hdr.data(), 2); | | |||
775 | file.close(); | | |||
776 | | ||||
777 | if (cnt == 2) { | | |||
778 | QIODevice* qfile = nullptr; | | |||
779 | if (QString(hdr) == QString("\037\213")) { // gzipped? | | |||
780 | qfile = new KCompressionDevice(filename, COMPRESSION_TYPE); | | |||
781 | } else if (QString(hdr) == QString("--") // PGP ASCII armored? | | |||
782 | || QString(hdr) == QString("\205\001") // PGP binary? | | |||
783 | || QString(hdr) == QString("\205\002")) { // PGP binary? | | |||
784 | if (KGPGFile::GPGAvailable()) { | | |||
785 | qfile = new KGPGFile(filename); | | |||
786 | haveAt = false; | | |||
787 | isEncrypted = true; | | |||
788 | } else { | | |||
789 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("GPG is not available for decryption of file <b>%1</b>", filename))); | | |||
790 | qfile = new QFile(file.fileName()); | | |||
791 | } | | |||
792 | } else { | | |||
793 | // we can't use file directly, as we delete qfile later on | | |||
794 | qfile = new QFile(file.fileName()); | | |||
795 | } | | |||
796 | | ||||
797 | if (qfile->open(QIODevice::ReadOnly)) { | | |||
798 | try { | | |||
799 | hdr.resize(8); | | |||
800 | if (qfile->read(hdr.data(), 8) == 8) { | | |||
801 | if (haveAt) | | |||
802 | qfile->seek(0); | | |||
803 | else | | |||
804 | ungetString(qfile, hdr.data(), 8); | | |||
805 | | ||||
806 | // Ok, we got the first block of 8 bytes. Read in the two | | |||
807 | // unsigned long int's by preserving endianess. This is | | |||
808 | // achieved by reading them through a QDataStream object | | |||
809 | qint32 magic0, magic1; | | |||
810 | QDataStream s(&hdr, QIODevice::ReadOnly); | | |||
811 | s >> magic0; | | |||
812 | s >> magic1; | | |||
813 | | ||||
814 | // If both magic numbers match (we actually read in the | | |||
815 | // text 'KMyMoney' then we assume a binary file and | | |||
816 | // construct a reader for it. Otherwise, we construct | | |||
817 | // an XML reader object. | | |||
818 | // | | |||
819 | // The expression magic0 < 30 is only used to create | | |||
820 | // a binary reader if we assume an old binary file. This | | |||
821 | // should be removed at some point. An alternative is to | | |||
822 | // check the beginning of the file against an pattern | | |||
823 | // of the XML file (e.g. '?<xml' ). | | |||
824 | if ((magic0 == MAGIC_0_50 && magic1 == MAGIC_0_51) | | |||
825 | || magic0 < 30) { | | |||
826 | // we do not support this file format anymore | | |||
827 | pReader = 0; | | |||
828 | m_fileType = KmmBinary; | | |||
829 | } else { | | |||
830 | // Scan the first 70 bytes to see if we find something | | |||
831 | // we know. For now, we support our own XML format and | | |||
832 | // GNUCash XML format. If the file is smaller, then it | | |||
833 | // contains no valid data and we reject it anyway. | | |||
834 | hdr.resize(70); | | |||
835 | if (qfile->read(hdr.data(), 70) == 70) { | | |||
836 | if (haveAt) | | |||
837 | qfile->seek(0); | | |||
838 | else | | |||
839 | ungetString(qfile, hdr.data(), 70); | | |||
840 | QRegExp kmyexp("<!DOCTYPE KMYMONEY-FILE>"); | | |||
841 | QRegExp gncexp("<gnc-v(\\d+)"); | | |||
842 | QByteArray txt(hdr, 70); | | |||
843 | if (kmyexp.indexIn(txt) != -1) { | | |||
844 | pReader = new MyMoneyStorageXML; | | |||
845 | m_fileType = KmmXML; | | |||
846 | } else if (gncexp.indexIn(txt) != -1) { | | |||
847 | if (pExtReader) { | | |||
848 | MyMoneyFile::instance()->attachStorage(storage); | | |||
849 | loadAllCurrencies(); // currency list required for gnc | | |||
850 | MyMoneyFile::instance()->detachStorage(storage); | | |||
851 | | ||||
852 | pReader = pExtReader; | | |||
853 | m_fileType = GncXML; | | |||
854 | } | | |||
855 | } | | |||
856 | } | | |||
857 | } | | |||
858 | if (pReader) { | | |||
859 | pReader->setProgressCallback(&KMyMoneyView::progressCallback); | | |||
860 | pReader->readFile(qfile, storage); | | |||
861 | } else { | | |||
862 | if (m_fileType == KmmBinary) { | | |||
863 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.", filename))); | | |||
864 | } else { | | |||
865 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> contains an unknown file format.", filename))); | | |||
866 | } | | |||
867 | rc = false; | | |||
868 | } | | |||
869 | } else { | | |||
870 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("Cannot read from file <b>%1</b>.", filename))); | | |||
871 | rc = false; | | |||
872 | } | | |||
873 | } catch (const MyMoneyException &e) { | | |||
874 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("Cannot load file <b>%1</b>. Reason: %2", filename, e.what()))); | | |||
875 | rc = false; | | |||
876 | } | | |||
877 | if (pReader) { | | |||
878 | pReader->setProgressCallback(0); | | |||
879 | delete pReader; | | |||
880 | } | | |||
881 | qfile->close(); | | |||
882 | } else { | | |||
883 | KGPGFile *gpgFile = qobject_cast<KGPGFile *>(qfile); | | |||
884 | if (gpgFile && !gpgFile->errorToString().isEmpty()) { | | |||
885 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("The following error was encountered while decrypting file <b>%1</b>: %2", filename, gpgFile->errorToString()))); | | |||
886 | } else { | | |||
887 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> not found.", filename))); | | |||
888 | } | | |||
889 | rc = false; | | |||
890 | } | | |||
891 | delete qfile; | | |||
892 | } | | |||
893 | } else { | | |||
894 | KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> not found.", filename))); | | |||
895 | rc = false; | | |||
896 | } | | |||
897 | | ||||
898 | // things are finished, now we connect the storage to the engine | | |||
899 | // which forces a reload of the cache in the engine with those | | |||
900 | // objects that are cached | | |||
901 | MyMoneyFile::instance()->attachStorage(storage); | | |||
902 | | ||||
903 | if (rc == false) | | |||
904 | return rc; | | |||
905 | | ||||
906 | // encapsulate transactions to the engine to be able to commit/rollback | | |||
907 | MyMoneyFileTransaction ft; | | |||
908 | | ||||
909 | // make sure we setup the encryption key correctly | | |||
910 | if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { | | |||
911 | MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneySettings::gpgRecipientList().join(",")); | | |||
912 | } | | |||
913 | | ||||
914 | // make sure we setup the name of the base accounts in translated form | | |||
915 | try { | | |||
916 | MyMoneyFile *file = MyMoneyFile::instance(); | | |||
917 | checkAccountName(file->asset(), i18n("Asset")); | | |||
918 | checkAccountName(file->liability(), i18n("Liability")); | | |||
919 | checkAccountName(file->income(), i18n("Income")); | | |||
920 | checkAccountName(file->expense(), i18n("Expense")); | | |||
921 | checkAccountName(file->equity(), i18n("Equity")); | | |||
922 | ft.commit(); | | |||
923 | } catch (const MyMoneyException &) { | | |||
924 | } | | |||
925 | 598 | | |||
926 | // if a temporary file was downloaded, then it will be removed | 599 | emit viewStateChanged(fileOpen); | ||
927 | // with the next call. Otherwise, it stays untouched on the local | | |||
928 | // filesystem. | | |||
929 | if (downloadedFile) { | | |||
930 | QFile::remove(filename); | | |||
931 | } | 600 | } | ||
932 | 601 | | |||
933 | return initializeStorage(); | 602 | void KMyMoneyView::switchToDefaultView() | ||
934 | } | | |||
935 | | ||||
936 | void KMyMoneyView::checkAccountName(const MyMoneyAccount& _acc, const QString& name) const | | |||
937 | { | | |||
938 | auto file = MyMoneyFile::instance(); | | |||
939 | if (_acc.name() != name) { | | |||
940 | MyMoneyAccount acc(_acc); | | |||
941 | acc.setName(name); | | |||
942 | file->modifyAccount(acc); | | |||
943 | } | | |||
944 | } | | |||
945 | | ||||
946 | bool KMyMoneyView::openDatabase(const QUrl &url) | | |||
947 | { | 603 | { | ||
948 | m_fileOpen = false; | | |||
949 | | ||||
950 | // open the database | | |||
951 | auto pStorage = MyMoneyFile::instance()->storage(); | | |||
952 | if (!pStorage) | | |||
953 | pStorage = new MyMoneyStorageMgr; | | |||
954 | | ||||
955 | auto rc = false; | | |||
956 | auto pluginFound = false; | | |||
957 | if (m_storagePlugins) { | | |||
958 | for (const auto& plugin : *m_storagePlugins) { | | |||
959 | if (plugin->formatName().compare(QLatin1String("SQL")) == 0) { | | |||
960 | rc = plugin->open(pStorage, url); | | |||
961 | pluginFound = true; | | |||
962 | break; | | |||
963 | } | | |||
964 | } | | |||
965 | } | | |||
966 | | ||||
967 | if(!pluginFound) | | |||
968 | KMessageBox::error(this, i18n("Couldn't find suitable plugin to read your storage.")); | | |||
969 | | ||||
970 | if(!rc) { | | |||
971 | removeStorage(); | | |||
972 | delete pStorage; | | |||
973 | return false; | | |||
974 | } | | |||
975 | | ||||
976 | if (pStorage) { | | |||
977 | removeStorage(); | | |||
978 | MyMoneyFile::instance()->attachStorage(pStorage); | | |||
979 | } | | |||
980 | | ||||
981 | m_fileOpen = true; | | |||
982 | return initializeStorage(); | | |||
983 | } | | |||
984 | | ||||
985 | bool KMyMoneyView::initializeStorage() | | |||
986 | { | | |||
987 | bool blocked = MyMoneyFile::instance()->signalsBlocked(); | | |||
988 | MyMoneyFile::instance()->blockSignals(true); | | |||
989 | | ||||
990 | // we check, if we have any currency in the file. If not, we load | | |||
991 | // all the default currencies we know. | | |||
992 | MyMoneyFileTransaction ft; | | |||
993 | try { | | |||
994 | updateCurrencyNames(); | | |||
995 | ft.commit(); | | |||
996 | } catch (const MyMoneyException &) { | | |||
997 | MyMoneyFile::instance()->blockSignals(blocked); | | |||
998 | return false; | | |||
999 | } | | |||
1000 | | ||||
1001 | // make sure, we have a base currency and all accounts are | | |||
1002 | // also assigned to a currency. | | |||
1003 | QString baseId; | | |||
1004 | try { | | |||
1005 | baseId = MyMoneyFile::instance()->baseCurrency().id(); | | |||
1006 | } catch (const MyMoneyException &e) { | | |||
1007 | qDebug() << e.what(); | | |||
1008 | } | | |||
1009 | | ||||
1010 | if (baseId.isEmpty()) { | | |||
1011 | // Stay in this endless loop until we have a base currency, | | |||
1012 | // as without it the application does not work anymore. | | |||
1013 | while (baseId.isEmpty()) { | | |||
1014 | selectBaseCurrency(); | | |||
1015 | try { | | |||
1016 | baseId = MyMoneyFile::instance()->baseCurrency().id(); | | |||
1017 | } catch (const MyMoneyException &e) { | | |||
1018 | qDebug() << e.what(); | | |||
1019 | } | | |||
1020 | } | | |||
1021 | } else { | | |||
1022 | // in some odd intermediate cases there could be files out there | | |||
1023 | // that have a base currency set, but still have accounts that | | |||
1024 | // do not have a base currency assigned. This call will take | | |||
1025 | // care of it. We can safely remove it later. | | |||
1026 | // | | |||
1027 | // Another work-around for this scenario is to remove the base | | |||
1028 | // currency setting from the XML file by removing the line | | |||
1029 | // | | |||
1030 | // <PAIR key="kmm-baseCurrency" value="xxx" /> | | |||
1031 | // | | |||
1032 | // and restart the application with this file. This will force to | | |||
1033 | // run the above loop. | | |||
1034 | selectBaseCurrency(); | | |||
1035 | } | | |||
1036 | | ||||
1037 | // setup the standard precision | | |||
1038 | AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); | | |||
1039 | KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); | | |||
1040 | | ||||
1041 | KSharedConfigPtr config = KSharedConfig::openConfig(); | | |||
1042 | KPageWidgetItem* page; | 604 | KPageWidgetItem* page; | ||
1043 | KConfigGroup grp = config->group("General Options"); | | |||
1044 | | ||||
1045 | if (KMyMoneySettings::startLastViewSelected() != 0) | 605 | if (KMyMoneySettings::startLastViewSelected() != 0) | ||
1046 | page = viewFrames.value(static_cast<View>(KMyMoneySettings::lastViewSelected())); | 606 | page = viewFrames.value(static_cast<View>(KMyMoneySettings::lastViewSelected())); | ||
1047 | else | 607 | else | ||
1048 | page = viewFrames[View::Home]; | 608 | page = viewFrames[View::Home]; | ||
1049 | | ||||
1050 | // For debugging purposes, we can turn off the automatic fix manually | | |||
1051 | // by setting the entry in kmymoneyrc to true | | |||
1052 | grp = config->group("General Options"); | | |||
1053 | if (grp.readEntry("SkipFix", false) != true) { | | |||
1054 | MyMoneyFileTransaction ft; | | |||
1055 | try { | | |||
1056 | // Check if we have to modify the file before we allow to work with it | | |||
1057 | auto s = MyMoneyFile::instance()->storage(); | | |||
1058 | while (s->fileFixVersion() < s->currentFixVersion()) { | | |||
1059 | qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); | | |||
1060 | switch (s->fileFixVersion()) { | | |||
1061 | case 0: | | |||
1062 | fixFile_0(); | | |||
1063 | s->setFileFixVersion(1); | | |||
1064 | break; | | |||
1065 | | ||||
1066 | case 1: | | |||
1067 | fixFile_1(); | | |||
1068 | s->setFileFixVersion(2); | | |||
1069 | break; | | |||
1070 | | ||||
1071 | case 2: | | |||
1072 | fixFile_2(); | | |||
1073 | s->setFileFixVersion(3); | | |||
1074 | break; | | |||
1075 | | ||||
1076 | case 3: | | |||
1077 | fixFile_3(); | | |||
1078 | s->setFileFixVersion(4); | | |||
1079 | break; | | |||
1080 | | ||||
1081 | // add new levels above. Don't forget to increase currentFixVersion() for all | | |||
1082 | // the storage backends this fix applies to | | |||
1083 | default: | | |||
1084 | throw MYMONEYEXCEPTION(i18n("Unknown fix level in input file")); | | |||
1085 | } | | |||
1086 | } | | |||
1087 | ft.commit(); | | |||
1088 | } catch (const MyMoneyException &) { | | |||
1089 | MyMoneyFile::instance()->blockSignals(blocked); | | |||
1090 | return false; | | |||
1091 | } | | |||
1092 | } else { | | |||
1093 | qDebug("Skipping automatic transaction fix!"); | | |||
1094 | } | | |||
1095 | MyMoneyFile::instance()->blockSignals(blocked); | | |||
1096 | | ||||
1097 | // FIXME: we need to check, if it's necessary to have this | | |||
1098 | // automatic funcitonality | | |||
1099 | // if there's no asset account, then automatically start the | | |||
1100 | // new account wizard | | |||
1101 | // kmymoney->createInitialAccount(); | | |||
1102 | | ||||
1103 | m_fileOpen = true; | | |||
1104 | emit kmmFilePlugin(postOpen); | | |||
1105 | | ||||
1106 | Models::instance()->fileOpened(); | | |||
1107 | | ||||
1108 | // connect the needed signals | | |||
1109 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
1110 | Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); | | |||
1111 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
1112 | Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); | | |||
1113 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
1114 | Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); | | |||
1115 | connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, | | |||
1116 | Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
1117 | connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, | | |||
1118 | Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
1119 | | ||||
1120 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
1121 | Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); | | |||
1122 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
1123 | Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); | | |||
1124 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
1125 | Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); | | |||
1126 | connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, | | |||
1127 | Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
1128 | connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, | | |||
1129 | Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); | | |||
1130 | | ||||
1131 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
1132 | Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); | | |||
1133 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
1134 | Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); | | |||
1135 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
1136 | Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); | | |||
1137 | connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, | | |||
1138 | Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); | | |||
1139 | connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, | | |||
1140 | Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); | | |||
1141 | | ||||
1142 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, | | |||
1143 | Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); | | |||
1144 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, | | |||
1145 | Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); | | |||
1146 | connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, | | |||
1147 | Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); | | |||
1148 | | ||||
1149 | // inform everyone about new data | | |||
1150 | MyMoneyFile::instance()->forceDataChanged(); | | |||
1151 | | ||||
1152 | // views can wait since they are going to be refresed in slotRefreshViews | | |||
1153 | connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); | | |||
1154 | | ||||
1155 | // if we currently see a different page, then select the right one | 609 | // if we currently see a different page, then select the right one | ||
1156 | if (page != currentPage()) { | 610 | if (page != currentPage()) | ||
1157 | showPage(page); | 611 | showPage(page); | ||
1158 | } | 612 | } | ||
1159 | 613 | | |||
1160 | emit fileOpened(); | 614 | void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) | ||
1161 | return true; | | |||
1162 | } | | |||
1163 | | ||||
1164 | void KMyMoneyView::saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList) | | |||
1165 | { | | |||
1166 | // Check GPG encryption | | |||
1167 | bool encryptFile = true; | | |||
1168 | bool encryptRecover = false; | | |||
1169 | if (!keyList.isEmpty()) { | | |||
1170 | if (!KGPGFile::GPGAvailable()) { | | |||
1171 | KMessageBox::sorry(this, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); | | |||
1172 | encryptFile = false; | | |||
1173 | } else { | | |||
1174 | if (KMyMoneySettings::encryptRecover()) { | | |||
1175 | encryptRecover = true; | | |||
1176 | if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { | | |||
1177 | KMessageBox::sorry(this, i18n("<p>You have selected to encrypt your data also with the KMyMoney recover key, but the key with id</p><p><center><b>%1</b></center></p><p>has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the <a href=\"https://kmymoney.org/\">KMyMoney web-site</a>. This time your data will not be encrypted with the KMyMoney recover key.</p>", QString(recoveryKeyId)), i18n("GPG Key not found")); | | |||
1178 | encryptRecover = false; | | |||
1179 | } | | |||
1180 | } | | |||
1181 | | ||||
1182 | for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { | | |||
1183 | if (!KGPGFile::keyAvailable(key)) { | | |||
1184 | KMessageBox::sorry(this, i18n("<p>You have specified to encrypt your data for the user-id</p><p><center><b>%1</b>.</center></p><p>Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.</p>", key), i18n("GPG Key not found")); | | |||
1185 | encryptFile = false; | | |||
1186 | break; | | |||
1187 | } | | |||
1188 | } | | |||
1189 | | ||||
1190 | if (encryptFile == true) { | | |||
1191 | QString msg = i18n("<p>You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer <b>No</b>.</p>"); | | |||
1192 | if (KMessageBox::questionYesNo(this, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { | | |||
1193 | encryptFile = false; | | |||
1194 | } | | |||
1195 | } | | |||
1196 | } | | |||
1197 | } | | |||
1198 | | ||||
1199 | | ||||
1200 | // Create a temporary file if needed | | |||
1201 | QString writeFile = localFile; | | |||
1202 | QTemporaryFile tmpFile; | | |||
1203 | if (QFile::exists(localFile)) { | | |||
1204 | tmpFile.open(); | | |||
1205 | writeFile = tmpFile.fileName(); | | |||
1206 | tmpFile.close(); | | |||
1207 | } | | |||
1208 | | ||||
1209 | /** | | |||
1210 | * @brief Automatically restore settings when scope is left | | |||
1211 | */ | | |||
1212 | struct restorePreviousSettingsHelper { | | |||
1213 | restorePreviousSettingsHelper() | | |||
1214 | : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()} | | |||
1215 | { | | |||
1216 | MyMoneyFile::instance()->blockSignals(true); | | |||
1217 | } | | |||
1218 | | ||||
1219 | ~restorePreviousSettingsHelper() | | |||
1220 | { | | |||
1221 | MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); | | |||
1222 | } | | |||
1223 | const bool m_signalsWereBlocked; | | |||
1224 | } restoreHelper; | | |||
1225 | | ||||
1226 | MyMoneyFileTransaction ft; | | |||
1227 | MyMoneyFile::instance()->deletePair("kmm-encryption-key"); | | |||
1228 | std::unique_ptr<QIODevice> device; | | |||
1229 | | ||||
1230 | if (!keyList.isEmpty() && encryptFile && !plaintext) { | | |||
1231 | std::unique_ptr<KGPGFile> kgpg = std::unique_ptr<KGPGFile>(new KGPGFile{writeFile}); | | |||
1232 | if (kgpg) { | | |||
1233 | for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { | | |||
1234 | kgpg->addRecipient(key.toLatin1()); | | |||
1235 | } | | |||
1236 | | ||||
1237 | if (encryptRecover) { | | |||
1238 | kgpg->addRecipient(recoveryKeyId); | | |||
1239 | } | | |||
1240 | MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); | | |||
1241 | device = std::unique_ptr<decltype(device)::element_type>(kgpg.release()); | | |||
1242 | } | | |||
1243 | } else { | | |||
1244 | QFile *file = new QFile(writeFile); | | |||
1245 | // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object | | |||
1246 | device = std::unique_ptr<decltype(device)::element_type>(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); | | |||
1247 | } | | |||
1248 | | ||||
1249 | ft.commit(); | | |||
1250 | | ||||
1251 | if (!device || !device->open(QIODevice::WriteOnly)) { | | |||
1252 | throw MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.", localFile)); | | |||
1253 | } | | |||
1254 | | ||||
1255 | pWriter->setProgressCallback(&KMyMoneyView::progressCallback); | | |||
1256 | pWriter->writeFile(device.get(), MyMoneyFile::instance()->storage()); | | |||
1257 | device->close(); | | |||
1258 | | ||||
1259 | // Check for errors if possible, only possible for KGPGFile | | |||
1260 | QFileDevice *fileDevice = qobject_cast<QFileDevice*>(device.get()); | | |||
1261 | if (fileDevice && fileDevice->error() != QFileDevice::NoError) { | | |||
1262 | throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); | | |||
1263 | } | | |||
1264 | | ||||
1265 | if (writeFile != localFile) { | | |||
1266 | // This simple comparison is possible because the strings are equal if no temporary file was created. | | |||
1267 | // If a temporary file was created, it is made in a way that the name is definitely different. So no | | |||
1268 | // symlinks etc. have to be evaluated. | | |||
1269 | if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) | | |||
1270 | throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); | | |||
1271 | } | | |||
1272 | QFile::setPermissions(localFile, m_fmode); | | |||
1273 | pWriter->setProgressCallback(0); | | |||
1274 | } | | |||
1275 | | ||||
1276 | bool KMyMoneyView::saveFile(const QUrl &url, const QString& keyList) | | |||
1277 | { | 615 | { | ||
1278 | QString filename = url.path(); | 616 | showPage(viewFrames[View::Payees]); | ||
1279 | 617 | m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); | |||
1280 | if (!fileOpen()) { | | |||
1281 | KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); | | |||
1282 | return false; | | |||
1283 | } | | |||
1284 | | ||||
1285 | emit kmmFilePlugin(preSave); | | |||
1286 | std::unique_ptr<IMyMoneyOperationsFormat> storageWriter; | | |||
1287 | | ||||
1288 | // If this file ends in ".ANON.XML" then this should be written using the | | |||
1289 | // anonymous writer. | | |||
1290 | bool plaintext = filename.right(4).toLower() == ".xml"; | | |||
1291 | if (filename.right(9).toLower() == ".anon.xml") { | | |||
1292 | //! @todo C++14: use std::make_unique, also some lines below | | |||
1293 | storageWriter = std::unique_ptr<IMyMoneyOperationsFormat>(new MyMoneyStorageANON); | | |||
1294 | } else { | | |||
1295 | storageWriter = std::unique_ptr<IMyMoneyOperationsFormat>(new MyMoneyStorageXML); | | |||
1296 | } | | |||
1297 | | ||||
1298 | // actually, url should be the parameter to this function | | |||
1299 | // but for now, this would involve too many changes | | |||
1300 | bool rc = true; | | |||
1301 | try { | | |||
1302 | if (! url.isValid()) { | | |||
1303 | throw MYMONEYEXCEPTION(i18n("Malformed URL '%1'", url.url())); | | |||
1304 | } | | |||
1305 | | ||||
1306 | if (url.isLocalFile()) { | | |||
1307 | filename = url.toLocalFile(); | | |||
1308 | try { | | |||
1309 | const unsigned int nbak = KMyMoneySettings::autoBackupCopies(); | | |||
1310 | if (nbak) { | | |||
1311 | KBackup::numberedBackupFile(filename, QString(), QString::fromLatin1("~"), nbak); | | |||
1312 | } | | |||
1313 | saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); | | |||
1314 | } catch (const MyMoneyException &) { | | |||
1315 | throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); | | |||
1316 | } | | |||
1317 | } else { | | |||
1318 | QTemporaryFile tmpfile; | | |||
1319 | tmpfile.open(); // to obtain the name | | |||
1320 | tmpfile.close(); | | |||
1321 | saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); | | |||
1322 | | ||||
1323 | Q_CONSTEXPR int permission = -1; | | |||
1324 | QFile file(tmpfile.fileName()); | | |||
1325 | file.open(QIODevice::ReadOnly); | | |||
1326 | KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); | | |||
1327 | if (!putjob->exec()) { | | |||
1328 | throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'.<br />%2", url.toDisplayString(), putjob->errorString())); | | |||
1329 | } | | |||
1330 | file.close(); | | |||
1331 | } | | |||
1332 | m_fileType = KmmXML; | | |||
1333 | } catch (const MyMoneyException &e) { | | |||
1334 | KMessageBox::error(this, e.what()); | | |||
1335 | MyMoneyFile::instance()->setDirty(); | | |||
1336 | rc = false; | | |||
1337 | } | | |||
1338 | emit kmmFilePlugin(postSave); | | |||
1339 | return rc; | | |||
1340 | } | 618 | } | ||
1341 | 619 | | |||
1342 | bool KMyMoneyView::dirty() | 620 | void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) | ||
1343 | { | 621 | { | ||
1344 | if (!fileOpen()) | 622 | showPage(viewFrames[View::Tags]); | ||
1345 | return false; | 623 | m_tagsView->slotSelectTagAndTransaction(tag, account, transaction); | ||
1346 | | ||||
1347 | return MyMoneyFile::instance()->dirty(); | | |||
1348 | } | 624 | } | ||
1349 | 625 | | |||
1350 | void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) | 626 | void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) | ||
1351 | { | 627 | { | ||
1352 | Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); | 628 | Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); | ||
1353 | m_ledgerView->slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); | 629 | m_ledgerView->slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); | ||
1354 | } | 630 | } | ||
1355 | 631 | | |||
1356 | void KMyMoneyView::newFile() | | |||
1357 | { | | |||
1358 | closeFile(); | | |||
1359 | m_fileType = KmmXML; // assume native type until saved | | |||
1360 | m_fileOpen = true; | | |||
1361 | } | | |||
1362 | | ||||
1363 | void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) | 632 | void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) | ||
1364 | { | 633 | { | ||
1365 | if (!baseCurrency.id().isEmpty()) { | 634 | if (!baseCurrency.id().isEmpty()) { | ||
1366 | QString baseId; | 635 | QString baseId; | ||
1367 | try { | 636 | try { | ||
1368 | baseId = MyMoneyFile::instance()->baseCurrency().id(); | 637 | baseId = MyMoneyFile::instance()->baseCurrency().id(); | ||
1369 | } catch (const MyMoneyException &e) { | 638 | } catch (const MyMoneyException &e) { | ||
1370 | qDebug("%s", qPrintable(e.what())); | 639 | qDebug("%s", qPrintable(e.what())); | ||
1371 | } | 640 | } | ||
1372 | 641 | | |||
1373 | if (baseCurrency.id() != baseId) { | 642 | if (baseCurrency.id() != baseId) { | ||
1374 | MyMoneyFileTransaction ft; | 643 | MyMoneyFileTransaction ft; | ||
1375 | try { | 644 | try { | ||
1376 | MyMoneyFile::instance()->setBaseCurrency(baseCurrency); | 645 | MyMoneyFile::instance()->setBaseCurrency(baseCurrency); | ||
1377 | ft.commit(); | 646 | ft.commit(); | ||
1378 | } catch (const MyMoneyException &e) { | 647 | } catch (const MyMoneyException &e) { | ||
1379 | KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), e.what()), i18n("Set base currency")); | 648 | KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), e.what()), i18n("Set base currency")); | ||
1380 | } | 649 | } | ||
1381 | } | 650 | } | ||
1382 | AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); | 651 | AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); | ||
1383 | KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); | 652 | KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); | ||
1384 | } | 653 | } | ||
1385 | } | 654 | } | ||
1386 | 655 | | |||
1387 | void KMyMoneyView::selectBaseCurrency() | | |||
1388 | { | | |||
1389 | auto file = MyMoneyFile::instance(); | | |||
1390 | | ||||
1391 | // check if we have a base currency. If not, we need to select one | | |||
1392 | QString baseId; | | |||
1393 | try { | | |||
1394 | baseId = MyMoneyFile::instance()->baseCurrency().id(); | | |||
1395 | } catch (const MyMoneyException &e) { | | |||
1396 | qDebug("%s", qPrintable(e.what())); | | |||
1397 | } | | |||
1398 | | ||||
1399 | if (baseId.isEmpty()) { | | |||
1400 | QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(this); | | |||
1401 | connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); | | |||
1402 | dlg->exec(); | | |||
1403 | delete dlg; | | |||
1404 | } | | |||
1405 | | ||||
1406 | try { | | |||
1407 | baseId = MyMoneyFile::instance()->baseCurrency().id(); | | |||
1408 | } catch (const MyMoneyException &e) { | | |||
1409 | qDebug("%s", qPrintable(e.what())); | | |||
1410 | } | | |||
1411 | | ||||
1412 | if (!baseId.isEmpty()) { | | |||
1413 | // check that all accounts have a currency | | |||
1414 | QList<MyMoneyAccount> list; | | |||
1415 | file->accountList(list); | | |||
1416 | QList<MyMoneyAccount>::Iterator it; | | |||
1417 | | ||||
1418 | // don't forget those standard accounts | | |||
1419 | list << file->asset(); | | |||
1420 | list << file->liability(); | | |||
1421 | list << file->income(); | | |||
1422 | list << file->expense(); | | |||
1423 | list << file->equity(); | | |||
1424 | | ||||
1425 | | ||||
1426 | for (it = list.begin(); it != list.end(); ++it) { | | |||
1427 | QString cid; | | |||
1428 | try { | | |||
1429 | if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) | | |||
1430 | cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); | | |||
1431 | } catch (const MyMoneyException& e) { | | |||
1432 | qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what(); | | |||
1433 | } | | |||
1434 | | ||||
1435 | if (cid.isEmpty()) { | | |||
1436 | (*it).setCurrencyId(baseId); | | |||
1437 | MyMoneyFileTransaction ft; | | |||
1438 | try { | | |||
1439 | file->modifyAccount(*it); | | |||
1440 | ft.commit(); | | |||
1441 | } catch (const MyMoneyException &e) { | | |||
1442 | qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), qPrintable(e.what())); | | |||
1443 | } | | |||
1444 | } | | |||
1445 | } | | |||
1446 | } | | |||
1447 | } | | |||
1448 | | ||||
1449 | void KMyMoneyView::updateCurrencyNames() | | |||
1450 | { | | |||
1451 | auto file = MyMoneyFile::instance(); | | |||
1452 | MyMoneyFileTransaction ft; | | |||
1453 | | ||||
1454 | QList<MyMoneySecurity> storedCurrencies = MyMoneyFile::instance()->currencyList(); | | |||
1455 | QList<MyMoneySecurity> availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); | | |||
1456 | QStringList currencyIDs; | | |||
1457 | | ||||
1458 | foreach (auto currency, availableCurrencies) | | |||
1459 | currencyIDs.append(currency.id()); | | |||
1460 | | ||||
1461 | try { | | |||
1462 | foreach (auto currency, storedCurrencies) { | | |||
1463 | int i = currencyIDs.indexOf(currency.id()); | | |||
1464 | if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { | | |||
1465 | currency.setName(availableCurrencies.at(i).name()); | | |||
1466 | file->modifyCurrency(currency); | | |||
1467 | } | | |||
1468 | } | | |||
1469 | ft.commit(); | | |||
1470 | } catch (const MyMoneyException &e) { | | |||
1471 | qDebug("Error %s updating currency names", qPrintable(e.what())); | | |||
1472 | } | | |||
1473 | } | | |||
1474 | | ||||
1475 | void KMyMoneyView::loadAllCurrencies() | | |||
1476 | { | | |||
1477 | auto file = MyMoneyFile::instance(); | | |||
1478 | MyMoneyFileTransaction ft; | | |||
1479 | if (!file->currencyList().isEmpty()) | | |||
1480 | return; | | |||
1481 | QMap<MyMoneySecurity, MyMoneyPrice> ancientCurrencies = file->ancientCurrencies(); | | |||
1482 | try { | | |||
1483 | foreach (auto currency, file->availableCurrencyList()) { | | |||
1484 | file->addCurrency(currency); | | |||
1485 | MyMoneyPrice price = ancientCurrencies.value(currency, MyMoneyPrice()); | | |||
1486 | if (price != MyMoneyPrice()) | | |||
1487 | file->addPrice(price); | | |||
1488 | } | | |||
1489 | ft.commit(); | | |||
1490 | } catch (const MyMoneyException &e) { | | |||
1491 | qDebug("Error %s loading currency", qPrintable(e.what())); | | |||
1492 | } | | |||
1493 | } | | |||
1494 | | ||||
1495 | void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) | 656 | void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) | ||
1496 | { | 657 | { | ||
1497 | if (viewFrames[View::Accounts] != currentPage()) | 658 | if (viewFrames[View::Accounts] != currentPage()) | ||
1498 | showPage(viewFrames[View::Accounts]); | 659 | showPage(viewFrames[View::Accounts]); | ||
1499 | m_accountsView->show(); | 660 | m_accountsView->show(); | ||
1500 | } | 661 | } | ||
1501 | 662 | | |||
1502 | void KMyMoneyView::slotRefreshViews() | 663 | void KMyMoneyView::slotRefreshViews() | ||
Show All 30 Lines | |||||
1533 | } | 694 | } | ||
1534 | 695 | | |||
1535 | void KMyMoneyView::slotShowTransactionDetail(bool detailed) | 696 | void KMyMoneyView::slotShowTransactionDetail(bool detailed) | ||
1536 | { | 697 | { | ||
1537 | KMyMoneySettings::setShowRegisterDetailed(detailed); | 698 | KMyMoneySettings::setShowRegisterDetailed(detailed); | ||
1538 | slotRefreshViews(); | 699 | slotRefreshViews(); | ||
1539 | } | 700 | } | ||
1540 | 701 | | |||
1541 | | ||||
1542 | void KMyMoneyView::progressCallback(int current, int total, const QString& msg) | | |||
1543 | { | | |||
1544 | kmymoney->progressCallback(current, total, msg); | | |||
1545 | } | | |||
1546 | | ||||
1547 | void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex) | 702 | void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex) | ||
1548 | { | 703 | { | ||
1549 | // remember the current page | 704 | // remember the current page | ||
1550 | m_lastViewSelected = current.row(); | 705 | m_lastViewSelected = current.row(); | ||
1551 | // set the current page's title in the header | 706 | // set the current page's title in the header | ||
1552 | if (m_header) | 707 | if (m_header) | ||
1553 | m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); | 708 | m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); | ||
1554 | } | 709 | } | ||
1555 | 710 | | |||
1556 | /* DO NOT ADD code to this function or any of it's called ones. | | |||
1557 | Instead, create a new function, fixFile_n, and modify the initializeStorage() | | |||
1558 | logic above to call it */ | | |||
1559 | | ||||
1560 | void KMyMoneyView::fixFile_3() | | |||
1561 | { | | |||
1562 | // make sure each storage object contains a (unique) id | | |||
1563 | MyMoneyFile::instance()->storageId(); | | |||
1564 | } | | |||
1565 | | ||||
1566 | void KMyMoneyView::fixFile_2() | | |||
1567 | { | | |||
1568 | auto file = MyMoneyFile::instance(); | | |||
1569 | MyMoneyTransactionFilter filter; | | |||
1570 | filter.setReportAllSplits(false); | | |||
1571 | QList<MyMoneyTransaction> transactionList; | | |||
1572 | file->transactionList(transactionList, filter); | | |||
1573 | | ||||
1574 | // scan the transactions and modify transactions with two splits | | |||
1575 | // which reference an account and a category to have the memo text | | |||
1576 | // of the account. | | |||
1577 | auto count = 0; | | |||
1578 | foreach (const auto transaction, transactionList) { | | |||
1579 | if (transaction.splitCount() == 2) { | | |||
1580 | QString accountId; | | |||
1581 | QString categoryId; | | |||
1582 | QString accountMemo; | | |||
1583 | QString categoryMemo; | | |||
1584 | foreach (const auto split, transaction.splits()) { | | |||
1585 | auto acc = file->account(split.accountId()); | | |||
1586 | if (acc.isIncomeExpense()) { | | |||
1587 | categoryId = split.id(); | | |||
1588 | categoryMemo = split.memo(); | | |||
1589 | } else { | | |||
1590 | accountId = split.id(); | | |||
1591 | accountMemo = split.memo(); | | |||
1592 | } | | |||
1593 | } | | |||
1594 | | ||||
1595 | if (!accountId.isEmpty() && !categoryId.isEmpty() | | |||
1596 | && accountMemo != categoryMemo) { | | |||
1597 | MyMoneyTransaction t(transaction); | | |||
1598 | MyMoneySplit s(t.splitById(categoryId)); | | |||
1599 | s.setMemo(accountMemo); | | |||
1600 | t.modifySplit(s); | | |||
1601 | file->modifyTransaction(t); | | |||
1602 | ++count; | | |||
1603 | } | | |||
1604 | } | | |||
1605 | } | | |||
1606 | qDebug("%d transactions fixed in fixFile_2", count); | | |||
1607 | } | | |||
1608 | | ||||
1609 | void KMyMoneyView::fixFile_1() | | |||
1610 | { | | |||
1611 | // we need to fix reports. If the account filter list contains | | |||
1612 | // investment accounts, we need to add the stock accounts to the list | | |||
1613 | // as well if we don't have the expert mode enabled | | |||
1614 | if (!KMyMoneySettings::expertMode()) { | | |||
1615 | try { | | |||
1616 | QList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList(); | | |||
1617 | QList<MyMoneyReport>::iterator it_r; | | |||
1618 | for (it_r = reports.begin(); it_r != reports.end(); ++it_r) { | | |||
1619 | QStringList list; | | |||
1620 | (*it_r).accounts(list); | | |||
1621 | QStringList missing; | | |||
1622 | QStringList::const_iterator it_a, it_b; | | |||
1623 | for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { | | |||
1624 | auto acc = MyMoneyFile::instance()->account(*it_a); | | |||
1625 | if (acc.accountType() == Account::Type::Investment) { | | |||
1626 | foreach (const auto accountID, acc.accountList()) { | | |||
1627 | if (!list.contains(accountID)) { | | |||
1628 | missing.append(accountID); | | |||
1629 | } | | |||
1630 | } | | |||
1631 | } | | |||
1632 | } | | |||
1633 | if (!missing.isEmpty()) { | | |||
1634 | (*it_r).addAccount(missing); | | |||
1635 | MyMoneyFile::instance()->modifyReport(*it_r); | | |||
1636 | } | | |||
1637 | } | | |||
1638 | } catch (const MyMoneyException &) { | | |||
1639 | } | | |||
1640 | } | | |||
1641 | } | | |||
1642 | | ||||
1643 | #if 0 | | |||
1644 | if (!m_accountsView->allItemsSelected()) | | |||
1645 | { | | |||
1646 | // retrieve a list of selected accounts | | |||
1647 | QStringList list; | | |||
1648 | m_accountsView->selectedItems(list); | | |||
1649 | | ||||
1650 | // if we're not in expert mode, we need to make sure | | |||
1651 | // that all stock accounts for the selected investment | | |||
1652 | // account are also selected | | |||
1653 | if (!KMyMoneySettings::expertMode()) { | | |||
1654 | QStringList missing; | | |||
1655 | QStringList::const_iterator it_a, it_b; | | |||
1656 | for (it_a = list.begin(); it_a != list.end(); ++it_a) { | | |||
1657 | auto acc = MyMoneyFile::instance()->account(*it_a); | | |||
1658 | if (acc.accountType() == Account::Type::Investment) { | | |||
1659 | foreach (const auto accountID, acc.accountList()) { | | |||
1660 | if (!list.contains(accountID)) { | | |||
1661 | missing.append(accountID); | | |||
1662 | } | | |||
1663 | } | | |||
1664 | } | | |||
1665 | } | | |||
1666 | list += missing; | | |||
1667 | } | | |||
1668 | | ||||
1669 | m_filter.addAccount(list); | | |||
1670 | } | | |||
1671 | | ||||
1672 | #endif | | |||
1673 | | ||||
1674 | | ||||
1675 | | ||||
1676 | | ||||
1677 | | ||||
1678 | void KMyMoneyView::fixFile_0() | | |||
1679 | { | | |||
1680 | /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the | | |||
1681 | * file, it is really a warning. So I'm going to print a debug warning, and | | |||
1682 | * then go track them down when I see them to figure out how they got saved | | |||
1683 | * out needing fixing anyway. | | |||
1684 | */ | | |||
1685 | | ||||
1686 | auto file = MyMoneyFile::instance(); | | |||
1687 | QList<MyMoneyAccount> accountList; | | |||
1688 | file->accountList(accountList); | | |||
1689 | QList<MyMoneyAccount>::Iterator it_a; | | |||
1690 | QList<MyMoneySchedule> scheduleList = file->scheduleList(); | | |||
1691 | QList<MyMoneySchedule>::Iterator it_s; | | |||
1692 | | ||||
1693 | MyMoneyAccount equity = file->equity(); | | |||
1694 | MyMoneyAccount asset = file->asset(); | | |||
1695 | bool equityListEmpty = equity.accountList().count() == 0; | | |||
1696 | | ||||
1697 | for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { | | |||
1698 | if ((*it_a).accountType() == Account::Type::Loan | | |||
1699 | || (*it_a).accountType() == Account::Type::AssetLoan) { | | |||
1700 | fixLoanAccount_0(*it_a); | | |||
1701 | } | | |||
1702 | // until early before 0.8 release, the equity account was not saved to | | |||
1703 | // the file. If we have an equity account with no sub-accounts but | | |||
1704 | // find and equity account that has equity() as it's parent, we reparent | | |||
1705 | // this account. Need to move it to asset() first, because otherwise | | |||
1706 | // MyMoneyFile::reparent would act as NOP. | | |||
1707 | if (equityListEmpty && (*it_a).accountType() == Account::Type::Equity) { | | |||
1708 | if ((*it_a).parentAccountId() == equity.id()) { | | |||
1709 | auto acc = *it_a; | | |||
1710 | // tricky, force parent account to be empty so that we really | | |||
1711 | // can re-parent it | | |||
1712 | acc.setParentAccountId(QString()); | | |||
1713 | file->reparentAccount(acc, equity); | | |||
1714 | qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); | | |||
1715 | } | | |||
1716 | } | | |||
1717 | } | | |||
1718 | | ||||
1719 | for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { | | |||
1720 | fixSchedule_0(*it_s); | | |||
1721 | } | | |||
1722 | | ||||
1723 | fixTransactions_0(); | | |||
1724 | } | | |||
1725 | | ||||
1726 | void KMyMoneyView::fixSchedule_0(MyMoneySchedule sched) | | |||
1727 | { | | |||
1728 | MyMoneyTransaction t = sched.transaction(); | | |||
1729 | QList<MyMoneySplit> splitList = t.splits(); | | |||
1730 | QList<MyMoneySplit>::ConstIterator it_s; | | |||
1731 | bool updated = false; | | |||
1732 | | ||||
1733 | try { | | |||
1734 | // Check if the splits contain valid data and set it to | | |||
1735 | // be valid. | | |||
1736 | for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) { | | |||
1737 | // the first split is always the account on which this transaction operates | | |||
1738 | // and if the transaction commodity is not set, we take this | | |||
1739 | if (it_s == splitList.constBegin() && t.commodity().isEmpty()) { | | |||
1740 | qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; | | |||
1741 | try { | | |||
1742 | auto acc = MyMoneyFile::instance()->account((*it_s).accountId()); | | |||
1743 | t.setCommodity(acc.currencyId()); | | |||
1744 | updated = true; | | |||
1745 | } catch (const MyMoneyException &) { | | |||
1746 | } | | |||
1747 | } | | |||
1748 | // make sure the account exists. If not, remove the split | | |||
1749 | try { | | |||
1750 | MyMoneyFile::instance()->account((*it_s).accountId()); | | |||
1751 | } catch (const MyMoneyException &) { | | |||
1752 | qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; | | |||
1753 | t.removeSplit(*it_s); | | |||
1754 | updated = true; | | |||
1755 | } | | |||
1756 | if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) { | | |||
1757 | qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; | | |||
1758 | MyMoneySplit split = *it_s; | | |||
1759 | split.setReconcileDate(QDate()); | | |||
1760 | split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); | | |||
1761 | t.modifySplit(split); | | |||
1762 | updated = true; | | |||
1763 | } | | |||
1764 | // the schedule logic used to operate only on the value field. | | |||
1765 | // This is now obsolete. | | |||
1766 | if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) { | | |||
1767 | MyMoneySplit split = *it_s; | | |||
1768 | split.setShares(split.value()); | | |||
1769 | t.modifySplit(split); | | |||
1770 | updated = true; | | |||
1771 | } | | |||
1772 | } | | |||
1773 | | ||||
1774 | // If there have been changes, update the schedule and | | |||
1775 | // the engine data. | | |||
1776 | if (updated) { | | |||
1777 | sched.setTransaction(t); | | |||
1778 | MyMoneyFile::instance()->modifySchedule(sched); | | |||
1779 | } | | |||
1780 | } catch (const MyMoneyException &e) { | | |||
1781 | qWarning("Unable to update broken schedule: %s", qPrintable(e.what())); | | |||
1782 | } | | |||
1783 | } | | |||
1784 | | ||||
1785 | void KMyMoneyView::fixLoanAccount_0(MyMoneyAccount acc) | | |||
1786 | { | | |||
1787 | if (acc.value("final-payment").isEmpty() | | |||
1788 | || acc.value("term").isEmpty() | | |||
1789 | || acc.value("periodic-payment").isEmpty() | | |||
1790 | || acc.value("loan-amount").isEmpty() | | |||
1791 | || acc.value("interest-calculation").isEmpty() | | |||
1792 | || acc.value("schedule").isEmpty() | | |||
1793 | || acc.value("fixed-interest").isEmpty()) { | | |||
1794 | KMessageBox::information(this, | | |||
1795 | i18n("<p>The account \"%1\" was previously created as loan account but some information is missing.</p><p>The new loan wizard will be started to collect all relevant information.</p><p>Please use KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.</p>" | | |||
1796 | , acc.name()), | | |||
1797 | i18n("Account problem")); | | |||
1798 | | ||||
1799 | throw MYMONEYEXCEPTION("Fix LoanAccount0 not supported anymore"); | | |||
1800 | } | | |||
1801 | } | | |||
1802 | | ||||
1803 | void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) | 711 | void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) | ||
1804 | { | 712 | { | ||
1805 | // Add the schedule only if one exists | 713 | // Add the schedule only if one exists | ||
1806 | // | 714 | // | ||
1807 | // Remember to modify the first split to reference the newly created account | 715 | // Remember to modify the first split to reference the newly created account | ||
1808 | if (!newSchedule.name().isEmpty()) { | 716 | if (!newSchedule.name().isEmpty()) { | ||
1809 | MyMoneyFileTransaction ft; | 717 | MyMoneyFileTransaction ft; | ||
1810 | try { | 718 | try { | ||
Show All 27 Lines | |||||
1838 | } | 746 | } | ||
1839 | ft.commit(); | 747 | ft.commit(); | ||
1840 | } catch (const MyMoneyException &e) { | 748 | } catch (const MyMoneyException &e) { | ||
1841 | KMessageBox::information(this, i18n("Unable to add schedule: %1", e.what())); | 749 | KMessageBox::information(this, i18n("Unable to add schedule: %1", e.what())); | ||
1842 | } | 750 | } | ||
1843 | } | 751 | } | ||
1844 | } | 752 | } | ||
1845 | 753 | | |||
1846 | void KMyMoneyView::fixTransactions_0() | | |||
1847 | { | | |||
1848 | auto file = MyMoneyFile::instance(); | | |||
1849 | | ||||
1850 | QList<MyMoneySchedule> scheduleList = file->scheduleList(); | | |||
1851 | MyMoneyTransactionFilter filter; | | |||
1852 | filter.setReportAllSplits(false); | | |||
1853 | QList<MyMoneyTransaction> transactionList; | | |||
1854 | file->transactionList(transactionList, filter); | | |||
1855 | | ||||
1856 | QList<MyMoneySchedule>::Iterator it_x; | | |||
1857 | QStringList interestAccounts; | | |||
1858 | | ||||
1859 | KMSTATUS(i18n("Fix transactions")); | | |||
1860 | kmymoney->slotStatusProgressBar(0, scheduleList.count() + transactionList.count()); | | |||
1861 | | ||||
1862 | int cnt = 0; | | |||
1863 | // scan the schedules to find interest accounts | | |||
1864 | for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) { | | |||
1865 | MyMoneyTransaction t = (*it_x).transaction(); | | |||
1866 | QList<MyMoneySplit>::ConstIterator it_s; | | |||
1867 | QStringList accounts; | | |||
1868 | bool hasDuplicateAccounts = false; | | |||
1869 | | ||||
1870 | foreach (const auto split, t.splits()) { | | |||
1871 | if (accounts.contains(split.accountId())) { | | |||
1872 | hasDuplicateAccounts = true; | | |||
1873 | qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId(); | | |||
1874 | } else { | | |||
1875 | accounts << split.accountId(); | | |||
1876 | } | | |||
1877 | | ||||
1878 | if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { | | |||
1879 | if (interestAccounts.contains(split.accountId()) == 0) { | | |||
1880 | interestAccounts << split.accountId(); | | |||
1881 | } | | |||
1882 | } | | |||
1883 | } | | |||
1884 | if (hasDuplicateAccounts) { | | |||
1885 | fixDuplicateAccounts_0(t); | | |||
1886 | } | | |||
1887 | ++cnt; | | |||
1888 | if (!(cnt % 10)) | | |||
1889 | kmymoney->slotStatusProgressBar(cnt); | | |||
1890 | } | | |||
1891 | | ||||
1892 | // scan the transactions and modify loan transactions | | |||
1893 | for (auto& transaction : transactionList) { | | |||
1894 | QString defaultAction; | | |||
1895 | QList<MyMoneySplit> splits = transaction.splits(); | | |||
1896 | QStringList accounts; | | |||
1897 | | ||||
1898 | // check if base commodity is set. if not, set baseCurrency | | |||
1899 | if (transaction.commodity().isEmpty()) { | | |||
1900 | qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency"; | | |||
1901 | transaction.setCommodity(file->baseCurrency().id()); | | |||
1902 | file->modifyTransaction(transaction); | | |||
1903 | } | | |||
1904 | | ||||
1905 | bool isLoan = false; | | |||
1906 | // Determine default action | | |||
1907 | if (transaction.splitCount() == 2) { | | |||
1908 | // check for transfer | | |||
1909 | int accountCount = 0; | | |||
1910 | MyMoneyMoney val; | | |||
1911 | foreach (const auto split, splits) { | | |||
1912 | auto acc = file->account(split.accountId()); | | |||
1913 | if (acc.accountGroup() == Account::Type::Asset | | |||
1914 | || acc.accountGroup() == Account::Type::Liability) { | | |||
1915 | val = split.value(); | | |||
1916 | accountCount++; | | |||
1917 | if (acc.accountType() == Account::Type::Loan | | |||
1918 | || acc.accountType() == Account::Type::AssetLoan) | | |||
1919 | isLoan = true; | | |||
1920 | } else | | |||
1921 | break; | | |||
1922 | } | | |||
1923 | if (accountCount == 2) { | | |||
1924 | if (isLoan) | | |||
1925 | defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization); | | |||
1926 | else | | |||
1927 | defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer); | | |||
1928 | } else { | | |||
1929 | if (val.isNegative()) | | |||
1930 | defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); | | |||
1931 | else | | |||
1932 | defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); | | |||
1933 | } | | |||
1934 | } | | |||
1935 | | ||||
1936 | isLoan = false; | | |||
1937 | foreach (const auto split, splits) { | | |||
1938 | auto acc = file->account(split.accountId()); | | |||
1939 | MyMoneyMoney val = split.value(); | | |||
1940 | if (acc.accountGroup() == Account::Type::Asset | | |||
1941 | || acc.accountGroup() == Account::Type::Liability) { | | |||
1942 | if (!val.isPositive()) { | | |||
1943 | defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); | | |||
1944 | break; | | |||
1945 | } else { | | |||
1946 | defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); | | |||
1947 | break; | | |||
1948 | } | | |||
1949 | } | | |||
1950 | } | | |||
1951 | | ||||
1952 | #if 0 | | |||
1953 | // Check for correct actions in transactions referencing credit cards | | |||
1954 | bool needModify = false; | | |||
1955 | // The action fields are actually not used anymore in the ledger view logic | | |||
1956 | // so we might as well skip this whole thing here! | | |||
1957 | for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) { | | |||
1958 | auto acc = file->account((*it_s).accountId()); | | |||
1959 | MyMoneyMoney val = (*it_s).value(); | | |||
1960 | if (acc.accountType() == Account::Type::CreditCard) { | | |||
1961 | if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) | | |||
1962 | needModify = true; | | |||
1963 | if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) | | |||
1964 | needModify = true; | | |||
1965 | } | | |||
1966 | } | | |||
1967 | | ||||
1968 | // (Ace) Extended the #endif down to cover this conditional, because as-written | | |||
1969 | // it will ALWAYS be skipped. | | |||
1970 | | ||||
1971 | if (needModify == true) { | | |||
1972 | for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { | | |||
1973 | (*it_s).setAction(defaultAction); | | |||
1974 | transaction.modifySplit(*it_s); | | |||
1975 | file->modifyTransaction(transaction); | | |||
1976 | } | | |||
1977 | splits = transaction.splits(); // update local copy | | |||
1978 | qDebug("Fixed credit card assignment in %s", transaction.id().data()); | | |||
1979 | } | | |||
1980 | #endif | | |||
1981 | | ||||
1982 | // Check for correct assignment of ActionInterest in all splits | | |||
1983 | // and check if there are any duplicates in this transactions | | |||
1984 | for (auto& split : splits) { | | |||
1985 | MyMoneyAccount splitAccount = file->account(split.accountId()); | | |||
1986 | if (!accounts.contains(split.accountId())) { | | |||
1987 | accounts << split.accountId(); | | |||
1988 | } | | |||
1989 | // if this split references an interest account, the action | | |||
1990 | // must be of type ActionInterest | | |||
1991 | if (interestAccounts.contains(split.accountId())) { | | |||
1992 | if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { | | |||
1993 | qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest"; | | |||
1994 | split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); | | |||
1995 | transaction.modifySplit(split); | | |||
1996 | file->modifyTransaction(transaction); | | |||
1997 | qDebug("Fixed interest action in %s", qPrintable(transaction.id())); | | |||
1998 | } | | |||
1999 | // if it does not reference an interest account, it must not be | | |||
2000 | // of type ActionInterest | | |||
2001 | } else { | | |||
2002 | if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { | | |||
2003 | qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest"; | | |||
2004 | split.setAction(defaultAction); | | |||
2005 | transaction.modifySplit(split); | | |||
2006 | file->modifyTransaction(transaction); | | |||
2007 | qDebug("Fixed interest action in %s", qPrintable(transaction.id())); | | |||
2008 | } | | |||
2009 | } | | |||
2010 | | ||||
2011 | // check that for splits referencing an account that has | | |||
2012 | // the same currency as the transactions commodity the value | | |||
2013 | // and shares field are the same. | | |||
2014 | if (transaction.commodity() == splitAccount.currencyId() | | |||
2015 | && split.value() != split.shares()) { | | |||
2016 | qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value"; | | |||
2017 | split.setShares(split.value()); | | |||
2018 | transaction.modifySplit(split); | | |||
2019 | file->modifyTransaction(transaction); | | |||
2020 | } | | |||
2021 | | ||||
2022 | // fix the shares and values to have the correct fraction | | |||
2023 | if (!splitAccount.isInvest()) { | | |||
2024 | try { | | |||
2025 | int fract = splitAccount.fraction(); | | |||
2026 | if (split.shares() != split.shares().convert(fract)) { | | |||
2027 | qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id())); | | |||
2028 | split.setShares(split.shares().convert(fract)); | | |||
2029 | split.setValue(split.value().convert(fract)); | | |||
2030 | transaction.modifySplit(split); | | |||
2031 | file->modifyTransaction(transaction); | | |||
2032 | } | | |||
2033 | } catch (const MyMoneyException &) { | | |||
2034 | qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); | | |||
2035 | } | | |||
2036 | } | | |||
2037 | } | | |||
2038 | | ||||
2039 | ++cnt; | | |||
2040 | if (!(cnt % 10)) | | |||
2041 | kmymoney->slotStatusProgressBar(cnt); | | |||
2042 | } | | |||
2043 | | ||||
2044 | kmymoney->slotStatusProgressBar(-1, -1); | | |||
2045 | } | | |||
2046 | | ||||
2047 | void KMyMoneyView::fixDuplicateAccounts_0(MyMoneyTransaction& t) | | |||
2048 | { | | |||
2049 | qDebug("Duplicate account in transaction %s", qPrintable(t.id())); | | |||
2050 | } | | |||
2051 | | ||||
2052 | void KMyMoneyView::slotPrintView() | 754 | void KMyMoneyView::slotPrintView() | ||
2053 | { | 755 | { | ||
2054 | if (viewFrames[View::Reports] == currentPage()) | 756 | if (viewFrames[View::Reports] == currentPage()) | ||
2055 | m_reportsView->slotPrintView(); | 757 | m_reportsView->slotPrintView(); | ||
2056 | else if (viewFrames[View::Home] == currentPage()) | 758 | else if (viewFrames[View::Home] == currentPage()) | ||
2057 | m_homeView->slotPrintView(); | 759 | m_homeView->slotPrintView(); | ||
2058 | } | 760 | } | ||
2059 | 761 | | |||
2060 | void KMyMoneyView::resetViewSelection(const View) | 762 | void KMyMoneyView::resetViewSelection(const View) | ||
2061 | { | 763 | { | ||
2062 | emit aboutToChangeView(); | 764 | emit aboutToChangeView(); | ||
2063 | } | 765 | } | ||
2064 | 766 | | |||
2065 | void KMyMoneyView::connectView(const View view) | 767 | void KMyMoneyView::connectView(const View view) | ||
2066 | { | 768 | { | ||
2067 | KMyMoneyAccountTreeView *treeView; | 769 | KMyMoneyAccountTreeView *treeView; | ||
2068 | switch (view) { | 770 | switch (view) { | ||
2069 | case View::Home: | 771 | case View::Home: | ||
2070 | disconnect(m_homeView, &KHomeView::aboutToShow, this, &KMyMoneyView::connectView); | 772 | disconnect(m_homeView, &KHomeView::aboutToShow, this, &KMyMoneyView::connectView); | ||
2071 | connect(m_homeView, &KHomeView::objectSelected, this, &KMyMoneyView::slotObjectSelected); | 773 | connect(m_homeView, &KHomeView::objectSelected, this, &KMyMoneyView::slotObjectSelected); | ||
2072 | connect(m_homeView, &KHomeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); | 774 | connect(m_homeView, &KHomeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); | ||
775 | // views can wait since they are going to be refresed in slotRefreshViews | ||||
776 | connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); | ||||
2073 | break; | 777 | break; | ||
2074 | 778 | | |||
2075 | case View::Accounts: | 779 | case View::Accounts: | ||
2076 | disconnect(m_accountsView, &KAccountsView::aboutToShow, this, &KMyMoneyView::connectView); | 780 | disconnect(m_accountsView, &KAccountsView::aboutToShow, this, &KMyMoneyView::connectView); | ||
2077 | treeView = m_accountsView->getTreeView(); | 781 | treeView = m_accountsView->getTreeView(); | ||
2078 | connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); | 782 | connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); | ||
2079 | connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); | 783 | connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); | ||
2080 | connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); | 784 | connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); | ||
▲ Show 20 Lines • Show All 173 Lines • Show Last 20 Lines |