1 // winstarbrowser.cpp
2 //
3 // Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
4 //
5 // Star browser tool for Windows.
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 #include <string>
13 #include <algorithm>
14 #include <set>
15 #include <windows.h>
16 #include <commctrl.h>
17 #include <cstring>
18 #include "winstarbrowser.h"
19 #include "celutil/winutil.h"
20
21 #include "res/resource.h"
22
23 extern void SetMouseCursor(LPCTSTR lpCursor);
24
25 using namespace std;
26
27 static const int MinListStars = 10;
28 static const int MaxListStars = 500;
29 static const int DefaultListStars = 100;
30
31
32 // TODO: More of the functions in this module should be converted to
33 // methods of the StarBrowser class.
34
35 enum {
36 BrightestStars = 0,
37 NearestStars = 1,
38 StarsWithPlanets = 2,
39 };
40
toMicroLY(const Point3f & p)41 static Point3f toMicroLY(const Point3f& p)
42 {
43 return Point3f(p.x * 1e6f, p.y * 1e6f, p.z * 1e6f);
44 }
45
fromMicroLY(const Point3f & p)46 static Point3f fromMicroLY(const Point3f& p)
47 {
48 return Point3f(p.x * 1e-6f, p.y * 1e-6f, p.z * 1e-6f);
49 }
50
51
InitStarBrowserColumns(HWND listView)52 bool InitStarBrowserColumns(HWND listView)
53 {
54 LVCOLUMN lvc;
55 LVCOLUMN columns[5];
56
57 lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
58 lvc.fmt = LVCFMT_LEFT;
59 lvc.cx = 60;
60 lvc.pszText = "";
61
62 int nColumns = sizeof(columns) / sizeof(columns[0]);
63 int i;
64
65 for (i = 0; i < nColumns; i++)
66 columns[i] = lvc;
67
68 bind_textdomain_codeset("celestia", CurrentCP());
69
70 columns[0].pszText = _("Name");
71 columns[0].cx = 100;
72 columns[1].pszText = _("Distance (ly)");
73 columns[1].fmt = LVCFMT_RIGHT;
74 columns[1].cx = 75;
75 columns[2].pszText = _("App. mag");
76 columns[2].fmt = LVCFMT_RIGHT;
77 columns[3].pszText = _("Abs. mag");
78 columns[3].fmt = LVCFMT_RIGHT;
79 columns[4].pszText = _("Type");
80
81 bind_textdomain_codeset("celestia", "UTF8");
82
83 for (i = 0; i < nColumns; i++)
84 {
85 columns[i].iSubItem = i;
86 if (ListView_InsertColumn(listView, i, &columns[i]) == -1)
87 return false;
88 }
89
90 return true;
91 }
92
93
94 struct CloserStarPredicate
95 {
96 Point3f pos;
operator ()CloserStarPredicate97 bool operator()(const Star* star0, const Star* star1) const
98 {
99 return ((pos - star0->getPosition()).lengthSquared() <
100 (pos - star1->getPosition()).lengthSquared());
101
102 }
103 };
104
105 struct BrighterStarPredicate
106 {
107 Point3f pos;
108 UniversalCoord ucPos;
operator ()BrighterStarPredicate109 bool operator()(const Star* star0, const Star* star1) const
110 {
111 float d0 = pos.distanceTo(star0->getPosition());
112 float d1 = pos.distanceTo(star1->getPosition());
113
114 // If the stars are closer than one light year, use
115 // a more precise distance estimate.
116 if (d0 < 1.0f)
117 d0 = (toMicroLY(star0->getPosition()) - ucPos).length() * 1e-6f;
118 if (d1 < 1.0f)
119 d1 = (toMicroLY(star1->getPosition()) - ucPos).length() * 1e-6f;
120
121 return (star0->getApparentMagnitude(d0) <
122 star1->getApparentMagnitude(d1));
123 }
124 };
125
126 struct SolarSystemPredicate
127 {
128 Point3f pos;
129 SolarSystemCatalog* solarSystems;
130
operator ()SolarSystemPredicate131 bool operator()(const Star* star0, const Star* star1) const
132 {
133 SolarSystemCatalog::iterator iter;
134
135 iter = solarSystems->find(star0->getCatalogNumber());
136 bool hasPlanets0 = (iter != solarSystems->end());
137 iter = solarSystems->find(star1->getCatalogNumber());
138 bool hasPlanets1 = (iter != solarSystems->end());
139 if (hasPlanets1 == hasPlanets0)
140 {
141 return ((pos - star0->getPosition()).lengthSquared() <
142 (pos - star1->getPosition()).lengthSquared());
143 }
144 else
145 {
146 return hasPlanets0;
147 }
148 }
149 };
150
151
152 // Find the nearest/brightest/X-est N stars in a database. The
153 // supplied predicate determines which of two stars is a better match.
154 template<class Pred> vector<const Star*>*
FindStars(const StarDatabase & stardb,Pred pred,int nStars)155 FindStars(const StarDatabase& stardb, Pred pred, int nStars)
156 {
157 vector<const Star*>* finalStars = new vector<const Star*>();
158 if (nStars == 0)
159 return finalStars;
160
161 typedef multiset<const Star*, Pred> StarSet;
162 StarSet firstStars(pred);
163
164 int totalStars = stardb.size();
165 if (totalStars < nStars)
166 nStars = totalStars;
167
168 // We'll need at least nStars in the set, so first fill
169 // up the list indiscriminately.
170 int i = 0;
171 for (i = 0; i < nStars; i++)
172 {
173 Star* star = stardb.getStar(i);
174 if (star->getVisibility())
175 firstStars.insert(star);
176 }
177
178 // From here on, only add a star to the set if it's
179 // a better match than the worst matching star already
180 // in the set.
181 const Star* lastStar = *--firstStars.end();
182 for (; i < totalStars; i++)
183 {
184 Star* star = stardb.getStar(i);
185 if (star->getVisibility() && pred(star, lastStar))
186 {
187 firstStars.insert(star);
188 firstStars.erase(--firstStars.end());
189 lastStar = *--firstStars.end();
190 }
191 }
192
193 // Move the best matching stars into the vector
194 finalStars->reserve(nStars);
195 for (StarSet::const_iterator iter = firstStars.begin();
196 iter != firstStars.end(); iter++)
197 {
198 finalStars->insert(finalStars->end(), *iter);
199 }
200
201 return finalStars;
202 }
203
204
InitStarBrowserLVItems(HWND listView,vector<const Star * > & stars)205 bool InitStarBrowserLVItems(HWND listView, vector<const Star*>& stars)
206 {
207 LVITEM lvi;
208
209 lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
210 lvi.state = 0;
211 lvi.stateMask = 0;
212 lvi.pszText = LPSTR_TEXTCALLBACK;
213
214 for (unsigned int i = 0; i < stars.size(); i++)
215 {
216 lvi.iItem = i;
217 lvi.iSubItem = 0;
218 lvi.lParam = (LPARAM) stars[i];
219 ListView_InsertItem(listView, &lvi);
220 }
221
222 return true;
223 }
224
225
InitStarBrowserItems(HWND listView,StarBrowser * browser)226 bool InitStarBrowserItems(HWND listView, StarBrowser* browser)
227 {
228 Universe* univ = browser->appCore->getSimulation()->getUniverse();
229 StarDatabase* stardb = univ->getStarCatalog();
230 SolarSystemCatalog* solarSystems = univ->getSolarSystemCatalog();
231
232 vector<const Star*>* stars = NULL;
233 switch (browser->predicate)
234 {
235 case BrightestStars:
236 {
237 BrighterStarPredicate brighterPred;
238 brighterPred.pos = browser->pos;
239 brighterPred.ucPos = browser->ucPos;
240 stars = FindStars(*stardb, brighterPred, browser->nStars);
241 }
242 break;
243
244 case NearestStars:
245 {
246 CloserStarPredicate closerPred;
247 closerPred.pos = browser->pos;
248 stars = FindStars(*stardb, closerPred, browser->nStars);
249 }
250 break;
251
252 case StarsWithPlanets:
253 {
254 if (solarSystems == NULL)
255 return false;
256 SolarSystemPredicate solarSysPred;
257 solarSysPred.pos = browser->pos;
258 solarSysPred.solarSystems = solarSystems;
259 stars = FindStars(*stardb, solarSysPred,
260 min((unsigned int) browser->nStars, solarSystems->size()));
261 }
262 break;
263
264 default:
265 return false;
266 }
267
268 bool succeeded = InitStarBrowserLVItems(listView, *stars);
269 delete stars;
270
271 return succeeded;
272 }
273
274
275 // Crud used for the list item display callbacks
276 static string starNameString("");
277 static char callbackScratch[256];
278
279 struct StarBrowserSortInfo
280 {
281 int subItem;
282 Point3f pos;
283 UniversalCoord ucPos;
284 };
285
StarBrowserCompareFunc(LPARAM lParam0,LPARAM lParam1,LPARAM lParamSort)286 int CALLBACK StarBrowserCompareFunc(LPARAM lParam0, LPARAM lParam1,
287 LPARAM lParamSort)
288 {
289 StarBrowserSortInfo* sortInfo = reinterpret_cast<StarBrowserSortInfo*>(lParamSort);
290 Star* star0 = reinterpret_cast<Star*>(lParam0);
291 Star* star1 = reinterpret_cast<Star*>(lParam1);
292
293 switch (sortInfo->subItem)
294 {
295 case 0:
296 return 0;
297
298 case 1:
299 {
300 float d0 = sortInfo->pos.distanceTo(star0->getPosition());
301 float d1 = sortInfo->pos.distanceTo(star1->getPosition());
302 return (int) sign(d0 - d1);
303 }
304
305 case 2:
306 {
307 float d0 = sortInfo->pos.distanceTo(star0->getPosition());
308 float d1 = sortInfo->pos.distanceTo(star1->getPosition());
309 if (d0 < 1.0f)
310 d0 = (toMicroLY(star0->getPosition()) - sortInfo->ucPos).length() * 1e-6f;
311 if (d1 < 1.0f)
312 d1 = (toMicroLY(star1->getPosition()) - sortInfo->ucPos).length() * 1e-6f;
313 return (int) sign(astro::absToAppMag(star0->getAbsoluteMagnitude(), d0) -
314 astro::absToAppMag(star1->getAbsoluteMagnitude(), d1));
315 }
316
317 case 3:
318 return (int) sign(star0->getAbsoluteMagnitude() - star1->getAbsoluteMagnitude());
319
320 case 4:
321 return strcmp(star0->getSpectralType(), star1->getSpectralType());
322
323 default:
324 return 0;
325 }
326 }
327
328
StarBrowserDisplayItem(LPNMLVDISPINFOA nm,StarBrowser * browser)329 void StarBrowserDisplayItem(LPNMLVDISPINFOA nm, StarBrowser* browser)
330 {
331 double tdb = browser->appCore->getSimulation()->getTime();
332
333 Star* star = reinterpret_cast<Star*>(nm->item.lParam);
334 if (star == NULL)
335 {
336 nm->item.pszText = "";
337 return;
338 }
339
340 switch (nm->item.iSubItem)
341 {
342 case 0:
343 {
344 Universe* u = browser->appCore->getSimulation()->getUniverse();
345 starNameString = UTF8ToCurrentCP(u->getStarCatalog()->getStarName(*star));
346 nm->item.pszText = const_cast<char*>(starNameString.c_str());
347 }
348 break;
349
350 case 1:
351 {
352 Vec3d r = star->getPosition(tdb) - browser->ucPos;
353 sprintf(callbackScratch, "%.4g", r.length() * 1.0e-6);
354 nm->item.pszText = callbackScratch;
355 }
356 break;
357
358 case 2:
359 {
360 Vec3d r = star->getPosition(tdb) - browser->ucPos;
361 double appMag = astro::absToAppMag((double) star->getAbsoluteMagnitude(),
362 (r.length() * 1e-6));
363 sprintf(callbackScratch, "%.2f", appMag);
364 nm->item.pszText = callbackScratch;
365 }
366 break;
367
368 case 3:
369 sprintf(callbackScratch, "%.2f", star->getAbsoluteMagnitude());
370 nm->item.pszText = callbackScratch;
371 break;
372
373 case 4:
374 strncpy(callbackScratch, star->getSpectralType(),
375 sizeof(callbackScratch));
376 callbackScratch[sizeof(callbackScratch) - 1] = '\0';
377 nm->item.pszText = callbackScratch;
378 break;
379 }
380 }
381
RefreshItems(HWND hDlg,StarBrowser * browser)382 void RefreshItems(HWND hDlg, StarBrowser* browser)
383 {
384 SetMouseCursor(IDC_WAIT);
385
386 Simulation* sim = browser->appCore->getSimulation();
387 browser->ucPos = sim->getObserver().getPosition();
388 browser->pos = fromMicroLY((Point3f) browser->ucPos);
389 HWND hwnd = GetDlgItem(hDlg, IDC_STARBROWSER_LIST);
390 if (hwnd != 0)
391 {
392 ListView_DeleteAllItems(hwnd);
393 InitStarBrowserItems(hwnd, browser);
394 }
395
396 SetMouseCursor(IDC_ARROW);
397 }
398
StarBrowserProc(HWND hDlg,UINT message,UINT wParam,LONG lParam)399 BOOL APIENTRY StarBrowserProc(HWND hDlg,
400 UINT message,
401 UINT wParam,
402 LONG lParam)
403 {
404 StarBrowser* browser = reinterpret_cast<StarBrowser*>(GetWindowLong(hDlg, DWL_USER));
405
406 switch (message)
407 {
408 case WM_INITDIALOG:
409 {
410 StarBrowser* browser = reinterpret_cast<StarBrowser*>(lParam);
411 if (browser == NULL)
412 return EndDialog(hDlg, 0);
413 SetWindowLong(hDlg, DWL_USER, lParam);
414
415 HWND hwnd = GetDlgItem(hDlg, IDC_STARBROWSER_LIST);
416 InitStarBrowserColumns(hwnd);
417 InitStarBrowserItems(hwnd, browser);
418 CheckRadioButton(hDlg, IDC_RADIO_NEAREST, IDC_RADIO_WITHPLANETS, IDC_RADIO_NEAREST);
419
420 //Initialize Max Stars edit box
421 char val[16];
422 hwnd = GetDlgItem(hDlg, IDC_MAXSTARS_EDIT);
423 sprintf(val, "%d", DefaultListStars);
424 SetWindowText(hwnd, val);
425 SendMessage(hwnd, EM_LIMITTEXT, 3, 0);
426
427 //Initialize Max Stars Slider control
428 SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_SETRANGE,
429 (WPARAM)TRUE, (LPARAM)MAKELONG(MinListStars, MaxListStars));
430 SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_SETPOS,
431 (WPARAM)TRUE, (LPARAM)DefaultListStars);
432
433 return(TRUE);
434 }
435
436 case WM_DESTROY:
437 if (browser != NULL && browser->parent != NULL)
438 {
439 SendMessage(browser->parent, WM_COMMAND, IDCLOSE,
440 reinterpret_cast<LPARAM>(browser));
441 }
442 break;
443
444 case WM_COMMAND:
445 switch (LOWORD(wParam))
446 {
447 case IDOK:
448 case IDCANCEL:
449 if (browser != NULL && browser->parent != NULL)
450 {
451 SendMessage(browser->parent, WM_COMMAND, IDCLOSE,
452 reinterpret_cast<LPARAM>(browser));
453 }
454 EndDialog(hDlg, 0);
455 return TRUE;
456
457 case IDC_BUTTON_CENTER:
458 browser->appCore->charEntered('c');
459 break;
460
461 case IDC_BUTTON_GOTO:
462 browser->appCore->charEntered('G');
463 break;
464
465 case IDC_RADIO_BRIGHTEST:
466 browser->predicate = BrightestStars;
467 RefreshItems(hDlg, browser);
468 break;
469
470 case IDC_RADIO_NEAREST:
471 browser->predicate = NearestStars;
472 RefreshItems(hDlg, browser);
473 break;
474
475 case IDC_RADIO_WITHPLANETS:
476 browser->predicate = StarsWithPlanets;
477 RefreshItems(hDlg, browser);
478 break;
479
480 case IDC_BUTTON_REFRESH:
481 RefreshItems(hDlg, browser);
482 break;
483
484 case IDC_MAXSTARS_EDIT:
485 // TODO: browser != NULL check should be in a lot more places
486 if (HIWORD(wParam) == EN_KILLFOCUS && browser != NULL)
487 {
488 char val[16];
489 DWORD nNewStars;
490 DWORD minRange, maxRange;
491 GetWindowText((HWND) lParam, val, sizeof(val));
492 nNewStars = atoi(val);
493
494 // Check if new value is different from old. Don't want to
495 // cause a refresh to occur if not necessary.
496 if (nNewStars != browser->nStars)
497 {
498 minRange = SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_GETRANGEMIN, 0, 0);
499 maxRange = SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_GETRANGEMAX, 0, 0);
500 if (nNewStars < minRange)
501 nNewStars = minRange;
502 else if (nNewStars > maxRange)
503 nNewStars = maxRange;
504
505 // If new value has been adjusted from what was entered,
506 // reflect new value back in edit control.
507 if (atoi(val) != nNewStars)
508 {
509 sprintf(val, "%d", nNewStars);
510 SetWindowText((HWND)lParam, val);
511 }
512
513 // Recheck value if different from original.
514 if (nNewStars != browser->nStars)
515 {
516 browser->nStars = nNewStars;
517 SendDlgItemMessage(hDlg,
518 IDC_MAXSTARS_SLIDER,
519 TBM_SETPOS,
520 (WPARAM) TRUE,
521 (LPARAM) browser->nStars);
522 RefreshItems(hDlg, browser);
523 }
524 }
525 }
526 break;
527 }
528 break;
529
530 case WM_NOTIFY:
531 {
532 LPNMHDR hdr = (LPNMHDR) lParam;
533
534 if (hdr->idFrom == IDC_STARBROWSER_LIST && browser != NULL)
535 {
536 switch(hdr->code)
537 {
538 case LVN_GETDISPINFO:
539 StarBrowserDisplayItem((LPNMLVDISPINFOA) lParam, browser);
540 break;
541 case LVN_ITEMCHANGED:
542 {
543 LPNMLISTVIEW nm = (LPNMLISTVIEW) lParam;
544 if ((nm->uNewState & LVIS_SELECTED) != 0)
545 {
546 Simulation* sim = browser->appCore->getSimulation();
547 Star* star = reinterpret_cast<Star*>(nm->lParam);
548 if (star != NULL)
549 sim->setSelection(Selection(star));
550 }
551 break;
552 }
553 case LVN_COLUMNCLICK:
554 {
555 HWND hwnd = GetDlgItem(hDlg, IDC_STARBROWSER_LIST);
556 if (hwnd != 0)
557 {
558 LPNMLISTVIEW nm = (LPNMLISTVIEW) lParam;
559 StarBrowserSortInfo sortInfo;
560 sortInfo.subItem = nm->iSubItem;
561 sortInfo.ucPos = browser->ucPos;
562 sortInfo.pos = browser->pos;
563 ListView_SortItems(hwnd, StarBrowserCompareFunc,
564 reinterpret_cast<LPARAM>(&sortInfo));
565 }
566 }
567
568 }
569 }
570 }
571 break;
572
573 case WM_HSCROLL:
574 {
575 WORD sbValue = LOWORD(wParam);
576 switch(sbValue)
577 {
578 case SB_THUMBTRACK:
579 {
580 char val[16];
581 HWND hwnd = GetDlgItem(hDlg, IDC_MAXSTARS_EDIT);
582 sprintf(val, "%d", HIWORD(wParam));
583 SetWindowText(hwnd, val);
584 break;
585 }
586 case SB_THUMBPOSITION:
587 {
588 browser->nStars = (int)HIWORD(wParam);
589 RefreshItems(hDlg, browser);
590 break;
591 }
592 }
593 }
594 break;
595 }
596
597 return FALSE;
598 }
599
600
StarBrowser(HINSTANCE appInstance,HWND _parent,CelestiaCore * _appCore)601 StarBrowser::StarBrowser(HINSTANCE appInstance,
602 HWND _parent,
603 CelestiaCore* _appCore) :
604 appCore(_appCore),
605 parent(_parent)
606 {
607 ucPos = appCore->getSimulation()->getObserver().getPosition();
608 pos = fromMicroLY((Point3f) ucPos);
609
610 predicate = NearestStars;
611 nStars = DefaultListStars;
612
613 hwnd = CreateDialogParam(appInstance,
614 MAKEINTRESOURCE(IDD_STARBROWSER),
615 parent,
616 StarBrowserProc,
617 reinterpret_cast<LONG>(this));
618 }
619
620
~StarBrowser()621 StarBrowser::~StarBrowser()
622 {
623 SetWindowLong(hwnd, DWL_USER, 0);
624 }
625