From e55f55f861c28093d0b4289ea852f97fcce108eb Mon Sep 17 00:00:00 2001 From: Arnis Armans Date: Sun, 30 Aug 2026 19:41:32 +0300 Subject: [PATCH] Add station profiles: one KLog installation, several callsigns Turns the existing logs into full station profiles. On startup a dialog lists the profiles with their QSO counts; choosing one switches the log and the station configuration in a single step. - New tables: profiles, profile_variants, profile_clubs, plus log.profile_id and log.variant_id - Migration from the logs table runs automatically on first start and is idempotent - Profile editor with Station / Address / Equipment / Clubs tabs - CQ and ITU zones and the DXCC entity are filled in from the callsign prefix; /MM and /AM are excluded since they belong to no entity - A profile holding QSOs cannot be deleted - New QSOs get their profile through an SQLite trigger, so manual entry, ADIF import and WSJT-X UDP all work without touching existing code Tested on a live log of 20,200 QSOs across three callsigns. --- src/CMakeLists.txt | 2 + src/main.cpp | 11 + src/profilemanager.cpp | 495 ++++++++++++++++++++++++++++++++++ src/profilemanager.h | 77 ++++++ src/startprofiledialog.cpp | 536 +++++++++++++++++++++++++++++++++++++ src/startprofiledialog.h | 44 +++ 6 files changed, 1165 insertions(+) create mode 100644 src/profilemanager.cpp create mode 100644 src/profilemanager.h create mode 100644 src/startprofiledialog.cpp create mode 100644 src/startprofiledialog.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95901e43..87b0e18a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -280,6 +280,8 @@ set(CMAKE_AUTORCC ON) # Add executable add_executable(klog WIN32 MACOSX_BUNDLE + profilemanager.cpp profilemanager.h + startprofiledialog.cpp startprofiledialog.h ${KLOG_SOURCES} ${KLOG_HEADERS} ${SRC_UI} diff --git a/src/main.cpp b/src/main.cpp index b906c2d2..e90a3f5a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,8 @@ * * *****************************************************************************/ #include +#include "profilemanager.h" +#include "startprofiledialog.h" // #include #include // #include @@ -359,6 +361,15 @@ int main(int argc, char *argv[]) splash.showMessage("Creating window..."); QApplication::processEvents(); + ProfileManager profileManager; + QString profileErr; + if (!profileManager.ensureSchemaAndMigrate(&profileErr)) + qWarning() << "Profiles:" << profileErr; + splash.hide(); + const int activeProfileId = StartProfileDialog::chooseProfileOnStartup(&profileManager, &world, &dataProxy); + if (activeProfileId < 0) + return 0; + MainWindow mw(&dataProxy, &world); //qInfo() << "[KLOG-TIMING] main 081 - MainWindow ctor:" << timer.elapsed() << "ms"; timer.restart(); diff --git a/src/profilemanager.cpp b/src/profilemanager.cpp new file mode 100644 index 00000000..8f5de1ae --- /dev/null +++ b/src/profilemanager.cpp @@ -0,0 +1,495 @@ +#include "profilemanager.h" +#include +#include +#include +#include +#include +#include + +bool ProfileManager::exec(QSqlQuery &q, const char *ctx) const +{ + if (q.exec()) + return true; + qWarning() << "ProfileManager" << ctx << "SQL:" << q.lastError().text(); + return false; +} + +bool ProfileManager::ensureSchemaAndMigrate(QString *errorOut) +{ + QSqlDatabase db = QSqlDatabase::database(); + if (!db.isOpen()) { + if (errorOut) *errorOut = QStringLiteral("Datubaze nav atverta"); + return false; + } + QSqlQuery q(db); + + q.prepare(QStringLiteral( + "CREATE TABLE IF NOT EXISTS profiles (" + " profile_id INTEGER PRIMARY KEY AUTOINCREMENT," + " callsign VARCHAR(15) NOT NULL UNIQUE," + " operator_name VARCHAR, gridsquare VARCHAR(12), qth VARCHAR," + " cq_zone INTEGER, itu_zone INTEGER, dxcc INTEGER," + " default_rig VARCHAR, default_antenna VARCHAR, default_tx_pwr REAL," + " qsl_via VARCHAR, comment VARCHAR," + " created_at TEXT DEFAULT (datetime('now')))")); + if (!exec(q, "createProfiles")) return false; + + q.prepare(QStringLiteral( + "CREATE TABLE IF NOT EXISTS profile_variants (" + " variant_id INTEGER PRIMARY KEY AUTOINCREMENT," + " profile_id INTEGER NOT NULL REFERENCES profiles(profile_id)," + " station_callsign VARCHAR(20) NOT NULL," + " kind VARCHAR(8) NOT NULL DEFAULT 'BASE'" + " CHECK (kind IN ('BASE','P','M','MM','AM','CONTEST','OTHER'))," + " gridsquare VARCHAR(12)," + " dxcc_counts INTEGER NOT NULL DEFAULT 1," + " note VARCHAR," + " UNIQUE (profile_id, station_callsign))")); + if (!exec(q, "createVariants")) return false; + + q.prepare(QStringLiteral( + "CREATE TABLE IF NOT EXISTS profile_clubs (" + " profile_id INTEGER NOT NULL REFERENCES profiles(profile_id)," + " club VARCHAR(20) NOT NULL," + " member_nr VARCHAR(20)," + " PRIMARY KEY (profile_id, club))")); + if (!exec(q, "createClubs")) return false; + + // Papildu KLog My Data lauki profila (idempotenti - kluda ja jau ir, to ignorejam) + const QStringList newCols = { + "name VARCHAR", "address1 VARCHAR", "address2 VARCHAR", "address3 VARCHAR", + "address4 VARCHAR", "city VARCHAR", "zip_code VARCHAR", "province VARCHAR", + "country VARCHAR", "rig1 VARCHAR", "rig2 VARCHAR", "rig3 VARCHAR", + "antenna1 VARCHAR", "antenna2 VARCHAR", "antenna3 VARCHAR", "power REAL", + "clublog_email VARCHAR", "clublog_pass VARCHAR", "clublog_app_pass VARCHAR", + "qrz_user VARCHAR", "qrz_pass VARCHAR", "qrz_logbook_key VARCHAR", + "eqsl_call VARCHAR", "eqsl_pass VARCHAR", + "lotw_user VARCHAR", "lotw_pass VARCHAR" + }; + { + QStringList have; + q.prepare(QStringLiteral("PRAGMA table_info(profiles)")); + if (exec(q, "profilesInfo")) + while (q.next()) have << q.value(1).toString(); + for (const QString &def : newCols) + { + const QString col = def.section(' ', 0, 0); + if (!have.contains(col)) + { + q.prepare(QStringLiteral("ALTER TABLE profiles ADD COLUMN ") + def); + exec(q, "addProfileCol"); + } + } + } + + bool hasProfileCol = false; + q.prepare(QStringLiteral("PRAGMA table_info(log)")); + if (exec(q, "tableInfo")) + while (q.next()) + if (q.value(1).toString() == QLatin1String("profile_id")) + hasProfileCol = true; + + if (!hasProfileCol) + { + db.transaction(); + const QStringList steps = { + QStringLiteral("ALTER TABLE log ADD COLUMN profile_id INTEGER REFERENCES profiles(profile_id)"), + QStringLiteral("ALTER TABLE log ADD COLUMN variant_id INTEGER REFERENCES profile_variants(variant_id)"), + QStringLiteral( + "INSERT OR IGNORE INTO profiles (callsign, operator_name, comment) " + "SELECT DISTINCT CASE WHEN instr(upper(trim(stationcall)),'/')>0 " + " THEN substr(upper(trim(stationcall)),1,instr(upper(trim(stationcall)),'/')-1) " + " ELSE upper(trim(stationcall)) END, operators, comment FROM logs"), + QStringLiteral( + "INSERT OR IGNORE INTO profile_variants (profile_id, station_callsign, kind, dxcc_counts) " + "SELECT DISTINCT p.profile_id, upper(trim(l.stationcall)), " + " CASE WHEN upper(trim(l.stationcall)) LIKE '%/MM' THEN 'MM' " + " WHEN upper(trim(l.stationcall)) LIKE '%/AM' THEN 'AM' " + " WHEN upper(trim(l.stationcall)) LIKE '%/P' THEN 'P' " + " WHEN upper(trim(l.stationcall)) LIKE '%/M' THEN 'M' " + " WHEN l.logtype IS NOT NULL AND l.logtype<>'DX' THEN 'CONTEST' " + " ELSE 'OTHER' END, " + " CASE WHEN upper(trim(l.stationcall)) LIKE '%/MM' THEN 0 ELSE 1 END " + "FROM logs l JOIN profiles p ON p.callsign = " + " CASE WHEN instr(upper(trim(l.stationcall)),'/')>0 " + " THEN substr(upper(trim(l.stationcall)),1,instr(upper(trim(l.stationcall)),'/')-1) " + " ELSE upper(trim(l.stationcall)) END " + "WHERE upper(trim(l.stationcall)) <> p.callsign"), + QStringLiteral( + "UPDATE log SET " + " profile_id = (SELECT p.profile_id FROM logs l JOIN profiles p ON p.callsign = " + " CASE WHEN instr(upper(trim(l.stationcall)),'/')>0 " + " THEN substr(upper(trim(l.stationcall)),1,instr(upper(trim(l.stationcall)),'/')-1) " + " ELSE upper(trim(l.stationcall)) END WHERE l.id = log.lognumber), " + " variant_id = (SELECT v.variant_id FROM logs l JOIN profile_variants v " + " ON v.station_callsign = upper(trim(l.stationcall)) WHERE l.id = log.lognumber)"), + QStringLiteral("CREATE INDEX IF NOT EXISTS idx_log_profile ON log (profile_id)") + }; + for (const QString &s : steps) { + q.prepare(s); + if (!exec(q, "migrate")) { + db.rollback(); + if (errorOut) *errorOut = q.lastError().text(); + return false; + } + } + db.commit(); + } + + // Trigeris: katrs jauns QSO automatiski sanem profile_id/variant_id + // pec lognumber -> logs.stationcall kartejuma. Darbojas visiem ievades + // celiem (manuala ievade, ADIF imports, UDP) bez izmainam KLog koda. + q.prepare(QStringLiteral( + "CREATE TRIGGER IF NOT EXISTS trg_log_profile " + "AFTER INSERT ON log " + "WHEN NEW.profile_id IS NULL " + "BEGIN " + " UPDATE log SET " + " profile_id = (SELECT p.profile_id FROM logs l JOIN profiles p ON p.callsign = " + " CASE WHEN instr(upper(trim(l.stationcall)),'/')>0 " + " THEN substr(upper(trim(l.stationcall)),1,instr(upper(trim(l.stationcall)),'/')-1) " + " ELSE upper(trim(l.stationcall)) END WHERE l.id = NEW.lognumber), " + " variant_id = (SELECT v.variant_id FROM logs l JOIN profile_variants v " + " ON v.station_callsign = upper(trim(l.stationcall)) WHERE l.id = NEW.lognumber) " + " WHERE id = NEW.id; " + "END")); + if (!exec(q, "createTrigger")) return false; + + return true; +} + +QList ProfileManager::listProfiles() const +{ + QList out; + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "SELECT p.profile_id, p.callsign, p.operator_name, p.gridsquare, p.qth," + " p.cq_zone, p.itu_zone, p.dxcc, p.comment," + " (SELECT count(*) FROM log WHERE log.profile_id = p.profile_id) " + "FROM profiles p ORDER BY p.callsign")); + if (!exec(q, "listProfiles")) return out; + while (q.next()) { + Profile p; + p.id = q.value(0).toInt(); + p.callsign = q.value(1).toString(); + p.operatorName = q.value(2).toString(); + p.gridsquare = q.value(3).toString(); + p.qth = q.value(4).toString(); + p.cqZone = q.value(5).toInt(); + p.ituZone = q.value(6).toInt(); + p.dxcc = q.value(7).toInt(); + p.comment = q.value(8).toString(); + p.qsoCount = q.value(9).toInt(); + out.append(p); + } + return out; +} + +Profile ProfileManager::getProfile(int id) const +{ + Profile p; + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "SELECT profile_id, callsign, operator_name, gridsquare, qth," + " cq_zone, itu_zone, dxcc, comment, name, address1, address2, address3," + " address4, city, zip_code, province, country, rig1, rig2, rig3," + " antenna1, antenna2, antenna3, power," + " clublog_email, clublog_pass, clublog_app_pass," + " qrz_user, qrz_pass, qrz_logbook_key," + " eqsl_call, eqsl_pass, lotw_user, lotw_pass" + " FROM profiles WHERE profile_id = :id")); + q.bindValue(QStringLiteral(":id"), id); + if (exec(q, "getProfile") && q.next()) { + p.id = q.value(0).toInt(); + p.callsign = q.value(1).toString(); + p.operatorName = q.value(2).toString(); + p.gridsquare = q.value(3).toString(); + p.qth = q.value(4).toString(); + p.cqZone = q.value(5).toInt(); + p.ituZone = q.value(6).toInt(); + p.dxcc = q.value(7).toInt(); + p.comment = q.value(8).toString(); + p.name = q.value(9).toString(); + p.address1 = q.value(10).toString(); + p.address2 = q.value(11).toString(); + p.address3 = q.value(12).toString(); + p.address4 = q.value(13).toString(); + p.city = q.value(14).toString(); + p.zipCode = q.value(15).toString(); + p.province = q.value(16).toString(); + p.country = q.value(17).toString(); + p.rig1 = q.value(18).toString(); + p.rig2 = q.value(19).toString(); + p.rig3 = q.value(20).toString(); + p.antenna1 = q.value(21).toString(); + p.antenna2 = q.value(22).toString(); + p.antenna3 = q.value(23).toString(); + p.power = q.value(24).toDouble(); + p.clublogEmail = q.value(25).toString(); + p.clublogPass = q.value(26).toString(); + p.clublogAppPass = q.value(27).toString(); + p.qrzUser = q.value(28).toString(); + p.qrzPass = q.value(29).toString(); + p.qrzLogbookKey = q.value(30).toString(); + p.eqslCall = q.value(31).toString(); + p.eqslPass = q.value(32).toString(); + p.lotwUser = q.value(33).toString(); + p.lotwPass = q.value(34).toString(); + } + return p; +} + +int ProfileManager::createProfile(const Profile &p) +{ + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "INSERT INTO profiles (callsign, operator_name, gridsquare, qth," + " cq_zone, itu_zone, dxcc, comment) " + "VALUES (:c,:o,:g,:q,:cq,:itu,:dx,:cm)")); + q.bindValue(QStringLiteral(":c"), p.callsign.trimmed().toUpper()); + q.bindValue(QStringLiteral(":o"), p.operatorName); + q.bindValue(QStringLiteral(":g"), p.gridsquare); + q.bindValue(QStringLiteral(":q"), p.qth); + q.bindValue(QStringLiteral(":cq"), p.cqZone); + q.bindValue(QStringLiteral(":itu"),p.ituZone); + q.bindValue(QStringLiteral(":dx"), p.dxcc); + q.bindValue(QStringLiteral(":cm"), p.comment); + if (!exec(q, "createProfile")) return -1; + return q.lastInsertId().toInt(); +} + +bool ProfileManager::updateProfile(const Profile &p) +{ + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "UPDATE profiles SET callsign=:c, operator_name=:o, gridsquare=:g," + " qth=:q, cq_zone=:cq, itu_zone=:itu, dxcc=:dx, comment=:cm," + " name=:nm, address1=:a1, address2=:a2, address3=:a3, address4=:a4," + " city=:ct, zip_code=:zp, province=:pv, country=:co," + " rig1=:r1, rig2=:r2, rig3=:r3," + " antenna1=:n1, antenna2=:n2, antenna3=:n3, power=:pw," + " clublog_email=:ce, clublog_pass=:cp, clublog_app_pass=:ca," + " qrz_user=:qu, qrz_pass=:qp, qrz_logbook_key=:qk," + " eqsl_call=:ec, eqsl_pass=:ep," + " lotw_user=:lu, lotw_pass=:lp " + "WHERE profile_id=:id")); + q.bindValue(QStringLiteral(":c"), p.callsign.trimmed().toUpper()); + q.bindValue(QStringLiteral(":o"), p.operatorName); + q.bindValue(QStringLiteral(":g"), p.gridsquare); + q.bindValue(QStringLiteral(":q"), p.qth); + q.bindValue(QStringLiteral(":cq"), p.cqZone); + q.bindValue(QStringLiteral(":itu"),p.ituZone); + q.bindValue(QStringLiteral(":dx"), p.dxcc); + q.bindValue(QStringLiteral(":cm"), p.comment); + q.bindValue(QStringLiteral(":nm"), p.name); + q.bindValue(QStringLiteral(":a1"), p.address1); + q.bindValue(QStringLiteral(":a2"), p.address2); + q.bindValue(QStringLiteral(":a3"), p.address3); + q.bindValue(QStringLiteral(":a4"), p.address4); + q.bindValue(QStringLiteral(":ct"), p.city); + q.bindValue(QStringLiteral(":zp"), p.zipCode); + q.bindValue(QStringLiteral(":pv"), p.province); + q.bindValue(QStringLiteral(":co"), p.country); + q.bindValue(QStringLiteral(":r1"), p.rig1); + q.bindValue(QStringLiteral(":r2"), p.rig2); + q.bindValue(QStringLiteral(":r3"), p.rig3); + q.bindValue(QStringLiteral(":n1"), p.antenna1); + q.bindValue(QStringLiteral(":n2"), p.antenna2); + q.bindValue(QStringLiteral(":n3"), p.antenna3); + q.bindValue(QStringLiteral(":pw"), p.power); + q.bindValue(QStringLiteral(":ce"), p.clublogEmail); + q.bindValue(QStringLiteral(":cp"), p.clublogPass); + q.bindValue(QStringLiteral(":ca"), p.clublogAppPass); + q.bindValue(QStringLiteral(":qu"), p.qrzUser); + q.bindValue(QStringLiteral(":qp"), p.qrzPass); + q.bindValue(QStringLiteral(":qk"), p.qrzLogbookKey); + q.bindValue(QStringLiteral(":ec"), p.eqslCall); + q.bindValue(QStringLiteral(":ep"), p.eqslPass); + q.bindValue(QStringLiteral(":lu"), p.lotwUser); + q.bindValue(QStringLiteral(":lp"), p.lotwPass); + q.bindValue(QStringLiteral(":id"), p.id); + return exec(q, "updateProfile"); +} + +bool ProfileManager::deleteProfile(int id, QString *errorOut) +{ + const int n = qsoCount(id); + if (n > 0) { + if (errorOut) + *errorOut = QObject::tr("The profile holds %1 QSOs. Export them to ADIF " + "or move them to another profile first.").arg(n); + return false; + } + QSqlDatabase db = QSqlDatabase::database(); + db.transaction(); + QSqlQuery q(db); + q.prepare(QStringLiteral("DELETE FROM profile_variants WHERE profile_id=:id")); + q.bindValue(QStringLiteral(":id"), id); + if (!exec(q, "deleteVariantsOfProfile")) { db.rollback(); return false; } + q.prepare(QStringLiteral("DELETE FROM profiles WHERE profile_id=:id")); + q.bindValue(QStringLiteral(":id"), id); + if (!exec(q, "deleteProfile")) { db.rollback(); return false; } + db.commit(); + return true; +} + +int ProfileManager::qsoCount(int profileId) const +{ + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral("SELECT count(*) FROM log WHERE profile_id=:id")); + q.bindValue(QStringLiteral(":id"), profileId); + if (exec(q, "qsoCount") && q.next()) + return q.value(0).toInt(); + return 0; +} + +QList ProfileManager::listVariants(int profileId) const +{ + QList out; + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "SELECT variant_id, profile_id, station_callsign, kind, gridsquare," + " dxcc_counts FROM profile_variants WHERE profile_id=:id " + "ORDER BY station_callsign")); + q.bindValue(QStringLiteral(":id"), profileId); + if (!exec(q, "listVariants")) return out; + while (q.next()) { + ProfileVariant v; + v.id = q.value(0).toInt(); + v.profileId = q.value(1).toInt(); + v.stationCallsign = q.value(2).toString(); + v.kind = q.value(3).toString(); + v.gridsquare = q.value(4).toString(); + v.dxccCounts = q.value(5).toInt() != 0; + out.append(v); + } + return out; +} + +int ProfileManager::createVariant(const ProfileVariant &v) +{ + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "INSERT INTO profile_variants (profile_id, station_callsign, kind," + " gridsquare, dxcc_counts) VALUES (:p,:s,:k,:g,:d)")); + q.bindValue(QStringLiteral(":p"), v.profileId); + q.bindValue(QStringLiteral(":s"), v.stationCallsign.trimmed().toUpper()); + q.bindValue(QStringLiteral(":k"), v.kind); + q.bindValue(QStringLiteral(":g"), v.gridsquare); + q.bindValue(QStringLiteral(":d"), v.dxccCounts ? 1 : 0); + if (!exec(q, "createVariant")) return -1; + return q.lastInsertId().toInt(); +} + +bool ProfileManager::deleteVariant(int variantId) +{ + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral("DELETE FROM profile_variants WHERE variant_id=:id")); + q.bindValue(QStringLiteral(":id"), variantId); + return exec(q, "deleteVariant"); +} + + +bool ProfileManager::saveSettingsToProfile(int profileId, const QString &cfgFile) +{ + if (profileId < 0) return false; + Profile p = getProfile(profileId); + if (p.id < 0) return false; + + QSettings st(cfgFile, QSettings::IniFormat); + st.beginGroup(QStringLiteral("UserData")); + const QString call = st.value(QStringLiteral("Callsign")).toString().trimmed().toUpper(); + // zimi nemainam - tas ir profila identitate + if (!call.isEmpty() && call != p.callsign) + { + st.endGroup(); + return false; // Setup pusē nomainita zime - neparrakstam profilu + } + p.operatorName = st.value(QStringLiteral("Operators")).toString(); + p.name = st.value(QStringLiteral("Name")).toString(); + p.gridsquare = st.value(QStringLiteral("StationLocator")).toString(); + p.cqZone = st.value(QStringLiteral("CQz")).toInt(); + p.ituZone = st.value(QStringLiteral("ITUz")).toInt(); + p.address1 = st.value(QStringLiteral("Address1")).toString(); + p.address2 = st.value(QStringLiteral("Address2")).toString(); + p.address3 = st.value(QStringLiteral("Address3")).toString(); + p.address4 = st.value(QStringLiteral("Address4")).toString(); + p.city = st.value(QStringLiteral("City")).toString(); + p.zipCode = st.value(QStringLiteral("ZipCode")).toString(); + p.province = st.value(QStringLiteral("ProvinceState")).toString(); + p.country = st.value(QStringLiteral("Country")).toString(); + p.rig1 = st.value(QStringLiteral("Rig1")).toString(); + p.rig2 = st.value(QStringLiteral("Rig2")).toString(); + p.rig3 = st.value(QStringLiteral("Rig3")).toString(); + p.antenna1 = st.value(QStringLiteral("Antenna1")).toString(); + p.antenna2 = st.value(QStringLiteral("Antenna2")).toString(); + p.antenna3 = st.value(QStringLiteral("Antenna3")).toString(); + p.power = st.value(QStringLiteral("Power")).toDouble(); + st.endGroup(); + + st.beginGroup(QStringLiteral("ClubLog")); + p.clublogEmail = st.value(QStringLiteral("ClubLogEmail")).toString(); + p.clublogPass = st.value(QStringLiteral("ClubLogPass")).toString(); + p.clublogAppPass = st.value(QStringLiteral("ClubLogAppPass")).toString(); + st.endGroup(); + + st.beginGroup(QStringLiteral("QRZcom")); + p.qrzUser = st.value(QStringLiteral("QRZcomUser")).toString(); + p.qrzPass = st.value(QStringLiteral("QRZcomPass")).toString(); + p.qrzLogbookKey = st.value(QStringLiteral("QRZcomLogBookKey")).toString(); + st.endGroup(); + + st.beginGroup(QStringLiteral("eQSL")); + p.eqslCall = st.value(QStringLiteral("eQSLCall")).toString(); + p.eqslPass = st.value(QStringLiteral("eQSLPass")).toString(); + st.endGroup(); + + st.beginGroup(QStringLiteral("LoTW")); + p.lotwUser = st.value(QStringLiteral("LoTWUser")).toString(); + p.lotwPass = st.value(QStringLiteral("LoTWPass")).toString(); + st.endGroup(); + + return updateProfile(p); +} + + +QList ProfileManager::listClubs(int profileId) const +{ + QList out; + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "SELECT profile_id, club, member_nr FROM profile_clubs " + "WHERE profile_id=:id ORDER BY club")); + q.bindValue(QStringLiteral(":id"), profileId); + if (!exec(q, "listClubs")) return out; + while (q.next()) { + ProfileClub c; + c.profileId = q.value(0).toInt(); + c.club = q.value(1).toString(); + c.memberNr = q.value(2).toString(); + out.append(c); + } + return out; +} + +bool ProfileManager::setClubs(int profileId, const QList &clubs) +{ + QSqlDatabase db = QSqlDatabase::database(); + db.transaction(); + QSqlQuery q(db); + q.prepare(QStringLiteral("DELETE FROM profile_clubs WHERE profile_id=:id")); + q.bindValue(QStringLiteral(":id"), profileId); + if (!exec(q, "clearClubs")) { db.rollback(); return false; } + for (const ProfileClub &c : clubs) { + if (c.club.trimmed().isEmpty()) continue; + q.prepare(QStringLiteral( + "INSERT INTO profile_clubs (profile_id, club, member_nr) " + "VALUES (:p, :c, :n)")); + q.bindValue(QStringLiteral(":p"), profileId); + q.bindValue(QStringLiteral(":c"), c.club.trimmed().toUpper()); + q.bindValue(QStringLiteral(":n"), c.memberNr.trimmed()); + if (!exec(q, "insertClub")) { db.rollback(); return false; } + } + db.commit(); + return true; +} diff --git a/src/profilemanager.h b/src/profilemanager.h new file mode 100644 index 00000000..77540d64 --- /dev/null +++ b/src/profilemanager.h @@ -0,0 +1,77 @@ +#ifndef KLOGNG_PROFILEMANAGER_H +#define KLOGNG_PROFILEMANAGER_H + +#include +#include +#include + +struct Profile { + int id = -1; + QString callsign; + QString operatorName; + QString gridsquare; + QString qth; + int cqZone = 0; + int ituZone = 0; + int dxcc = 0; + QString comment; + // KLog My Data lauki + QString name; + QString address1, address2, address3, address4; + QString city, zipCode, province, country; + QString rig1, rig2, rig3; + QString antenna1, antenna2, antenna3; + double power = 0.0; + // eLog servisi + QString clublogEmail, clublogPass, clublogAppPass; + QString qrzUser, qrzPass, qrzLogbookKey; + QString eqslCall, eqslPass; + QString lotwUser, lotwPass; + int qsoCount = 0; +}; + +struct ProfileVariant { + int id = -1; + int profileId = -1; + QString stationCallsign; + QString kind; + QString gridsquare; + bool dxccCounts = true; +}; + +struct ProfileClub { + int profileId = -1; + QString club; // isais nosaukums (SKCC, FISTS, AGB...) + QString memberNr; // numurs var but ar burtiem (SKCC C/T/S limeni) +}; + +class ProfileManager +{ +public: + ProfileManager() = default; + + bool ensureSchemaAndMigrate(QString *errorOut = nullptr); + + QList listProfiles() const; + Profile getProfile(int id) const; + int createProfile(const Profile &p); + bool updateProfile(const Profile &p); + bool deleteProfile(int id, QString *errorOut); + + QList listVariants(int profileId) const; + int createVariant(const ProfileVariant &v); + bool deleteVariant(int variantId); + + int qsoCount(int profileId) const; + + // Nolasa KLog [UserData] iestatijumus no klogrc un saglaba tos profila + bool saveSettingsToProfile(int profileId, const QString &cfgFile); + + QList listClubs(int profileId) const; + bool setClubs(int profileId, const QList &clubs); + +private: + bool exec(QSqlQuery &q, const char *ctx) const; +}; + +#endif diff --git a/src/startprofiledialog.cpp b/src/startprofiledialog.cpp new file mode 100644 index 00000000..38ea6b50 --- /dev/null +++ b/src/startprofiledialog.cpp @@ -0,0 +1,536 @@ +#include "startprofiledialog.h" +#include "world.h" +#include "setupdialog.h" +#include "dataproxy_sqlite.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void forceLightPalette(QWidget *w) +{ + QPalette p; + p.setColor(QPalette::Window, QColor(0xf0,0xf0,0xf0)); + p.setColor(QPalette::WindowText, Qt::black); + p.setColor(QPalette::Base, Qt::white); + p.setColor(QPalette::AlternateBase, QColor(0xe9,0xe9,0xe9)); + p.setColor(QPalette::Text, Qt::black); + p.setColor(QPalette::Button, QColor(0xf0,0xf0,0xf0)); + p.setColor(QPalette::ButtonText, Qt::black); + p.setColor(QPalette::Highlight, QColor(0x30,0x8c,0xc6)); + p.setColor(QPalette::HighlightedText, Qt::white); + p.setColor(QPalette::ToolTipBase, Qt::white); + p.setColor(QPalette::ToolTipText, Qt::black); + w->setPalette(p); +} + +static const char *SETT_OPEN_LAST = "profiles/openLastOnStart"; +static const char *SETT_LAST_PROFILE= "profiles/lastProfileId"; + +#include +// Tas pats ini fails, ko lieto KLog (Utilities::getCfgFile ekvivalents) +#include +#include + +// Ieraksta aktiva profila vardu ~/.klogng/active-profile un nodrosina, +// ka profilam ir savs klogrc (pirmaja reize nokope esoso). +static void writeActiveProfile(const QString &callsign) +{ + const QString home = QDir::homePath() + QStringLiteral("/.klogng"); + QFile ap(home + QStringLiteral("/active-profile")); + if (ap.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream out(&ap); + out << callsign; + ap.close(); + } + const QString pdir = home + QStringLiteral("/profiles/") + callsign; + QDir().mkpath(pdir); + const QString pcfg = pdir + QStringLiteral("/klogrc"); + if (!QFile::exists(pcfg)) + QFile::copy(home + QStringLiteral("/klogrc"), pcfg); +} + +static QString klogngCfgFile() +{ + const QString home = QDir::homePath() + QStringLiteral("/.klogng"); + QFile ap(home + QStringLiteral("/active-profile")); + if (ap.exists() && ap.open(QIODevice::ReadOnly | QIODevice::Text)) + { + const QString n = QString::fromUtf8(ap.readAll()).trimmed(); + ap.close(); + if (!n.isEmpty()) + return home + QStringLiteral("/profiles/") + n + QStringLiteral("/klogrc"); + } + return home + QStringLiteral("/klogrc"); +} + +StartProfileDialog::StartProfileDialog(ProfileManager *pm_, World *world_, DataProxy_SQLite *dp_, QWidget *parent) + : QDialog(parent), pm(pm_), world(world_), dataProxy(dp_) +{ + setStyle(QStyleFactory::create(QStringLiteral("Fusion"))); + forceLightPalette(this); + setWindowTitle(tr("Select profile")); + setModal(true); + resize(420, 380); + + table = new QTableWidget(this); + table->setColumnCount(3); + table->setHorizontalHeaderLabels({tr("Callsign"), tr("QSOs"), tr("Note")}); + table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setSelectionMode(QAbstractItemView::SingleSelection); + table->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(table, &QTableWidget::cellDoubleClicked, + this, [this](int, int){ openSelected(); }); + + openBtn = new QPushButton(tr("Open"), this); + auto *newBtn = new QPushButton(tr("New profile"), this); + editBtn = new QPushButton(tr("Edit"), this); + deleteBtn = new QPushButton(tr("Delete"), this); + auto *cancelBtn = new QPushButton(tr("Cancel"), this); + openBtn->setDefault(true); + + connect(openBtn, &QPushButton::clicked, this, &StartProfileDialog::openSelected); + connect(newBtn, &QPushButton::clicked, this, &StartProfileDialog::newProfile); + connect(editBtn, &QPushButton::clicked, this, &StartProfileDialog::editSelected); + connect(deleteBtn, &QPushButton::clicked, this, &StartProfileDialog::deleteSelected); + connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); + + openLastCheck = new QCheckBox(tr("Open this profile automatically next time"), this); + QSettings sett(klogngCfgFile(), QSettings::IniFormat); + openLastCheck->setChecked(sett.value(QLatin1String(SETT_OPEN_LAST), false).toBool()); + + auto *btnCol = new QVBoxLayout; + btnCol->addWidget(openBtn); + btnCol->addWidget(cancelBtn); + btnCol->addSpacing(16); + btnCol->addWidget(newBtn); + btnCol->addWidget(editBtn); + btnCol->addWidget(deleteBtn); + btnCol->addStretch(); + + auto *mid = new QHBoxLayout; + mid->addWidget(table, 1); + mid->addLayout(btnCol); + + auto *root = new QVBoxLayout(this); + root->addLayout(mid, 1); + root->addWidget(openLastCheck); + + reload(); +} + +void StartProfileDialog::reload() +{ + const QList profiles = pm->listProfiles(); + table->setRowCount(profiles.size()); + int row = 0; + for (const Profile &p : profiles) { + auto *c0 = new QTableWidgetItem(p.callsign); + c0->setData(Qt::UserRole, p.id); + table->setItem(row, 0, c0); + table->setItem(row, 1, new QTableWidgetItem(QString::number(p.qsoCount))); + table->setItem(row, 2, new QTableWidgetItem(p.comment)); + ++row; + } + const bool any = !profiles.isEmpty(); + openBtn->setEnabled(any); + editBtn->setEnabled(any); + deleteBtn->setEnabled(any); + if (any) + table->selectRow(0); +} + +int StartProfileDialog::currentRowProfileId() const +{ + const int row = table->currentRow(); + if (row < 0) return -1; + return table->item(row, 0)->data(Qt::UserRole).toInt(); +} + +void StartProfileDialog::openSelected() +{ + const int id = currentRowProfileId(); + if (id < 0) return; + selectedId = id; + QSettings sett(klogngCfgFile(), QSettings::IniFormat); + sett.setValue(QLatin1String(SETT_OPEN_LAST), openLastCheck->isChecked()); + sett.setValue(QLatin1String(SETT_LAST_PROFILE), id); + accept(); +} + +#include +#include +#include +#include +#include +#include + +static bool editProfileDialog(QWidget *parent, Profile &p, const QString &title, World *world, ProfileManager *pm, DataProxy_SQLite *dp) + +{ + QDialog dlg(parent); + dlg.setWindowTitle(title); + dlg.resize(520, 460); + + auto *callP = new QLineEdit(p.callsign, &dlg); + auto *operP = new QLineEdit(p.operatorName, &dlg); + auto *gridP = new QLineEdit(p.gridsquare, &dlg); + auto *qthP = new QLineEdit(p.qth, &dlg); + auto *commentP = new QLineEdit(p.comment, &dlg); + auto *nameP = new QLineEdit(p.name, &dlg); + QLineEdit &call = *callP, &oper = *operP, &grid = *gridP, + &qth = *qthP, &comment = *commentP, &name = *nameP; + auto *cqzP = new QSpinBox(&dlg); auto *ituzP = new QSpinBox(&dlg); + QSpinBox &cqz = *cqzP, &ituz = *ituzP; + cqz.setRange(0, 40); cqz.setValue(p.cqZone); + ituz.setRange(0, 90); ituz.setValue(p.ituZone); + + auto *a1P=new QLineEdit(p.address1,&dlg); auto *a2P=new QLineEdit(p.address2,&dlg); + auto *a3P=new QLineEdit(p.address3,&dlg); auto *a4P=new QLineEdit(p.address4,&dlg); + auto *cityP=new QLineEdit(p.city,&dlg); auto *zipP=new QLineEdit(p.zipCode,&dlg); + auto *provP=new QLineEdit(p.province,&dlg); auto *countryP=new QLineEdit(p.country,&dlg); + QLineEdit &a1=*a1P,&a2=*a2P,&a3=*a3P,&a4=*a4P,&city=*cityP,&zip=*zipP,&prov=*provP,&country=*countryP; + + auto *r1P=new QLineEdit(p.rig1,&dlg); auto *r2P=new QLineEdit(p.rig2,&dlg); + auto *r3P=new QLineEdit(p.rig3,&dlg); auto *n1P=new QLineEdit(p.antenna1,&dlg); + auto *n2P=new QLineEdit(p.antenna2,&dlg); auto *n3P=new QLineEdit(p.antenna3,&dlg); + QLineEdit &r1=*r1P,&r2=*r2P,&r3=*r3P,&n1=*n1P,&n2=*n2P,&n3=*n3P; + auto *pwrP = new QDoubleSpinBox(&dlg); QDoubleSpinBox &pwr = *pwrP; + pwr.setRange(0, 10000); pwr.setDecimals(1); pwr.setSuffix(" W"); pwr.setValue(p.power); + + // Automatiska zonu/DXCC aizpilde pec zimes. + // /MM un /AM: nav DXCC, neko neaizpildam (kustigi objekti). + auto fillFromCallsign = [&call, &cqz, &ituz, &country, world]() { + if (!world) return; + const QString c = call.text().trimmed().toUpper(); + if (c.isEmpty()) return; + if (c.endsWith(QStringLiteral("/MM")) || c.endsWith(QStringLiteral("/AM"))) + return; // juras/gaisa stacija - bez DXCC + const int cq = world->getQRZCqz(c); + const int itu = world->getQRZItuz(c); + const int dx = world->getQRZARRLId(c); + if (dx < 0) return; // prefikss nav atpazits + if (cqz.value() == 0 && cq > 0) cqz.setValue(cq); + if (ituz.value() == 0 && itu > 0) ituz.setValue(itu); + if (country.text().trimmed().isEmpty()) + country.setText(world->getQRZEntityName(c)); + }; + QObject::connect(&call, &QLineEdit::editingFinished, fillFromCallsign); + fillFromCallsign(); // uzreiz ari atverot dialogu (tuksiem laukiem) + + // Automatiska zonu/DXCC aizpilde pec zimes. + // /MM un /AM: nav DXCC, neko neaizpildam (kustigi objekti). + + + auto *tabsP = new QTabWidget(&dlg); QTabWidget &tabs = *tabsP; + + auto *wStationP = new QWidget(&dlg); QWidget &wStation = *wStationP; QFormLayout fs(&wStation); + fs.addRow(QObject::tr("Callsign:"), &call); + fs.addRow(QObject::tr("Operator name:"), &name); + fs.addRow(QObject::tr("Operator callsigns:"), &oper); + fs.addRow(QObject::tr("Locator:"), &grid); + fs.addRow(QObject::tr("QTH:"), &qth); + fs.addRow(QObject::tr("CQ zone:"), &cqz); + fs.addRow(QObject::tr("ITU zone:"), &ituz); + fs.addRow(QObject::tr("Note:"), &comment); + tabs.addTab(&wStation, QObject::tr("Station")); + + auto *wAddrP = new QWidget(&dlg); QWidget &wAddr = *wAddrP; QFormLayout fa(&wAddr); + fa.addRow(QObject::tr("Address 1:"), &a1); + fa.addRow(QObject::tr("Address 2:"), &a2); + fa.addRow(QObject::tr("Address 3:"), &a3); + fa.addRow(QObject::tr("Address 4:"), &a4); + fa.addRow(QObject::tr("City:"), &city); + fa.addRow(QObject::tr("Postal code:"), &zip); + fa.addRow(QObject::tr("Province/State:"), &prov); + fa.addRow(QObject::tr("Country:"), &country); + tabs.addTab(&wAddr, QObject::tr("Address")); + + auto *wRigP = new QWidget(&dlg); QWidget &wRig = *wRigP; QFormLayout fr(&wRig); + fr.addRow(QObject::tr("Rig 1:"), &r1); + fr.addRow(QObject::tr("Rig 2:"), &r2); + fr.addRow(QObject::tr("Rig 3:"), &r3); + fr.addRow(QObject::tr("Antenna 1:"), &n1); + fr.addRow(QObject::tr("Antenna 2:"), &n2); + fr.addRow(QObject::tr("Antenna 3:"), &n3); + fr.addRow(QObject::tr("Power:"), &pwr); + tabs.addTab(&wRig, QObject::tr("Equipment")); + + // --- Klubi --- + auto *wClub = new QWidget(&dlg); + auto *clubTable = new QTableWidget(wClub); + clubTable->setColumnCount(2); + clubTable->setHorizontalHeaderLabels({QObject::tr("Club"), QObject::tr("Member number")}); + clubTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + clubTable->setSelectionBehavior(QAbstractItemView::SelectRows); + auto *addClub = new QPushButton(QObject::tr("Add"), wClub); + auto *delClub = new QPushButton(QObject::tr("Remove"), wClub); + QObject::connect(addClub, &QPushButton::clicked, [clubTable]() { + const int r = clubTable->rowCount(); + clubTable->insertRow(r); + clubTable->setItem(r, 0, new QTableWidgetItem()); + clubTable->setItem(r, 1, new QTableWidgetItem()); + clubTable->editItem(clubTable->item(r, 0)); + }); + QObject::connect(delClub, &QPushButton::clicked, [clubTable]() { + const int r = clubTable->currentRow(); + if (r >= 0) clubTable->removeRow(r); + }); + auto *clubBtns = new QHBoxLayout; + clubBtns->addWidget(addClub); + clubBtns->addWidget(delClub); + clubBtns->addStretch(); + auto *clubLay = new QVBoxLayout(wClub); + clubLay->addWidget(clubTable, 1); + clubLay->addLayout(clubBtns); + if (pm && p.id > 0) { + const QList cl = pm->listClubs(p.id); + for (const ProfileClub &c : cl) { + const int r = clubTable->rowCount(); + clubTable->insertRow(r); + clubTable->setItem(r, 0, new QTableWidgetItem(c.club)); + clubTable->setItem(r, 1, new QTableWidgetItem(c.memberNr)); + } + } + tabs.addTab(wClub, QObject::tr("Clubs")); + + // Poga uz pilno KLog konfiguraciju + auto *fullCfgBtn = new QPushButton(QObject::tr("Full settings..."), &dlg); + fullCfgBtn->setEnabled(dp != nullptr); + QObject::connect(fullCfgBtn, &QPushButton::clicked, [&dlg, dp, world, pm, &p]() { + if (!dp) return; + SetupDialog sd(dp, world, &dlg); + sd.init(QString(), 0, true); + sd.exec(); + // pec aizversanas nolasam UserData atpakal profila + if (pm && p.id > 0) + pm->saveSettingsToProfile(p.id, QDir::homePath() + QStringLiteral("/.klogng/klogrc")); + }); + + auto *boxP = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); + QDialogButtonBox &box = *boxP; + QObject::connect(&box, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + QObject::connect(&box, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + + auto *bottom = new QHBoxLayout; + bottom->addWidget(fullCfgBtn); + bottom->addStretch(); + bottom->addWidget(&box); + + QVBoxLayout root(&dlg); + root.addWidget(&tabs); + root.addLayout(bottom); + + if (dlg.exec() != QDialog::Accepted) return false; + if (call.text().trimmed().isEmpty()) return false; + + p.callsign = call.text().trimmed().toUpper(); + p.operatorName = oper.text(); + p.name = name.text(); + p.gridsquare = grid.text().trimmed().toUpper(); + p.qth = qth.text(); + p.cqZone = cqz.value(); + p.ituZone = ituz.value(); + p.comment = comment.text(); + p.address1 = a1.text(); p.address2 = a2.text(); + p.address3 = a3.text(); p.address4 = a4.text(); + p.city = city.text(); p.zipCode = zip.text(); + p.province = prov.text(); p.country = country.text(); + p.rig1 = r1.text(); p.rig2 = r2.text(); p.rig3 = r3.text(); + p.antenna1 = n1.text(); p.antenna2 = n2.text(); p.antenna3 = n3.text(); + p.power = pwr.value(); + + if (pm && p.id > 0) { + QList cl; + for (int r = 0; r < clubTable->rowCount(); ++r) { + ProfileClub c; + c.profileId = p.id; + c.club = clubTable->item(r,0) ? clubTable->item(r,0)->text() : QString(); + c.memberNr = clubTable->item(r,1) ? clubTable->item(r,1)->text() : QString(); + if (!c.club.trimmed().isEmpty()) cl.append(c); + } + pm->setClubs(p.id, cl); + } + return true; +} + +void StartProfileDialog::newProfile() +{ + Profile p; + if (!editProfileDialog(this, p, tr("New profile"), world, pm, dataProxy)) + return; + if (pm->createProfile(p) < 0) + QMessageBox::warning(this, tr("Error"), + tr("Could not create the profile. Does callsign %1 already exist?").arg(p.callsign)); + reload(); +} + +void StartProfileDialog::editSelected() +{ + const int id = currentRowProfileId(); + if (id < 0) return; + Profile p = pm->getProfile(id); + if (!editProfileDialog(this, p, tr("Edit profile %1").arg(p.callsign), world, pm, dataProxy)) + return; + pm->updateProfile(p); + reload(); +} + +void StartProfileDialog::deleteSelected() +{ + const int id = currentRowProfileId(); + if (id < 0) return; + const Profile p = pm->getProfile(id); + const int n = pm->qsoCount(id); + if (QMessageBox::question(this, tr("Delete profile?"), + tr("Delete profile %1 (%2 QSOs)?").arg(p.callsign).arg(n)) + != QMessageBox::Yes) + return; + QString err; + if (!pm->deleteProfile(id, &err)) + QMessageBox::warning(this, tr("Cannot delete"), err); + reload(); +} + +#include +#include +#include +#include + +// Uzstada visu profila datu komplektu KLog konfigura (UserData grupa) +static void applyProfileToSettings(QSettings &st, const Profile &ap) +{ + st.beginGroup(QStringLiteral("UserData")); + if (!ap.callsign.isEmpty()) st.setValue(QStringLiteral("Callsign"), ap.callsign); + if (!ap.gridsquare.isEmpty()) st.setValue(QStringLiteral("StationLocator"), ap.gridsquare); + if (!ap.operatorName.isEmpty()) st.setValue(QStringLiteral("Operators"), ap.operatorName); + if (!ap.name.isEmpty()) st.setValue(QStringLiteral("Name"), ap.name); + if (ap.cqZone > 0) st.setValue(QStringLiteral("CQz"), ap.cqZone); + if (ap.ituZone > 0) st.setValue(QStringLiteral("ITUz"), ap.ituZone); + if (!ap.address1.isEmpty()) st.setValue(QStringLiteral("Address1"), ap.address1); + if (!ap.address2.isEmpty()) st.setValue(QStringLiteral("Address2"), ap.address2); + if (!ap.address3.isEmpty()) st.setValue(QStringLiteral("Address3"), ap.address3); + if (!ap.address4.isEmpty()) st.setValue(QStringLiteral("Address4"), ap.address4); + if (!ap.city.isEmpty()) st.setValue(QStringLiteral("City"), ap.city); + if (!ap.zipCode.isEmpty()) st.setValue(QStringLiteral("ZipCode"), ap.zipCode); + if (!ap.province.isEmpty()) st.setValue(QStringLiteral("ProvinceState"), ap.province); + if (!ap.country.isEmpty()) st.setValue(QStringLiteral("Country"), ap.country); + if (!ap.rig1.isEmpty()) st.setValue(QStringLiteral("Rig1"), ap.rig1); + if (!ap.rig2.isEmpty()) st.setValue(QStringLiteral("Rig2"), ap.rig2); + if (!ap.rig3.isEmpty()) st.setValue(QStringLiteral("Rig3"), ap.rig3); + if (!ap.antenna1.isEmpty()) st.setValue(QStringLiteral("Antenna1"), ap.antenna1); + if (!ap.antenna2.isEmpty()) st.setValue(QStringLiteral("Antenna2"), ap.antenna2); + if (!ap.antenna3.isEmpty()) st.setValue(QStringLiteral("Antenna3"), ap.antenna3); + if (ap.power > 0) st.setValue(QStringLiteral("Power"), ap.power); + st.endGroup(); + + st.beginGroup(QStringLiteral("ClubLog")); + if (!ap.clublogEmail.isEmpty()) st.setValue(QStringLiteral("ClubLogEmail"), ap.clublogEmail); + if (!ap.clublogPass.isEmpty()) st.setValue(QStringLiteral("ClubLogPass"), ap.clublogPass); + if (!ap.clublogAppPass.isEmpty()) st.setValue(QStringLiteral("ClubLogAppPass"), ap.clublogAppPass); + st.endGroup(); + + st.beginGroup(QStringLiteral("QRZcom")); + if (!ap.qrzUser.isEmpty()) st.setValue(QStringLiteral("QRZcomUser"), ap.qrzUser); + if (!ap.qrzPass.isEmpty()) st.setValue(QStringLiteral("QRZcomPass"), ap.qrzPass); + if (!ap.qrzLogbookKey.isEmpty()) st.setValue(QStringLiteral("QRZcomLogBookKey"), ap.qrzLogbookKey); + st.endGroup(); + + st.beginGroup(QStringLiteral("eQSL")); + if (!ap.eqslCall.isEmpty()) st.setValue(QStringLiteral("eQSLCall"), ap.eqslCall); + if (!ap.eqslPass.isEmpty()) st.setValue(QStringLiteral("eQSLPass"), ap.eqslPass); + st.endGroup(); + + st.beginGroup(QStringLiteral("LoTW")); + if (!ap.lotwUser.isEmpty()) st.setValue(QStringLiteral("LoTWUser"), ap.lotwUser); + if (!ap.lotwPass.isEmpty()) st.setValue(QStringLiteral("LoTWPass"), ap.lotwPass); + st.endGroup(); +} + +static int lognumberForProfile(ProfileManager *pm, int profileId) +{ + const Profile p = pm->getProfile(profileId); + if (p.id < 0) return -1; + QSqlQuery q(QSqlDatabase::database()); + q.prepare(QStringLiteral( + "SELECT id FROM logs WHERE upper(trim(stationcall)) = :c " + "ORDER BY id LIMIT 1")); + q.bindValue(QStringLiteral(":c"), p.callsign); + if (q.exec() && q.next()) + return q.value(0).toInt(); + // logs ieraksta nav (jauns profils) - izveidojam + q.prepare(QStringLiteral( + "INSERT INTO logs (logdate, stationcall, logtype, logtypen) " + "VALUES (:d, :c, 'DX', 1)")); + q.bindValue(QStringLiteral(":d"), QDate::currentDate().toString(QStringLiteral("yyyy-MM-dd"))); + q.bindValue(QStringLiteral(":c"), p.callsign); + if (q.exec()) + return q.lastInsertId().toInt(); + return -1; +} + +int StartProfileDialog::chooseProfileOnStartup(ProfileManager *pm, World *world, DataProxy_SQLite *dp, QWidget *parent) +{ + qApp->setStyle(QStyleFactory::create(QStringLiteral("Fusion"))); + QPalette lp; + lp.setColor(QPalette::Window, QColor(0xf0,0xf0,0xf0)); + lp.setColor(QPalette::WindowText, Qt::black); + lp.setColor(QPalette::Base, Qt::white); + lp.setColor(QPalette::AlternateBase, QColor(0xe9,0xe9,0xe9)); + lp.setColor(QPalette::Text, Qt::black); + lp.setColor(QPalette::Button, QColor(0xf0,0xf0,0xf0)); + lp.setColor(QPalette::ButtonText, Qt::black); + lp.setColor(QPalette::Highlight, QColor(0x30,0x8c,0xc6)); + lp.setColor(QPalette::HighlightedText, Qt::white); + lp.setColor(QPalette::ToolTipBase, Qt::white); + lp.setColor(QPalette::ToolTipText, Qt::black); + lp.setColor(QPalette::PlaceholderText, QColor(0x80,0x80,0x80)); + qApp->setPalette(lp); + + QSettings sett(klogngCfgFile(), QSettings::IniFormat); + if (sett.value(QLatin1String(SETT_OPEN_LAST), false).toBool()) { + const int last = sett.value(QLatin1String(SETT_LAST_PROFILE), -1).toInt(); + if (last > 0 && pm->getProfile(last).id == last) + { + const int ln = lognumberForProfile(pm, last); + if (ln > 0) + { + const Profile ap = pm->getProfile(last); + writeActiveProfile(ap.callsign); + QSettings s2(klogngCfgFile(), QSettings::IniFormat); + s2.setValue(QStringLiteral("SelectedLog"), ln); + applyProfileToSettings(s2, ap); + s2.sync(); + } + return last; + } + } + StartProfileDialog dlg(pm, world, dp, parent); + if (dlg.exec() == QDialog::Accepted) + { + const int ln = lognumberForProfile(pm, dlg.selectedProfileId()); + if (ln > 0) + { + const Profile ap = pm->getProfile(dlg.selectedProfileId()); + writeActiveProfile(ap.callsign); + QSettings s2(klogngCfgFile(), QSettings::IniFormat); + s2.setValue(QStringLiteral("SelectedLog"), ln); + applyProfileToSettings(s2, ap); + s2.sync(); + } + return dlg.selectedProfileId(); + } + return -1; +} diff --git a/src/startprofiledialog.h b/src/startprofiledialog.h new file mode 100644 index 00000000..706eab9a --- /dev/null +++ b/src/startprofiledialog.h @@ -0,0 +1,44 @@ +#ifndef KLOGNG_STARTPROFILEDIALOG_H +#define KLOGNG_STARTPROFILEDIALOG_H + +#include +#include "profilemanager.h" + +class QTableWidget; +class World; +class DataProxy_SQLite; +class QCheckBox; +class QPushButton; + +class StartProfileDialog : public QDialog +{ + Q_OBJECT +public: + explicit StartProfileDialog(ProfileManager *pm, World *world = nullptr, DataProxy_SQLite *dp = nullptr, QWidget *parent = nullptr); + + int selectedProfileId() const { return selectedId; } + + static int chooseProfileOnStartup(ProfileManager *pm, World *world = nullptr, DataProxy_SQLite *dp = nullptr, QWidget *parent = nullptr); + +private slots: + void openSelected(); + void newProfile(); + void editSelected(); + void deleteSelected(); + +private: + void reload(); + int currentRowProfileId() const; + + ProfileManager *pm; + World *world = nullptr; + DataProxy_SQLite *dataProxy = nullptr; + QTableWidget *table; + QCheckBox *openLastCheck; + QPushButton *openBtn; + QPushButton *editBtn; + QPushButton *deleteBtn; + int selectedId = -1; +}; + +#endif