• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 6685397774

29 Oct 2023 07:37PM UTC coverage: 11.735% (-0.002%) from 11.737%
6685397774

push

github

10110111
Deduplicate title bar implementation

131 of 131 new or added lines in 56 files covered. (100.0%)

14842 of 126472 relevant lines covered (11.74%)

21617.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/plugins/Satellites/src/gui/SatellitesDialog.cpp
1
/*
2
 * Stellarium Satellites plugin config dialog
3
 *
4
 * Copyright (C) 2009, 2012 Matthew Gates
5
 * Copyright (C) 2015 Nick Fedoseev (Iridium flares)
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
20
*/
21

22
#include <QDebug>
23
#include <QDateTime>
24
#include <QFileDialog>
25
#include <QSortFilterProxyModel>
26
#include <QStandardItemModel>
27
#include <QTimer>
28
#include <QUrl>
29
#include <QTabWidget>
30
#include <QAction>
31
#include <QColorDialog>
32
#include <QMessageBox>
33

34
#include "StelApp.hpp"
35
#include "ui_satellitesDialog.h"
36
#include "SatellitesDialog.hpp"
37
#include "SatellitesImportDialog.hpp"
38
#include "SatellitesFilterDialog.hpp"
39
#include "SatellitesCommDialog.hpp"
40
#include "SatellitesListModel.hpp"
41
#include "SatellitesListFilterModel.hpp"
42
#include "Satellites.hpp"
43
#include "StelModuleMgr.hpp"
44
#include "StelObjectMgr.hpp"
45
#include "StelMovementMgr.hpp"
46
#include "StelStyle.hpp"
47
#include "StelGui.hpp"
48
#include "StelMainView.hpp"
49
#include "StelFileMgr.hpp"
50
#include "StelTranslator.hpp"
51
#include "StelActionMgr.hpp"
52
#include "StelMainView.hpp"
53

54
#if defined(ENABLE_XLSX) && (SATELLITES_PLUGIN_IRIDIUM == 1)
55
#include <xlsxdocument.h>
56
#include <xlsxcellrange.h>
57
using namespace QXlsx;
58
#endif
59

60
const QString SatellitesDialog::dash = QChar(0x2014);
61

62
SatellitesDialog::SatellitesDialog()
×
63
        : StelDialog("Satellites")
64
        , satelliteModified(false)
×
65
        , updateTimer(nullptr)
×
66
        , importWindow(nullptr)
×
67
        , filterWindow(nullptr)
×
68
        , commWindow(nullptr)
×
69
        , filterModel(nullptr)
×
70
        , checkStateRole(Qt::UserRole)
×
71
        , delimiter(", ")        
×
72
{
73
        ui = new Ui_satellitesDialog;
×
74
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
75
        iridiumFlaresHeader.clear();
76
#endif
77
}
×
78

79
SatellitesDialog::~SatellitesDialog()
×
80
{
81
        if (updateTimer)
×
82
        {
83
                updateTimer->stop();
×
84
                delete updateTimer;
×
85
                updateTimer = nullptr;
×
86
        }
87

88
        if (importWindow)
×
89
        {
90
                delete importWindow;
×
91
                importWindow = nullptr;
×
92
        }
93

94
        if (filterWindow)
×
95
        {
96
                delete filterWindow;
×
97
                filterWindow = nullptr;
×
98
        }
99

100
        if (commWindow)
×
101
        {
102
                delete commWindow;
×
103
                commWindow = nullptr;
×
104
        }
105

106
        delete ui;
×
107
}
×
108

109
void SatellitesDialog::retranslate()
×
110
{
111
        if (dialog)
×
112
        {
113
                ui->retranslateUi(dialog);                
×
114
                updateSettingsPage(); // For the button; also calls updateCountdown()
×
115
                populateAboutPage();
×
116
                populateInfo();
×
117
                populateFilterMenu();
×
118
                updateSatelliteData();
×
119
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
120
                initListIridiumFlares();
121
#endif
122
        }
123
}
×
124

125
// Initialize the dialog widgets and connect the signals/slots
126
void SatellitesDialog::createDialogContent()
×
127
{
128
        ui->setupUi(dialog);
×
129
#if(SATELLITES_PLUGIN_IRIDIUM == 0)
130
        ui->tabs->removeTab(ui->tabs->indexOf(ui->iridiumTab));
×
131
#endif
132
        ui->tabs->setCurrentIndex(0);        
×
133
        connect(ui->titleBar, &TitleBar::closeClicked, this, &StelDialog::close);
×
134
        connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
×
135
        connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
×
136
        Satellites* plugin = GETSTELMODULE(Satellites);
×
137

138
        // Kinetic scrolling
139
        kineticScrollingList << ui->satellitesList << ui->sourceList << ui->aboutTextBrowser;
×
140
        StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
×
141
        if (gui)
×
142
        {
143
                enableKineticScrolling(gui->getFlagUseKineticScrolling());
×
144
                connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
×
145
        }
146

147
        // Remove any test from "color buttons"
148
        ui->satMarkerColorPickerButton->setText("");
×
149
        ui->satOrbitColorPickerButton->setText("");
×
150
        ui->satInfoColorPickerButton->setText("");
×
151

152
        // Set size of buttons
153
        QSize bs = QSize(26, 26);
×
154
        const QList<QPushButton*> buttons = {
155
                ui->customFilterButton, ui->addSatellitesButton, ui->removeSatellitesButton, ui->selectAllButton,
×
156
                ui->satMarkerColorPickerButton, ui->satOrbitColorPickerButton, ui->satInfoColorPickerButton,
×
157
                ui->addSourceButton, ui->deleteSourceButton, ui->editSourceButton, ui->saveSourceButton,
×
158
                ui->resetSourcesButton, ui->commSatelliteButton};
×
159
        for (auto btn: qAsConst(buttons)) { btn->setFixedSize(bs); }
×
160

161
        // Settings tab / updates group
162
        // These controls are refreshed by updateSettingsPage(), which in
163
        // turn is triggered by setting any of these values. Because
164
        // clicked() is issued only by user input, there's no endless loop.
165
        connectBoolProperty(ui->internetUpdatesCheckbox, "Satellites.updatesEnabled");
×
166
        connectBoolProperty(ui->checkBoxAutoAdd,         "Satellites.autoAddEnabled");
×
167
        connectBoolProperty(ui->checkBoxAutoRemove,      "Satellites.autoRemoveEnabled");
×
168
        connectIntProperty(ui->updateFrequencySpinBox,   "Satellites.updateFrequencyHours");
×
169
        ui->jumpToSourcesButton->setEnabled(ui->checkBoxAutoAdd);
×
170
        connect(ui->updateButton,            SIGNAL(clicked()),         this,   SLOT(updateTLEs()));
×
171
        connect(ui->jumpToSourcesButton,     SIGNAL(clicked()),         this,   SLOT(jumpToSourcesTab()));
×
172
        connect(plugin, SIGNAL(updateStateChanged(Satellites::UpdateState)), this, SLOT(showUpdateState(Satellites::UpdateState)));
×
173
        connect(plugin, SIGNAL(tleUpdateComplete(int, int, int, int)),       this, SLOT(showUpdateCompleted(int, int, int, int)));
×
174

175
        updateTimer = new QTimer(this);
×
176
        connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateCountdown()));
×
177
        updateTimer->start(7000);
×
178

179
        // Settings tab / Visualisation settings group
180
        // Logic sub-group: Labels
181
        connectBoolProperty(ui->labelsCheckBox,     "Satellites.flagLabelsVisible");
×
182
        connectIntProperty(ui->fontSizeSpinBox,     "Satellites.labelFontSize");
×
183
        connect(ui->labelsCheckBox, SIGNAL(clicked(bool)), ui->fontSizeSpinBox, SLOT(setEnabled(bool)));
×
184
        ui->fontSizeSpinBox->setEnabled(ui->labelsCheckBox->isChecked());
×
185
        // Logic sub-group: Orbit lines
186
        connectBoolProperty(ui->orbitLinesCheckBox, "Satellites.flagOrbitLines");
×
187
        connectIntProperty(ui->orbitSegmentsSpin,   "Satellites.orbitLineSegments");
×
188
        connectIntProperty(ui->orbitFadeSpin,       "Satellites.orbitLineFadeSegments");
×
189
        connectIntProperty(ui->orbitDurationSpin,   "Satellites.orbitLineSegmentDuration");
×
190
        connectIntProperty(ui->orbitThicknessSpin,  "Satellites.orbitLineThickness");
×
191
        connect(ui->orbitLinesCheckBox, SIGNAL(clicked(bool)), this, SLOT(handleOrbitLinesGroup(bool)));
×
192
        handleOrbitLinesGroup(ui->orbitLinesCheckBox->isChecked());
×
193
        // Logic sub-group: Umbra
194
        connectBoolProperty(ui->umbraCheckBox,      "Satellites.flagUmbraVisible");
×
195
        // V23.1: We must disable this button for now.
196
        //connectBoolProperty(ui->umbraAtDistance,    "Satellites.flagUmbraAtFixedDistance");
197
        //connectDoubleProperty(ui->umbraDistance,       "Satellites.umbraDistance");
198
        ui->umbraAtDistance->hide();
×
199
        ui->umbraDistance->hide();
×
200
        connect(ui->umbraCheckBox, SIGNAL(clicked(bool)), this, SLOT(handleUmbraGroup(bool)));
×
201
        handleUmbraGroup(ui->umbraCheckBox->isChecked());
×
202
        // Logic sub-group: Markers
203
        connectBoolProperty(ui->iconicCheckBox,                "Satellites.flagIconicMode");
×
204
        connectBoolProperty(ui->coloredInvisibleSatellites, "Satellites.flagColoredInvisible");
×
205
        connectBoolProperty(ui->hideInvisibleSatellites, "Satellites.flagHideInvisible");
×
206
        connect(ui->iconicCheckBox, SIGNAL(clicked(bool)), ui->hideInvisibleSatellites, SLOT(setEnabled(bool)));
×
207
        ui->hideInvisibleSatellites->setEnabled(ui->iconicCheckBox->isChecked());
×
208
        // Logic sub-group: Colors
209
        connectColorButton(ui->invisibleColorButton, "Satellites.invisibleSatelliteColor", "Satellites/invisible_satellite_color");
×
210
        connectColorButton(ui->transitColorButton,   "Satellites.transitSatelliteColor",   "Satellites/transit_satellite_color");
×
211
        connectColorButton(ui->umbraColor,           "Satellites.umbraColor",              "Satellites/umbra_color");
×
212
        connectColorButton(ui->penumbraColor,        "Satellites.penumbraColor",           "Satellites/penumbra_color");
×
213
        // Logic sub-group: Penumbra
214
        connectBoolProperty(ui->penumbraCheckBox,    "Satellites.flagPenumbraVisible");
×
215
        // Logic sub-group: Visual filter / Altitude range
216
        connectBoolProperty(ui->altitudeCheckBox,     "Satellites.flagVFAltitude");
×
217
        connectDoubleProperty(ui->minAltitude,        "Satellites.minVFAltitude");
×
218
        connectDoubleProperty(ui->maxAltitude,        "Satellites.maxVFAltitude");
×
219
        enableMinMaxAltitude(ui->altitudeCheckBox->isChecked());
×
220
        connect(ui->altitudeCheckBox, SIGNAL(clicked(bool)), this, SLOT(enableMinMaxAltitude(bool)));
×
221
        // Logic sub-group: Visual filter / Magnitude range
222
        connectBoolProperty(ui->magnitudeCheckBox,    "Satellites.flagVFMagnitude");
×
223
        connectDoubleProperty(ui->minMagnitude,       "Satellites.minVFMagnitude");
×
224
        connectDoubleProperty(ui->maxMagnitude,       "Satellites.maxVFMagnitude");
