#include #include #include "resource.h" #include #include #include #include // tray #include #include // CB_SETMINVISIBLE #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #include #include #include #include #define WM_TRAY (WM_APP+1) CComPtr getXMLRootByURI(LPCWSTR uri){ HRESULT hr; CComPtr iXMLDoc; hr = iXMLDoc.CoCreateInstance(__uuidof(DOMDocument)); if(iXMLDoc.p == NULL || FAILED(hr)){ MessageBox(NULL, L"Не удалось загрузить MSXML. Корректная работа приложения невозможна.\n" L"Проверьте, установлен ли корректно Internet Explorer.", L"", 0); exit(1); } VARIANT_BOOL bSuccess = false; iXMLDoc->put_async(VARIANT_FALSE); hr = iXMLDoc->load(CComVariant(uri),&bSuccess); if(!bSuccess || FAILED(hr)){ //MessageBox(NULL, L"Не удалось загрузить документ.", L"", 0); return NULL; } CComPtr iRootElm; hr = iXMLDoc->get_documentElement(&iRootElm); if(iRootElm.p == NULL || FAILED(hr)){ //MessageBox(NULL, L"Не удалось загрузить документ.", L"", 0); return NULL; } return iRootElm; } LPTSTR getTagValueByPath(CComPtr root, LPCWSTR path){ CComBSTR bstrSS(path); CComPtr iXMLSubElem; HRESULT hr = root->selectSingleNode(bstrSS,&iXMLSubElem); if(FAILED(hr) || iXMLSubElem.p == NULL) return NULL; CComVariant varValue(VT_EMPTY); hr = iXMLSubElem->get_nodeTypedValue(&varValue); if(FAILED(hr) || varValue.vt != VT_BSTR) return NULL; USES_CONVERSION; LPTSTR lpstrMsg = W2T(varValue.bstrVal); return lpstrMsg; } typedef void iterator(CComPtr); void forEachNode(CComPtr root, LPCWSTR path, iterator f){ CComBSTR bstrSS(path); CComPtr iXMLSubElems; HRESULT hr = root->selectNodes(bstrSS,&iXMLSubElems); if(FAILED(hr) || iXMLSubElems.p == NULL) return; CComPtr iXMLSubElem; while(true){ iXMLSubElem = NULL; hr = iXMLSubElems->nextNode(&iXMLSubElem); if(FAILED(hr)) break; if(!iXMLSubElem) break; f(iXMLSubElem); } } CComPtr getSubNode(CComPtr root, LPCWSTR path){ CComBSTR bstrSS(path); CComPtr iXMLSubElem; HRESULT hr = root->selectSingleNode(bstrSS,&iXMLSubElem); if(FAILED(hr) || iXMLSubElem.p == NULL) return NULL; return iXMLSubElem; } class Multiline { private: LPCWSTR ss[1024]; int sn, cur; public: Multiline(){ this->sn = 0; } Multiline(LPCWSTR fs, ...){ LPCWSTR param; this->sn = 0; va_list arglist; param = fs; va_start(arglist, fs); while(param){ this->append(param); param = va_arg(arglist, LPCWSTR); } va_end(arglist); } void append(LPCWSTR s){ if(this->sn < 1024){ this->ss[this->sn++] = s; } } void resetPointer(){ this->cur = 0; } LPCWSTR get(){ return (this->cur < this->sn) ? this->ss[this->cur++] : NULL; } }; void timerPopupEvent(HWND, UINT, UINT_PTR, DWORD); class TrayIcon { private: NOTIFYICONDATA nid; HINSTANCE hInstance; HICON hIcon; int tid; public: TrayIcon(){ ; } TrayIcon(HWND hWnd, HINSTANCE hInstance){ this->hInstance = hInstance; memset(&(this->nid), 0, sizeof(this->nid)); this->nid.cbSize = sizeof(this->nid); this->nid.hWnd = hWnd; this->hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WAIT)); this->nid.hIcon = this->hIcon; this->nid.uCallbackMessage = WM_TRAY; this->nid.uFlags = NIF_ICON|NIF_MESSAGE; this->show(); } void updateIcon(LPCWSTR icon){ //memset(&(this->nid), 0, sizeof(this->nid)); //this->nid.cbSize = sizeof(this->nid); DestroyIcon(this->hIcon); this->hIcon = LoadIcon(this->hInstance, icon); this->nid.hIcon = this->hIcon; this->nid.uFlags = NIF_ICON; Shell_NotifyIcon(NIM_MODIFY, &(this->nid)); } void updateTip(LPCWSTR text){ //memset(&(this->nid), 0, sizeof(this->nid)); //this->nid.cbSize = sizeof(this->nid); /* lstrcpyn(this->nid.szTip, text, sizeof(this->nid.szTip)/sizeof(this->nid.szTip[0])); this->nid.uFlags = NIF_TIP; Shell_NotifyIcon(NIM_MODIFY, &(this->nid)); */ ; } ~TrayIcon(){ this->hide(); DestroyIcon(this->hIcon); } void show(){ Shell_NotifyIcon(NIM_ADD, &(this->nid)); } void hide(){ Shell_NotifyIcon(NIM_DELETE, &(this->nid)); } }; class Popup { private: HWND hWnd; LPCWSTR caption; Multiline text; HBRUSH hbBackground; int w, h; public: int timerID; Popup(HWND hWnd){ this->hWnd = hWnd; this->w = 200; this->h = 150; this->caption = L""; this->hbBackground = CreateSolidBrush(RGB(240,240,80)); } void setCaption(LPCWSTR s){ this->caption = s; } void setText(Multiline s){ this->text = s; } void resize(int w, int h){ this->w = w; this->h = h; } void show(){ int x, y; // где же наш таскбарчик? APPBARDATA abd; abd.hWnd = FindWindow(L"Shell_TrayWnd", NULL); abd.cbSize = sizeof(abd); bool pOnTop = (SHAppBarMessage(ABM_GETSTATE, &abd) & ABS_ALWAYSONTOP); bool pAutoHide = (SHAppBarMessage(ABM_GETSTATE, &abd) & ABS_AUTOHIDE) ; RECT rT, rS; GetWindowRect(abd.hWnd, &rT); GetWindowRect(GetDesktopWindow(), &rS); if((rT.right-rT.left)h; x = (rT.left>0)?rT.left-this->w:rT.right; }else{ x = rS.right-this->w; y = (rT.top>0)?rT.top-this->h:rT.bottom; } ShowWindow(this->hWnd, SW_SHOW); MoveWindow(this->hWnd, x, y, this->w, this->h, TRUE); this->timerID = SetTimer(this->hWnd, 0, 5000, (TIMERPROC)(timerPopupEvent)); } void hide(){ ShowWindow(this->hWnd, SW_HIDE); } void draw(HDC hdc){ /* CFont f; memset(&f, 0, sizeof(f)); */ HFONT hf; // FIXME частичная перерисовка SelectObject(hdc, this->hbBackground); Rectangle(hdc, 0, 0, this->w, this->h); SetBkMode(hdc, TRANSPARENT); hf = CreateFont(14, 0, 0, 0, 800, FALSE, TRUE, FALSE, UNICODE, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Sans"); SelectObject(hdc, hf); TextOut(hdc, 12, 4, this->caption, lstrlen(this->caption)); DeleteObject(hf); hf = CreateFont(12, 0, 0, 0, 500, FALSE, FALSE, FALSE, UNICODE, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Sans"); SelectObject(hdc, hf); this->text.resetPointer(); for(int i=20; ; i+=14){ LPCWSTR ln = this->text.get(); if(!ln) break; TextOut(hdc, 12, i, ln, lstrlen(ln)); } DeleteObject(hf); } ~Popup(){ DeleteObject(this->hbBackground); } }; void registerCity(CComPtr); class Cities { private: LPCWSTR countries[512]; LPCWSTR cities[512][1024]; int codes[512][1024]; public: int getCountry(LPCWSTR country){ for(int i=0;i<512;i++) if(this->countries[i] && !wcscmp(this->countries[i], country)) return i; return -1; } LPCWSTR getCountry(int id){ return this->countries[id]; } LPCWSTR getCity(int countryID, int cityID){ return this->cities[countryID][cityID]; } int getCity(int countryID, LPCWSTR city){ for(int i=0;i<1024;i++) if(this->cities[countryID][i] && !wcscmp(this->cities[countryID][i], city)) return i; return -1; } LPCWSTR getCity(int city){ for(int i=0;i<512;i++){ if(!this->countries[i]) break; for(int j=0;j<1024;j++){ if(!this->cities[i][j]) break; if(this->codes[i][j]==city) return this->cities[i][j]; } } return NULL; } int getCityCode(int countryID, int cityID){ return this->codes[countryID][cityID]; } int countCountries(){ int i; for(i=0; i<512; i++) if(!countries[i]) break; return i; } int setCountry(LPCWSTR country){ int c = this->getCountry(country); if(c != -1) return c; c = this->countCountries(); this->countries[c] = country; return c; } int countCities(int countryID){ int i; for(i=0; i<1024; i++) if(!cities[countryID][i]) break; return i; } void setCity(LPCWSTR country, LPCWSTR city, int code){ int countryID = this->setCountry(country); int cities = this->countCities(countryID); this->cities[countryID][cities] = city; this->codes[countryID][cities] = code; } void load(){ CComPtr root = getXMLRootByURI(L"gmbartlist.xml"); // http://bar.gismeteo.ru/ if(root){ forEachNode(root, L"t", registerCity); }else{ MessageBox(NULL, L"Не удалось загрузить список городов. Корректная работа приложения невозможна", L":'(", 0); exit(1); } } Cities(){ for(int i=0;i<512;i++){ this->countries[i] = NULL; for(int j=0;j<1024;j++){ this->cities[i][j] = NULL; } } } }; struct Weather { LPCWSTR name; int day, month, year, hour, cloud, precip, mintemp, maxtemp, minpress, maxpress, minwind, maxwind, minrelwet, maxrelwet, winddir; }; // Эти переменные столь глобальны, что они живут здесь Popup* popup; TrayIcon* ti; Cities* cities; int city = 27612; Weather w; HINSTANCE hInst; void timerPopupEvent(HWND hWnd, UINT msg, UINT_PTR idTimer, DWORD dwTimer){ popup->hide(); KillTimer(hWnd, popup->timerID); } LPCWSTR getTagAttr(CComPtr item, LPCWSTR name){ CComPtr attrs; HRESULT hr = item->get_attributes(&attrs); if(FAILED(hr)) return NULL; CComPtr iXMLAttr = NULL; CComVariant varValue(VT_EMPTY); attrs->getNamedItem(CComBSTR(name), &iXMLAttr); iXMLAttr->get_nodeTypedValue(&varValue); // Они долбанулись в своём мелкософте! Придумали кучу типов строк, а простого способа // создать новый экземпляр с выделением памяти и копированием старого значения - нет wchar_t* result = (wchar_t*)malloc(1024*sizeof(wchar_t)); // если больше - ССЗБ wcscpy(result, W2T(varValue.bstrVal)); return result; } /* Do it indian way, dude */ void registerCity(CComPtr item){ cities->setCity( getTagAttr(item, L"c"), getTagAttr(item, L"n"), _wtoi(getTagAttr(item, L"i"))); } bool wloaded = true; void showWeather(){ Multiline m; wchar_t* s = (wchar_t*)malloc(1024*sizeof(wchar_t)); wchar_t* rs; popup->setCaption(w.name); swprintf((wchar_t*)s, (wchar_t*)L"%d.%d.%d %d:00", w.day, w.month, w.year, w.hour); rs = (wchar_t*)malloc(1024*sizeof(wchar_t)); wcscpy(rs, s); m.append(rs); switch(w.cloud){ case 0: m.append(L"ясно"); ti->updateIcon(MAKEINTRESOURCE(IDI_SUNNY)); break; case 1: m.append(L"малооблачно"); ti->updateIcon(MAKEINTRESOURCE(IDI_CLOUDY)); break; case 2: m.append(L"облачно"); ti->updateIcon(MAKEINTRESOURCE(IDI_CLOUDY)); break; case 3: m.append(L"пасмурно"); ti->updateIcon(MAKEINTRESOURCE(IDI_MOSTCLOUDY)); break; } switch(w.precip){ case 4: m.append(L"дождь"); break; case 5: m.append(L"ливень"); break; case 6: m.append(L"снег"); break; case 7: m.append(L"снег"); break; case 8: m.append(L"гроза"); break; case 9: break; case 10: m.append(L"без осадков"); break; } swprintf((wchar_t*)s, (wchar_t*)L"Температура %d...%d C", w.mintemp, w.maxtemp); rs = (wchar_t*)malloc(1024*sizeof(wchar_t)); wcscpy(rs, s); m.append(rs); swprintf((wchar_t*)s, (wchar_t*)L"Ветер %d...%d м/с", w.minwind, w.maxwind); rs = (wchar_t*)malloc(1024*sizeof(wchar_t)); wcscpy(rs, s); m.append(rs); popup->setText(m); popup->show(); } void registerWeather(CComPtr item){ if(!wloaded){ w.day = _wtoi(getTagAttr(item, L"day")); w.month = _wtoi(getTagAttr(item, L"month")); w.year = _wtoi(getTagAttr(item, L"year")); w.hour = _wtoi(getTagAttr(item, L"hour")); CComPtr temp; item->selectSingleNode(L"TEMPERATURE", &temp); w.mintemp = _wtoi(getTagAttr(temp, L"min")); w.maxtemp = _wtoi(getTagAttr(temp, L"max")); CComPtr press; item->selectSingleNode(L"PRESSURE", &press); w.minpress = _wtoi(getTagAttr(press, L"min")); w.maxpress = _wtoi(getTagAttr(press, L"max")); CComPtr wind; item->selectSingleNode(L"WIND", &wind); w.minwind = _wtoi(getTagAttr(wind, L"min")); w.maxwind = _wtoi(getTagAttr(wind, L"max")); w.winddir = _wtoi(getTagAttr(wind, L"direction")); /* switch(_wtoi(getTagAttr(wind, L"direction"))){ case 0: w.winddir = L"С"; break; case 1: w.winddir = L"СВ"; break; case 2: w.winddir = L"В"; break; case 3: w.winddir = L"ЮВ"; break; case 4: w.winddir = L"Ю"; break; case 5: w.winddir = L"ЮЗ"; break; case 6: w.winddir = L"З"; break; case 7: w.winddir = L"СЗ"; break; } */ CComPtr relwet; item->selectSingleNode(L"WIND", &relwet); w.minrelwet = _wtoi(getTagAttr(relwet, L"min")); w.maxrelwet = _wtoi(getTagAttr(relwet, L"max")); CComPtr phenom; item->selectSingleNode(L"PHENOMENA", &phenom); w.cloud = _wtoi(getTagAttr(phenom, L"cloudiness")); w.precip = _wtoi(getTagAttr(phenom, L"precipitation")); /* switch(_wtoi(getTagAttr(phenom, L"cloudiness"))){ case 0: w.cloud = L"ясно"; break; case 1: w.cloud = L"малооблачно"; break; case 2: w.cloud = L"облачно"; break; case 3: w.cloud = L"пасмурно"; break; } switch(_wtoi(getTagAttr(phenom, L"precipitation"))){ case 4: w.precip = L"дождь"; break; case 5: w.precip = L"ливень"; break; case 6: w.precip = L"снег"; break; case 7: w.precip = L"снег"; break; case 8: w.precip = L"гроза"; break; case 9: w.precip = L"нет данных"; break; case 10: w.precip = L"без осадков"; break; } */ wloaded = true; } } void loadCityWeather(){ wchar_t* uri = (wchar_t*)malloc(1024*sizeof(wchar_t)); swprintf(uri, (wchar_t*)L"http://informer.gismeteo.ru/xml/%d_1.xml", city); CComPtr root = getXMLRootByURI(L"27612_1.xml"); //;(LPCWSTR)uri); wloaded = false; if(root){ forEachNode(root, L"REPORT/TOWN/FORECAST", registerWeather); showWeather(); }else{ MessageBox(NULL, L"Не удалось загрузить погоду. Проверьте соединение с Сетью.", L":'(", 0); } } BOOL CALLBACK ChooseCityDlgProc(HWND hWndD, UINT message, WPARAM wParam, LPARAM lParam){ HWND comboCountries = GetDlgItem(hWndD, IDC_COMBO1); HWND comboCities = GetDlgItem(hWndD, IDC_COMBO2); switch (message){ case WM_INITDIALOG: SendMessage(comboCountries, CB_RESETCONTENT, 0, 0); for(int i=0;i<512;i++){ LPCWSTR country = cities->getCountry(i); if(!country) break; SendMessage(comboCountries, CB_ADDSTRING, 0, (LPARAM)country); } return TRUE; case WM_COMMAND: /* In all cases, the low-order word of the wParam parameter contains the control identifier, the high-order word of wParam contains the notification code, and the lParam parameter contains the control window handle. (c) MSDN */ switch (LOWORD(wParam)){ case IDC_COMBO1: char tmp[256]; if(HIWORD(wParam)==CBN_SELCHANGE){ SendDlgItemMessage(hWndD, IDC_COMBO1, WM_GETTEXT, sizeof(tmp), (LPARAM)tmp); //MessageBox(NULL, (LPCWSTR)tmp, L"", 0); int countryCode = cities->setCountry((LPCWSTR)tmp); SendMessage(comboCities, CB_RESETCONTENT, 0, 0); for(int i=0;i<1024;i++){ LPCWSTR city = cities->getCity(countryCode, i); if(!city) break; SendMessage(comboCities, CB_ADDSTRING, 0, (LPARAM)city); } } break; case IDOK: SendDlgItemMessage(hWndD, IDC_COMBO1, WM_GETTEXT, sizeof(tmp), (LPARAM)tmp); int countryCode; LPCWSTR country; country = (LPCWSTR)tmp; countryCode = cities->setCountry((LPCWSTR)tmp); SendDlgItemMessage(hWndD, IDC_COMBO2, WM_GETTEXT, sizeof(tmp), (LPARAM)tmp); if(strlen(tmp)==0){ MessageBox(hWndD, L"Эй! Ты же ничего не выбрал!", L"Нехорошо!", 0); break; } int cityCode; cityCode = cities->getCity(countryCode, (LPCWSTR)tmp); city = cities->getCityCode(countryCode, cityCode); wchar_t* rs; rs = (wchar_t*)malloc(1024*sizeof(wchar_t)); wcscpy(rs, (LPCWSTR)tmp); w.name = (LPCWSTR)rs; loadCityWeather(); //MessageBox(NULL, (LPCWSTR)tmp, L"", 0); // there is no break case IDCANCEL: EndDialog(hWndD, wParam); return TRUE; } default: return FALSE; } } void ShowPopupMenu(HWND hWnd){ HMENU hMenu = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1)), 0); //, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE) SetForegroundWindow(hWnd); POINT p; GetCursorPos(&p); TrackPopupMenu(hMenu, 0, p.x, p.y, 0, hWnd, NULL); DestroyMenu(hMenu); } void openSite(){ char *url; url = (char*)malloc(sizeof(char)*512); sprintf(url, "http://gismeteo.ru/city/daily/%d/", city); popup->hide(); // С недавних пор на сайте коды городов стали несовпадать О_о //ShellExecuteA(NULL, NULL, url, NULL, NULL, SW_SHOWNORMAL); free(url); } void quit(HWND hWnd){ SendMessage(hWnd, WM_DESTROY, 0, 0); } void chooseCity(HWND hWnd){ DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, (DLGPROC)ChooseCityDlgProc); } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message){ case WM_COMMAND: int wmID; wmID = LOWORD(wParam); switch(wmID){ case ID_EXIT: quit(hWnd); break; case ID_CHOOSECITY: chooseCity(hWnd); break; } break; case WM_LBUTTONUP: openSite(); break; case WM_RBUTTONUP: popup->hide(); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_TRAY: bool dclicked; dclicked = false; switch((UINT)lParam){ case WM_RBUTTONDOWN: ShowPopupMenu(hWnd); break; case WM_LBUTTONDOWN: // Вот индусы в Microsoft! Посылают сообщение о клике и двойном клике единовременно! long t; t = GetTickCount() + 100; while(t < GetTickCount()){ if(dclicked){ dclicked = false; return 0; // break не подходит, а жаль. } } showWeather(); break; case WM_LBUTTONDBLCLK: dclicked = true; openSite(); break; } break; case WM_PAINT: HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); popup->draw(hdc); EndPaint(hWnd, &ps); default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } ATOM RegMyWindowClass(HINSTANCE hInstance, LPCTSTR lpzClassName) { WNDCLASS wcWindowClass = {0}; wcWindowClass.lpfnWndProc = (WNDPROC)WndProc; wcWindowClass.style = CS_HREDRAW|CS_VREDRAW; wcWindowClass.hInstance = hInstance; wcWindowClass.lpszClassName = lpzClassName; wcWindowClass.hCursor = LoadCursor(NULL, IDC_ARROW); wcWindowClass.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE; return RegisterClass(&wcWindowClass); } void timerMainEvent(HWND hWnd, UINT msg, UINT_PTR idTimer, DWORD dwTimer){ loadCityWeather(); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ hInst = hInstance; CoInitialize(NULL); wchar_t* className = L"twHint"; if (!RegMyWindowClass(hInstance, className)) return 1; HWND hWnd = CreateWindowW(className, L"Loading...", (/* WS_VISIBLE | */WS_EX_TOOLWINDOW), 0, 0, 250, 100, NULL, NULL, hInstance, NULL); if(!hWnd) return 1; hWnd = CreateWindow(className, L"", (WS_POPUPWINDOW | WS_EX_TOPMOST) & (~WS_DLGFRAME), 0, 0, 300, 150, hWnd, NULL, hInstance, NULL); if(!hWnd) return 1; ti->updateTip(L"Подгружаю список городов..."); cities = new Cities(); cities->load(); FILE* f = _wfopen(L".settings", L"r"); if(f){ fwscanf(f, L"%ld", &city); // badptr - wtf? w.name = cities->getCity(city); fclose(f); } ti = new TrayIcon(hWnd, hInstance); popup = new Popup(hWnd); loadCityWeather(); int timerID = SetTimer(hWnd, 1, 60*60*1000, (TIMERPROC)(timerMainEvent)); // проверяем погоду каждый час MSG msg = {0}; int msgStatus = 0; while ((msgStatus = GetMessage(&msg, NULL, 0, 0)) != 0){ if (msgStatus == -1) return 1; TranslateMessage(&msg); DispatchMessage(&msg); } delete popup; delete ti; delete cities; KillTimer(hWnd, timerID); f = _wfopen(L".settings", L"w"); if(f){ city = fwprintf(f, L"%ld", city); fclose(f); } CoUninitialize(); return 0; }