Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

winstarbrowser.cpp

Go to the documentation of this file.
00001 // winstarbrowser.cpp
00002 // 
00003 // Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
00004 //
00005 // Star browser tool for Windows.
00006 //
00007 // This program is free software; you can redistribute it and/or
00008 // modify it under the terms of the GNU General Public License
00009 // as published by the Free Software Foundation; either version 2
00010 // of the License, or (at your option) any later version.
00011 
00012 #include <string>
00013 #include <algorithm>
00014 #include <set>
00015 #include <windows.h>
00016 #include <commctrl.h>
00017 #include <cstring>
00018 #include "winstarbrowser.h"
00019 
00020 #include "res/resource.h"
00021 
00022 extern void SetMouseCursor(LPCTSTR lpCursor);
00023 
00024 using namespace std;
00025 
00026 static const int MinListStars = 10;
00027 static const int MaxListStars = 500;
00028 static const int DefaultListStars = 100;
00029 
00030 
00031 // TODO: More of the functions in this module should be converted to
00032 // methods of the StarBrowser class.
00033 
00034 enum {
00035     BrightestStars = 0,
00036     NearestStars = 1,
00037     StarsWithPlanets = 2,
00038 };
00039 
00040 static Point3f toMicroLY(const Point3f& p)
00041 {
00042     return Point3f(p.x * 1e6f, p.y * 1e6f, p.z * 1e6f);
00043 }
00044 
00045 static Point3f fromMicroLY(const Point3f& p)
00046 {
00047     return Point3f(p.x * 1e-6f, p.y * 1e-6f, p.z * 1e-6f);
00048 }
00049 
00050 
00051 bool InitStarBrowserColumns(HWND listView)
00052 {
00053     LVCOLUMN lvc;
00054     LVCOLUMN columns[5];
00055 
00056     lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
00057     lvc.fmt = LVCFMT_LEFT;
00058     lvc.cx = 60;
00059     lvc.pszText = "";
00060 
00061     int nColumns = sizeof(columns) / sizeof(columns[0]);
00062     int i;
00063 
00064     for (i = 0; i < nColumns; i++)
00065         columns[i] = lvc;
00066 
00067     columns[0].pszText = "Name";
00068     columns[0].cx = 100;
00069     columns[1].pszText = "Distance (ly)";
00070     columns[1].fmt = LVCFMT_RIGHT;
00071     columns[1].cx = 75;
00072     columns[2].pszText = "App. mag";
00073     columns[2].fmt = LVCFMT_RIGHT;
00074     columns[3].pszText = "Abs. mag";
00075     columns[3].fmt = LVCFMT_RIGHT;
00076     columns[4].pszText = "Type";
00077 
00078     for (i = 0; i < nColumns; i++)
00079     {
00080         columns[i].iSubItem = i;
00081         if (ListView_InsertColumn(listView, i, &columns[i]) == -1)
00082             return false;
00083     }
00084 
00085     return true;
00086 }
00087 
00088 
00089 struct CloserStarPredicate
00090 {
00091     Point3f pos;
00092     bool operator()(const Star* star0, const Star* star1) const
00093     {
00094         return ((pos - star0->getPosition()).lengthSquared() <
00095                 (pos - star1->getPosition()).lengthSquared());
00096                                
00097     }
00098 };
00099 
00100 struct BrighterStarPredicate
00101 {
00102     Point3f pos;
00103     UniversalCoord ucPos;
00104     bool operator()(const Star* star0, const Star* star1) const
00105     {
00106         float d0 = pos.distanceTo(star0->getPosition());
00107         float d1 = pos.distanceTo(star1->getPosition());
00108 
00109         // If the stars are closer than one light year, use
00110         // a more precise distance estimate.
00111         if (d0 < 1.0f)
00112             d0 = (toMicroLY(star0->getPosition()) - ucPos).length() * 1e-6f;
00113         if (d1 < 1.0f)
00114             d1 = (toMicroLY(star1->getPosition()) - ucPos).length() * 1e-6f;
00115 
00116         return (star0->getApparentMagnitude(d0) <
00117                 star1->getApparentMagnitude(d1));
00118     }
00119 };
00120 
00121 struct SolarSystemPredicate
00122 {
00123     Point3f pos;
00124     SolarSystemCatalog* solarSystems;
00125 
00126     bool operator()(const Star* star0, const Star* star1) const
00127     {
00128         SolarSystemCatalog::iterator iter;
00129 
00130         iter = solarSystems->find(star0->getCatalogNumber());
00131         bool hasPlanets0 = (iter != solarSystems->end());
00132         iter = solarSystems->find(star1->getCatalogNumber());
00133         bool hasPlanets1 = (iter != solarSystems->end());
00134         if (hasPlanets1 == hasPlanets0)
00135         {
00136             return ((pos - star0->getPosition()).lengthSquared() <
00137                     (pos - star1->getPosition()).lengthSquared());
00138         }
00139         else
00140         {
00141             return hasPlanets0;
00142         }
00143     }
00144 };
00145 
00146 
00147 // Find the nearest/brightest/X-est N stars in a database.  The
00148 // supplied predicate determines which of two stars is a better match.
00149 template<class Pred> vector<const Star*>*
00150 FindStars(const StarDatabase& stardb, Pred pred, int nStars)
00151 {
00152     vector<const Star*>* finalStars = new vector<const Star*>();
00153     if (nStars == 0)
00154         return finalStars;
00155 
00156     typedef multiset<const Star*, Pred> StarSet;
00157     StarSet firstStars(pred);
00158 
00159     int totalStars = stardb.size();
00160     if (totalStars < nStars)
00161         nStars = totalStars;
00162 
00163     // We'll need at least nStars in the set, so first fill
00164     // up the list indiscriminately.
00165     int i = 0;
00166     for (i = 0; i < nStars; i++)
00167     {
00168         Star* star = stardb.getStar(i);
00169         if (star->getVisibility())
00170             firstStars.insert(star);
00171     }
00172 
00173     // From here on, only add a star to the set if it's
00174     // a better match than the worst matching star already
00175     // in the set.
00176     const Star* lastStar = *--firstStars.end();
00177     for (; i < totalStars; i++)
00178     {
00179         Star* star = stardb.getStar(i);
00180         if (star->getVisibility() && pred(star, lastStar))
00181         {
00182             firstStars.insert(star);
00183             firstStars.erase(--firstStars.end());
00184             lastStar = *--firstStars.end();
00185         }
00186     }
00187 
00188     // Move the best matching stars into the vector
00189     finalStars->reserve(nStars);
00190     for (StarSet::const_iterator iter = firstStars.begin();
00191          iter != firstStars.end(); iter++)
00192     {
00193         finalStars->insert(finalStars->end(), *iter);
00194     }
00195 
00196     return finalStars;
00197 }
00198 
00199 
00200 bool InitStarBrowserLVItems(HWND listView, vector<const Star*>& stars)
00201 {
00202     LVITEM lvi;
00203 
00204     lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
00205     lvi.state = 0;
00206     lvi.stateMask = 0;
00207     lvi.pszText = LPSTR_TEXTCALLBACK;
00208 
00209     for (unsigned int i = 0; i < stars.size(); i++)
00210     {
00211         lvi.iItem = i;
00212         lvi.iSubItem = 0;
00213         lvi.lParam = (LPARAM) stars[i];
00214         ListView_InsertItem(listView, &lvi);
00215     }
00216 
00217     return true;
00218 }
00219 
00220 
00221 bool InitStarBrowserItems(HWND listView, StarBrowser* browser)
00222 {
00223     Universe* univ = browser->appCore->getSimulation()->getUniverse();
00224     StarDatabase* stardb = univ->getStarCatalog();
00225     SolarSystemCatalog* solarSystems = univ->getSolarSystemCatalog();
00226 
00227     vector<const Star*>* stars = NULL;
00228     switch (browser->predicate)
00229     {
00230     case BrightestStars:
00231         {
00232             BrighterStarPredicate brighterPred;
00233             brighterPred.pos = browser->pos;
00234             brighterPred.ucPos = browser->ucPos;
00235             stars = FindStars(*stardb, brighterPred, browser->nStars);
00236         }
00237         break;
00238 
00239     case NearestStars:
00240         {
00241             CloserStarPredicate closerPred;
00242             closerPred.pos = browser->pos;
00243             stars = FindStars(*stardb, closerPred, browser->nStars);
00244         }
00245         break;
00246 
00247     case StarsWithPlanets:
00248         {
00249             if (solarSystems == NULL)
00250                 return false;
00251             SolarSystemPredicate solarSysPred;
00252             solarSysPred.pos = browser->pos;
00253             solarSysPred.solarSystems = solarSystems;
00254             stars = FindStars(*stardb, solarSysPred,
00255                               min((unsigned int) browser->nStars, solarSystems->size()));
00256         }
00257         break;
00258 
00259     default:
00260         return false;
00261     }
00262             
00263     bool succeeded = InitStarBrowserLVItems(listView, *stars);
00264     delete stars;
00265 
00266     return succeeded;
00267 }
00268 
00269 
00270 // Crud used for the list item display callbacks
00271 static string starNameString("");
00272 static char callbackScratch[256];
00273 
00274 struct StarBrowserSortInfo
00275 {
00276     int subItem;
00277     Point3f pos;
00278     UniversalCoord ucPos;
00279 };
00280 
00281 int CALLBACK StarBrowserCompareFunc(LPARAM lParam0, LPARAM lParam1,
00282                                     LPARAM lParamSort)
00283 {
00284     StarBrowserSortInfo* sortInfo = reinterpret_cast<StarBrowserSortInfo*>(lParamSort);
00285     Star* star0 = reinterpret_cast<Star*>(lParam0);
00286     Star* star1 = reinterpret_cast<Star*>(lParam1);
00287 
00288     switch (sortInfo->subItem)
00289     {
00290     case 0:
00291         return 0;
00292 
00293     case 1:
00294         {
00295             float d0 = sortInfo->pos.distanceTo(star0->getPosition());
00296             float d1 = sortInfo->pos.distanceTo(star1->getPosition());
00297             return (int) sign(d0 - d1);
00298         }
00299 
00300     case 2:
00301         {
00302             float d0 = sortInfo->pos.distanceTo(star0->getPosition());
00303             float d1 = sortInfo->pos.distanceTo(star1->getPosition());
00304             if (d0 < 1.0f)
00305                 d0 = (toMicroLY(star0->getPosition()) - sortInfo->ucPos).length() * 1e-6f;
00306             if (d1 < 1.0f)
00307                 d1 = (toMicroLY(star1->getPosition()) - sortInfo->ucPos).length() * 1e-6f;
00308             return (int) sign(astro::absToAppMag(star0->getAbsoluteMagnitude(), d0) -
00309                               astro::absToAppMag(star1->getAbsoluteMagnitude(), d1));
00310         }
00311 
00312     case 3:
00313         return (int) sign(star0->getAbsoluteMagnitude() - star1->getAbsoluteMagnitude());
00314 
00315     case 4:
00316         return strcmp(star0->getSpectralType(), star1->getSpectralType());
00317 
00318     default:
00319         return 0;
00320     }
00321 }
00322 
00323 
00324 void StarBrowserDisplayItem(LPNMLVDISPINFOA nm, StarBrowser* browser)
00325 {
00326     Star* star = reinterpret_cast<Star*>(nm->item.lParam);
00327     if (star == NULL)
00328     {
00329         nm->item.pszText = "";
00330         return;
00331     }
00332 
00333     switch (nm->item.iSubItem)
00334     {
00335     case 0:
00336         {
00337             Universe* u = browser->appCore->getSimulation()->getUniverse();
00338             starNameString = u->getStarCatalog()->getStarName(*star);
00339             nm->item.pszText = const_cast<char*>(starNameString.c_str());
00340         }
00341         break;
00342             
00343     case 1:
00344         sprintf(callbackScratch, "%.3f",
00345                 browser->pos.distanceTo(star->getPosition()));
00346         nm->item.pszText = callbackScratch;
00347         break;
00348 
00349     case 2:
00350         {
00351             Vec3f r = toMicroLY(star->getPosition()) - browser->ucPos;
00352             float appMag = astro::absToAppMag(star->getAbsoluteMagnitude(),
00353                                               r.length() * 1e-6f);
00354             sprintf(callbackScratch, "%.2f", appMag);
00355             nm->item.pszText = callbackScratch;
00356         }
00357         break;
00358             
00359     case 3:
00360         sprintf(callbackScratch, "%.2f", star->getAbsoluteMagnitude());
00361         nm->item.pszText = callbackScratch;
00362         break;
00363 
00364     case 4:
00365         strncpy(callbackScratch, star->getSpectralType(),
00366                 sizeof(callbackScratch));
00367         callbackScratch[sizeof(callbackScratch) - 1] = '\0';
00368         nm->item.pszText = callbackScratch;
00369         break;
00370     }
00371 }
00372 
00373 void RefreshItems(HWND hDlg, StarBrowser* browser)
00374 {
00375     SetMouseCursor(IDC_WAIT);
00376 
00377     Simulation* sim = browser->appCore->getSimulation();
00378     browser->ucPos = sim->getObserver().getPosition();
00379     browser->pos = fromMicroLY((Point3f) browser->ucPos);
00380     HWND hwnd = GetDlgItem(hDlg, IDC_STARBROWSER_LIST);
00381     if (hwnd != 0)
00382     {
00383         ListView_DeleteAllItems(hwnd);
00384         InitStarBrowserItems(hwnd, browser);
00385     }
00386 
00387     SetMouseCursor(IDC_ARROW);
00388 }
00389 
00390 BOOL APIENTRY StarBrowserProc(HWND hDlg,
00391                               UINT message,
00392                               UINT wParam,
00393                               LONG lParam)
00394 {
00395     StarBrowser* browser = reinterpret_cast<StarBrowser*>(GetWindowLong(hDlg, DWL_USER));
00396 
00397     switch (message)
00398     {
00399     case WM_INITDIALOG:
00400         {
00401             StarBrowser* browser = reinterpret_cast<StarBrowser*>(lParam);
00402             if (browser == NULL)
00403                 return EndDialog(hDlg, 0);
00404             SetWindowLong(hDlg, DWL_USER, lParam);
00405 
00406             HWND hwnd = GetDlgItem(hDlg, IDC_STARBROWSER_LIST);
00407             InitStarBrowserColumns(hwnd);
00408             InitStarBrowserItems(hwnd, browser);
00409             CheckRadioButton(hDlg, IDC_RADIO_NEAREST, IDC_RADIO_WITHPLANETS, IDC_RADIO_NEAREST);
00410 
00411             //Initialize Max Stars edit box
00412             char val[16];
00413             hwnd = GetDlgItem(hDlg, IDC_MAXSTARS_EDIT);
00414             sprintf(val, "%d", DefaultListStars);
00415             SetWindowText(hwnd, val);
00416             SendMessage(hwnd, EM_LIMITTEXT, 3, 0);
00417 
00418             //Initialize Max Stars Slider control
00419             SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_SETRANGE,
00420                 (WPARAM)TRUE, (LPARAM)MAKELONG(MinListStars, MaxListStars));
00421             SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_SETPOS,
00422                 (WPARAM)TRUE, (LPARAM)DefaultListStars);
00423             
00424             return(TRUE);
00425         }
00426 
00427     case WM_DESTROY:
00428         if (browser != NULL && browser->parent != NULL)
00429         {
00430             SendMessage(browser->parent, WM_COMMAND, IDCLOSE,
00431                         reinterpret_cast<LPARAM>(browser));
00432         }
00433         break;
00434 
00435     case WM_COMMAND:
00436         switch (LOWORD(wParam))
00437         {
00438         case IDOK:
00439         case IDCANCEL:
00440             if (browser != NULL && browser->parent != NULL)
00441             {
00442                 SendMessage(browser->parent, WM_COMMAND, IDCLOSE,
00443                             reinterpret_cast<LPARAM>(browser));
00444             }
00445             EndDialog(hDlg, 0);
00446             return TRUE;
00447 
00448         case IDC_BUTTON_CENTER:
00449             browser->appCore->charEntered('c');
00450             break;
00451 
00452         case IDC_BUTTON_GOTO:
00453             browser->appCore->charEntered('G');
00454             break;
00455 
00456         case IDC_RADIO_BRIGHTEST:
00457             browser->predicate = BrightestStars;
00458             RefreshItems(hDlg, browser);
00459             break;
00460 
00461         case IDC_RADIO_NEAREST:
00462             browser->predicate = NearestStars;
00463             RefreshItems(hDlg, browser);
00464             break;
00465 
00466         case IDC_RADIO_WITHPLANETS:
00467             browser->predicate = StarsWithPlanets;
00468             RefreshItems(hDlg, browser);
00469             break;
00470 
00471         case IDC_BUTTON_REFRESH:
00472             RefreshItems(hDlg, browser);
00473             break;
00474 
00475         case IDC_MAXSTARS_EDIT:
00476             // TODO: browser != NULL check should be in a lot more places
00477             if (HIWORD(wParam) == EN_KILLFOCUS && browser != NULL)
00478             {
00479                 char val[16];
00480                 DWORD nNewStars;
00481                 DWORD minRange, maxRange;
00482                 GetWindowText((HWND) lParam, val, sizeof(val));
00483                 nNewStars = atoi(val);
00484 
00485                 // Check if new value is different from old. Don't want to
00486                 // cause a refresh to occur if not necessary.
00487                 if (nNewStars != browser->nStars)
00488                 {
00489                     minRange = SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_GETRANGEMIN, 0, 0);
00490                     maxRange = SendDlgItemMessage(hDlg, IDC_MAXSTARS_SLIDER, TBM_GETRANGEMAX, 0, 0);
00491                     if (nNewStars < minRange)
00492                         nNewStars = minRange;
00493                     else if (nNewStars > maxRange)
00494                         nNewStars = maxRange;
00495 
00496                     // If new value has been adjusted from what was entered,
00497                     // reflect new value back in edit control.
00498                     if (atoi(val) != nNewStars)
00499                     {
00500                         sprintf(val, "%d", nNewStars);
00501                         SetWindowText((HWND)lParam, val);
00502                     }
00503 
00504                     // Recheck value if different from original.
00505                     if (nNewStars != browser->nStars)
00506                     {
00507                         browser->nStars = nNewStars;
00508                         SendDlgItemMessage(hDlg,
00509                                            IDC_MAXSTARS_SLIDER,
00510                                            TBM_SETPOS,
00511                                            (WPARAM) TRUE,
00512                                            (LPARAM) browser->nStars);
00513                         RefreshItems(hDlg, browser);
00514                     }
00515                 }
00516             }
00517             break;
00518         }
00519         break;
00520 
00521     case WM_NOTIFY:
00522         {
00523             LPNMHDR hdr = (LPNMHDR) lParam;
00524 
00525             if(hdr->idFrom == IDC_STARBROWSER_LIST)
00526             {
00527                 switch(hdr->code)
00528                 {
00529                 case LVN_GETDISPINFO:
00530                     StarBrowserDisplayItem((LPNMLVDISPINFOA) lParam, browser);
00531                     break;
00532                 case LVN_ITEMCHANGED:
00533                     {
00534                         LPNMLISTVIEW nm = (LPNMLISTVIEW) lParam;
00535                         if ((nm->uNewState & LVIS_SELECTED) != 0)
00536                         {
00537                             Simulation* sim = browser->appCore->getSimulation();
00538                             Star* star = reinterpret_cast<Star*>(nm->lParam);
00539                             if (star != NULL)
00540                                 sim->setSelection(Selection(star));
00541                         }
00542                         break;
00543                     }
00544                 case LVN_COLUMNCLICK:
00545                     {
00546                         HWND hwnd = GetDlgItem(hDlg, IDC_STARBROWSER_LIST);
00547                         if (hwnd != 0)
00548                         {
00549                             LPNMLISTVIEW nm = (LPNMLISTVIEW) lParam;
00550                             StarBrowserSortInfo sortInfo;
00551                             sortInfo.subItem = nm->iSubItem;
00552                             sortInfo.ucPos = browser->ucPos;
00553                             sortInfo.pos = browser->pos;
00554                             ListView_SortItems(hwnd, StarBrowserCompareFunc,
00555                                                reinterpret_cast<LPARAM>(&sortInfo));
00556                         }
00557                     }
00558 
00559                 }
00560             }
00561         }
00562         break;
00563 
00564     case WM_HSCROLL:
00565         {
00566             WORD sbValue = LOWORD(wParam);
00567             switch(sbValue)
00568             {
00569                 case SB_THUMBTRACK:
00570                 {
00571                     char val[16];
00572                     HWND hwnd = GetDlgItem(hDlg, IDC_MAXSTARS_EDIT);
00573                     sprintf(val, "%d", HIWORD(wParam));
00574                     SetWindowText(hwnd, val);
00575                     break;
00576                 }
00577                 case SB_THUMBPOSITION:
00578                 {
00579                     browser->nStars = (int)HIWORD(wParam);
00580                     RefreshItems(hDlg, browser);
00581                     break;
00582                 }
00583             }
00584         }
00585         break;
00586     }
00587 
00588     return FALSE;
00589 }
00590 
00591 
00592 StarBrowser::StarBrowser(HINSTANCE appInstance,
00593                          HWND _parent,
00594                          CelestiaCore* _appCore) :
00595     appCore(_appCore),
00596     parent(_parent)
00597 {
00598     ucPos = appCore->getSimulation()->getObserver().getPosition();
00599     pos = fromMicroLY((Point3f) ucPos);
00600 
00601     predicate = NearestStars;
00602     nStars = DefaultListStars;
00603 
00604     hwnd = CreateDialogParam(appInstance,
00605                              MAKEINTRESOURCE(IDD_STARBROWSER),
00606                              parent,
00607                              StarBrowserProc,
00608                              reinterpret_cast<LONG>(this));
00609 }
00610 
00611 
00612 StarBrowser::~StarBrowser()
00613 {
00614     SetWindowLong(hwnd, DWL_USER, 0);
00615 }

Generated on Sat Jan 14 22:30:32 2006 for Celestia by  doxygen 1.4.1