×
225
        enableMinMaxMagnitude(ui->magnitudeCheckBox->isChecked());
×
226
        connect(ui->magnitudeCheckBox, SIGNAL(clicked(bool)), this, SLOT(enableMinMaxMagnitude(bool)));
×
227

228
        connect(ui->restoreDefaultsButton, SIGNAL(clicked()), this, SLOT(restoreDefaults()));
×
229
        connect(ui->saveSettingsButton,    SIGNAL(clicked()), this, SLOT(saveSettings()));
×
230
        updateSettingsPage();
×
231

232
        // Satellites tab
233
        filterModel = new SatellitesListFilterModel(this);
×
234
        filterModel->setSourceModel(plugin->getSatellitesListModel());
×
235
        filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
×
236
        ui->satellitesList->setModel(filterModel);
×
237
        connect(ui->lineEditSearch, SIGNAL(textChanged(QString)), filterModel, SLOT(setFilterWildcard(QString)));
×
238

239
        QAction *clearAction = ui->lineEditSearch->addAction(QIcon(":/graphicGui/backspace-white.png"),
×
240
                                                             QLineEdit::ActionPosition::TrailingPosition);
241
        connect(clearAction, SIGNAL(triggered()), this, SLOT(searchSatellitesClear()));
×
242

243
        QItemSelectionModel* selectionModel = ui->satellitesList->selectionModel();
×
244
        connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
×
245
                     this, SLOT(updateSatelliteData()));        
246
        connect(ui->satellitesList, SIGNAL(doubleClicked(QModelIndex)),
×
247
                     this, SLOT(trackSatellite(QModelIndex)));
248

249
        // Two-state input, three-state display
250
        setRightSideToROMode();
×
251
        connect(ui->displayedCheckbox, SIGNAL(clicked(bool)), ui->displayedCheckbox, SLOT(setChecked(bool)));
×
252
        connect(ui->orbitCheckbox,     SIGNAL(clicked(bool)), ui->orbitCheckbox,     SLOT(setChecked(bool)));
×
253
        connect(ui->userCheckBox,      SIGNAL(clicked(bool)), ui->userCheckBox,      SLOT(setChecked(bool)));
×
254

255
        // Because the previous signals and slots were connected first,
256
        // they will be executed before these.
257
        connect(ui->displayedCheckbox, SIGNAL(clicked()), this, SLOT(setFlags()));
×
258
        connect(ui->orbitCheckbox,     SIGNAL(clicked()), this, SLOT(setFlags()));
×
259
        connect(ui->userCheckBox,      SIGNAL(clicked()), this, SLOT(setFlags()));
×
260

261
        connect(ui->satMarkerColorPickerButton, SIGNAL(clicked(bool)), this, SLOT(askSatMarkerColor()));
×
262
        connect(ui->satOrbitColorPickerButton,  SIGNAL(clicked(bool)), this, SLOT(askSatOrbitColor()));
×
263
        connect(ui->satInfoColorPickerButton,   SIGNAL(clicked(bool)), this, SLOT(askSatInfoColor()));
×
264
        connect(ui->descriptionTextEdit,        SIGNAL(textChanged()), this, SLOT(descriptionTextChanged()));
×
265
        // Satellites tab / TLE group
266
        connectIntProperty(ui->validAgeSpinBox, "Satellites.tleEpochAgeDays");
×
267
        connect(ui->validAgeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateFilteredSatellitesList()));
×
268

269
        connect(ui->groupsListWidget, SIGNAL(itemChanged(QListWidgetItem*)),
×
270
                     this, SLOT(handleGroupChanges(QListWidgetItem*)));
271

272
        connect(ui->groupFilterCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(filterListByGroup(int)));
×
273
        connect(ui->groupFilterCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setRightSideToROMode()));
×
274

275
        importWindow = new SatellitesImportDialog();
×
276
        connect(ui->addSatellitesButton, SIGNAL(clicked()),            importWindow, SLOT(setVisible()));
×
277
        connect(importWindow, SIGNAL(satellitesAccepted(TleDataList)), this,         SLOT(addSatellites(TleDataList)));
×
278
        connect(ui->removeSatellitesButton, SIGNAL(clicked()),         this,         SLOT(removeSatellites()));
×
279
        connect(ui->selectAllButton, SIGNAL(clicked()),                this,         SLOT(selectFilteredSatellitesList()));
×
280

281
        filterWindow = new SatellitesFilterDialog();
×
282
        connect(ui->customFilterButton, SIGNAL(clicked()), filterWindow, SLOT(setVisible()));
×
283

284
        commWindow = new SatellitesCommDialog();
×
285
        connect(ui->commSatelliteButton, SIGNAL(clicked()), commWindow, SLOT(setVisible()));
×
286

287
        filterWindow = new SatellitesFilterDialog();
×
288
        connect(ui->customFilterButton, SIGNAL(clicked()), filterWindow, SLOT(setVisible()));
×
289

290
        // Sources tab
291
        connect(ui->sourceList, SIGNAL(currentRowChanged(int)),                        this, SLOT(updateButtonsProperties()));
×
292
        connect(ui->sourceList, SIGNAL(itemChanged(QListWidgetItem*)),                this,        SLOT(saveSourceList()));
×
293
        connect(ui->sourceList, SIGNAL(itemDoubleClicked(QListWidgetItem *)),        this,        SLOT(editSourceRow()));
×
294
        //FIXME: pressing Enter cause a call of addSourceRow() method...
295
        //connect(ui->sourceEdit, SIGNAL(returnPressed()),        this,        SLOT(saveEditedSource()));
296
        connect(ui->deleteSourceButton, SIGNAL(clicked()),        this, SLOT(deleteSourceRow()));
×
297
        connect(ui->addSourceButton, SIGNAL(clicked()),                this, SLOT(addSourceRow()));
×
298
        connect(ui->editSourceButton, SIGNAL(clicked()),        this, SLOT(editSourceRow()));
×
299
        connect(ui->saveSourceButton, SIGNAL(clicked()),        this, SLOT(saveEditedSource()));
×
300
        connect(ui->resetSourcesButton, SIGNAL(clicked()),        this, SLOT(restoreTleSources()));
×
301
        connect(plugin, SIGNAL(satGroupVisibleChanged()),       this, SLOT(updateSatelliteAndSaveData()));
×
302
        connect(plugin, SIGNAL(settingsChanged()),              this, SLOT(toggleCheckableSources()));
×
303
        connect(plugin, SIGNAL(customFilterChanged()),          this, SLOT(updateFilteredSatellitesList()));        
×
304
        // bug #1350669 (https://bugs.launchpad.net/stellarium/+bug/1350669)
305
        connect(ui->sourceList, SIGNAL(currentRowChanged(int)), ui->sourceList, SLOT(repaint()));
×
306
        ui->editSourceButton->setEnabled(false);
×
307
        ui->deleteSourceButton->setEnabled(false);
×
308
        ui->saveSourceButton->setEnabled(false);
×
309
        ui->sourceEdit->setEnabled(false);
×
310

311
        // About tab
312
        populateAboutPage();
×
313
        populateInfo();
×
314
        populateFilterMenu();
×
315
        populateSourcesList();
×
316

317
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
318
        initListIridiumFlares();
319
        ui->flaresPredictionDepthSpinBox->setValue(plugin->getIridiumFlaresPredictionDepth());
320
        connect(ui->flaresPredictionDepthSpinBox, SIGNAL(valueChanged(int)), plugin, SLOT(setIridiumFlaresPredictionDepth(int)));
321
        connect(ui->predictIridiumFlaresPushButton, SIGNAL(clicked()), this, SLOT(predictIridiumFlares()));
322
        connect(ui->predictedIridiumFlaresSaveButton, SIGNAL(clicked()), this, SLOT(savePredictedIridiumFlares()));
323
        connect(ui->iridiumFlaresTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentIridiumFlare(QModelIndex)));
324
#endif
325
}
×
326

327
void SatellitesDialog::enableMinMaxAltitude(bool state)
×
328
{
329
        ui->minAltitude->setEnabled(state);
×
330
        ui->maxAltitude->setEnabled(state);
×
331
}
×
332

333
void SatellitesDialog::enableMinMaxMagnitude(bool state)
×
334
{
335
        ui->minMagnitude->setEnabled(state);
×
336
        ui->maxMagnitude->setEnabled(state);
×
337
}
×
338

339
void SatellitesDialog::handleOrbitLinesGroup(bool state)
×
340
{
341
        ui->orbitSegmentsSpin->setEnabled(state);
×
342
        ui->orbitFadeSpin->setEnabled(state);
×
343
        ui->orbitDurationSpin->setEnabled(state);
×
344
        ui->orbitThicknessSpin->setEnabled(state);
×
345
}
×
346

347
void SatellitesDialog::handleUmbraGroup(bool state)
×
348
{
349
        ui->umbraAtDistance->setEnabled(state);
×
350
        ui->umbraDistance->setEnabled(state);
×
351
        ui->penumbraCheckBox->setEnabled(state);
×
352
}
×
353

354
void SatellitesDialog::askSatMarkerColor()
×
355
{
356
        QModelIndexList selection = ui->satellitesList->selectionModel()->selectedIndexes();
×
357

358
        if (selection.isEmpty()) return;
×
359

360
        Satellites* SatellitesMgr = GETSTELMODULE(Satellites);
×
361
        Q_ASSERT(SatellitesMgr);
×
362

363
        QColor c = QColorDialog::getColor(buttonMarkerColor, &StelMainView::getInstance(), "");
×
364
        if (c.isValid())
×
365
        {
366
                Vec3f vColor(c);
×
367
                SatelliteP sat;
×
368
                // colourize all selected satellites
369
                for (int i = 0; i < selection.size(); i++)
×
370
                {
371
                        const QModelIndex& index = selection.at(i);
×
372
                        sat = SatellitesMgr->getById(index.data(Qt::UserRole).toString());
×
373
                        sat->hintColor = vColor;
×
374
                }
375
                // colourize the button
376
                buttonMarkerColor = c;
×
377
                ui->satMarkerColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonMarkerColor.name() + "; }");
×
378
                saveSatellites();
×
379
        }
×
380
}
×
381

382
void SatellitesDialog::askSatOrbitColor()
×
383
{
384
        QModelIndexList selection = ui->satellitesList->selectionModel()->selectedIndexes();
×
385

386
        if (selection.isEmpty()) return;
×
387

388
        Satellites* SatellitesMgr = GETSTELMODULE(Satellites);
×
389
        Q_ASSERT(SatellitesMgr);
×
390

391
        QColor c = QColorDialog::getColor(buttonOrbitColor, &StelMainView::getInstance(), "");
×
392
        if (c.isValid())
×
393
        {
394
                Vec3f vColor(c);
×
395
                SatelliteP sat;
×
396
                // colourize all selected satellites
397
                for (int i = 0; i < selection.size(); i++)
×
398
                {
399
                        const QModelIndex& index = selection.at(i);
×
400
                        sat = SatellitesMgr->getById(index.data(Qt::UserRole).toString());
×
401
                        sat->orbitColor = vColor;
×
402
                }
403
                // colourize the button
404
                buttonOrbitColor = c;
×
405
                ui->satOrbitColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonOrbitColor.name() + "; }");
×
406
                saveSatellites();
×
407
        }
×
408
}
×
409

