1 /*
2 * Stellarium
3 * Copyright (C) 2008 Guillaume Chereau
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19
20 #include "Dialog.hpp"
21 #include "SearchDialog.hpp"
22 #include "ui_searchDialogGui.h"
23 #include "StelApp.hpp"
24 #include "StelCore.hpp"
25 #include "StelModuleMgr.hpp"
26 #include "StelMovementMgr.hpp"
27 #include "StelLocaleMgr.hpp"
28 #include "StelTranslator.hpp"
29 #include "Planet.hpp"
30 #include "SpecialMarkersMgr.hpp"
31 #include "CustomObjectMgr.hpp"
32 #include "SolarSystem.hpp"
33
34 #include "StelObjectMgr.hpp"
35 #include "StelGui.hpp"
36 #include "StelUtils.hpp"
37
38 #include "StelFileMgr.hpp"
39 #include "StelJsonParser.hpp"
40
41 #include <QDebug>
42 #include <QFrame>
43 #include <QLabel>
44 #include <QPushButton>
45 #include <QSettings>
46 #include <QString>
47 #include <QStringList>
48 #include <QtAlgorithms>
49 #include <QTextEdit>
50 #include <QLineEdit>
51 #include <QComboBox>
52 #include <QMenu>
53 #include <QMetaEnum>
54 #include <QClipboard>
55 #include <QSortFilterProxyModel>
56 #include <QStringListModel>
57 #include <QFileDialog>
58 #include <QDir>
59 #include <QSet>
60 #include <QDialog>
61 #include <QAbstractItemModel>
62
63 #include "SimbadSearcher.hpp"
64
65 // Start of members for class CompletionListModel
CompletionListModel(QObject * parent)66 CompletionListModel::CompletionListModel(QObject* parent):
67 QStringListModel(parent),
68 selectedIdx(0)
69 {
70 }
71
CompletionListModel(const QStringList & string,QObject * parent)72 CompletionListModel::CompletionListModel(const QStringList &string, QObject* parent):
73 QStringListModel(string, parent),
74 selectedIdx(0)
75 {
76 }
77
~CompletionListModel()78 CompletionListModel::~CompletionListModel()
79 {
80 }
81
setValues(const QStringList & v,const QStringList & rv)82 void CompletionListModel::setValues(const QStringList& v, const QStringList& rv)
83 {
84 values=v;
85 recentValues=rv;
86 updateText();
87 }
88
appendRecentValues(const QStringList & v)89 void CompletionListModel::appendRecentValues(const QStringList& v)
90 {
91 recentValues+=v;
92 }
93
appendValues(const QStringList & v)94 void CompletionListModel::appendValues(const QStringList& v)
95 {
96 values+=v;
97 updateText();
98 }
99
clearValues()100 void CompletionListModel::clearValues()
101 {
102 // Default: Show recent values
103 values.clear();
104 values = recentValues;
105 selectedIdx=0;
106 updateText();
107 }
108
getSelected() const109 QString CompletionListModel::getSelected() const
110 {
111 if (values.isEmpty())
112 return QString();
113 return values.at(selectedIdx);
114 }
115
selectNext()116 void CompletionListModel::selectNext()
117 {
118 ++selectedIdx;
119 if (selectedIdx>=values.size())
120 selectedIdx=0;
121 updateText();
122 }
123
selectPrevious()124 void CompletionListModel::selectPrevious()
125 {
126 --selectedIdx;
127 if (selectedIdx<0)
128 selectedIdx = values.size()-1;
129 updateText();
130 }
131
selectFirst()132 void CompletionListModel::selectFirst()
133 {
134 selectedIdx=0;
135 updateText();
136 }
137
updateText()138 void CompletionListModel::updateText()
139 {
140 this->setStringList(values);
141 }
142
data(const QModelIndex & index,int role) const143 QVariant CompletionListModel::data(const QModelIndex &index, int role) const
144 {
145 if (!index.isValid())
146 return QVariant();
147
148 // Bold recent objects
149 if(role == Qt::FontRole)
150 {
151 QFont font;
152 bool toBold = recentValues.contains(index.data(Qt::DisplayRole).toString()) ?
153 true : false;
154 font.setBold(toBold);
155 return font;
156 }
157
158 return QStringListModel::data(index, role);
159 }
160
161 // Start of members for class SearchDialog
162
163 const char* SearchDialog::DEF_SIMBAD_URL = "https://simbad.u-strasbg.fr/";
164 SearchDialog::SearchDialogStaticData SearchDialog::staticData;
165 QString SearchDialog::extSearchText = "";
166
SearchDialog(QObject * parent)167 SearchDialog::SearchDialog(QObject* parent)
168 : StelDialog("Search", parent)
169 , simbadReply(Q_NULLPTR)
170 , listModel(Q_NULLPTR)
171 , proxyModel(Q_NULLPTR)
172 , flagHasSelectedText(false)
173 , shiftPressed(false)
174 {
175 setObjectName("SearchDialog");
176 ui = new Ui_searchDialogForm;
177 simbadSearcher = new SimbadSearcher(this);
178 objectMgr = GETSTELMODULE(StelObjectMgr);
179 Q_ASSERT(objectMgr);
180
181 StelApp::getInstance().getStelPropertyManager()->registerObject(this);
182 conf = StelApp::getInstance().getSettings();
183 enableSimbadSearch(conf->value("search/flag_search_online", true).toBool());
184 useStartOfWords = conf->value("search/flag_start_words", false).toBool();
185 useLockPosition = conf->value("search/flag_lock_position", true).toBool();
186 useFOVCenterMarker = conf->value("search/flag_fov_center_marker", true).toBool();
187 fovCenterMarkerState = GETSTELMODULE(SpecialMarkersMgr)->getFlagFOVCenterMarker();
188 simbadServerUrl = conf->value("search/simbad_server_url", DEF_SIMBAD_URL).toString();
189 setCurrentCoordinateSystemKey(conf->value("search/coordinate_system", "equatorialJ2000").toString());
190
191 setSimbadQueryDist( conf->value("search/simbad_query_dist", 30).toInt());
192 setSimbadQueryCount(conf->value("search/simbad_query_count", 3).toInt());
193 setSimbadGetsIds( conf->value("search/simbad_query_IDs", true ).toBool());
194 setSimbadGetsSpec( conf->value("search/simbad_query_spec", false).toBool());
195 setSimbadGetsMorpho(conf->value("search/simbad_query_morpho", false).toBool());
196 setSimbadGetsTypes( conf->value("search/simbad_query_types", false).toBool());
197 setSimbadGetsDims( conf->value("search/simbad_query_dimensions", false).toBool());
198
199 // Init CompletionListModel
200 searchListModel = new CompletionListModel();
201
202 // Find recent object search data file
203 recentObjectSearchesJsonPath = StelFileMgr::findFile("data", static_cast<StelFileMgr::Flags>(StelFileMgr::Directory | StelFileMgr::Writable)) + "/recentObjectSearches.json";
204 }
205
~SearchDialog()206 SearchDialog::~SearchDialog()
207 {
208 delete ui;
209 if (simbadReply)
210 {
211 simbadReply->deleteLater();
212 simbadReply = Q_NULLPTR;
213 }
214 }
215
retranslate()216 void SearchDialog::retranslate()
217 {
218 if (dialog)
219 {
220 QString text(ui->lineEditSearchSkyObject->text());
221 ui->retranslateUi(dialog);
222 ui->lineEditSearchSkyObject->setText(text);
223 populateSimbadServerList();
224 populateCoordinateSystemsList();
225 populateCoordinateAxis();
226 populateRecentSearch();
227 updateListTab();
228 }
229 }
230
setCurrentCoordinateSystemKey(QString key)231 void SearchDialog::setCurrentCoordinateSystemKey(QString key)
232 {
233 const QMetaEnum& en = metaObject()->enumerator(metaObject()->indexOfEnumerator("CoordinateSystem"));
234 CoordinateSystem coordSystem = static_cast<CoordinateSystem>(en.keyToValue(key.toLatin1().data()));
235 if (coordSystem<0)
236 {
237 qWarning() << "[Search Tool] Unknown coordinate system: " << key << "setting \"equatorialJ2000\" instead";
238 coordSystem = equatorialJ2000;
239 }
240 setCurrentCoordinateSystem(coordSystem);
241 }
242
getCurrentCoordinateSystemKey() const243 QString SearchDialog::getCurrentCoordinateSystemKey() const
244 {
245 return metaObject()->enumerator(metaObject()->indexOfEnumerator("CoordinateSystem")).key(currentCoordinateSystem);
246 }
247
populateCoordinateSystemsList()248 void SearchDialog::populateCoordinateSystemsList()
249 {
250 Q_ASSERT(ui->coordinateSystemComboBox);
251
252 QComboBox* csys = ui->coordinateSystemComboBox;
253
254 //Save the current selection to be restored later
255 csys->blockSignals(true);
256 int index = csys->currentIndex();
257 QVariant selectedSystemId = csys->itemData(index);
258 csys->clear();
259 //For each coordinate system, display the localized name and store the key as user
260 //data. Unfortunately, there's no other way to do this than with a cycle.
261 csys->addItem(qc_("Equatorial (J2000.0)", "coordinate system"), "equatorialJ2000");
262 csys->addItem(qc_("Equatorial", "coordinate system"), "equatorial");
263 csys->addItem(qc_("Horizontal", "coordinate system"), "horizontal");
264 csys->addItem(qc_("Galactic", "coordinate system"), "galactic");
265 csys->addItem(qc_("Supergalactic", "coordinate system"), "supergalactic");
266 csys->addItem(qc_("Ecliptic", "coordinate system"), "ecliptic");
267 csys->addItem(qc_("Ecliptic (J2000.0)", "coordinate system"), "eclipticJ2000");
268
269 //Restore the selection
270 index = csys->findData(selectedSystemId, Qt::UserRole, Qt::MatchCaseSensitive);
271 csys->setCurrentIndex(index);
272 csys->blockSignals(false);
273 }
274
populateCoordinateAxis()275 void SearchDialog::populateCoordinateAxis()
276 {
277 bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
278 bool xnormal = true;
279
280 ui->AxisXSpinBox->setDecimals(2);
281 ui->AxisYSpinBox->setDecimals(2);
282
283 // Allow rotating through longitudinal coordinates, but stop at poles.
284 ui->AxisXSpinBox->setMinimum( 0., true);
285 ui->AxisXSpinBox->setMaximum(360., true);
286 ui->AxisXSpinBox->setWrapping(true);
287 ui->AxisYSpinBox->setMinimum(-90., true);
288 ui->AxisYSpinBox->setMaximum( 90., true);
289 ui->AxisYSpinBox->setWrapping(false);
290
291 switch (getCurrentCoordinateSystem()) {
292 case equatorialJ2000:
293 case equatorial:
294 {
295 ui->AxisXLabel->setText(q_("Right ascension"));
296 ui->AxisXSpinBox->setDisplayFormat(AngleSpinBox::HMSLetters);
297 ui->AxisXSpinBox->setPrefixType(AngleSpinBox::Normal);
298 ui->AxisYLabel->setText(q_("Declination"));
299 ui->AxisYSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
300 ui->AxisYSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
301 xnormal = true;
302 break;
303 }
304 case horizontal:
305 {
306 ui->AxisXLabel->setText(q_("Azimuth"));
307 ui->AxisXSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbolsUnsigned);
308 ui->AxisXSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
309 ui->AxisYLabel->setText(q_("Altitude"));
310 ui->AxisYSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
311 ui->AxisYSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
312 xnormal = false;
313 break;
314 }
315 case ecliptic:
316 case eclipticJ2000:
317 case galactic:
318 case supergalactic:
319 {
320 ui->AxisXLabel->setText(q_("Longitude"));
321 ui->AxisXSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbolsUnsigned);
322 ui->AxisXSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
323 ui->AxisYLabel->setText(q_("Latitude"));
324 ui->AxisYSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
325 ui->AxisYSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
326 xnormal = false;
327 break;
328 }
329 }
330
331 if (withDecimalDegree)
332 {
333 ui->AxisXSpinBox->setDecimals(5);
334 ui->AxisYSpinBox->setDecimals(5);
335 ui->AxisXSpinBox->setDisplayFormat(AngleSpinBox::DecimalDeg);
336 ui->AxisYSpinBox->setDisplayFormat(AngleSpinBox::DecimalDeg);
337 ui->AxisXSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
338 }
339 else
340 {
341 if (xnormal)
342 ui->AxisXSpinBox->setPrefixType(AngleSpinBox::Normal);
343 }
344 }
345
setCoordinateSystem(int csID)346 void SearchDialog::setCoordinateSystem(int csID)
347 {
348 QString currentCoordinateSystemID = ui->coordinateSystemComboBox->itemData(csID).toString();
349 setCurrentCoordinateSystemKey(currentCoordinateSystemID);
350 populateCoordinateAxis();
351 ui->AxisXSpinBox->setRadians(0.);
352 ui->AxisYSpinBox->setRadians(0.);
353 conf->setValue("search/coordinate_system", currentCoordinateSystemID);
354 }
355
356 // Initialize the dialog widgets and connect the signals/slots
createDialogContent()357 void SearchDialog::createDialogContent()
358 {
359 ui->setupUi(dialog);
360 connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
361 connect(&StelApp::getInstance(), SIGNAL(flagShowDecimalDegreesChanged(bool)), this, SLOT(populateCoordinateAxis()));
362 connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(close()));
363 connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
364 connect(ui->lineEditSearchSkyObject, SIGNAL(textChanged(const QString&)), this, SLOT(onSearchTextChanged(const QString&)));
365 connect(ui->simbadCooQueryButton, SIGNAL(clicked()), this, SLOT(lookupCoordinates()));
366 connect(GETSTELMODULE(StelObjectMgr), SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(clearSimbadText(StelModule::StelModuleSelectAction)));
367 connect(ui->pushButtonGotoSearchSkyObject, SIGNAL(clicked()), this, SLOT(gotoObject()));
368 onSearchTextChanged(ui->lineEditSearchSkyObject->text());
369 connect(ui->lineEditSearchSkyObject, SIGNAL(returnPressed()), this, SLOT(gotoObject()));
370 connect(ui->lineEditSearchSkyObject, SIGNAL(selectionChanged()), this, SLOT(setHasSelectedFlag()));
371 connect(ui->lineEditSearchSkyObject, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
372
373 ui->lineEditSearchSkyObject->installEventFilter(this);
374
375 // Kinetic scrolling
376 kineticScrollingList << ui->objectsListView;
377 StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
378 if (gui)
379 {
380 enableKineticScrolling(gui->getFlagUseKineticScrolling());
381 connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
382 }
383
384 populateCoordinateSystemsList();
385 populateCoordinateAxis();
386 int idx = ui->coordinateSystemComboBox->findData(getCurrentCoordinateSystemKey(), Qt::UserRole, Qt::MatchCaseSensitive);
387 if (idx==-1) // Use equatorialJ2000 as default
388 idx = ui->coordinateSystemComboBox->findData(QVariant("equatorialJ2000"), Qt::UserRole, Qt::MatchCaseSensitive);
389 ui->coordinateSystemComboBox->setCurrentIndex(idx);
390 connect(ui->coordinateSystemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setCoordinateSystem(int)));
391 connect(ui->AxisXSpinBox, SIGNAL(valueChanged()), this, SLOT(manualPositionChanged()));
392 connect(ui->AxisYSpinBox, SIGNAL(valueChanged()), this, SLOT(manualPositionChanged()));
393
394 connect(ui->alphaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
395 connect(ui->betaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
396 connect(ui->gammaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
397 connect(ui->deltaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
398 connect(ui->epsilonPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
399 connect(ui->zetaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
400 connect(ui->etaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
401 connect(ui->thetaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
402 connect(ui->iotaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
403 connect(ui->kappaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
404 connect(ui->lambdaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
405 connect(ui->muPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
406 connect(ui->nuPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
407 connect(ui->xiPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
408 connect(ui->omicronPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
409 connect(ui->piPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
410 connect(ui->rhoPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
411 connect(ui->sigmaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
412 connect(ui->tauPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
413 connect(ui->upsilonPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
414 connect(ui->phiPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
415 connect(ui->chiPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
416 connect(ui->psiPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
417 connect(ui->omegaPushButton, SIGNAL(clicked(bool)), this, SLOT(greekLetterClicked()));
418
419 connectBoolProperty(ui->simbadGroupBox, "SearchDialog.useSimbad");
420 connectIntProperty(ui->searchRadiusSpinBox, "SearchDialog.simbadDist");
421 connectIntProperty(ui->resultsSpinBox, "SearchDialog.simbadCount");
422 connectBoolProperty(ui->allIDsCheckBox, "SearchDialog.simbadGetIds");
423 connectBoolProperty(ui->spectralClassCheckBox, "SearchDialog.simbadGetSpec");
424 connectBoolProperty(ui->morphoCheckBox, "SearchDialog.simbadGetMorpho");
425 connectBoolProperty(ui->typesCheckBox, "SearchDialog.simbadGetTypes");
426 connectBoolProperty(ui->dimsCheckBox, "SearchDialog.simbadGetDims");
427
428 populateSimbadServerList();
429 idx = ui->serverListComboBox->findData(simbadServerUrl, Qt::UserRole, Qt::MatchCaseSensitive);
430 if (idx==-1) // Use University of Strasbourg as default
431 idx = ui->serverListComboBox->findData(QVariant(DEF_SIMBAD_URL), Qt::UserRole, Qt::MatchCaseSensitive);
432 ui->serverListComboBox->setCurrentIndex(idx);
433 connect(ui->serverListComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(selectSimbadServer(int)));
434
435 connect(ui->checkBoxUseStartOfWords, SIGNAL(clicked(bool)), this, SLOT(enableStartOfWordsAutofill(bool)));
436 ui->checkBoxUseStartOfWords->setChecked(useStartOfWords);
437
438 connect(ui->checkBoxFOVCenterMarker, SIGNAL(clicked(bool)), this, SLOT(enableFOVCenterMarker(bool)));
439 ui->checkBoxFOVCenterMarker->setChecked(useFOVCenterMarker);
440
441 connect(ui->checkBoxLockPosition, SIGNAL(clicked(bool)), this, SLOT(enableLockPosition(bool)));
442 ui->checkBoxLockPosition->setChecked(useLockPosition);
443
444 // list views initialization
445 listModel = new QStringListModel(this);
446 proxyModel = new QSortFilterProxyModel(ui->objectsListView);
447 proxyModel->setSourceModel(listModel);
448 proxyModel->sort(0, Qt::AscendingOrder);
449 proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
450 ui->objectsListView->setModel(proxyModel);
451
452 connect(ui->objectTypeComboBox, SIGNAL(activated(int)), this, SLOT(updateListView(int)));
453 connect(ui->searchInListLineEdit, SIGNAL(textChanged(const QString&)), proxyModel, SLOT(setFilterWildcard(const QString&)));
454 connect(ui->searchInEnglishCheckBox, SIGNAL(toggled(bool)), this, SLOT(updateListTab()));
455 QAction *clearAction = ui->searchInListLineEdit->addAction(QIcon(":/graphicGui/uieBackspaceInputButton.png"), QLineEdit::ActionPosition::TrailingPosition);
456 connect(clearAction, SIGNAL(triggered()), this, SLOT(searchListClear()));
457 updateListTab();
458
459 connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(changeTab(int)));
460 // Set the focus directly on the line editDe if (ui->tabWidget->currentIndex()==0)
461 ui->lineEditSearchSkyObject->setFocus();
462
463 connect(StelApp::getInstance().getCore(), SIGNAL(updateSearchLists()), this, SLOT(updateListTab()));
464
465 QString style = "QLabel { color: rgb(238, 238, 238); }";
466 ui->simbadStatusLabel->setStyleSheet(style);
467 ui->labelGreekLetterTitle->setStyleSheet(style);
468 ui->simbadCooStatusLabel->setStyleSheet(style);
469
470 // Get data from previous session
471 loadRecentSearches();
472
473 // Create list model view
474 ui->searchListView->setModel(searchListModel);
475 searchListModel->setStringList(searchListModel->getValues());
476
477 // Auto display recent searches
478 QStringList recentMatches = listMatchingRecentObjects("", recentObjectSearchesData.maxSize, useStartOfWords);
479 resetSearchResultDisplay(recentMatches, recentMatches);
480 setPushButtonGotoSearch();
481
482 // Update max size of "recent object searches"
483 connect(ui->recentSearchSizeSpinBox, SIGNAL(editingFinished()), this, SLOT(recentSearchSizeEditingFinished()));
484 // Clear data from recent search object
485 connect(ui->recentSearchClearDataPushButton, SIGNAL(clicked()), this, SLOT(recentSearchClearDataClicked()));
486 populateRecentSearch();
487 }
488
populateRecentSearch()489 void SearchDialog::populateRecentSearch()
490 {
491 // Tooltip for recentSearchSizeSpinBox
492 QString toolTipComment = QString("%1: %2 | %3: %4 - %5 %6")
493 .arg(qc_("Default", "search tool"))
494 .arg(defaultMaxSize)
495 .arg(qc_("Range", "search tool"))
496 .arg(ui->recentSearchSizeSpinBox->minimum())
497 .arg(ui->recentSearchSizeSpinBox->maximum())
498 .arg(qc_("searches", "search tool"));
499 ui->recentSearchSizeSpinBox->setToolTip(toolTipComment);
500 setRecentSearchClearDataPushButton();
501 }
502
changeTab(int index)503 void SearchDialog::changeTab(int index)
504 {
505 if (index==0) // Search Tab
506 ui->lineEditSearchSkyObject->setFocus();
507
508 if (index==2) // Position
509 {
510 if (useFOVCenterMarker)
511 GETSTELMODULE(SpecialMarkersMgr)->setFlagFOVCenterMarker(true);
512 }
513 else
514 GETSTELMODULE(SpecialMarkersMgr)->setFlagFOVCenterMarker(fovCenterMarkerState);
515
516 if (index==3) // Lists
517 {
518 updateListTab();
519 ui->searchInListLineEdit->setFocus();
520 }
521 }
522
setHasSelectedFlag()523 void SearchDialog::setHasSelectedFlag()
524 {
525 flagHasSelectedText = true;
526 }
527
enableSimbadSearch(bool enable)528 void SearchDialog::enableSimbadSearch(bool enable)
529 {
530 useSimbad = enable;
531 conf->setValue("search/flag_search_online", useSimbad);
532 if (dialog && ui->simbadStatusLabel) ui->simbadStatusLabel->clear();
533 if (dialog && ui->simbadCooStatusLabel) ui->simbadCooStatusLabel->clear();
534 if (dialog && ui->simbadTab) ui->simbadTab->setEnabled(enable);
535 emit simbadUseChanged(enable);
536 }
537
setSimbadQueryDist(int dist)538 void SearchDialog::setSimbadQueryDist(int dist)
539 {
540 simbadDist=dist;
541 conf->setValue("search/simbad_query_dist", simbadDist);
542 emit simbadQueryDistChanged(dist);
543 }
544
setSimbadQueryCount(int count)545 void SearchDialog::setSimbadQueryCount(int count)
546 {
547 simbadCount=count;
548 conf->setValue("search/simbad_query_count", simbadCount);
549 emit simbadQueryCountChanged(count);
550 }
setSimbadGetsIds(bool b)551 void SearchDialog::setSimbadGetsIds(bool b)
552 {
553 simbadGetIds=b;
554 conf->setValue("search/simbad_query_IDs", b);
555 emit simbadGetsIdsChanged(b);
556 }
557
setSimbadGetsSpec(bool b)558 void SearchDialog::setSimbadGetsSpec(bool b)
559 {
560 simbadGetSpec=b;
561 conf->setValue("search/simbad_query_spec", b);
562 emit simbadGetsSpecChanged(b);
563 }
564
setSimbadGetsMorpho(bool b)565 void SearchDialog::setSimbadGetsMorpho(bool b)
566 {
567 simbadGetMorpho=b;
568 conf->setValue("search/simbad_query_morpho", b);
569 emit simbadGetsMorphoChanged(b);
570 }
571
setSimbadGetsTypes(bool b)572 void SearchDialog::setSimbadGetsTypes(bool b)
573 {
574 simbadGetTypes=b;
575 conf->setValue("search/simbad_query_types", b);
576 emit simbadGetsTypesChanged(b);
577 }
578
setSimbadGetsDims(bool b)579 void SearchDialog::setSimbadGetsDims(bool b)
580 {
581 simbadGetDims=b;
582 conf->setValue("search/simbad_query_dimensions", b);
583 emit simbadGetsDimsChanged(b);
584 }
585
recentSearchSizeEditingFinished()586 void SearchDialog::recentSearchSizeEditingFinished()
587 {
588 // Update max size in dialog and user data
589 int maxSize = ui->recentSearchSizeSpinBox->value();
590 setRecentSearchSize(maxSize);
591 maxSize = recentObjectSearchesData.maxSize; // Might not be the same
592
593 // Save maxSize to user's data
594 saveRecentSearches();
595
596 // Update search result on "Object" tab
597 onSearchTextChanged(ui->lineEditSearchSkyObject->text());
598 }
599
recentSearchClearDataClicked()600 void SearchDialog::recentSearchClearDataClicked()
601 {
602 // Clear recent list from current run
603 recentObjectSearchesData.recentList.clear();
604
605 // Save empty list to user's data file
606 saveRecentSearches();
607
608 // Update search result on "Object" tab
609 onSearchTextChanged(ui->lineEditSearchSkyObject->text());
610 }
611
setRecentSearchClearDataPushButton()612 void SearchDialog::setRecentSearchClearDataPushButton()
613 {
614 // Enable clear button if recent list is greater than 0
615 bool toEnable = recentObjectSearchesData.recentList.size() > 0;
616 ui->recentSearchClearDataPushButton->setEnabled(toEnable);
617 // Tool tip depends on recent list size
618 QString toolTipText;
619 toolTipText = toEnable ? q_("Clear search history: delete all search objects data") : q_("Clear search history: no data to delete");
620 ui->recentSearchClearDataPushButton->setToolTip(toolTipText);
621 }
622
enableStartOfWordsAutofill(bool enable)623 void SearchDialog::enableStartOfWordsAutofill(bool enable)
624 {
625 useStartOfWords = enable;
626 conf->setValue("search/flag_start_words", useStartOfWords);
627
628 // Update search result on "Object" tab
629 onSearchTextChanged(ui->lineEditSearchSkyObject->text());
630 }
631
enableLockPosition(bool enable)632 void SearchDialog::enableLockPosition(bool enable)
633 {
634 useLockPosition = enable;
635 conf->setValue("search/flag_lock_position", useLockPosition);
636 }
637
enableFOVCenterMarker(bool enable)638 void SearchDialog::enableFOVCenterMarker(bool enable)
639 {
640 useFOVCenterMarker = enable;
641 fovCenterMarkerState = GETSTELMODULE(SpecialMarkersMgr)->getFlagFOVCenterMarker();
642 conf->setValue("search/flag_fov_center_marker", useFOVCenterMarker);
643 }
644
setSimpleStyle()645 void SearchDialog::setSimpleStyle()
646 {
647 ui->AxisXSpinBox->setVisible(false);
648 ui->AxisXSpinBox->setVisible(false);
649 ui->simbadStatusLabel->setVisible(false);
650 ui->simbadCooStatusLabel->setVisible(false);
651 ui->AxisXLabel->setVisible(false);
652 ui->AxisYLabel->setVisible(false);
653 ui->coordinateSystemLabel->setVisible(false);
654 ui->coordinateSystemComboBox->setVisible(false);
655 }
656
657
manualPositionChanged()658 void SearchDialog::manualPositionChanged()
659 {
660 searchListModel->clearValues();
661 StelCore* core = StelApp::getInstance().getCore();
662 StelMovementMgr* mvmgr = GETSTELMODULE(StelMovementMgr);
663 Vec3d pos;
664 Vec3d aimUp;
665 double spinLong=ui->AxisXSpinBox->valueRadians();
666 double spinLat=ui->AxisYSpinBox->valueRadians();
667
668 // Since 0.15: proper handling of aimUp vector. This does not depend on the searched coordinate system, but on the MovementManager's C.S.
669 // However, if those are identical, we have a problem when we want to look right into the pole. (e.g. zenith), which requires a special up vector.
670 // aimUp depends on MovementMgr::MountMode mvmgr->mountMode!
671 mvmgr->setViewUpVector(Vec3d(0., 0., 1.));
672 aimUp=mvmgr->getViewUpVectorJ2000();
673 StelMovementMgr::MountMode mountMode=mvmgr->getMountMode();
674
675 switch (getCurrentCoordinateSystem())
676 {
677 case equatorialJ2000:
678 {
679 StelUtils::spheToRect(spinLong, spinLat, pos);
680 if ( (mountMode==StelMovementMgr::MountEquinoxEquatorial) && (fabs(spinLat)> (0.9*M_PI_2)) )
681 {
682 // make up vector more stable.
683 // Strictly mount should be in a new J2000 mode, but this here also stabilizes searching J2000 coordinates.
684 mvmgr->setViewUpVector(Vec3d(-cos(spinLong), -sin(spinLong), 0.) * (spinLat>0. ? 1. : -1. ));
685 aimUp=mvmgr->getViewUpVectorJ2000();
686 }
687 break;
688 }
689 case equatorial:
690 {
691 StelUtils::spheToRect(spinLong, spinLat, pos);
692 pos = core->equinoxEquToJ2000(pos, StelCore::RefractionOff);
693
694 if ( (mountMode==StelMovementMgr::MountEquinoxEquatorial) && (fabs(spinLat)> (0.9*M_PI_2)) )
695 {
696 // make up vector more stable.
697 mvmgr->setViewUpVector(Vec3d(-cos(spinLong), -sin(spinLong), 0.) * (spinLat>0. ? 1. : -1. ));
698 aimUp=mvmgr->getViewUpVectorJ2000();
699 }
700 break;
701 }
702 case horizontal:
703 {
704 double cx;
705 cx = 3.*M_PI - spinLong; // N is zero, E is 90 degrees
706 if (cx > 2.*M_PI)
707 cx -= 2.*M_PI;
708 StelUtils::spheToRect(cx, spinLat, pos);
709 pos = core->altAzToJ2000(pos, StelCore::RefractionOff);
710 core->setTimeRate(0.);
711
712 if ( (mountMode==StelMovementMgr::MountAltAzimuthal) && (fabs(spinLat)> (0.9*M_PI_2)) )
713 {
714 // make up vector more stable.
715 mvmgr->setViewUpVector(Vec3d(-cos(cx), -sin(cx), 0.) * (spinLat>0. ? 1. : -1. ));
716 aimUp=mvmgr->getViewUpVectorJ2000();
717 }
718 break;
719 }
720 case galactic:
721 {
722 StelUtils::spheToRect(spinLong, spinLat, pos);
723 pos = core->galacticToJ2000(pos);
724 if ( (mountMode==StelMovementMgr::MountGalactic) && (fabs(spinLat)> (0.9*M_PI_2)) )
725 {
726 // make up vector more stable.
727 mvmgr->setViewUpVector(Vec3d(-cos(spinLong), -sin(spinLong), 0.) * (spinLat>0. ? 1. : -1. ));
728 aimUp=mvmgr->getViewUpVectorJ2000();
729 }
730 break;
731 }
732 case supergalactic:
733 {
734 StelUtils::spheToRect(spinLong, spinLat, pos);
735 pos = core->supergalacticToJ2000(pos);
736 if ( (mountMode==StelMovementMgr::MountSupergalactic) && (fabs(spinLat)> (0.9*M_PI_2)) )
737 {
738 // make up vector more stable.
739 mvmgr->setViewUpVector(Vec3d(-cos(spinLong), -sin(spinLong), 0.) * (spinLat>0. ? 1. : -1. ));
740 aimUp=mvmgr->getViewUpVectorJ2000();
741 }
742 break;
743 }
744 case eclipticJ2000:
745 {
746 double ra, dec;
747 StelUtils::eclToEqu(spinLong, spinLat, GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(2451545.0), &ra, &dec);
748 StelUtils::spheToRect(ra, dec, pos);
749 break;
750 }
751 case ecliptic:
752 {
753 double ra, dec;
754 StelUtils::eclToEqu(spinLong, spinLat, GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(core->getJDE()), &ra, &dec);
755 StelUtils::spheToRect(ra, dec, pos);
756 pos = core->equinoxEquToJ2000(pos, StelCore::RefractionOff);
757 break;
758 }
759 }
760 mvmgr->setFlagTracking(false);
761 mvmgr->moveToJ2000(pos, aimUp, mvmgr->getAutoMoveDuration());
762 mvmgr->setFlagLockEquPos(useLockPosition);
763 }
764
onSearchTextChanged(const QString & text)765 void SearchDialog::onSearchTextChanged(const QString& text)
766 {
767 clearSimbadText(StelModule::ReplaceSelection);
768 // This block needs to go before the trimmedText.isEmpty() or the SIMBAD result does not
769 // get properly cleared.
770 if (useSimbad)
771 {
772 if (simbadReply)
773 {
774 disconnect(simbadReply, SIGNAL(statusChanged()), this, SLOT(onSimbadStatusChanged()));
775 delete simbadReply;
776 simbadReply=Q_NULLPTR;
777 }
778 simbadResults.clear();
779 }
780
781 // Use to adjust matches to be within range of maxNbItem
782 int maxNbItem;
783 QString trimmedText = text.trimmed();
784 if (trimmedText.isEmpty())
785 {
786 searchListModel->clearValues();
787
788 maxNbItem = recentObjectSearchesData.maxSize;
789 // Auto display recent searches
790 QStringList recentMatches = listMatchingRecentObjects(trimmedText, maxNbItem, useStartOfWords);
791 resetSearchResultDisplay(recentMatches, recentMatches);
792
793 ui->simbadStatusLabel->setText("");
794 ui->simbadCooStatusLabel->setText("");
795 setPushButtonGotoSearch();
796 }
797 else
798 {
799 if (useSimbad)
800 {
801 simbadReply = simbadSearcher->lookup(simbadServerUrl, trimmedText, 4);
802 onSimbadStatusChanged();
803 connect(simbadReply, SIGNAL(statusChanged()), this, SLOT(onSimbadStatusChanged()));
804 }
805
806 // Get possible objects
807 QStringList matches;
808 QStringList recentMatches;
809 QStringList allMatches;
810
811 QString greekText = substituteGreek(trimmedText);
812
813 int trimmedTextMaxNbItem = 13;
814 int greekTextMaxMbItem = 0;
815
816 if(greekText != trimmedText)
817 {
818 trimmedTextMaxNbItem = 8;
819 greekTextMaxMbItem = 18;
820
821 // Get recent matches
822 // trimmedText
823 recentMatches = listMatchingRecentObjects(trimmedText, trimmedTextMaxNbItem, useStartOfWords);
824 // greekText
825 recentMatches += listMatchingRecentObjects(greekText, (greekTextMaxMbItem - recentMatches.size()), useStartOfWords);
826
827 // Get rest of matches
828 // trimmedText
829 matches = objectMgr->listMatchingObjects(trimmedText, trimmedTextMaxNbItem, useStartOfWords);
830 // greekText
831 matches += objectMgr->listMatchingObjects(greekText, (greekTextMaxMbItem - matches.size()), useStartOfWords);
832 }
833 else
834 {
835 trimmedTextMaxNbItem = 13;
836
837 // Get recent matches
838 recentMatches = listMatchingRecentObjects(trimmedText, trimmedTextMaxNbItem, useStartOfWords);
839
840 // Get rest of matches
841 matches = objectMgr->listMatchingObjects(trimmedText, trimmedTextMaxNbItem, useStartOfWords);
842 }
843 // Check in case either number changes since they were
844 // hard coded
845 maxNbItem = qMax(greekTextMaxMbItem, trimmedTextMaxNbItem);
846
847 // Clean up matches
848 adjustMatchesResult(allMatches, recentMatches, matches, maxNbItem);
849
850 // Updates values
851 resetSearchResultDisplay(allMatches, recentMatches);
852
853 // Update push button enabled state
854 setPushButtonGotoSearch();
855 }
856
857 // Goto object when clicking in list
858 connect(ui->searchListView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(gotoObject(const QModelIndex&)), Qt::UniqueConnection);
859 connect(ui->searchListView, SIGNAL(activated(const QModelIndex&)), this, SLOT(gotoObject(const QModelIndex&)), Qt::UniqueConnection);
860 }
861
updateRecentSearchList(const QString & nameI18n)862 void SearchDialog::updateRecentSearchList(const QString &nameI18n)
863 {
864 if(nameI18n.isEmpty())
865 return;
866
867 // Prepend & remove duplicates
868 recentObjectSearchesData.recentList.prepend(nameI18n);
869 recentObjectSearchesData.recentList.removeDuplicates();
870
871 adjustRecentList(recentObjectSearchesData.maxSize);
872
873 // Auto display recent searches
874 QStringList recentMatches = listMatchingRecentObjects("", recentObjectSearchesData.maxSize, useStartOfWords);
875 resetSearchResultDisplay(recentMatches, recentMatches);
876 }
877
adjustRecentList(int maxSize)878 void SearchDialog::adjustRecentList(int maxSize)
879 {
880 // Check if max size was updated recently
881 maxSize = (maxSize >= 0) ? maxSize : recentObjectSearchesData.maxSize;
882 recentObjectSearchesData.maxSize = maxSize;
883
884 // Max amount of saved values "allowed"
885 int spinBoxMaxSize = ui->recentSearchSizeSpinBox->maximum();
886
887 // Only removing old searches if the list grows larger than the largest
888 // "allowed" size (to retain data in case the user switches from
889 // high to low size)
890 if( recentObjectSearchesData.recentList.size() > spinBoxMaxSize)
891 recentObjectSearchesData.recentList = recentObjectSearchesData.recentList.mid(0, spinBoxMaxSize);
892 }
893
adjustMatchesResult(QStringList & allMatches,QStringList & recentMatches,QStringList & matches,int maxNbItem)894 void SearchDialog::adjustMatchesResult(QStringList &allMatches, QStringList& recentMatches, QStringList& matches, int maxNbItem)
895 {
896 int tempSize;
897 QStringList tempMatches; // unsorted matches use for calculation
898 // not displaying
899
900 // remove possible duplicates from completion list
901 matches.removeDuplicates();
902
903 matches.sort(Qt::CaseInsensitive);
904 // objects with short names should be searched first
905 // examples: Moon, Hydra (moon); Jupiter, Ghost of Jupiter
906 stringLengthCompare comparator;
907 std::sort(matches.begin(), matches.end(), comparator);
908
909 // Adjust recent matches to prefered max size
910 recentMatches = recentMatches.mid(0, recentObjectSearchesData.maxSize);
911
912 // Find total size of both matches
913 tempMatches << recentMatches << matches; // unsorted
914 tempMatches.removeDuplicates();
915 tempSize = tempMatches.size();
916
917 // Adjust match size to be within range
918 if(tempSize>maxNbItem)
919 {
920 int i = tempSize - maxNbItem;
921 matches = matches.mid(0, matches.size() - i);
922 }
923
924 // Combine list: ordered by recent searches then relevance
925 allMatches << recentMatches << matches;
926
927 // Remove possible duplicates from both listQSt
928 allMatches.removeDuplicates();
929 }
930
931
resetSearchResultDisplay(QStringList allMatches,QStringList recentMatches)932 void SearchDialog::resetSearchResultDisplay(QStringList allMatches,
933 QStringList recentMatches)
934 {
935 // Updates values
936 searchListModel->appendValues(allMatches);
937 searchListModel->appendRecentValues(recentMatches);
938
939 // Update display
940 searchListModel->setValues(allMatches, recentMatches);
941 searchListModel->selectFirst();
942
943 // Update highlight to top
944 ui->searchListView->scrollToTop();
945 int row = searchListModel->getSelectedIdx();
946 ui->searchListView->setCurrentIndex(searchListModel->index(row));
947
948 // Enable clear data button
949 setRecentSearchClearDataPushButton();
950 }
951
setPushButtonGotoSearch()952 void SearchDialog::setPushButtonGotoSearch()
953 {
954 // Empty search and empty recently search object list
955 if (searchListModel->isEmpty() && (recentObjectSearchesData.recentList.size() == 0))
956 ui->pushButtonGotoSearchSkyObject->setEnabled(false); // Do not enable search button
957 else
958 ui->pushButtonGotoSearchSkyObject->setEnabled(true); // Do enable search button
959 }
960
loadRecentSearches()961 void SearchDialog::loadRecentSearches()
962 {
963 QVariantMap map;
964 QFile jsonFile(recentObjectSearchesJsonPath);
965 if(!jsonFile.open(QIODevice::ReadOnly))
966 {
967 qWarning() << "[Search] Can not open data file for recent searches"
968 << QDir::toNativeSeparators(recentObjectSearchesJsonPath);
969
970 // Use default value for recent search size
971 setRecentSearchSize(ui->recentSearchSizeSpinBox->value());
972 }
973 else
974 {
975 try
976 {
977 int readMaxSize;
978
979 map = StelJsonParser::parse(jsonFile.readAll()).toMap();
980 jsonFile.close();
981
982 QVariantMap recentSearchData = map.value("recentObjectSearches").toMap();
983
984 // Get user's maxSize data (if possible)
985 readMaxSize = recentSearchData.value("maxSize").toInt();
986 // Non-negative size only
987 recentObjectSearchesData.maxSize = (readMaxSize >= 0) ? readMaxSize : recentObjectSearchesData.maxSize;
988
989 // Update dialog size to match user's preference
990 ui->recentSearchSizeSpinBox->setValue(recentObjectSearchesData.maxSize);
991
992 // Get user's recentList data (if possible)
993 recentObjectSearchesData.recentList = recentSearchData.value("recentList").toStringList();
994 }
995 catch (std::runtime_error &e)
996 {
997 qWarning() << "[Search] File format is Wrong! Error:"
998 << e.what();
999 return;
1000 }
1001 }
1002 }
1003
saveRecentSearches()1004 void SearchDialog::saveRecentSearches()
1005 {
1006 if(recentObjectSearchesJsonPath.isEmpty())
1007 {
1008 qWarning() << "[Search] Error in saving recent object searches";
1009 return;
1010 }
1011
1012 QFile jsonFile(recentObjectSearchesJsonPath);
1013 if(!jsonFile.open(QFile::WriteOnly | QFile::Text))
1014 {
1015 qWarning() << "[Search] Recent search could not be save. A file can not be open for writing:"
1016 << QDir::toNativeSeparators(recentObjectSearchesJsonPath);
1017 return;
1018 }
1019
1020 QVariantMap rslDataList;
1021 rslDataList.insert("maxSize", recentObjectSearchesData.maxSize);
1022 rslDataList.insert("recentList", recentObjectSearchesData.recentList);
1023
1024 QVariantMap rsList;
1025 rsList.insert("recentObjectSearches", rslDataList);
1026
1027 // Convert the tree to JSON
1028 StelJsonParser::write(rsList, &jsonFile);
1029 jsonFile.flush();
1030 jsonFile.close();
1031 }
1032
listMatchingRecentObjects(const QString & objPrefix,int maxNbItem,bool useStartOfWords) const1033 QStringList SearchDialog::listMatchingRecentObjects(const QString& objPrefix, int maxNbItem, bool useStartOfWords) const
1034 {
1035 QStringList result;
1036
1037 if(maxNbItem <= 0)
1038 return result;
1039
1040 // For all recent objects:
1041 for (int i = 0; i < recentObjectSearchesData.recentList.size(); i++)
1042 {
1043 bool toAppend = useStartOfWords ? recentObjectSearchesData.recentList[i].startsWith(objPrefix, Qt::CaseInsensitive)
1044 : recentObjectSearchesData.recentList[i].contains(objPrefix, Qt::CaseInsensitive);
1045
1046 if(toAppend)
1047 result.append(recentObjectSearchesData.recentList[i]);
1048
1049 if (result.size() >= maxNbItem)
1050 break;
1051 }
1052 return result;
1053 }
1054
1055
lookupCoordinates()1056 void SearchDialog::lookupCoordinates()
1057 {
1058 if (!useSimbad)
1059 return;
1060
1061 StelCore * core=StelApp::getInstance().getCore();
1062 const QList<StelObjectP>& sel=GETSTELMODULE(StelObjectMgr)->getSelectedObject();
1063 if (sel.length()==0)
1064 return;
1065
1066 Vec3d coords=sel.at(0)->getJ2000EquatorialPos(core);
1067
1068 simbadReply = simbadSearcher->lookupCoords(simbadServerUrl, coords, getSimbadQueryCount(), 500,
1069 getSimbadQueryDist(), getSimbadGetsIds(), getSimbadGetsTypes(),
1070 getSimbadGetsSpec(), getSimbadGetsMorpho(), getSimbadGetsDims());
1071 onSimbadStatusChanged();
1072 connect(simbadReply, SIGNAL(statusChanged()), this, SLOT(onSimbadStatusChanged()));
1073 }
1074
clearSimbadText(StelModule::StelModuleSelectAction)1075 void SearchDialog::clearSimbadText(StelModule::StelModuleSelectAction)
1076 {
1077 ui->simbadCooResultsTextBrowser->clear();
1078 }
1079
1080 // Called when the current simbad query status changes
onSimbadStatusChanged()1081 void SearchDialog::onSimbadStatusChanged()
1082 {
1083 Q_ASSERT(simbadReply);
1084 int index = ui->tabWidget->currentIndex();
1085 QString info;
1086 if (simbadReply->getCurrentStatus()==SimbadLookupReply::SimbadLookupErrorOccured)
1087 {
1088 info = QString("%1: %2").arg(q_("Simbad Lookup Error")).arg(simbadReply->getErrorString());
1089 index==1 ? ui->simbadCooStatusLabel->setText(info) : ui->simbadStatusLabel->setText(info);
1090 setPushButtonGotoSearch();
1091 ui->simbadCooResultsTextBrowser->clear();
1092 }
1093 else
1094 {
1095 info = QString("%1: %2").arg(q_("Simbad Lookup")).arg(simbadReply->getCurrentStatusString());
1096 index==1 ? ui->simbadCooStatusLabel->setText(info) : ui->simbadStatusLabel->setText(info);
1097 // Query not over, don't disable button
1098 ui->pushButtonGotoSearchSkyObject->setEnabled(true);
1099 }
1100
1101 if (simbadReply->getCurrentStatus()==SimbadLookupReply::SimbadLookupFinished)
1102 {
1103 simbadResults = simbadReply->getResults();
1104 searchListModel->appendValues(simbadResults.keys());
1105 // Update push button enabled state
1106 setPushButtonGotoSearch();
1107 }
1108
1109 if (simbadReply->getCurrentStatus()==SimbadLookupReply::SimbadCoordinateLookupFinished)
1110 {
1111 QString ret = simbadReply->getResult();
1112 ui->simbadCooResultsTextBrowser->setText(ret);
1113 }
1114
1115 if (simbadReply->getCurrentStatus()!=SimbadLookupReply::SimbadLookupQuerying)
1116 {
1117 disconnect(simbadReply, SIGNAL(statusChanged()), this, SLOT(onSimbadStatusChanged()));
1118 delete simbadReply;
1119 simbadReply=Q_NULLPTR;
1120
1121 // Update push button enabled state
1122 setPushButtonGotoSearch();
1123 }
1124 }
1125
greekLetterClicked()1126 void SearchDialog::greekLetterClicked()
1127 {
1128 QPushButton *sender = reinterpret_cast<QPushButton *>(this->sender());
1129 QLineEdit* sso = ui->lineEditSearchSkyObject;
1130 QString text;
1131 if (sender)
1132 {
1133 shiftPressed ? text = sender->text().toUpper() : text = sender->text();
1134 if (flagHasSelectedText)
1135 {
1136 sso->setText(text);
1137 flagHasSelectedText = false;
1138 }
1139 else
1140 sso->setText(sso->text() + text);
1141 }
1142 sso->setFocus();
1143 }
1144
gotoObject()1145 void SearchDialog::gotoObject()
1146 {
1147 gotoObject(searchListModel->getSelected());
1148 }
1149
gotoObject(const QString & nameI18n)1150 void SearchDialog::gotoObject(const QString &nameI18n)
1151 {
1152 if (nameI18n.isEmpty())
1153 return;
1154
1155 // Save recent search list
1156 updateRecentSearchList(nameI18n);
1157 saveRecentSearches();
1158
1159 StelMovementMgr* mvmgr = GETSTELMODULE(StelMovementMgr);
1160 if (simbadResults.contains(nameI18n))
1161 {
1162 if (objectMgr->findAndSelect(nameI18n))
1163 {
1164 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1165 if (!newSelected.empty())
1166 {
1167 close();
1168 ui->lineEditSearchSkyObject->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
1169
1170 // Can't point to home planet
1171 if (newSelected[0]->getEnglishName()!=StelApp::getInstance().getCore()->getCurrentLocation().planetName)
1172 {
1173 mvmgr->moveToObject(newSelected[0], mvmgr->getAutoMoveDuration());
1174 mvmgr->setFlagTracking(true);
1175 }
1176 else
1177 GETSTELMODULE(StelObjectMgr)->unSelect();
1178 }
1179 }
1180 else
1181 {
1182 close();
1183 GETSTELMODULE(CustomObjectMgr)->addCustomObject(nameI18n, simbadResults[nameI18n]);
1184 ui->lineEditSearchSkyObject->clear();
1185 searchListModel->clearValues();
1186 if (objectMgr->findAndSelect(nameI18n))
1187 {
1188 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1189 // Can't point to home planet
1190 if (newSelected[0]->getEnglishName()!=StelApp::getInstance().getCore()->getCurrentLocation().planetName)
1191 {
1192 mvmgr->moveToObject(newSelected[0], mvmgr->getAutoMoveDuration());
1193 mvmgr->setFlagTracking(true);
1194 }
1195 else
1196 GETSTELMODULE(StelObjectMgr)->unSelect();
1197 }
1198 }
1199 }
1200 else if (objectMgr->findAndSelectI18n(nameI18n) || objectMgr->findAndSelect(nameI18n))
1201 {
1202 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1203 if (!newSelected.empty())
1204 {
1205 close();
1206 ui->lineEditSearchSkyObject->clear();
1207
1208 // Can't point to home planet
1209 if (newSelected[0]->getEnglishName()!=StelApp::getInstance().getCore()->getCurrentLocation().planetName)
1210 {
1211 mvmgr->moveToObject(newSelected[0], mvmgr->getAutoMoveDuration());
1212 mvmgr->setFlagTracking(true);
1213 }
1214 else
1215 GETSTELMODULE(StelObjectMgr)->unSelect();
1216 }
1217 }
1218 simbadResults.clear();
1219 }
1220
gotoObject(const QModelIndex & modelIndex)1221 void SearchDialog::gotoObject(const QModelIndex &modelIndex)
1222 {
1223 gotoObject(modelIndex.model()->data(modelIndex, Qt::DisplayRole).toString());
1224 }
1225
searchListClear()1226 void SearchDialog::searchListClear()
1227 {
1228 ui->searchInListLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
1229 }
1230
eventFilter(QObject *,QEvent * event)1231 bool SearchDialog::eventFilter(QObject*, QEvent *event)
1232 {
1233 if (event->type() == QEvent::KeyPress)
1234 {
1235 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
1236
1237 if (keyEvent->key() == Qt::Key_Shift)
1238 {
1239 shiftPressed = true;
1240 event->accept();
1241 return true;
1242 }
1243 }
1244 if (event->type() == QEvent::KeyRelease)
1245 {
1246 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
1247
1248 if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Down)
1249 {
1250 searchListModel->selectNext();
1251 int row = searchListModel->getSelectedIdx();
1252 ui->searchListView->scrollTo(searchListModel->index(row));
1253 ui->searchListView->setCurrentIndex(searchListModel->index(row));
1254 event->accept();
1255 return true;
1256 }
1257 if (keyEvent->key() == Qt::Key_Up)
1258 {
1259 searchListModel->selectPrevious();
1260 int row = searchListModel->getSelectedIdx();
1261 ui->searchListView->scrollTo(searchListModel->index(row));
1262 ui->searchListView->setCurrentIndex(searchListModel->index(row));
1263 event->accept();
1264 return true;
1265 }
1266 if (keyEvent->key() == Qt::Key_Shift)
1267 {
1268 shiftPressed = false;
1269 event->accept();
1270 return true;
1271 }
1272 }
1273 if (event->type() == QEvent::Show)
1274 {
1275 if (!extSearchText.isEmpty())
1276 {
1277 ui->lineEditSearchSkyObject->setText(extSearchText);
1278 ui->lineEditSearchSkyObject->selectAll();
1279 extSearchText.clear();
1280 }
1281 }
1282 return false;
1283 }
1284
substituteGreek(const QString & keyString)1285 QString SearchDialog::substituteGreek(const QString& keyString)
1286 {
1287 if (!keyString.contains(' '))
1288 return getGreekLetterByName(keyString);
1289 else
1290 {
1291 #if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
1292 QStringList nameComponents = keyString.split(" ", Qt::SkipEmptyParts);
1293 #else
1294 QStringList nameComponents = keyString.split(" ", QString::SkipEmptyParts);
1295 #endif
1296 if(!nameComponents.empty())
1297 nameComponents[0] = getGreekLetterByName(nameComponents[0]);
1298 return nameComponents.join(" ");
1299 }
1300 }
1301
getGreekLetterByName(const QString & potentialGreekLetterName)1302 QString SearchDialog::getGreekLetterByName(const QString& potentialGreekLetterName)
1303 {
1304 if(staticData.greekLetters.contains(potentialGreekLetterName))
1305 return staticData.greekLetters[potentialGreekLetterName];
1306
1307 // There can be indices (e.g. "α1 Cen" instead of "α Cen A"), so strip
1308 // any trailing digit.
1309 int lastCharacterIndex = potentialGreekLetterName.length()-1;
1310 if(potentialGreekLetterName.at(lastCharacterIndex).isDigit())
1311 {
1312 QChar digit = potentialGreekLetterName.at(lastCharacterIndex);
1313 QString name = potentialGreekLetterName.left(lastCharacterIndex);
1314 if(staticData.greekLetters.contains(name))
1315 return staticData.greekLetters[name] + digit;
1316 }
1317
1318 return potentialGreekLetterName;
1319 }
1320
populateSimbadServerList()1321 void SearchDialog::populateSimbadServerList()
1322 {
1323 Q_ASSERT(ui->serverListComboBox);
1324
1325 QComboBox* servers = ui->serverListComboBox;
1326 //Save the current selection to be restored later
1327 servers->blockSignals(true);
1328 int index = servers->currentIndex();
1329 QVariant selectedUrl = servers->itemData(index);
1330 servers->clear();
1331 //For each server, display the localized description and store the URL as user data.
1332 servers->addItem(q_("University of Strasbourg (France)"), DEF_SIMBAD_URL);
1333 servers->addItem(q_("Harvard University (USA)"), "http://simbad.harvard.edu/");
1334
1335 //Restore the selection
1336 index = servers->findData(selectedUrl, Qt::UserRole, Qt::MatchCaseSensitive);
1337 servers->setCurrentIndex(index);
1338 servers->model()->sort(0);
1339 servers->blockSignals(false);
1340 }
1341
setRecentSearchSize(int maxSize)1342 void SearchDialog::setRecentSearchSize(int maxSize)
1343 {
1344 adjustRecentList(maxSize);
1345 saveRecentSearches();
1346 conf->setValue("search/recentSearchSize", recentObjectSearchesData.maxSize);
1347 }
1348
selectSimbadServer(int index)1349 void SearchDialog::selectSimbadServer(int index)
1350 {
1351 index < 0 ? simbadServerUrl = DEF_SIMBAD_URL : simbadServerUrl = ui->serverListComboBox->itemData(index).toString();
1352 conf->setValue("search/simbad_server_url", simbadServerUrl);
1353 }
1354
updateListView(int index)1355 void SearchDialog::updateListView(int index)
1356 {
1357 QString moduleId = ui->objectTypeComboBox->itemData(index).toString();
1358 bool englishNames = ui->searchInEnglishCheckBox->isChecked();
1359 ui->searchInListLineEdit->setText(""); // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
1360 ui->objectsListView->blockSignals(true);
1361 ui->objectsListView->reset();
1362 listModel->setStringList(objectMgr->listAllModuleObjects(moduleId, englishNames));
1363 proxyModel->setSourceModel(listModel);
1364 proxyModel->sort(0, Qt::AscendingOrder);
1365 ui->objectsListView->blockSignals(false);
1366 //bugfix: prevent multiple connections, which seems to have happened before
1367 connect(ui->objectsListView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(gotoObject(const QModelIndex&)), Qt::UniqueConnection);
1368 }
1369
updateListTab()1370 void SearchDialog::updateListTab()
1371 {
1372 QVariant selectedObjectId;
1373 QComboBox* objects = ui->objectTypeComboBox;
1374 int index = objects->currentIndex();
1375 if (index < 0)
1376 selectedObjectId = QVariant("AsterismMgr"); // Let's enable "Asterisms" by default!
1377 else
1378 selectedObjectId = objects->itemData(index, Qt::UserRole);
1379
1380 if (StelApp::getInstance().getLocaleMgr().getAppLanguage().startsWith("en"))
1381 ui->searchInEnglishCheckBox->hide(); // hide "names in English" checkbox
1382 else
1383 ui->searchInEnglishCheckBox->show();
1384 objects->blockSignals(true);
1385 objects->clear();
1386 QMap<QString, QString> modulesMap = objectMgr->objectModulesMap();
1387 for (auto it = modulesMap.begin(); it != modulesMap.end(); ++it)
1388 {
1389 // List of objects is not empty, let's add name of module or section!
1390 if (!objectMgr->listAllModuleObjects(it.key(), ui->searchInEnglishCheckBox->isChecked()).isEmpty())
1391 objects->addItem(q_(it.value()), QVariant(it.key()));
1392 }
1393 index = objects->findData(selectedObjectId, Qt::UserRole, Qt::MatchCaseSensitive);
1394 objects->setCurrentIndex(index);
1395 objects->model()->sort(0, Qt::AscendingOrder);
1396 objects->blockSignals(false);
1397 updateListView(objects->currentIndex());
1398 }
1399
showContextMenu(const QPoint & pt)1400 void SearchDialog::showContextMenu(const QPoint &pt)
1401 {
1402 QMenu *menu = ui->lineEditSearchSkyObject->createStandardContextMenu();
1403 menu->addSeparator();
1404 QString clipText;
1405 QClipboard *clipboard = QApplication::clipboard();
1406 if (clipboard)
1407 clipText = clipboard->text();
1408 if (!clipText.isEmpty())
1409 {
1410 if (clipText.length()>12)
1411 clipText = clipText.right(9) + "...";
1412 clipText = "\t(" + clipText + ")";
1413 }
1414
1415 menu->addAction(q_("Paste and Search") + clipText, this, SLOT(pasteAndGo()));
1416 menu->exec(ui->lineEditSearchSkyObject->mapToGlobal(pt));
1417 delete menu;
1418 }
1419
pasteAndGo()1420 void SearchDialog::pasteAndGo()
1421 {
1422 // https://wiki.qt.io/Technical_FAQ#Why_does_the_memory_keep_increasing_when_repeatedly_pasting_text_and_calling_clear.28.29_in_a_QLineEdit.3F
1423 ui->lineEditSearchSkyObject->setText(""); // clear current text
1424 ui->lineEditSearchSkyObject->paste(); // paste text from clipboard
1425 gotoObject(); // go to first finded object
1426 }
1427
setVisible(bool v)1428 void SearchDialog::setVisible(bool v)
1429 {
1430 StelDialog::setVisible(v);
1431
1432 // if from a previous search action, the first tab is shown but input line is not in focus,
1433 // force focus on input line.
1434 if (v && (ui->tabWidget->currentIndex()==0))
1435 {
1436 ui->lineEditSearchSkyObject->setFocus(Qt::PopupFocusReason);
1437 }
1438 }
1439