1 /* vi: set sw=4 ts=4:
2 *
3 * Copyright (C) 2001 - 2015 Christian Hohnstaedt.
4 *
5 * All rights reserved.
6 */
7
8
9 //#define MDEBUG
10 #include "MainWindow.h"
11 #include "XcaApplication.h"
12 #include "ImportMulti.h"
13 #include "dhgen.h"
14
15 #include <QApplication>
16 #include <QClipboard>
17 #include <QFileDialog>
18 #include <QLabel>
19 #include <QLineEdit>
20 #include <QTextBrowser>
21 #include <QStatusBar>
22 #include <QList>
23 #include <QTimer>
24 #include <QThread>
25 #include <QMimeData>
26 #include <QInputDialog>
27
28 #include <openssl/err.h>
29
30 #include "lib/Passwd.h"
31 #include "lib/database_model.h"
32 #include "lib/exception.h"
33 #include "lib/pki_evp.h"
34 #include "lib/pki_multi.h"
35 #include "lib/pki_scard.h"
36 #include "XcaDialog.h"
37 #include "XcaWarning.h"
38 #include "PwDialog.h"
39 #include "OpenDb.h"
40 #include "OidResolver.h"
41
42 OidResolver *MainWindow::resolver = NULL;
43
enableTokenMenu(bool enable)44 void MainWindow::enableTokenMenu(bool enable)
45 {
46 foreach(QWidget *w, scardList) {
47 w->setEnabled(enable);
48 }
49 }
50
initResolver()51 void MainWindow::initResolver()
52 {
53 bool shown = false;
54 QString search;
55
56 if (resolver) {
57 shown = resolver->isVisible();
58 search = resolver->input->text();
59 delete resolver;
60 }
61 resolver = new OidResolver(NULL);
62 resolver->setWindowTitle(XCA_TITLE);
63 if (shown)
64 resolver->searchOid(search);
65 }
66
MainWindow()67 MainWindow::MainWindow() : QMainWindow()
68 {
69 dbindex = new QLabel();
70 dbindex->setFrameStyle(QFrame::Plain | QFrame::NoFrame);
71 dbindex->setMargin(6);
72
73 dn_translations_setup();
74 statusBar()->addWidget(dbindex, 1);
75
76 setupUi(this);
77 setWindowTitle(XCA_TITLE);
78
79 OpenDb::checkSqLite();
80 initResolver();
81
82 wdList << keyButtons << reqButtons << certButtons <<
83 tempButtons << crlButtons;
84
85 QStringList drivers = QSqlDatabase::drivers();
86 foreach(QString driver, drivers) {
87 QSqlDatabase d = QSqlDatabase::addDatabase(driver, driver +"_C");
88 qDebug() << "DB driver:" << driver;
89 }
90
91 historyMenu = NULL;
92 helpdlg = new Help();
93 init_menu();
94 setItemEnabled(false);
95
96 init_images();
97 homedir = getHomeDir();
98
99 #ifdef MDEBUG
100 CRYPTO_malloc_debug_init();
101 CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
102 qWarning() << "malloc() debugging on.";
103 #endif
104
105 ERR_load_crypto_strings();
106 OpenSSL_add_all_algorithms();
107
108 EVP_add_digest_alias(SN_sha1,SN_ecdsa_with_SHA1);
109 EVP_add_digest_alias(SN_sha224,SN_ecdsa_with_SHA224);
110 EVP_add_digest_alias(SN_sha256,SN_ecdsa_with_SHA256);
111 EVP_add_digest_alias(SN_sha256,SN_dsa_with_SHA256);
112 EVP_add_digest_alias(SN_sha384,SN_ecdsa_with_SHA384);
113 EVP_add_digest_alias(SN_sha512,SN_ecdsa_with_SHA512);
114
115 setAcceptDrops(true);
116
117 searchEdit = new QLineEdit();
118 searchEdit->setPlaceholderText(tr("Search"));
119
120 keyView->setIconSize(QPixmap(":keyIco").size());
121 reqView->setIconSize(QPixmap(":reqIco").size());
122 certView->setIconSize(QPixmap(":validcertIco").size());
123 tempView->setIconSize(QPixmap(":templateIco").size());
124 crlView->setIconSize(QPixmap(":crlIco").size());
125
126 views << keyView << reqView << certView << crlView << tempView;
127
128 foreach(XcaTreeView *v, views)
129 v->setMainwin(this, searchEdit);
130
131 dhgen = NULL;
132 dhgenBar = new QProgressBar();
133 check_oom(dhgenBar);
134 dhgenBar->setMinimum(0);
135 dhgenBar->setMaximum(0);
136 }
137
dropEvent(QDropEvent * event)138 void MainWindow::dropEvent(QDropEvent *event)
139 {
140 if (event->mimeData()->hasUrls()) {
141 QList<QUrl> urls = event->mimeData()->urls();
142 QUrl u;
143 QStringList files;
144
145 foreach(u, urls) {
146 QString s = u.toLocalFile();
147 files << s;
148 }
149 openURLs(files);
150 event->acceptProposedAction();
151 } else if (event->mimeData()->hasText()) {
152 event->acceptProposedAction();
153 pastePem(event->mimeData()->text());
154 }
155 }
156
openURLs(QStringList & files)157 void MainWindow::openURLs(QStringList &files)
158 {
159 urlsToOpen = files;
160 QTimer::singleShot(100, this, SLOT(openURLs()));
161 }
162
openURLs()163 void MainWindow::openURLs()
164 {
165 foreach(QString file, urlsToOpen) {
166 if (file.endsWith(".xdb") ||
167 !database_model::splitRemoteDbName(file).isEmpty())
168 {
169 init_database(file);
170 if (Database.isOpen()) {
171 urlsToOpen.removeAll(file);
172 break;
173 }
174 }
175 }
176 importAnything(urlsToOpen);
177 urlsToOpen.clear();
178 }
179
dragEnterEvent(QDragEnterEvent * event)180 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
181 {
182 if (event->mimeData()->hasFormat(X_XCA_DRAG_DATA))
183 return;
184
185 if (event->mimeData()->hasUrls() || event->mimeData()->hasText())
186 event->acceptProposedAction();
187 }
188
setItemEnabled(bool enable)189 void MainWindow::setItemEnabled(bool enable)
190 {
191 foreach(QWidget *w, wdList) {
192 w->setEnabled(enable);
193 }
194 foreach(QWidget *w, wdMenuList) {
195 w->setEnabled(enable);
196 }
197 foreach(QAction *a, acList) {
198 a->setEnabled(enable);
199 }
200 enableTokenMenu(pkcs11::libraries.loaded());
201 }
202
init_images()203 void MainWindow::init_images()
204 {
205 bigKey->setPixmap(QPixmap(":keyImg"));
206 bigCsr->setPixmap(QPixmap(":csrImg"));
207 bigCert->setPixmap(QPixmap(":certImg"));
208 bigTemp->setPixmap(QPixmap(":tempImg"));
209 bigRev->setPixmap(QPixmap(":revImg"));
210 setWindowIcon(QPixmap(":appIco"));
211 }
212
loadPem()213 void MainWindow::loadPem()
214 {
215 load_pem l;
216 db_key *keys = Database.model<db_key>();
217 if (keys)
218 keys->load_default(l);
219 }
220
pastePem(QString text,bool silent)221 bool MainWindow::pastePem(QString text, bool silent)
222 {
223 bool success = false;
224 QByteArray pemdata = text.toLatin1();
225 if (pemdata.size() == 0)
226 return false;
227
228 pki_multi *pem = NULL;
229 try {
230 pem = new pki_multi();
231 pem->fromPEMbyteArray(pemdata, QString());
232 success = pem->failed_files.count() == 0;
233 importMulti(pem, 1);
234 }
235 catch (errorEx &err) {
236 delete pem;
237 if (!silent)
238 XCA_ERROR(err);
239 }
240 return success;
241 }
242
pastePem()243 void MainWindow::pastePem()
244 {
245 QClipboard *cb = QApplication::clipboard();
246 QString text;
247
248 text = cb->text(QClipboard::Selection);
249 if (text.isEmpty())
250 text = cb->text(QClipboard::Clipboard);
251
252 if (!text.isEmpty())
253 if (pastePem(text, true))
254 return;
255
256
257 QTextEdit *textbox = new QTextEdit();
258 textbox->setPlainText(text);
259 XcaDialog *input = new XcaDialog(this, x509, textbox,
260 tr("Import PEM data"), QString());
261 input->noSpacer();
262 if (input->exec()) {
263 text = textbox->toPlainText();
264 if (!text.isEmpty())
265 pastePem(text);
266 }
267 delete input;
268 }
269
initToken()270 void MainWindow::initToken()
271 {
272 bool ok;
273 if (!pkcs11::libraries.loaded())
274 return;
275 try {
276 pkcs11 p11;
277 slotid slot;
278 Passwd pin;
279 int ret;
280
281 if (!p11.selectToken(&slot, this))
282 return;
283
284 tkInfo ti = p11.tokenInfo(slot);
285 QString slotname = QString("%1 (#%2)").
286 arg(ti.label()).arg(ti.serial());
287
288 pass_info p(XCA_TITLE,
289 tr("Please enter the original SO PIN (PUK) of the token '%1'").
290 arg(slotname) + "\n" + ti.pinInfo());
291 p.setPin();
292 if (ti.tokenInitialized()) {
293 ret = PwDialog::execute(&p, &pin, false);
294 } else {
295 p.setDescription(tr("Please enter the new SO PIN (PUK) for the token '%1'").
296 arg(slotname) + "\n" + ti.pinInfo());
297 ret = PwDialog::execute(&p, &pin, true);
298 }
299 if (ret != 1)
300 return;
301 QString label = QInputDialog::getText(this, XCA_TITLE,
302 tr("The new label of the token '%1'").
303 arg(slotname), QLineEdit::Normal, QString(), &ok);
304 if (!ok)
305 return;
306 p11.initToken(slot, pin.constUchar(), pin.size(), label);
307 } catch (errorEx &err) {
308 XCA_ERROR(err);
309 }
310 }
311
changePin(bool so)312 void MainWindow::changePin(bool so)
313 {
314 if (!pkcs11::libraries.loaded())
315 return;
316 try {
317 pkcs11 p11;
318 slotid slot;
319
320 if (!p11.selectToken(&slot, this))
321 return;
322 p11.changePin(slot, so);
323 } catch (errorEx &err) {
324 XCA_ERROR(err);
325 }
326 }
327
changeSoPin()328 void MainWindow::changeSoPin()
329 {
330 changePin(true);
331 }
332
initPin()333 void MainWindow::initPin()
334 {
335 if (!pkcs11::libraries.loaded())
336 return;
337 try {
338 pkcs11 p11;
339 slotid slot;
340
341 if (!p11.selectToken(&slot, this))
342 return;
343 p11.initPin(slot);
344 } catch (errorEx &err) {
345 XCA_ERROR(err);
346 }
347 }
348
349
manageToken()350 void MainWindow::manageToken()
351 {
352 pkcs11 p11;
353 slotid slot;
354 pki_scard *card = NULL;
355 pki_x509 *cert = NULL;
356 ImportMulti *dlgi = NULL;
357
358 if (!pkcs11::libraries.loaded())
359 return;
360
361 try {
362 if (!p11.selectToken(&slot, this))
363 return;
364
365 ImportMulti *dlgi = new ImportMulti(this);
366
367 dlgi->tokenInfo(slot);
368 QList<CK_OBJECT_HANDLE> objects;
369
370 QList<CK_MECHANISM_TYPE> ml = p11.mechanismList(slot);
371 if (ml.count() == 0)
372 ml << CKM_SHA1_RSA_PKCS;
373 pk11_attlist atts(pk11_attr_ulong(CKA_CLASS,
374 CKO_PUBLIC_KEY));
375
376 p11.startSession(slot);
377 p11.getRandom();
378 objects = p11.objectList(atts);
379
380 for (int j=0; j< objects.count(); j++) {
381 card = new pki_scard("");
382 try {
383 card->load_token(p11, objects[j]);
384 card->setMech_list(ml);
385 dlgi->addItem(card);
386 } catch (errorEx &err) {
387 XCA_ERROR(err);
388 delete card;
389 }
390 card = NULL;
391 }
392 atts.reset();
393 atts << pk11_attr_ulong(CKA_CLASS, CKO_CERTIFICATE) <<
394 pk11_attr_ulong(CKA_CERTIFICATE_TYPE,CKC_X_509);
395 objects = p11.objectList(atts);
396
397 for (int j=0; j< objects.count(); j++) {
398 cert = new pki_x509("");
399 try {
400 cert->load_token(p11, objects[j]);
401 dlgi->addItem(cert);
402 } catch (errorEx &err) {
403 XCA_ERROR(err);
404 delete cert;
405 }
406 cert = NULL;
407 }
408 if (dlgi->entries() == 0) {
409 tkInfo ti = p11.tokenInfo();
410 XCA_INFO(tr("The token '%1' did not contain any keys or certificates").arg(ti.label()));
411 } else {
412 dlgi->execute(true);
413 }
414 } catch (errorEx &err) {
415 XCA_ERROR(err);
416 }
417 delete card;
418 delete cert;
419 delete dlgi;
420 }
421
~MainWindow()422 MainWindow::~MainWindow()
423 {
424 ERR_free_strings();
425 EVP_cleanup();
426 OBJ_cleanup();
427 delete dbindex;
428 delete helpdlg;
429 #ifdef MDEBUG
430 fprintf(stderr, "Memdebug:\n");
431 CRYPTO_mem_leaks_fp(stderr);
432 #endif
433 }
434
closeEvent(QCloseEvent * e)435 void MainWindow::closeEvent(QCloseEvent *e)
436 {
437 if (dhgen) {
438 if (!XCA_YESNO("Abort Diffie-Hellmann parameter generation?")){
439 e->ignore();
440 return;
441 }
442 dhgen->terminate();
443 }
444 delete resolver;
445 resolver = NULL;
446 delete helpdlg;
447 helpdlg = NULL;
448 close_database();
449 QMainWindow::closeEvent(e);
450 }
451
checkOldGetNewPass(Passwd & pass)452 int MainWindow::checkOldGetNewPass(Passwd &pass)
453 {
454 QString passHash = Settings["pwhash"];
455 if (!passHash.isEmpty()) {
456 pass_info p(tr("Current Password"),
457 tr("Please enter the current database password"), this);
458
459 /* Try empty password */
460 if (pki_evp::sha512passwT(pass, passHash) != passHash) {
461 /* Not the empty password, check it */
462 if (PwDialog::execute(&p, &pass, false) != 1)
463 return 0;
464 }
465
466 if (pki_evp::sha512passwT(pass, passHash) != passHash) {
467 XCA_WARN(tr("The entered password is wrong"));
468 return 0;
469 }
470 }
471
472 pass_info p(tr("New Password"), tr("Please enter the new password "
473 "to encrypt your private keys in the database-file"),
474 this);
475
476 return PwDialog::execute(&p, &pass, true) != 1 ? 0 : 1;
477 }
478
changeDbPass()479 void MainWindow::changeDbPass()
480 {
481 Passwd pass;
482 XSqlQuery q;
483 QSqlDatabase db = QSqlDatabase::database();
484
485 if (!checkOldGetNewPass(pass))
486 return;
487
488 QString salt = Entropy::makeSalt();
489 QString passhash = pki_evp::sha512passwT(pass, salt);
490 QList<pki_evp*> key_list = Store.sqlSELECTpki<pki_evp>(
491 "SELECT item FROM private_keys WHERE ownPass=0");
492
493 try {
494 Transaction;
495 if (!TransBegin()) {
496 errorEx e(tr("Transaction start failed"));
497 XCA_ERROR(e);
498 return;
499 }
500 foreach(pki_evp *key, key_list) {
501 EVP_PKEY *evp = key->decryptKey();
502 key->set_evp_key(evp);
503 key->encryptKey(pass.constData());
504 key->sqlUpdatePrivateKey();
505 }
506 Settings["pwhash"] = passhash;
507 TransCommit();
508 pki_evp::passHash = passhash;
509 pki_evp::passwd = pass;
510 } catch (errorEx &e) {
511 XCA_ERROR(e);
512 }
513 }
514
importAnything(QString file)515 void MainWindow::importAnything(QString file)
516 {
517 importAnything(QStringList(file));
518 }
519
importAnything(const QStringList & files)520 void MainWindow::importAnything(const QStringList &files)
521 {
522 pki_multi *multi = new pki_multi();
523
524 foreach(QString s, files)
525 multi->probeAnything(s);
526
527 importMulti(multi, 1);
528 }
529
importMulti(pki_multi * multi,int force)530 void MainWindow::importMulti(pki_multi *multi, int force)
531 {
532 if (!multi)
533 return;
534
535 QStringList failed_files = multi->failed_files;
536 ImportMulti *dlgi = new ImportMulti(this);
537
538 // dlgi->addItem() deletes "multi" if appropriate
539 dlgi->addItem(multi);
540 dlgi->execute(force, failed_files);
541 delete dlgi;
542 }
543
openRemoteSqlDB()544 void MainWindow::openRemoteSqlDB()
545 {
546 OpenDb *opendb = new OpenDb(this, QString());
547 QString descriptor;
548 Passwd pass;
549 DbMap params;
550
551 if (opendb->exec()) {
552 descriptor = opendb->getDescriptor();
553 pass = opendb->dbPassword->text().toLatin1();
554 params = database_model::splitRemoteDbName(descriptor);
555 }
556 delete opendb;
557
558 if (descriptor.isEmpty())
559 return;
560
561 init_database(descriptor, pass);
562 }
563
init_database(const QString & name,const Passwd & pass)564 enum open_result MainWindow::init_database(const QString &name,
565 const Passwd &pass)
566 {
567 close_database();
568 try {
569 Database.open(name, pass);
570 return setup_open_database();
571 } catch (errorEx &err) {
572 XCA_ERROR(err);
573 return open_abort;
574 } catch (enum open_result r) {
575 return r;
576 }
577 return pw_ok;
578 }
579
setup_open_database()580 enum open_result MainWindow::setup_open_database()
581 {
582 if (!Database.isOpen())
583 return open_abort;
584
585 if (!database_model::isRemoteDB(Database.name()))
586 homedir = QFileInfo(Database.name()).canonicalPath();
587
588 setItemEnabled(true);
589 dbindex->setText(tr("Database") + ": " +
590 compressFilename(Database.name()));
591 set_geometry(Settings["mw_geometry"]);
592
593 if (pki_evp::passwd.isNull())
594 XCA_INFO(tr("Using or exporting private keys will not be possible without providing the correct password"));
595
596 enableTokenMenu(pkcs11::libraries.loaded());
597
598 hashBox hb(this);
599 if (hb.isInsecure()) {
600 XCA_WARN(tr("The currently used default hash '%1' is insecure. Please select at least 'SHA 224' for security reasons.").arg(hb.currentHashName()));
601 setOptions();
602 }
603
604 keyView->setModel(Database.model<db_key>());
605 reqView->setModel(Database.model<db_x509req>());
606 certView->setModel(Database.model<db_x509>());
607 tempView->setModel(Database.model<db_temp>());
608 crlView->setModel(Database.model<db_crl>());
609
610 searchEdit->setText("");
611 searchEdit->show();
612 statusBar()->addWidget(searchEdit, 1);
613
614 connect(tempView, SIGNAL(newCert(pki_temp *)),
615 Database.model<db_x509>(), SLOT(newCert(pki_temp *)));
616 connect(tempView, SIGNAL(newReq(pki_temp *)),
617 Database.model<db_x509req>(), SLOT(newItem(pki_temp *)));
618
619 return pw_ok;
620 }
621
set_geometry(QString geo)622 void MainWindow::set_geometry(QString geo)
623 {
624 QStringList sl = geo.split(",");
625 if (sl.size() != 3)
626 return;
627 resize(sl[0].toInt(), sl[1].toInt());
628 int i = sl[2].toInt();
629 if (i != -1)
630 tabView->setCurrentIndex(i);
631 }
632
close_database()633 void MainWindow::close_database()
634 {
635 if (!Database.isOpen())
636 return;
637
638 Settings["mw_geometry"] = QString("%1,%2,%3")
639 .arg(size().width())
640 .arg(size().height())
641 .arg(tabView->currentIndex());
642
643 history.addEntry(Database.name());
644 foreach(XcaTreeView *v, views)
645 v->setModel(NULL);
646 Database.close();
647
648 setItemEnabled(false);
649 dbindex->clear();
650 update_history_menu();
651 enableTokenMenu(pkcs11::libraries.loaded());
652 }
653
exportIndex()654 void MainWindow::exportIndex()
655 {
656 exportIndex(QFileDialog::getSaveFileName(this, XCA_TITLE,
657 Settings["workingdir"],
658 tr("Certificate Index ( index.txt )") + ";;" +
659 tr("All files ( * )")),
660 false);
661 }
662
exportIndexHierarchy()663 void MainWindow::exportIndexHierarchy()
664 {
665 exportIndex(QFileDialog::getExistingDirectory(
666 this, XCA_TITLE, Settings["workingdir"]), true);
667 }
668
exportIndex(const QString & fname,bool hierarchy) const669 void MainWindow::exportIndex(const QString &fname, bool hierarchy) const
670 {
671 qDebug() << fname << hierarchy;
672 if (fname.isEmpty() || !Database.isOpen())
673 return;
674 db_x509 *certs = Database.model<db_x509>();
675 certs->writeIndex(fname, hierarchy);
676 }
677
generateDHparamDone()678 void MainWindow::generateDHparamDone()
679 {
680 statusBar()->removeWidget(dhgenBar);
681 errorEx e(dhgen->error);
682 if (e.isEmpty())
683 XCA_INFO(tr("Diffie-Hellman parameters saved as: %1")
684 .arg(dhgen->filename()));
685 else
686 XCA_ERROR(e);
687 dhgen->deleteLater();
688 dhgen = NULL;
689 }
690
generateDHparam()691 void MainWindow::generateDHparam()
692 {
693 bool ok;
694 int bits;
695
696 if (dhgen)
697 return;
698
699 bits = QInputDialog::getDouble(this, XCA_TITLE, tr("Diffie-Hellman parameters are needed for different applications, but not handled by XCA.\nPlease enter the DH parameter bits"),
700 1024, 1024, 4096, 0, &ok);
701 if (!ok)
702 return;
703
704 /*
705 * 1024: 6 sec
706 * 2048: 38 sec
707 * 4096: 864 sec
708 */
709
710 Entropy::seed_rng();
711 try {
712 QString fname = QString("%1/dh%2.pem").arg(homedir).arg(bits);
713 fname = QFileDialog::getSaveFileName(this, QString(),
714 fname, tr("All files ( * )"), NULL);
715 if (fname == "")
716 throw errorEx("");
717 dhgen = new DHgen(fname, bits);
718 check_oom(dhgen);
719 statusBar()->addPermanentWidget(dhgenBar, 1);
720 dhgenBar->show();
721 dhgen->start(QThread::LowestPriority);
722 connect(dhgen, SIGNAL(finished()),
723 this, SLOT(generateDHparamDone()));
724 } catch (errorEx &err) {
725 XCA_ERROR(err);
726 }
727 }
728
changeEvent(QEvent * event)729 void MainWindow::changeEvent(QEvent *event)
730 {
731 if (event->type() == QEvent::LanguageChange) {
732 retranslateUi(this);
733 dn_translations_setup();
734 init_menu();
735 foreach(db_base *model, Database.getModels())
736 model->updateHeaders();
737
738 if (Database.isOpen())
739 dbindex->setText(tr("Database") + ": " +
740 Database.name());
741 searchEdit->setPlaceholderText(tr("Search"));
742 }
743 QMainWindow::changeEvent(event);
744 }
745
keyPressEvent(QKeyEvent * e)746 void MainWindow::keyPressEvent(QKeyEvent *e)
747 {
748 if (e->modifiers() != Qt::ControlModifier) {
749 QMainWindow::keyPressEvent(e);
750 return;
751 }
752 int siz = XcaApplication::tableFont.pointSize();
753
754 switch (e->key()) {
755 case Qt::Key_Plus:
756 XcaApplication::tableFont.setPointSize(siz +1);
757 break;
758 case Qt::Key_Minus:
759 if (siz > 4) {
760 XcaApplication::tableFont.setPointSize(siz -1);
761 }
762 break;
763 case Qt::Key_V:
764 if (e->modifiers() == Qt::ControlModifier) {
765 pastePem();
766 break;
767 }
768 /* FALLTHROUGH */
769 default:
770 QMainWindow::keyPressEvent(e);
771 return;
772 }
773 foreach(XcaTreeView *v, views) {
774 if (v) {
775 v->header()->resizeSections(
776 QHeaderView::ResizeToContents);
777 v->reset();
778 }
779 }
780 update();
781 }
782
dump_database()783 void MainWindow::dump_database()
784 {
785 QString dirname = QFileDialog::getExistingDirectory(
786 NULL, XCA_TITLE, Settings["workingdir"]);
787 try {
788 Database.dump(dirname);
789 } catch (errorEx &err) {
790 XCA_ERROR(err);
791 }
792 }
793
default_database()794 void MainWindow::default_database()
795 {
796 Database.as_default();
797 }
798