410
void SatellitesDialog::askSatInfoColor()
×
411
{
412
        QModelIndexList selection = ui->satellitesList->selectionModel()->selectedIndexes();
×
413

414
        if (selection.isEmpty()) return;
×
415

416
        Satellites* SatellitesMgr = GETSTELMODULE(Satellites);
×
417
        Q_ASSERT(SatellitesMgr);
×
418

419
        QColor c = QColorDialog::getColor(buttonInfoColor, &StelMainView::getInstance(), "");
×
420
        if (c.isValid())
×
421
        {
422
                Vec3f vColor(c);
×
423
                SatelliteP sat;
×
424
                // colourize all selected satellites
425
                for (int i = 0; i < selection.size(); i++)
×
426
                {
427
                        const QModelIndex& index = selection.at(i);
×
428
                        sat = SatellitesMgr->getById(index.data(Qt::UserRole).toString());
×
429
                        sat->infoColor = vColor;
×
430
                }
431
                // colourize the button
432
                buttonInfoColor = c;
×
433
                ui->satInfoColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonInfoColor.name() + "; }");
×
434
                saveSatellites();
×
435
        }
×
436
}
×
437

438
// save new description text to selected satellite(s)
439
void SatellitesDialog::descriptionTextChanged()
×
440
{
441
        QModelIndexList selection = ui->satellitesList->selectionModel()->selectedIndexes();
×
442

443
        if (selection.isEmpty()) return;
×
444

445
        QString newdesc = ui->descriptionTextEdit->toPlainText();
×
446
        SatelliteP sat;
×
447

448
        Satellites* SatellitesMgr = GETSTELMODULE(Satellites);
×
449
        Q_ASSERT(SatellitesMgr);
×
450

451
        for (int i = 0; i < selection.size(); i++)
×
452
        {
453
                const QModelIndex& index = selection.at(i);
×
454
                sat = SatellitesMgr->getById(index.data(Qt::UserRole).toString());
×
455
                sat->description = newdesc;
×
456
        }
457
        saveSatellites();
×
458
}
×
459

460
void SatellitesDialog::searchSatellitesClear()
×
461
{
462
        ui->lineEditSearch->clear();
×
463
}
×
464

465
void SatellitesDialog::filterListByGroup(int index)
×
466
{
467
        if (index < 0)
×
468
                return;
×
469

470
        const QMap<QString, SatFlag> secondaryFilter = {
471
                { "all",                SatNoFlags },
×
472
                { "[displayed]",        SatDisplayed },
×
473
                { "[userdefined]",        SatUser },
×
474
                { "[undisplayed]",        SatNotDisplayed },
×
475
                { "[newlyadded]",        SatNew },
×
476
                { "[orbiterror]",        SatError },
×
477
                { "[reentry]",                SatReentry },
×
478
                { "[smallsize]",        SatSmallSize },
×
479
                { "[mediumsize]",        SatMediumSize },
×
480
                { "[largesize]",        SatLargeSize },
×
481
                { "[LEO]",                SatLEO },
×
482
                { "[GSO]",                SatGSO },
×
483
                { "[MEO]",                SatMEO },
×
484
                { "[HEO]",                SatHEO },
×
485
                { "[HGSO]",                SatHGSO },
×
486
                { "[polarorbit]",        SatPolarOrbit },
×
487
                { "[equatorialorbit]",        SatEquatOrbit },
×
488
                { "[PSSO]",                SatPSSO },
×
489
                { "[HEarthO]",                SatHEarthO },
×
490
                { "[outdatedTLE]",        SatOutdatedTLE },
×
491
                { "[custom]",                SatCustomFilter },
×
492
                { "[communication]",        SatCommunication }
×
493
        };
×
494

495
        ui->customFilterButton->setEnabled(false);
×
496
        QString groupId = ui->groupFilterCombo->itemData(index).toString();
×
497
        if (groupId == "[custom]")
×
498
                ui->customFilterButton->setEnabled(true);
×
499

500
        if (groupId.contains("[") || groupId=="all")
×
501
                filterModel->setSecondaryFilters(QString(), secondaryFilter.value(groupId, SatNoFlags));
×
502
        else
503
                filterModel->setSecondaryFilters(groupId, SatNoFlags);
×
504

505
        if (ui->satellitesList->model()->rowCount() <= 0)
×
506
                return;
×
507

508
        QItemSelectionModel* selectionModel = ui->satellitesList->selectionModel();
×
509
        QModelIndex first;
×
510
        if (selectionModel->hasSelection())
×
511
                first = selectionModel->selectedRows().first();
×
512
        else // Scroll to the top
513
                first = ui->satellitesList->model()->index(0, 0);
×
514
        selectionModel->setCurrentIndex(first, QItemSelectionModel::NoUpdate);
×
515
        ui->satellitesList->scrollTo(first);
×
516
}
×
517

518
void SatellitesDialog::updateFilteredSatellitesList()
×
519
{
520
        QString groupId = ui->groupFilterCombo->currentData(Qt::UserRole).toString();
×
521
        if (groupId == "[outdatedTLE]" || groupId == "[custom]" || groupId == "[communication]" || groupId == "[reentry]")
×
522
        {
523
                filterListByGroup(ui->groupFilterCombo->currentIndex());
×
524
        }
525
}
×
526

527
void SatellitesDialog::selectFilteredSatellitesList()
×
528
{
529
        ui->satellitesList->selectionModel()->clearSelection();
×
530
        ui->satellitesList->selectAll();
×
531
}
×
532

533
void SatellitesDialog::updateSatelliteAndSaveData()
×
534
{
535
        updateSatelliteData(); // update properties of selected satellite in the GUI
×
536
        saveSatellites(); // enforcement saving properties of satellites
×
537
}
×
538

539
void SatellitesDialog::updateSatelliteData()
×
540
{
541
        setRightSideToRWMode();
×
542

543
        // NOTE: This was probably going to be used for editing satellites?
544
        satelliteModified = false;
×
545

546
        QModelIndexList selection = ui->satellitesList->selectionModel()->selectedIndexes();
×
547
        if (selection.isEmpty())
×
548
                return; // TODO: Clear the fields?
×
549

550
        enableSatelliteDataForm(false);
×
551

552
        // needed for colorbutton
553
        Satellites* SatellitesMgr = GETSTELMODULE(Satellites);
×
554
        Q_ASSERT(SatellitesMgr);
×
555
        Vec3f mColor, oColor, iColor;
×
556

557
        // set default
558
        buttonMarkerColor = QColor(QColor::fromRgbF(0.7f, 0.7f, 0.7f));
×
559
        buttonOrbitColor = QColor(QColor::fromRgbF(0.7f, 0.7f, 0.7f));
×
560
        buttonInfoColor = QColor(QColor::fromRgbF(0.7f, 0.7f, 0.7f));
×
561

562
        if (selection.count() > 1)
×
563
        {
564
                ui->nameEdit->setText(QString());
×
565
                ui->noradNumberEdit->setText(QString());
×
566
                ui->cosparNumberEdit->setText(QString());
×
567
                ui->tleFirstLineEdit->setText(QString());
×
568
                ui->tleSecondLineEdit->setText(QString());
×
569
                ui->stdMagnitudeLineEdit->setText(QString());
×
570
                ui->rcsLineEdit->setText(QString());
×
571
                ui->perigeeLineEdit->setText(QString());
×
572
                ui->apogeeLineEdit->setText(QString());
×
573
                ui->periodLineEdit->setText(QString());
×
574
                ui->labelTleEpochData->setText(QString());
×
575

576
                // get color of first selected item and test against all other selections
577
                {
578
                        const QModelIndex& index = selection.at(0);
×
579
                        QString id = index.data(Qt::UserRole).toString();
×
580
                        SatelliteP sat = SatellitesMgr->getById(id);
×
581

582
                        mColor = sat->hintColor;
×
583
                        oColor = sat->orbitColor;
×
584
                        iColor = sat->infoColor;
×
585

586
                        for (int i = 1; i < selection.size(); i++)
×
587
                        {
588
                                const QModelIndex& idx = selection.at(i);
×
589

590
                                id = idx.data(Qt::UserRole).toString();
×
591
                                sat = SatellitesMgr->getById(id);
×
592

593
                                // test for more than one color in the selection.
594
                                // if there are, return grey
595
                                if (sat->hintColor != mColor || sat->orbitColor != oColor || sat->infoColor != iColor)
×
596
                                {
597
                                        mColor = Vec3f(0.7f, 0.7f, 0.7f);
×
598
                                        oColor = Vec3f(0.7f, 0.7f, 0.7f);
×
599
                                        iColor = Vec3f(0.7f, 0.7f, 0.7f);
×
600
                                        break;
×
601
                                }
602
                        }
603
                }
×
604

605
                // get description text of first selection and test against all other selections
606
                {
607
                        const QModelIndex& index = selection.at(0);
×
608
                        QString descText = index.data(SatDescriptionRole).toString();
×
609

610
                        if (!descText.isEmpty())
×
611
                        {
612
                                for (int i = 1; i < selection.size(); i++)
×
613
                                {
614
                                        const QModelIndex& idx = selection.at(i);
×
615

616
                                        if (descText != idx.data(SatDescriptionRole).toString())
×
617
                                        {
618
                                                descText.clear();
×
619
                                                break;
×
620
                                        }
621
                                }
622
                        }
623

624
                        ui->descriptionTextEdit->setText(descText);
×
625
                }
×
626

627
                emit SatellitesMgr->satSelectionChanged("");
×
628
        }
629
        else
630
        {
631
                QModelIndex& index = selection.first();
×
632
                QString id = index.data(Qt::UserRole).toString();
×
633

634
                float stdMagnitude = index.data(SatStdMagnitudeRole).toFloat();
×
635
                QString stdMagString = (stdMagnitude<99.f) ? QString::number(stdMagnitude, 'f', 2) : dash;
×
636
                float rcs = index.data(SatRCSRole).toFloat();
×
637
                QString rcsString = (rcs > 0.f) ? QString::number(rcs, 'f', 3) : dash;
×
638
                int perigee = qRound(index.data(SatPerigeeRole).toFloat());
×
639
                QString perigeeString = (perigee>0) ? QString::number(perigee) : dash;
×
640
                int apogee = qRound(index.data(SatApogeeRole).toFloat());
×
641
                QString apogeeString = (apogee>0) ? QString::number(apogee) : dash;
×
642
                float period = index.data(SatPeriodRole).toFloat();
×
643
                QString periodString = (period>0.f) ? QString::number(period, 'f', 2) : dash;
×
644
                QString cosparID = index.data(SatCosparIDRole).toString();
×
645

646
                ui->nameEdit->setText(index.data(Qt::DisplayRole).toString());
×
647
                ui->noradNumberEdit->setText(id);
×
648
                ui->cosparNumberEdit->setText(cosparID.isEmpty() ? dash : cosparID);
×
649
                // NOTE: Description is deliberately displayed untranslated!
650
                ui->descriptionTextEdit->setText(index.data(SatDescriptionRole).toString());
×
651
                ui->stdMagnitudeLineEdit->setText(stdMagString);
×
652
                ui->rcsLineEdit->setText(rcsString);
×
653
                ui->perigeeLineEdit->setText(perigeeString);
×
654
                ui->apogeeLineEdit->setText(apogeeString);
×
655
                ui->periodLineEdit->setText(periodString);
×
656
                ui->tleFirstLineEdit->setText(index.data(FirstLineRole).toString());
×
657
                ui->tleFirstLineEdit->setCursorPosition(0);
×
658
                ui->tleSecondLineEdit->setText(index.data(SecondLineRole).toString());
×
659
                ui->tleSecondLineEdit->setCursorPosition(0);
×
660
                ui->labelTleEpochData->setText(index.data(SatTLEEpochRole).toString());
×
661

662
                // get color of the one selected sat
663
                SatelliteP sat = SatellitesMgr->getById(id);
×
664
                mColor = sat->hintColor;
×
665
                oColor = sat->orbitColor;
×
666
                iColor = sat->infoColor;
×
667

668
                emit SatellitesMgr->satSelectionChanged(id);
×
669
        }
×
670

671
        // colourize the colorpicker button
672
        buttonMarkerColor=mColor.toQColor(); // .setRgbF(mColor.v[0], mColor.v[1], mColor.v[2]);
×
673
        ui->satMarkerColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonMarkerColor.name() + "; }");
×
674
        buttonOrbitColor=oColor.toQColor(); // .setRgbF(oColor.v[0], oColor.v[1], oColor.v[2]);
×
675
        ui->satOrbitColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonOrbitColor.name() + "; }");
×
676
        buttonInfoColor=iColor.toQColor(); // .setRgbF(iColor.v[0], iColor.v[1], iColor.v[2]);
×
677
        ui->satInfoColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonInfoColor.name() + "; }");
×
678

679
        // bug #1350669 (https://bugs.launchpad.net/stellarium/+bug/1350669)
680
        ui->satellitesList->repaint();
×
681

682
        // Things that are cumulative in a multi-selection
683
        GroupSet globalGroups = GETSTELMODULE(Satellites)->getGroups();
×
684
        GroupSet groupsUsedBySome;
×
685
        GroupSet groupsUsedByAll = globalGroups;
×
686
        ui->displayedCheckbox->setChecked(false);
×
687
        ui->orbitCheckbox->setChecked(false);
×
688
        ui->userCheckBox->setChecked(false);
×
689

690
        for (int i = 0; i < selection.size(); i++)
×
691
        {
692
                const QModelIndex& index = selection.at(i);
×
693

694
                // "Displayed" checkbox
695
                SatFlags flags = index.data(SatFlagsRole).value<SatFlags>();
×
696
                if (flags.testFlag(SatDisplayed))
×
697
                {
698
                        if (!ui->displayedCheckbox->isChecked())
×
699
                        {
700
                                if (i == 0)
×
701
                                        ui->displayedCheckbox->setChecked(true);
×
702
                                else
703
                                        ui->displayedCheckbox->setCheckState(Qt::PartiallyChecked);
×
704
                        }
705
                }
706
                else
707
                        if (ui->displayedCheckbox->isChecked())
×
708
                                ui->displayedCheckbox->setCheckState(Qt::PartiallyChecked);
×
709

710
                // "Orbit" box
711
                if (flags.testFlag(SatOrbit))
×
712
                {
713
                        if (!ui->orbitCheckbox->isChecked())
×
714
                        {
715
                                if (i == 0)
×
716
                                        ui->orbitCheckbox->setChecked(true);
×
717
                                else
718
                                        ui->orbitCheckbox->setCheckState(Qt::PartiallyChecked);
×
719
                        }
720
                }
721
                else
722
                        if (ui->orbitCheckbox->isChecked())
×
723
                                ui->orbitCheckbox->setCheckState(Qt::PartiallyChecked);
×
724

725
                // User ("do not update") box
726
                if (flags.testFlag(SatUser))
×
727
                {
728
                        if (!ui->userCheckBox->isChecked())
×
729
                        {
730
                                if (i == 0)
×
731
                                        ui->userCheckBox->setChecked(true);
×
732
                                else
733
                                        ui->userCheckBox->setCheckState(Qt::PartiallyChecked);
×
734
                        }
735
                }
736
                else
737
                        if (ui->userCheckBox->isChecked())
×
738
                                ui->userCheckBox->setCheckState(Qt::PartiallyChecked);
×
739

740

741
                // Accumulating groups
742
                GroupSet groups = index.data(SatGroupsRole).value<GroupSet>();
×
743
                groupsUsedBySome.unite(groups);
×
744
                groupsUsedByAll.intersect(groups);
×
745
        }
×
746

747
        // Repopulate the group selector
748
        // Nice list of checkable, translated groups that allows adding new groups
749
        ui->groupsListWidget->blockSignals(true);
×
750
        ui->groupsListWidget->clear();
×
751
        for (const auto& group : qAsConst(globalGroups))
×
752
        {
753
                QListWidgetItem* item = new QListWidgetItem(q_(group),
×
754
                                                            ui->groupsListWidget);
×
755
                item->setToolTip(q_(group));
×
756
                item->setData(Qt::UserRole, group);
×
757
                Qt::CheckState state = Qt::Unchecked;
×
758
                if (groupsUsedByAll.contains(group))
×
759
                        state = Qt::Checked;
×
760
                else if (groupsUsedBySome.contains(group))
×
761
                        state = Qt::PartiallyChecked;
×
762
                item->setData(Qt::CheckStateRole, state);
×
763
        }
764
        ui->groupsListWidget->sortItems();
×
765
        addSpecialGroupItem(); // Add the "Add new..." line
×
766
        ui->groupsListWidget->blockSignals(false);
×
767

768
        enableSatelliteDataForm(true);
×
769
}
×
770

771
void SatellitesDialog::saveSatellites(void)
×
772
{
773
        GETSTELMODULE(Satellites)->saveCatalog();
×
774
}
×
775

776
void SatellitesDialog::populateAboutPage()
×
777
{
778
        QString jsonFileName("<tt>satellites.json</tt>");
×
779
        QString oldJsonFileName("<tt>satellites.json.old</tt>");
×
780
        QString html = "<html><head></head><body>";
×
781
        html += "<h2>" + q_("Stellarium Satellites Plugin") + "</h2><table width=\"90%\">";
×
782
        html += "<tr width=\"30%\"><td><strong>" + q_("Version") + "</strong></td><td>" + SATELLITES_PLUGIN_VERSION + "</td></td>";
×
783
        html += "<tr><td><strong>" + q_("License") + ":</strong></td><td>" + SATELLITES_PLUGIN_LICENSE + "</td></tr>";
×
784
        html += "<tr><td rowspan=\"2\"><strong>" + q_("Authors") + "</strong></td><td>Matthew Gates &lt;matthewg42@gmail.com&gt;</td></td>";
×
785
        html += "<tr><td>Jose Luis Canales &lt;jlcanales.gasco@gmail.com&gt;</td></tr>";
×
786
        html += "<tr><td rowspan=\"5\"><strong>" + q_("Contributors") + "</strong></td><td>Bogdan Marinov &lt;bogdan.marinov84@gmail.com&gt;</td></tr>";
×
787
        html += "<tr><td>Nick Fedoseev &lt;nick.ut2uz@gmail.com&gt;</td></tr>";
×
788
        html += "<tr><td>Alexander Wolf</td></tr>";
×
789
        html += "<tr><td>Alexander Duytschaever</td></tr>";
×
790
        html += "<tr><td>Georg Zotti</td></tr></table>";
×
791

792
        html += "<p>" + q_("The Satellites plugin predicts the positions of artificial satellites in Earth orbit.") + "</p>";
×
793

794
        html += "<h3>" + q_("Notes for users") + "</h3><p><ul>";
×
795
        html += "<li>" + q_("Satellites and their orbits are only shown when the observer is on Earth.") + "</li>";
×
796
        html += "<li>" + q_("Predicted positions are only good for a fairly short time (on the order of days, weeks or perhaps a month into the past and future). Expect high weirdness when looking at dates outside this range.") + "</li>";
×
797
        html += "<li>" + q_("Orbital elements go out of date pretty quickly (over mere weeks, sometimes days).  To get useful data out, you need to update the TLE data regularly.") + "</li>";
×
798
        // TRANSLATORS: The translated names of the button and the tab are filled in automatically. You can check the original names in Stellarium. File names are not translated.
799
        QString resetSettingsText = QString(q_("Clicking the \"%1\" button in the \"%2\" tab of this dialog will revert to the default %3 file.  The old file will be backed up as %4.  This can be found in the user data directory, under \"modules/Satellites/\"."))
×
800
                        .arg(ui->restoreDefaultsButton->text(), ui->tabs->tabText(ui->tabs->indexOf(ui->settingsTab)), jsonFileName, oldJsonFileName);
×
801
        html += "<li>" + resetSettingsText + "</li>";
×
802
        html += "<li>" + q_("The value of perigee and apogee altitudes compute for mean Earth radius.") + "</li>";
×
803
        html += "<li>" + q_("The Satellites plugin is still under development.  Some features are incomplete, missing or buggy.") + "</li>";
×
804
        html += "</ul></p>";
×
805

806
        // Definitions are obtained from Roscosmos documents
807
        html += "<h3>" + q_("Altitude classifications for geocentric orbits") + "</h3><p><ul>";
×
808
        html += "<li>" + q_("Low Earth orbit (LEO): geocentric orbits with altitudes of apogee below 4400 km, inclination of orbits in range 0-180 degrees and eccentricity below 0.25.") + "</li>";
×
809
        html += "<li>" + q_("Medium Earth orbit (MEO): geocentric orbits with altitude of apogee at least 4400 km, inclination of orbits in range 0-180 degrees, eccentricity below 0.25 and period at least 1100 minutes.") + "</li>";
×
810
        html += "<li>" + q_("Geosynchronous orbit (GSO) and geostationary orbit (GEO) are orbits with inclination of orbits below 25 degrees, eccentricity below 0.25 and period in range 1100-2000 minutes (orbits around Earth matching Earth's sidereal rotation period). ") + "</li>";
×
811
        html += "<li>" + q_("Highly elliptical orbit (HEO): geocentric orbits with altitudes of perigee below 70000 km, inclination of orbits in range 0-180 degrees, eccentricity at least 0.25 and period below 14000 minutes.") + "</li>";
×
812
        html += "<li>" + q_("High geosynchronous orbit (HGSO): geocentric orbits above the altitude of geosynchronous orbit: inclination of orbits in range 25-180 degrees, eccentricity below 0.25 and period in range 1100-2000 minutes.") + "</li>";
×
813
        // Definition from WP: https://en.wikipedia.org/wiki/High_Earth_orbit
814
        html += "<li>" + q_("High Earth orbit (HEO or HEO/E): a geocentric orbit with an altitude entirely above that of a geosynchronous orbit (35786 kilometres). The orbital periods of such orbits are greater than 24 hours, therefore satellites in such orbits have an apparent retrograde motion.") + "</li>";
×
815
        html += "</ul></p>";
×
816

817
        html += "<h3>" + q_("Inclination classifications for geocentric orbits") + "</h3><p><ul>";
×
818
        html += "<li>" + q_("Equatorial orbit: an orbit whose inclination in reference to the equatorial plane is (or very close to) 0 degrees.") + "</li>";
×
819
        html += "<li>" + q_("Polar orbit: a satellite that passes above or nearly above both poles of the planet on each revolution. Therefore it has an inclination of (or very close to) 90 degrees.") + "</li>";
×
820
        html += "<li>" + q_("Polar sun-synchronous orbit (PSSO): A nearly polar orbit that passes the equator at the same local time on every pass. Useful for image-taking satellites because shadows will be the same on every pass. Typical Sun-synchronous orbits around Earth are about 600–800 km in altitude, with periods in the 96–100-minute range, and inclinations of around 98 degrees.") + "</li>";
×
821
        html += "</ul></p>";
×
822

823
        // TRANSLATORS: Title of a section in the About tab of the Satellites window
824
        html += "<h3>" + q_("TLE data updates") + "</h3>";
×
825
        html += "<p>" + q_("The Satellites plugin can automatically download TLE data from Internet sources, and by default the plugin will do this if the existing data is more than 72 hours old. ");
×
826
        html += "</p><p>" + QString(q_("If you disable Internet updates, you may update from a file on your computer.  This file must be in the same format as the Celestrak updates (see %1 for an example).").arg("<a href=\"https://celestrak.org/NORAD/elements/visual.txt\">visual.txt</a>"));
×
827
        html += "</p><p>" + q_("<b>Note:</b> if the name of a satellite in update data has anything in square brackets at the end, it will be removed before the data is used.");
×
828
        html += "</p>";
×
829

830
        html += "<h3>" + q_("Adding new satellites") + "</h3>";
×
831
        html += "<ol><li>" + q_("Make sure the satellite(s) you wish to add are included in one of the URLs listed in the Sources tab of the satellites configuration dialog.") + "</li>";
×
832
        html += "<li>" + q_("Go to the Satellites tab, and click the '+' button.  Select the satellite(s) you wish to add and select the 'add' button.") + "</li></ol>";
×
833

834
        html += "<h3>" + q_("Technical notes") + "</h3>";
×
835
        html += "<p>" + q_("Positions are calculated using the SGP4 & SDP4 methods, using NORAD TLE data as the input.") + " ";
×
836
        html +=               q_("The orbital calculation code is written by Jose Luis Canales according to the revised Spacetrack Report #3 (including Spacetrack Report #6)") + " <a href=\"https://celestrak.org/publications/AIAA/2006-6753\">[*]</a>. ";
×
837
        html +=               q_("To calculate an approximate visual magnitude of satellites we use data from Mike McCants' database (with permissions) of the radar cross-section (RCS) and standard magnitudes.") + " ";
×
838
        html +=               q_("Formula to calculate an approximate visual magnitude of satellites from the standard magnitude may be found at Mike McCants website") + " <a href=\"https://mmccants.org/tles/mccdesc.html\">[**]</a>. ";
×
839
        html +=               q_("We use a spherical shape of satellite to calculate an approximate visual magnitude from RCS values.") + " ";
×
840
        html +=               q_("For modelling Starlink magnitudes we use Anthony Mallama's formula") + " <a href=\"http://www.satobs.org/seesat/Aug-2020/0079.html\">[***]</a>.</p>";
×
841

842
        html += StelApp::getInstance().getModuleMgr().getStandardSupportLinksInfo("Satellites plugin");
×
843
        html += "</body></html>";
×
844

845
        StelGui* gui = dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
×
846
        if (gui)
×
847
                ui->aboutTextBrowser->document()->setDefaultStyleSheet(QString(gui->getStelStyle().htmlStyleSheet));
×
848

849
        ui->aboutTextBrowser->setHtml(html);
×
850
}
×
851

852
void SatellitesDialog::jumpToSourcesTab()
×
853
{
854
        ui->tabs->setCurrentWidget(ui->sourcesTab);
×
855
}
×
856

857
void SatellitesDialog::updateCountdown()
×
858
{
859
        QString nextUpdate = q_("Next update");
×
860
        Satellites* plugin = GETSTELMODULE(Satellites);
×
861
        const bool updatesEnabled = plugin->getUpdatesEnabled();
×
862

863
        if (!updatesEnabled)
×
864
                ui->nextUpdateLabel->setText(q_("Internet updates disabled"));
×
865
        else if (plugin->getUpdateState() == Satellites::Updating)
×
866
                ui->nextUpdateLabel->setText(q_("Updating now..."));
×
867
        else
868
        {
869
                int secondsToUpdate = plugin->getSecondsToUpdate();
×
870
                if (secondsToUpdate <= 60)
×
871
                        ui->nextUpdateLabel->setText(QString("%1: %2").arg(nextUpdate, q_("< 1 minute")));
×
872
                else if (secondsToUpdate < 3600)
×
873
                {
874
                        int n = (secondsToUpdate/60)+1;
×
875
                        // TRANSLATORS: minutes.
876
                        ui->nextUpdateLabel->setText(QString("%1: %2 %3").arg(nextUpdate, QString::number(n), qc_("m", "time")));
×
877
                }
878
                else
879
                {
880
                        int n = (secondsToUpdate/3600)+1;
×
881
                        // TRANSLATORS: hours.
882
                        ui->nextUpdateLabel->setText(QString("%1: %2 %3").arg(nextUpdate, QString::number(n), qc_("h", "time")));
×
883
                }
884
        }
885
}
×
886

887
void SatellitesDialog::showUpdateState(Satellites::UpdateState state)
×
888
{
889
        if (state==Satellites::Updating)
×
890
                ui->nextUpdateLabel->setText(q_("Updating now..."));
×
891
        else if (state==Satellites::DownloadError || state==Satellites::OtherError)
×
892
        {
893
                ui->nextUpdateLabel->setText(q_("Update error"));
×
894
                updateTimer->start();  // make sure message is displayed for a while...
×
895
        }
896
}
×
897

898
void SatellitesDialog::showUpdateCompleted(int updated, int total, int added, int missing)
×
899
{
900
        Satellites* plugin = GETSTELMODULE(Satellites);
×
901
        QString message;
×
902
        if (plugin->isAutoRemoveEnabled())
×
903
                message = q_("Updated %1/%2 satellite(s); %3 added; %4 removed");
×
904
        else
905
                message = q_("Updated %1/%2 satellite(s); %3 added; %4 missing");
×
906
        ui->nextUpdateLabel->setText(message.arg(updated).arg(total).arg(added).arg(missing));
×
907
        // display the status for another full interval before refreshing status
908
        updateTimer->start();
×
909
        ui->lastUpdateDateTimeEdit->setDateTime(plugin->getLastUpdate());
×
910
        populateFilterMenu();
×
911
}
×
912

913
void SatellitesDialog::saveEditedSource()
×
914
{
915
        // don't update the currently selected item in the source list if the text is empty or not a valid URL.
916
        QString u = ui->sourceEdit->text().trimmed();
×
917
        if (u.isEmpty() || u=="")
×
918
        {
919
                qDebug() << "SatellitesDialog::saveEditedSource empty string - not saving";
×
920
                QMessageBox::warning(&StelMainView::getInstance(), q_("Warning!"), q_("Empty string - not saving"), QMessageBox::Ok);
×
921
                return;
×
922
        }
923

924
        if (!QUrl(u).isValid() || !u.contains("://"))
×
925
        {
926
                qDebug() << "SatellitesDialog::saveEditedSource invalid URL - not saving : " << u;
×
927
                QMessageBox::warning(&StelMainView::getInstance(), q_("Warning!"), q_("Invalid URL - not saving"), QMessageBox::Ok);
×
928
                return;
×
929
        }
930

931
        // Changes to item data (text or check state) are connected to
932
        // saveSourceList(), so there's no need to call it explicitly.
933
        if (ui->sourceList->currentItem()!=nullptr)
×
934
                ui->sourceList->currentItem()->setText(u);
×
935
        else if (ui->sourceList->findItems(u, Qt::MatchExactly).count() <= 0)
×
936
        {
937
                QListWidgetItem* i = new QListWidgetItem(u, ui->sourceList);
×
938
                i->setData(checkStateRole, Qt::Unchecked);
×
939
                i->setSelected(true);
×
940
                ui->sourceList->setCurrentItem(i);
×
941
        }
942
        updateButtonsProperties();
×
943
        ui->sourceEdit->setText("");
×
944
}
×
945

946
void SatellitesDialog::saveSourceList(void)
×
947
{
948
        QStringList allSources;
×
949
        for(int i=0; i<ui->sourceList->count(); i++)
×
950
        {
951
                QString url = ui->sourceList->item(i)->text();
×
952
                if (ui->sourceList->item(i)->data(checkStateRole) == Qt::Checked)
×
953
                        url.prepend("1,");
×
954
                allSources << url;
×
955
        }
×
956
        GETSTELMODULE(Satellites)->setTleSources(allSources);
×
957
}
×
958

959
void SatellitesDialog::deleteSourceRow(void)
×
960
{
961
        if (askConfirmation())
×
962
        {
963
                ui->sourceEdit->setText("");
×
964
                if (ui->sourceList->currentItem())
×
965
                        delete ui->sourceList->currentItem();
×
966

967
                updateButtonsProperties();
×
968
                saveSourceList();
×
969
        }
970
}
×
971

972
void SatellitesDialog::editSourceRow(void)
×
973
{
974
        ui->addSourceButton->setEnabled(false);
×
975
        ui->editSourceButton->setEnabled(false);
×
976
        ui->deleteSourceButton->setEnabled(false);
×
977
        ui->saveSourceButton->setEnabled(true);
×
978
        if (ui->sourceList->currentItem())
×
979
        {
980
                ui->sourceEdit->setEnabled(true);
×
981
                ui->sourceEdit->setText(ui->sourceList->currentItem()->text());
×
982
                ui->sourceEdit->selectAll();
×
983
                ui->sourceEdit->setFocus();
×
984
        }
985
}
×
986

987
void SatellitesDialog::addSourceRow(void)
×
988
{
989
        ui->addSourceButton->setEnabled(false);
×
990
        ui->editSourceButton->setEnabled(false);
×
991
        ui->deleteSourceButton->setEnabled(false);
×
992
        ui->saveSourceButton->setEnabled(true);
×
993
        ui->sourceEdit->setEnabled(true);
×
994
        ui->sourceEdit->setText(q_("[new source]"));
×
995
        ui->sourceEdit->selectAll();
×
996
        ui->sourceEdit->setFocus();
×
997
        ui->sourceList->blockSignals(true);
×
998
        ui->sourceList->setCurrentItem(nullptr);
×
999
        ui->sourceList->blockSignals(false);
×
1000
}
×
1001

1002
void SatellitesDialog::updateButtonsProperties()
×
1003
{
1004
        ui->addSourceButton->setEnabled(true);
×
1005
        ui->editSourceButton->setEnabled(true);
×
1006
        ui->deleteSourceButton->setEnabled(true);
×
1007
        ui->saveSourceButton->setEnabled(false);
×
1008
        ui->sourceEdit->setEnabled(false);
×
1009
        ui->sourceEdit->setText("");
×
1010
}
×
1011

1012
void SatellitesDialog::toggleCheckableSources()
×
1013
{
1014
        QListWidget* list = ui->sourceList;
×
1015
        if (list->count() < 1)
×
1016
                return; // Saves effort checking it on every step
×
1017

1018
        const bool enabled = GETSTELMODULE(Satellites)->isAutoAddEnabled();
×
1019
        if (!enabled == list->item(0)->data(Qt::CheckStateRole).isNull())
×
1020
                return; // Nothing to do
×
1021

1022
        ui->sourceList->blockSignals(true); // Prevents saving the list...
×
1023
        for (int row = 0; row < list->count(); row++)
×
1024
        {
1025
                QListWidgetItem* item = list->item(row);
×
1026
                if (enabled)
×
1027
                {
1028
                        item->setData(Qt::CheckStateRole, item->data(Qt::UserRole));
×
1029
                }
1030
                else
1031
                {
1032
                        item->setData(Qt::UserRole, item->data(Qt::CheckStateRole));
×
1033
                        item->setData(Qt::CheckStateRole, QVariant());
×
1034
                }
1035
        }
1036
        ui->sourceList->blockSignals(false);
×
1037
        checkStateRole = enabled ? Qt::CheckStateRole : Qt::UserRole;
×
1038
}
1039

1040
void SatellitesDialog::restoreDefaults(void)
×
1041
{
1042
        if (askConfirmation())
×
1043
        {
1044
                qDebug() << "[Satellites] restore defaults...";
×
1045
                GETSTELMODULE(Satellites)->restoreDefaults();
×
1046
                GETSTELMODULE(Satellites)->loadSettings();
×
1047
                updateSettingsPage();
×
1048
                populateFilterMenu();
×
1049
                populateSourcesList();
×
1050
                // handle GUI elements
1051
                ui->fontSizeSpinBox->setEnabled(ui->labelsCheckBox->isChecked());
×
1052
                handleOrbitLinesGroup(ui->orbitLinesCheckBox->isChecked());
×
1053
                handleUmbraGroup(ui->umbraCheckBox->isChecked());
×
1054
                ui->hideInvisibleSatellites->setEnabled(ui->iconicCheckBox->isChecked());
×
1055
        }
1056
        else
1057
                qDebug() << "[Satellites] restore defaults is canceled...";
×
1058
}
×
1059

1060
void SatellitesDialog::restoreTleSources(void)
×
1061
{
1062
        if (askConfirmation())
×
1063
        {
1064
                qDebug() << "[Satellites] restore TLE sources...";
×
1065
                GETSTELMODULE(Satellites)->restoreDefaultTleSources();
×
1066
                GETSTELMODULE(Satellites)->loadSettings();
×
1067
                populateSourcesList();
×
1068
        }
1069
        else
1070
                qDebug() << "[Satellites] restore TLE sources is canceled...";
×
1071
}
×
1072

1073
void SatellitesDialog::updateSettingsPage()
×
1074
{
1075
        Satellites* plugin = GETSTELMODULE(Satellites);
×
1076

1077
        // Update stuff
1078
        const bool updatesEnabled = plugin->getUpdatesEnabled();
×
1079
        if(updatesEnabled)
×
1080
                ui->updateButton->setText(q_("Update now"));
×
1081
        else
1082
                ui->updateButton->setText(q_("Update from files"));
×
1083
        ui->lastUpdateDateTimeEdit->setDateTime(plugin->getLastUpdate());
×
1084

1085
        updateCountdown();
×
1086
}
×
1087

1088
void SatellitesDialog::populateFilterMenu()
×
1089
{
1090
        // Save current selection, if any...
1091
        QString selectedId;
×
1092
        int index = ui->groupFilterCombo->currentIndex();
×
1093
        if (ui->groupFilterCombo->count() > 0 && index >= 0)
×
1094
        {
1095
                selectedId = ui->groupFilterCombo->itemData(index).toString();
×
1096
        }
1097

1098
        // Prevent the list from re-filtering
1099
        ui->groupFilterCombo->blockSignals(true);
×
1100

1101
        // Populate with group names/IDs
1102
        ui->groupFilterCombo->clear();
×
1103
        for (const auto& group : GETSTELMODULE(Satellites)->getGroupIdList())
×
1104
        {
1105
                ui->groupFilterCombo->addItem(q_(group), group);
×
1106
        }
×
1107
        ui->groupFilterCombo->model()->sort(0);
×
1108

1109
        // Add special groups - their IDs deliberately use JSON-incompatible chars.
1110
        ui->groupFilterCombo->insertItem(0, q_("[orbit calculation error]"), QVariant("[orbiterror]"));
×
1111
        ui->groupFilterCombo->insertItem(0, q_("[atmospheric entry]"), QVariant("[reentry]"));
×
1112
        ui->groupFilterCombo->insertItem(0, q_("[all newly added]"), QVariant("[newlyadded]"));
×
1113
        ui->groupFilterCombo->insertItem(0, q_("[all not displayed]"), QVariant("[undisplayed]"));
×
1114
        ui->groupFilterCombo->insertItem(0, q_("[all displayed]"), QVariant("[displayed]"));
×
1115
        ui->groupFilterCombo->insertItem(0, q_("[all communications]"), QVariant("[communication]"));
×
1116
        ui->groupFilterCombo->insertItem(0, q_("[small satellites]"), QVariant("[smallsize]"));
×
1117
        ui->groupFilterCombo->insertItem(0, q_("[medium satellites]"), QVariant("[mediumsize]"));
×
1118
        ui->groupFilterCombo->insertItem(0, q_("[large satellites]"), QVariant("[largesize]"));
×
1119
        // TRANSLATORS: LEO = Low Earth orbit
1120
        ui->groupFilterCombo->insertItem(0, q_("[LEO satellites]"), QVariant("[LEO]"));
×
1121
        // TRANSLATORS: GEO = Geosynchronous equatorial orbit (Geostationary orbit)
1122
        ui->groupFilterCombo->insertItem(0, q_("[GEO/GSO satellites]"), QVariant("[GSO]"));
×
1123
        // TRANSLATORS: MEO = Medium Earth orbit
1124
        ui->groupFilterCombo->insertItem(0, q_("[MEO satellites]"), QVariant("[MEO]"));
×
1125
        // TRANSLATORS: HEO = Highly elliptical orbit
1126
        ui->groupFilterCombo->insertItem(0, q_("[HEO satellites]"), QVariant("[HEO]"));
×
1127
        // TRANSLATORS: HGEO = High geosynchronous orbit
1128
        ui->groupFilterCombo->insertItem(0, q_("[HGSO satellites]"), QVariant("[HGSO]"));
×
1129
        ui->groupFilterCombo->insertItem(0, q_("[polar orbit satellites]"), QVariant("[polarorbit]"));
×
1130
        ui->groupFilterCombo->insertItem(0, q_("[equatorial orbit satellites]"), QVariant("[equatorialorbit]"));
×
1131
        // TRANSLATORS: PSSO = Polar sun synchronous orbit
1132
        ui->groupFilterCombo->insertItem(0, q_("[PSSO satellites]"), QVariant("[PSSO]"));
×
1133
        // TRANSLATORS: HEO/E = High Earth orbit
1134
        ui->groupFilterCombo->insertItem(0, q_("[HEO/E satellites]"), QVariant("[HEarthO]"));
×
1135
        ui->groupFilterCombo->insertItem(0, q_("[outdated TLE]"), QVariant("[outdatedTLE]"));
×
1136
        ui->groupFilterCombo->insertItem(0, q_("[custom filter]"), QVariant("[custom]"));
×
1137
        ui->groupFilterCombo->insertItem(0, q_("[all user defined]"), QVariant("[userdefined]"));
×
1138
        ui->groupFilterCombo->insertItem(0, q_("[all]"), QVariant("all"));        
×
1139

1140
        // Restore current selection
1141
        index = (!selectedId.isEmpty()) ? qMax(0, ui->groupFilterCombo->findData(selectedId)) : 0;
×
1142

1143
        ui->groupFilterCombo->setCurrentIndex(index);
×
1144
        ui->groupFilterCombo->blockSignals(false);
×
1145
}
×
1146

1147
void SatellitesDialog::populateInfo()
×
1148
{
1149
        QString vr = q_("Valid range");
×
1150
        ui->labelRCS->setText(QString("%1, %2<sup>2</sup>:").arg(q_("RCS"), qc_("m","distance")));
×
1151
        ui->labelRCS->setToolTip(QString("<p>%1</p>").arg(q_("Radar cross-section (RCS) is a measure of how detectable an object is with a radar. A larger RCS indicates that an object is more easily detected.")));
×
1152
        ui->labelStdMagnitude->setToolTip(QString("<p>%1</p>").arg(q_("The standard magnitude of a satellite is defined as its apparent magnitude when at half-phase and at a distance 1000 km from the observer.")));
×
1153
        // TRANSLATORS: duration
1154
        QString s = qc_("s","time");
×
1155
        ui->orbitDurationSpin->setSuffix(QString(" %1").arg(s));
×
1156
        // TRANSLATORS: duration
1157
        ui->updateFrequencySpinBox->setSuffix(QString(" %1").arg(qc_("h","time")));
×
1158
        // TRANSLATORS: Unit of measure for distance - kilometers
1159
        QString km = qc_("km", "distance");
×
1160
        QString px = qc_("px", "pixels");
×
1161
        ui->minAltitude->setSuffix(QString(" %1").arg(km));
×
1162
        ui->minAltitude->setToolTip(QString("%1: %2..%3 %4").arg(vr, QString::number(ui->minAltitude->minimum(), 'f', 0), QString::number(ui->minAltitude->maximum(), 'f', 0), km));
×
1163
        ui->maxAltitude->setSuffix(QString(" %1").arg(km));
×
1164
        ui->maxAltitude->setToolTip(QString("%1: %2..%3 %4").arg(vr, QString::number(ui->maxAltitude->minimum(), 'f', 0), QString::number(ui->maxAltitude->maximum(), 'f', 0), km));
×
1165
        ui->altitudeCheckBox->setToolTip(QString("<p>%1</p>").arg(q_("Display satellites and their orbits within selected range of altitudes only.")));
×
1166
        ui->umbraDistance->setSuffix(QString(" %1").arg(km));
×
1167
        ui->umbraDistance->setToolTip(QString("<p>%1. %2: %3..%4 %5</p>").arg(q_("Distance to the center of umbra from Earth's surface (height of imagined satellite)"), vr, QString::number(ui->umbraDistance->minimum(), 'f', 1), QString::number(ui->umbraDistance->maximum(), 'f', 1), km));
×
1168
        ui->orbitSegmentsSpin->setToolTip(QString("<p>%1. %2: %3..%4</p>").arg(q_("Number of segments: number of segments used to draw the line"), vr, QString::number(ui->orbitSegmentsSpin->minimum()), QString::number(ui->orbitSegmentsSpin->maximum())));
×
1169
        ui->orbitDurationSpin->setToolTip(QString("<p>%1. %2: %3..%4 %5</p>").arg(q_("Segment length: duration of a single segment in seconds"), vr, QString::number(ui->orbitDurationSpin->minimum()), QString::number(ui->orbitDurationSpin->maximum()), s));
×
1170
        ui->orbitFadeSpin->setToolTip(QString("<p>%1. %2: %3..%4</p>").arg(q_("Fade length: number of segments used to draw each end of the line"), vr, QString::number(ui->orbitFadeSpin->minimum()), QString::number(ui->orbitFadeSpin->maximum())));
×
1171
        ui->orbitThicknessSpin->setToolTip(QString("%1: %2..%3 %4").arg(q_("Orbit line thickness"), QString::number(ui->orbitThicknessSpin->minimum()), QString::number(ui->orbitThicknessSpin->maximum()), px));
×
1172
        ui->orbitThicknessSpin->setSuffix(QString(" %1").arg(px));
×
1173
        ui->minMagnitude->setToolTip(QString("%1: %2..%3").arg(vr, QString::number(ui->minMagnitude->minimum(), 'f', 2), QString::number(ui->minMagnitude->maximum(), 'f', 2)));
×
1174
        ui->maxMagnitude->setToolTip(QString("%1: %2..%3").arg(vr, QString::number(ui->maxMagnitude->minimum(), 'f', 2), QString::number(ui->maxMagnitude->maximum(), 'f', 2)));
×
1175
}
×
1176

1177
void SatellitesDialog::populateSourcesList()
×
1178
{
1179
        ui->sourceList->blockSignals(true);
×
1180
        ui->sourceList->clear();
×
1181

1182
        Satellites* plugin = GETSTELMODULE(Satellites);
×
1183
        QStringList urls = plugin->getTleSources();
×
1184
        checkStateRole = plugin->isAutoAddEnabled() ? Qt::CheckStateRole : Qt::UserRole;
×
1185
        for (auto url : urls)
×
1186
        {
1187
                bool checked = false;
×
1188
                if (url.startsWith("1,"))
×
1189
                {
1190
                        checked = true;
×
1191
                        url.remove(0, 2);
×
1192
                }
1193
                else if (url.startsWith("0,"))
×
1194
                        url.remove(0, 2);
×
1195
                QListWidgetItem* item = new QListWidgetItem(url, ui->sourceList);
×
1196
                item->setData(checkStateRole, checked ? Qt::Checked : Qt::Unchecked);
×
1197
        }
×
1198
        ui->sourceList->blockSignals(false);
×
1199
        // if (ui->sourceList->count() > 0) ui->sourceList->setCurrentRow(0);
1200
}
×
1201

1202
void SatellitesDialog::addSpecialGroupItem()
×
1203
{
1204
        // TRANSLATORS: Displayed in the satellite group selection box.
1205
        QListWidgetItem* item = new QListWidgetItem(q_("New group..."));
×
1206
        item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsEditable|Qt::ItemIsSelectable);
×
1207
        // Assuming this is also the font used for the list items...
1208
        QFont font = ui->groupsListWidget->font();
×
1209
        font.setItalic(true);
×
1210
        item->setFont(font);
×
1211
        ui->groupsListWidget->insertItem(0, item);
×
1212
}
×
1213

1214
void SatellitesDialog::setGroups()
×
1215
{
1216
        QModelIndexList selection = ui->satellitesList->selectionModel()->selectedIndexes();
×
1217
        if (selection.isEmpty())
×
1218
                return;
×
1219

1220
        // Let's determine what to add or remove
1221
        // (partially checked groups are not modified)
1222
        GroupSet groupsToAdd;
×
1223
        GroupSet groupsToRemove;
×
1224
        for (int row = 0; row < ui->groupsListWidget->count(); row++)
×
1225
        {
1226
                QListWidgetItem* item = ui->groupsListWidget->item(row);
×
1227
                if (item->flags().testFlag(Qt::ItemIsEditable))
×
1228
                        continue;
×
1229
                if (item->checkState() == Qt::Checked)
×
1230
                        groupsToAdd.insert(item->data(Qt::UserRole).toString());
×
1231
                else if (item->checkState() == Qt::Unchecked)
×
1232
                        groupsToRemove.insert(item->data(Qt::UserRole).toString());
×
1233
        }
1234
        for (int i = 0; i < selection.count(); i++)
×
1235
        {
1236
                const QModelIndex& index = selection.at(i);
×
1237
                GroupSet groups = index.data(SatGroupsRole).value<GroupSet>();
×
1238
                groups.subtract(groupsToRemove);
×
1239
                groups.unite(groupsToAdd);
×
1240
                QVariant newGroups = QVariant::fromValue<GroupSet>(groups);
×
1241
                ui->satellitesList->model()->setData(index, newGroups, SatGroupsRole);
×
1242
        }
×
1243
        saveSatellites();
×
1244
}
×
1245

1246
void SatellitesDialog::saveSettings(void)
×
1247
{
1248
        GETSTELMODULE(Satellites)->saveSettingsToConfig();
×
1249
        GETSTELMODULE(Satellites)->saveCatalog();
×
1250
}
×
1251

1252
void SatellitesDialog::addSatellites(const TleDataList& newSatellites)
×
1253
{
1254
        GETSTELMODULE(Satellites)->add(newSatellites);
×
1255
        saveSatellites();
×
1256

1257
        // Trigger re-loading the list to display the new satellites
1258
        int index = ui->groupFilterCombo->findData(QVariant("[newlyadded]"));
×
1259
        // TODO: Unnecessary once the model can handle changes? --BM
1260
        if (ui->groupFilterCombo->currentIndex() == index)
×
1261
                filterListByGroup(index);
×
1262
        else
1263
                ui->groupFilterCombo->setCurrentIndex(index); //Triggers the same operation
×
1264

1265
        // Select the satellites that were added just now
1266
        QItemSelectionModel* selectionModel = ui->satellitesList->selectionModel();
×
1267
        selectionModel->clearSelection();
×
1268
        QModelIndex firstSelectedIndex;
×
1269
        QSet<QString> newIds;
×
1270
        for (const auto& sat : newSatellites)
×
1271
                newIds.insert(sat.id);
×
1272
        for (int row = 0; row < ui->satellitesList->model()->rowCount(); row++)
×
1273
        {
1274
                QModelIndex index = ui->satellitesList->model()->index(row, 0);
×
1275
                QString id = index.data(Qt::UserRole).toString();
×
1276
                if (newIds.remove(id))
×
1277
                {
1278
                        selectionModel->select(index, QItemSelectionModel::Select);
×
1279
                        if (!firstSelectedIndex.isValid())
×
1280
                                firstSelectedIndex = index;
×
1281
                }
1282
        }
×
1283
        if (firstSelectedIndex.isValid())
×
1284
                ui->satellitesList->scrollTo(firstSelectedIndex, QAbstractItemView::PositionAtTop);
×
1285
        else
1286
                ui->satellitesList->scrollToTop();
×
1287
}
×
1288

1289
void SatellitesDialog::removeSatellites()
×
1290
{
1291
        if (askConfirmation())
×
1292
        {
1293
                QStringList idList;
×
1294
                QItemSelectionModel* selectionModel = ui->satellitesList->selectionModel();
×
1295
                QModelIndexList selectedIndexes = selectionModel->selectedRows();
×
1296
                for (const auto& index : selectedIndexes)
×
1297
                {
1298
                        QString id = index.data(Qt::UserRole).toString();
×
1299
                        idList.append(id);
×
1300
                }
×
1301
                if (!idList.isEmpty())
×
1302
                {
1303
                        GETSTELMODULE(Satellites)->remove(idList);
×
1304
                        saveSatellites();
×
1305
                }
1306
        }
×
1307
}
×
1308

1309
void SatellitesDialog::setFlags()
×
1310
{
1311
        QItemSelectionModel* selectionModel = ui->satellitesList->selectionModel();
×
1312
        QModelIndexList selection = selectionModel->selectedIndexes();
×
1313
        for (int row = 0; row < selection.count(); row++)
×
1314
        {
1315
                const QModelIndex& index = selection.at(row);
×
1316
                SatFlags flags = index.data(SatFlagsRole).value<SatFlags>();
×
1317

1318
                // If a checkbox is partially checked, the respective flag is not
1319
                // changed.
1320
                if (ui->displayedCheckbox->isChecked())
×
1321
                        flags |= SatDisplayed;
×
1322
                else if (ui->displayedCheckbox->checkState() == Qt::Unchecked)
×
1323
                        flags &= ~SatDisplayed;
×
1324

1325
                if (ui->orbitCheckbox->isChecked())
×
1326
                        flags |= SatOrbit;
×
1327
                else if (ui->orbitCheckbox->checkState() == Qt::Unchecked)
×
1328
                        flags &= ~SatOrbit;
×
1329

1330
                if (ui->userCheckBox->isChecked())
×
1331
                        flags |= SatUser;
×
1332
                else if (ui->userCheckBox->checkState() == Qt::Unchecked)
×
1333
                        flags &= ~SatUser;
×
1334

1335
                QVariant value = QVariant::fromValue<SatFlags>(flags);
×
1336
                ui->satellitesList->model()->setData(index, value, SatFlagsRole);
×
1337
        }
×
1338
        saveSatellites();
×
1339
}
×
1340

1341
// Right side of GUI should be read only and clean by default (for example group in left top corner is was changed at the moment)
1342
void SatellitesDialog::setRightSideToROMode()
×
1343
{
1344
        ui->removeSatellitesButton->setEnabled(false);
×
1345
        ui->commSatelliteButton->setEnabled(false);
×
1346
        ui->displayedCheckbox->setEnabled(false);
×
1347
        ui->displayedCheckbox->setChecked(false);
×
1348
        ui->orbitCheckbox->setEnabled(false);
×
1349
        ui->orbitCheckbox->setChecked(false);
×
1350
        ui->userCheckBox->setEnabled(false);
×
1351
        ui->userCheckBox->setChecked(false);
×
1352
        ui->nameEdit->setEnabled(false);
×
1353
        ui->nameEdit->setText(QString());
×
1354
        ui->noradNumberEdit->setEnabled(false);
×
1355
        ui->noradNumberEdit->setText(QString());
×
1356
        ui->cosparNumberEdit->setEnabled(false);
×
1357
        ui->cosparNumberEdit->setText(QString());
×
1358
        ui->descriptionTextEdit->setEnabled(false);
×
1359
        ui->descriptionTextEdit->setText(QString());
×
1360
        ui->groupsListWidget->setEnabled(false);
×
1361
        ui->groupsListWidget->clear();
×
1362
        ui->tleFirstLineEdit->setEnabled(false);
×
1363
        ui->tleFirstLineEdit->setText(QString());
×
1364
        ui->tleSecondLineEdit->setEnabled(false);
×
1365
        ui->tleSecondLineEdit->setText(QString());
×
1366
        ui->labelTleEpochData->setText(QString());
×
1367
        ui->stdMagnitudeLineEdit->setEnabled(false);
×
1368
        ui->stdMagnitudeLineEdit->setText(QString());
×
1369
        ui->rcsLineEdit->setEnabled(false);
×
1370
        ui->rcsLineEdit->setText(QString());
×
1371
        ui->perigeeLineEdit->setEnabled(false);
×
1372
        ui->perigeeLineEdit->setText(QString());
×
1373
        ui->apogeeLineEdit->setEnabled(false);
×
1374
        ui->apogeeLineEdit->setText(QString());
×
1375
        ui->periodLineEdit->setEnabled(false);
×
1376
        ui->periodLineEdit->setText(QString());
×
1377

1378
        // set default
1379
        buttonMarkerColor = QColor(QColor::fromRgbF(0.7f, 0.7f, 0.7f));
×
1380
        buttonOrbitColor = QColor(QColor::fromRgbF(0.7f, 0.7f, 0.7f));
×
1381
        buttonInfoColor = QColor(QColor::fromRgbF(0.7f, 0.7f, 0.7f));
×
1382
        ui->satMarkerColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonMarkerColor.name() + "; }");
×
1383
        ui->satOrbitColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonOrbitColor.name() + "; }");
×
1384
        ui->satInfoColorPickerButton->setStyleSheet("QPushButton { background-color:" + buttonInfoColor.name() + "; }");
×
1385
}
×
1386

1387
// The status of elements on right side of GUI may be changed when satellite is selected
1388
void SatellitesDialog::setRightSideToRWMode()
×
1389
{
1390
        ui->displayedCheckbox->setEnabled(true);
×
1391
        ui->orbitCheckbox->setEnabled(true);
×
1392
        ui->userCheckBox->setEnabled(true);
×
1393
        ui->nameEdit->setEnabled(true);
×
1394
        ui->noradNumberEdit->setEnabled(true);
×
1395
        ui->cosparNumberEdit->setEnabled(true);
×
1396
        ui->descriptionTextEdit->setEnabled(true);
×
1397
        ui->groupsListWidget->setEnabled(true);
×
1398
        ui->tleFirstLineEdit->setEnabled(true);
×
1399
        ui->tleSecondLineEdit->setEnabled(true);
×
1400
        ui->stdMagnitudeLineEdit->setEnabled(true);
×
1401
        ui->rcsLineEdit->setEnabled(true);
×
1402
        ui->removeSatellitesButton->setEnabled(true);
×
1403
        ui->commSatelliteButton->setEnabled(true);
×
1404
        ui->perigeeLineEdit->setEnabled(true);
×
1405
        ui->apogeeLineEdit->setEnabled(true);
×
1406
        ui->periodLineEdit->setEnabled(true);
×
1407
}
×
1408

1409
void SatellitesDialog::handleGroupChanges(QListWidgetItem* item)
×
1410
{
1411
        ui->groupsListWidget->blockSignals(true);
×
1412
        Qt::ItemFlags flags = item->flags();
×
1413
        if (flags.testFlag(Qt::ItemIsEditable))
×
1414
        {
1415
                // Harmonize the item with the rest...
1416
                flags ^= Qt::ItemIsEditable;
×
1417
                item->setFlags(flags | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
×
1418
                item->setCheckState(Qt::Checked);
×
1419
                QString groupId = item->text().trimmed();
×
1420
                item->setData(Qt::UserRole, groupId);
×
1421
                item->setToolTip(q_(groupId));
×
1422
                QFont font = item->font();
×
1423
                font.setItalic(false);
×
1424
                item->setFont(font);
×
1425

1426
                // ...and add a new one in its place.
1427
                addSpecialGroupItem();
×
1428

1429
                GETSTELMODULE(Satellites)->addGroup(groupId);
×
1430
                populateFilterMenu();
×
1431
        }
×
1432
        ui->groupsListWidget->blockSignals(false);
×
1433
        setGroups();
×
1434
}
×
1435

1436
void SatellitesDialog::trackSatellite(const QModelIndex& index)
×
1437
{
1438
        Satellites* SatellitesMgr = GETSTELMODULE(Satellites);
×
1439
        Q_ASSERT(SatellitesMgr);
×
1440
        QString id = index.data(Qt::UserRole).toString();
×
1441
        SatelliteP sat = SatellitesMgr->getById(id);
×
1442
        if (sat.isNull())
×
1443
                return;
×
1444

1445
        if (!sat->orbitValid)
×
1446
                return;
×
1447

1448
        // Turn on Satellite rendering if it is not already on        
1449
        if (!ui->displayedCheckbox->isChecked())
×
1450
        {
1451
                ui->displayedCheckbox->setChecked(true);
×
1452
                setFlags(); // sync GUI and model
×
1453
        }
1454

1455
        // If Satellites are not currently displayed, make them visible.
1456
        if (!SatellitesMgr->getFlagHintsVisible())
×
1457
        {
1458
                StelAction* setHintsAction = StelApp::getInstance().getStelActionManager()->findAction("actionShow_Satellite_Hints");
×
1459
                Q_ASSERT(setHintsAction);
×
1460
                setHintsAction->setChecked(true);
×
1461
        }
1462

1463
        StelObjectP obj = qSharedPointerDynamicCast<StelObject>(sat);
×
1464
        StelObjectMgr& objectMgr = StelApp::getInstance().getStelObjectMgr();
×
1465
        if (objectMgr.setSelectedObject(obj))
×
1466
        {
1467
                GETSTELMODULE(StelMovementMgr)->autoZoomIn();
×
1468
                GETSTELMODULE(StelMovementMgr)->setFlagTracking(true);
×
1469
        }
1470
}
×
1471

1472
void SatellitesDialog::updateTLEs(void)
×
1473
{
1474
        if(GETSTELMODULE(Satellites)->getUpdatesEnabled())
×
1475
        {
1476
                GETSTELMODULE(Satellites)->updateFromOnlineSources();
×
1477
        }
1478
        else
1479
        {
1480
                QStringList updateFiles = QFileDialog::getOpenFileNames(&StelMainView::getInstance(),
×
1481
                                                                        q_("Select TLE Update File"),
×
1482
                                                                        StelFileMgr::getDesktopDir(),
×
1483
                                                                        "*.*");
×
1484
                GETSTELMODULE(Satellites)->updateFromFiles(updateFiles, false);
×
1485
        }
×
1486
}
×
1487

1488
void SatellitesDialog::enableSatelliteDataForm(bool enabled)
×
1489
{
1490
        // NOTE: I'm still not sure if this is necessary, if the right signals are used to trigger changes...--BM
1491
        ui->displayedCheckbox->blockSignals(!enabled);
×
1492
        ui->orbitCheckbox->blockSignals(!enabled);
×
1493
        ui->userCheckBox->blockSignals(!enabled);
×
1494
        ui->descriptionTextEdit->blockSignals(!enabled);        
×
1495
}
×
1496

1497
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
1498
void SatellitesDialog::setIridiumFlaresHeaderNames()
1499
{
1500
        iridiumFlaresHeader.clear();
1501

1502
        iridiumFlaresHeader << q_("Time");
1503
        iridiumFlaresHeader << q_("Brightness");
1504
        iridiumFlaresHeader << q_("Altitude");
1505
        iridiumFlaresHeader << q_("Azimuth");
1506
        iridiumFlaresHeader << q_("Satellite");
1507

1508
        ui->iridiumFlaresTreeWidget->setHeaderLabels(iridiumFlaresHeader);
1509

1510
        // adjust the column width
1511
        for(int i = 0; i < IridiumFlaresCount; ++i)
1512
        {
1513
            ui->iridiumFlaresTreeWidget->resizeColumnToContents(i);
1514
        }
1515

1516
        // sort-by-date
1517
        ui->iridiumFlaresTreeWidget->sortItems(IridiumFlaresDate, Qt::AscendingOrder);
1518
}
1519

1520
void SatellitesDialog::initListIridiumFlares()
1521
{
1522
        ui->iridiumFlaresTreeWidget->clear();
1523
        ui->iridiumFlaresTreeWidget->setColumnCount(IridiumFlaresCount);
1524
        setIridiumFlaresHeaderNames();
1525
        ui->iridiumFlaresTreeWidget->header()->setSectionsMovable(false);
1526
}
1527

1528
void SatellitesDialog::predictIridiumFlares()
1529
{
1530
        IridiumFlaresPredictionList predictions = GETSTELMODULE(Satellites)->getIridiumFlaresPrediction();
1531

1532
        ui->iridiumFlaresTreeWidget->clear();
1533
        for (const auto& flare : predictions)
1534
        {
1535
                SatPIFTreeWidgetItem *treeItem = new SatPIFTreeWidgetItem(ui->iridiumFlaresTreeWidget);
1536
                QString dt = flare.datetime;
1537
                treeItem->setText(IridiumFlaresDate, QString("%1 %2").arg(dt.left(10)).arg(dt.right(8)));
1538
                treeItem->setText(IridiumFlaresMagnitude, QString::number(flare.magnitude,'f',1));
1539
                treeItem->setTextAlignment(IridiumFlaresMagnitude, Qt::AlignRight);
1540
                treeItem->setText(IridiumFlaresAltitude, StelUtils::radToDmsStr(flare.altitude));
1541
                treeItem->setTextAlignment(IridiumFlaresAltitude, Qt::AlignRight);
1542
                treeItem->setText(IridiumFlaresAzimuth, StelUtils::radToDmsStr(flare.azimuth));
1543
                treeItem->setTextAlignment(IridiumFlaresAzimuth, Qt::AlignRight);
1544
                treeItem->setText(IridiumFlaresSatellite, flare.satellite);
1545
        }
1546

1547
        for(int i = 0; i < IridiumFlaresCount; ++i)
1548
        {
1549
            ui->iridiumFlaresTreeWidget->resizeColumnToContents(i);
1550
        }
1551
}
1552

1553
void SatellitesDialog::selectCurrentIridiumFlare(const QModelIndex &modelIndex)
1554
{
1555
        StelCore* core = StelApp::getInstance().getCore();
1556
        // Find the object
1557
        QString name = modelIndex.sibling(modelIndex.row(), IridiumFlaresSatellite).data().toString();
1558
        QString date = modelIndex.sibling(modelIndex.row(), IridiumFlaresDate).data().toString();
1559
        bool ok;
1560
        double JD  = StelUtils::getJulianDayFromISO8601String(date.left(10) + "T" + date.right(8), &ok);
1561
        JD -= core->getUTCOffset(JD)/24.;
1562
        JD -= core->JD_SECOND*15; // Set start point on 15 seconds before flash (TODO: should be an option in the GUI?)
1563

1564
        StelObjectMgr* objectMgr = GETSTELMODULE(StelObjectMgr);
1565
        if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
1566
        {
1567
                StelApp::getInstance().getCore()->setJD(JD);
1568
                const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1569
                if (!newSelected.empty())
1570
                {
1571
                        StelMovementMgr* mvmgr = GETSTELMODULE(StelMovementMgr);
1572
                        mvmgr->moveToObject(newSelected[0], mvmgr->getAutoMoveDuration());
1573
                        mvmgr->setFlagTracking(true);
1574
                }
1575
        }
1576
}
1577

1578
void SatellitesDialog::savePredictedIridiumFlares()
1579
{
1580
        QString csv  = QString("%1 (*.csv)").arg(q_("CSV (Comma delimited)"));
1581
        QString xlsx = QString("%1 (*.xlsx)").arg(q_("Microsoft Excel Open XML Spreadsheet"));
1582
        QString filter, defaultExtension;
1583

1584
        #ifdef ENABLE_XLSX
1585
        filter = QString("%1;;%2") .arg(xlsx, csv);
1586
        defaultExtension = "xlsx";
1587
        #else
1588
        filter = csv;
1589
        defaultExtension = "csv";
1590
        #endif
1591

1592
        QString defaultFilter = QString("(*.%1)").arg(defaultExtension);
1593
        QString dir = QString("%1/iridium_flares.%2").arg(QDir::homePath(), defaultExtension);
1594
        QString filePath = QFileDialog::getSaveFileName(&StelMainView::getInstance(), q_("Save predicted Iridium flares as..."), dir, filter, &defaultFilter);
1595

1596
        int count = ui->iridiumFlaresTreeWidget->topLevelItemCount();
1597
        int columns = iridiumFlaresHeader.size();
1598

1599
        if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
1600
        {
1601
                QFile predictedIridiumFlares(filePath);
1602
                if (!predictedIridiumFlares.open(QFile::WriteOnly | QFile::Truncate))
1603
                {
1604
                        qWarning() << "[Satellites]: Unable to open file"
1605
                                          << QDir::toNativeSeparators(filePath);
1606
                        return;
1607
                }
1608

1609
                QTextStream predictedIridiumFlaresList(&predictedIridiumFlares);
1610
                #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
1611
                predictedIridiumFlaresList.setEncoding(QStringConverter::Utf8);
1612
                #else
1613
                predictedIridiumFlaresList.setCodec("UTF-8");
1614
                #endif
1615
                predictedIridiumFlaresList << iridiumFlaresHeader.join(delimiter) << StelUtils::getEndLineChar();
1616

1617
                for (int i = 0; i < count; i++)
1618
                {
1619
                        int columns = iridiumFlaresHeader.size();
1620
                        for (int j=0; j<columns; j++)
1621
                        {
1622
                                predictedIridiumFlaresList << ui->iridiumFlaresTreeWidget->topLevelItem(i)->text(j);
1623
                                if (j<columns-1)
1624
                                        predictedIridiumFlaresList << delimiter;
1625
                                else
1626
                                        predictedIridiumFlaresList << StelUtils::getEndLineChar();
1627
                        }
1628
                }
1629
                predictedIridiumFlares.close();
1630
        }
1631
        else
1632
        {
1633
                #ifdef ENABLE_XLSX
1634
                int *width;
1635
                width = new int[columns];
1636
                QString sData;
1637
                int w;
1638

1639
                QXlsx::Document xlsx;
1640
                xlsx.setDocumentProperty("title", q_("Predicted Iridium flares"));
1641
                xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
1642
                xlsx.addSheet(q_("Predicted Iridium flares"), AbstractSheet::ST_WorkSheet);
1643

1644
                QXlsx::Format header;
1645
                header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1646
                header.setPatternBackgroundColor(Qt::yellow);
1647
                header.setBorderStyle(QXlsx::Format::BorderThin);
1648
                header.setBorderColor(Qt::black);
1649
                header.setFontBold(true);
1650
                for (int i = 0; i < columns; i++)
1651
                {
1652
                        // Row 1: Names of columns
1653
                        sData = iridiumFlaresHeader.at(i).trimmed();
1654
                        xlsx.write(1, i + 1, sData, header);
1655
                        width[i] = sData.size();
1656
                }
1657

1658
                QXlsx::Format data;
1659
                data.setHorizontalAlignment(QXlsx::Format::AlignRight);
1660
                for (int i = 0; i < count; i++)
1661
                {
1662
                        for (int j = 0; j < columns; j++)
1663
                        {
1664
                                // Row 2 and next: the data
1665
                                sData = ui->iridiumFlaresTreeWidget->topLevelItem(i)->text(j).trimmed();
1666
                                xlsx.write(i + 2, j + 1, sData, data);
1667
                                w = sData.size();
1668
                                if (w > width[j])
1669
                                {
1670
                                        width[j] = w;
1671
                                }
1672
                        }
1673
                }
1674

1675
                for (int i = 0; i < columns; i++)
1676
                {
1677
                        xlsx.setColumnWidth(i+1, width[i]+2);
1678
                }
1679

1680
                delete[] width;
1681
                xlsx.saveAs(filePath);
1682
                #endif
1683
        }
1684
}
1685
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc