/* Jabber Protocol Plugin for Miranda NG Copyright (c) 2002-04 Santithorn Bunchua Copyright (c) 2005-12 George Hazan Copyright (c) 2007 Artem Shpynov Copyright (c) 2012-18 Miranda NG team Module implements a search according to XEP-0055: Jabber Search http://www.xmpp.org/extensions/xep-0055.html This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdafx.h" #include #include "jabber_iq.h" #include "jabber_caps.h" /////////////////////////////////////////////////////////////////////////////// // Subclassing of IDC_FRAME to implement more user-friendly fields scrolling static int JabberSearchFrameProc(HWND hwnd, int msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_COMMAND && lParam != 0) { HWND hwndDlg = GetParent(hwnd); JabberSearchData *dat = (JabberSearchData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (dat && lParam) { int pos = dat->curPos; RECT MineRect; RECT FrameRect; GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME), &FrameRect); GetWindowRect((HWND)lParam, &MineRect); if (MineRect.top - 10 < FrameRect.top) { pos = dat->curPos + (MineRect.top - 14 - FrameRect.top); if (pos < 0) pos = 0; } else if (MineRect.bottom > FrameRect.bottom) { pos = dat->curPos + (MineRect.bottom - FrameRect.bottom); if (dat->frameHeight + pos > dat->CurrentHeight) pos = dat->CurrentHeight - dat->frameHeight; } if (pos != dat->curPos) { ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, nullptr, &(dat->frameRect)); SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE); RECT Invalid = dat->frameRect; if (dat->curPos - pos > 0) Invalid.bottom = Invalid.top + (dat->curPos - pos); else Invalid.top = Invalid.bottom + (dat->curPos - pos); RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); dat->curPos = pos; } } if (HIWORD(wParam) == EN_SETFOCUS) { //Transmit focus set notification to parent window PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg); } } if (msg == WM_PAINT) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); FillRect(hdc, &(ps.rcPaint), GetSysColorBrush(COLOR_BTNFACE)); EndPaint(hwnd, &ps); } return DefWindowProc(hwnd, msg, wParam, lParam); } /////////////////////////////////////////////////////////////////////////////// // Add Search field to form static int JabberSearchAddField(HWND hwndDlg, Data* FieldDat) { if (!FieldDat || !FieldDat->Label || !FieldDat->Var) return FALSE; HFONT hFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0); HWND hwndParent = GetDlgItem(hwndDlg, IDC_FRAME); LONG_PTR frameExStyle = GetWindowLongPtr(hwndParent, GWL_EXSTYLE); frameExStyle |= WS_EX_CONTROLPARENT; SetWindowLongPtr(hwndParent, GWL_EXSTYLE, frameExStyle); SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_FRAME), GWLP_WNDPROC, (LONG_PTR)JabberSearchFrameProc); int CornerX = 1; int CornerY = 1; RECT rect; GetClientRect(hwndParent, &rect); int width = rect.right - 5 - CornerX; int Order = (FieldDat->bHidden) ? -1 : FieldDat->Order; HWND hwndLabel = CreateWindowEx(0, L"STATIC", (const wchar_t *)TranslateW(FieldDat->Label), WS_CHILD, CornerX, CornerY + Order * 40, width, 13, hwndParent, nullptr, g_plugin.getInst(), nullptr); HWND hwndVar = CreateWindowEx(0 | WS_EX_CLIENTEDGE, L"EDIT", (const wchar_t *)FieldDat->defValue, WS_CHILD | WS_TABSTOP, CornerX + 5, CornerY + Order * 40 + 14, width, 20, hwndParent, nullptr, g_plugin.getInst(), nullptr); SendMessage(hwndLabel, WM_SETFONT, (WPARAM)hFont, 0); SendMessage(hwndVar, WM_SETFONT, (WPARAM)hFont, 0); if (!FieldDat->bHidden) { ShowWindow(hwndLabel, SW_SHOW); ShowWindow(hwndVar, SW_SHOW); EnableWindow(hwndLabel, !FieldDat->bReadOnly); SendMessage(hwndVar, EM_SETREADONLY, (WPARAM)FieldDat->bReadOnly, 0); } //remade list //reallocation JabberSearchData *dat = (JabberSearchData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (dat) { dat->pJSInf = (JabberSearchFieldsInfo*)realloc(dat->pJSInf, sizeof(JabberSearchFieldsInfo)*(dat->nJSInfCount + 1)); dat->pJSInf[dat->nJSInfCount].hwndCaptionItem = hwndLabel; dat->pJSInf[dat->nJSInfCount].hwndValueItem = hwndVar; dat->pJSInf[dat->nJSInfCount].szFieldCaption = wcsdup(FieldDat->Label); dat->pJSInf[dat->nJSInfCount].szFieldName = wcsdup(FieldDat->Var); dat->nJSInfCount++; } return CornerY + Order * 40 + 14 + 20; } //////////////////////////////////////////////////////////////////////////////// // Available search field request result handler (XEP-0055. Examples 2, 7) void CJabberProto::OnIqResultGetSearchFields(HXML iqNode, CJabberIqInfo*) { if (!searchHandleDlg) return; const wchar_t *type = XmlGetAttrValue(iqNode, L"type"); if (type == nullptr) return; if (!mir_wstrcmp(type, L"result")) { HXML queryNode = XmlGetNthChild(iqNode, L"query", 1); HXML xNode = XmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS); ShowWindow(searchHandleDlg, SW_HIDE); if (xNode) { //1. Form PostMessage(searchHandleDlg, WM_USER + 11, (WPARAM)xmlCopyNode(xNode), 0); HXML xcNode = XmlGetNthChild(xNode, L"instructions", 1); if (xcNode) SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, XmlGetText(xcNode)); } else { int Order = 0; for (int i = 0;; i++) { HXML chNode = XmlGetChild(queryNode, i); if (!chNode) break; if (!mir_wstrcmpi(XmlGetName(chNode), L"instructions") && XmlGetText(chNode)) SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, TranslateW(XmlGetText(chNode))); else if (XmlGetName(chNode)) { Data *MyData = (Data*)malloc(sizeof(Data)); memset(MyData, 0, sizeof(Data)); MyData->Label = mir_wstrdup(XmlGetName(chNode)); MyData->Var = mir_wstrdup(XmlGetName(chNode)); MyData->defValue = mir_wstrdup(XmlGetText(chNode)); MyData->Order = Order; if (MyData->defValue) MyData->bReadOnly = TRUE; PostMessage(searchHandleDlg, WM_USER + 10, FALSE, (LPARAM)MyData); Order++; } } } const wchar_t *szFrom = XmlGetAttrValue(iqNode, L"from"); if (szFrom) SearchAddToRecent(szFrom, searchHandleDlg); PostMessage(searchHandleDlg, WM_USER + 10, 0, 0); ShowWindow(searchHandleDlg, SW_SHOW); } else if (!mir_wstrcmp(type, L"error")) { const wchar_t *code = nullptr; const wchar_t *description = nullptr; wchar_t buff[255]; HXML errorNode = XmlGetChild(iqNode, "error"); if (errorNode) { code = XmlGetAttrValue(errorNode, L"code"); description = XmlGetText(errorNode); } mir_snwprintf(buff, TranslateT("Error %s %s\r\nPlease select other server"), code ? code : L"", description ? description : L""); SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, buff); } else SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, TranslateT("Error: unknown reply received\r\nPlease select other server")); } ////////////////////////////////////////////////////////////////////////////////////////// // Return results to search dialog // The pmFields is the pointer to map of Not unical but ordered // This can help to made result parser routines more simple static wchar_t *nickfields[] = { L"nick", L"nickname", L"fullname", L"name", L"given", L"first", L"jid", nullptr }; void CJabberProto::SearchReturnResults(HANDLE id, void * pvUsersInfo, U_TCHAR_MAP * pmAllFields) { LIST ListOfNonEmptyFields(20, (LIST::FTSortFunc)TCharKeyCmp); LIST ListOfFields(20); LIST *plUsersInfo = (LIST*)pvUsersInfo; // lets fill the ListOfNonEmptyFields but in users order for (auto &pmUserData : *plUsersInfo) { int nUserFields = pmUserData->getCount(); for (int j = 0; j < nUserFields; j++) { wchar_t *var = pmUserData->getKeyName(j); if (var && ListOfNonEmptyFields.getIndex(var) < 0) ListOfNonEmptyFields.insert(var); } } // now fill the ListOfFields but order is from pmAllFields int nAllCount = pmAllFields->getCount(); for (int i = 0; i < nAllCount; i++) { wchar_t *var = pmAllFields->getUnOrderedKeyName(i); if (var && ListOfNonEmptyFields.getIndex(var) < 0) continue; ListOfFields.insert(var); } // now lets transfer field names int nFieldCount = ListOfFields.getCount(); CUSTOMSEARCHRESULTS Results = { 0 }; Results.nSize = sizeof(Results); Results.pszFields = (wchar_t**)mir_alloc(sizeof(wchar_t*)*nFieldCount); Results.nFieldCount = nFieldCount; /* Sending Columns Titles */ for (int i = 0; i < nFieldCount; i++) { wchar_t *var = ListOfFields[i]; if (var) Results.pszFields[i] = pmAllFields->operator [](var); } Results.psr.cbSize = 0; // sending column names ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM)&Results); /* Sending Users Data */ Results.psr.cbSize = sizeof(Results.psr); // sending user data for (auto &pmUserData : *plUsersInfo) { wchar_t buff[200]; buff[0] = 0; for (int j = 0; j < nFieldCount; j++) { wchar_t *var = ListOfFields[j]; wchar_t *value = pmUserData->operator [](var); Results.pszFields[j] = value ? value : (wchar_t *)L" "; if (!mir_wstrcmpi(var, L"jid") && value) Results.psr.id.w = value; } wchar_t *nick = nullptr; for (int k = 0; k < _countof(nickfields) && !nick; k++) nick = pmUserData->operator [](nickfields[k]); if (nick) { if (mir_wstrcmpi(nick, Results.psr.id.w)) mir_snwprintf(buff, L"%s (%s)", nick, Results.psr.id.w); else wcsncpy_s(buff, nick, _TRUNCATE); nick = buff; } Results.psr.nick.w = nick; Results.psr.flags = PSR_UNICODE; ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SEARCHRESULT, id, (LPARAM)&Results); Results.psr.nick.w = nullptr; } mir_free(Results.pszFields); } void DestroyKey(wchar_t* key) { mir_free(key); } wchar_t* CopyKey(wchar_t* key) { return mir_wstrdup(key); } //////////////////////////////////////////////////////////////////////////////// // Search field request result handler (XEP-0055. Examples 3, 8) void CJabberProto::OnIqResultAdvancedSearch(HXML iqNode, CJabberIqInfo*) { const wchar_t *type; int id; U_TCHAR_MAP mColumnsNames(10); LIST SearchResults(2); if (((id = JabberGetPacketID(iqNode)) == -1) || ((type = XmlGetAttrValue(iqNode, L"type")) == nullptr)) { ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); return; } if (!mir_wstrcmp(type, L"result")) { HXML queryNode = XmlGetNthChild(iqNode, L"query", 1); HXML xNode = XmlGetChildByTag(queryNode, "x", "xmlns", JABBER_FEAT_DATA_FORMS); if (xNode) { //1. Form search results info HXML reportNode = XmlGetNthChild(xNode, L"reported", 1); if (reportNode) { int i = 1; while (HXML fieldNode = XmlGetNthChild(reportNode, L"field", i++)) { wchar_t *var = (wchar_t*)XmlGetAttrValue(fieldNode, L"var"); if (var) { wchar_t *Label = (wchar_t*)XmlGetAttrValue(fieldNode, L"label"); mColumnsNames.insert(var, (Label != nullptr) ? Label : var); } } } int i = 1; HXML itemNode; while (itemNode = XmlGetNthChild(xNode, L"item", i++)) { U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10); int j = 1; while (HXML fieldNode = XmlGetNthChild(itemNode, L"field", j++)) { if (wchar_t* var = (wchar_t*)XmlGetAttrValue(fieldNode, L"var")) { if (wchar_t* Text = (wchar_t*)XmlGetText(XmlGetChild(fieldNode, L"value"))) { if (!mColumnsNames[var]) mColumnsNames.insert(var, var); pUserColumn->insert(var, Text); } } } SearchResults.insert((void*)pUserColumn); } } else { //2. Field list search results info int i = 1; while (HXML itemNode = XmlGetNthChild(queryNode, L"item", i++)) { U_TCHAR_MAP *pUserColumn = new U_TCHAR_MAP(10); wchar_t *jid = (wchar_t*)XmlGetAttrValue(itemNode, L"jid"); wchar_t *keyReturned; mColumnsNames.insertCopyKey(L"jid", L"jid", &keyReturned, CopyKey, DestroyKey); mColumnsNames.insert(L"jid", keyReturned); pUserColumn->insertCopyKey(L"jid", jid, nullptr, CopyKey, DestroyKey); for (int j = 0;; j++) { HXML child = XmlGetChild(itemNode, j); if (!child) break; const wchar_t *szColumnName = XmlGetName(child); if (szColumnName) { const wchar_t *ptszChild = XmlGetText(child); if (ptszChild && *ptszChild) { mColumnsNames.insertCopyKey((wchar_t*)szColumnName, L"", &keyReturned, CopyKey, DestroyKey); mColumnsNames.insert((wchar_t*)szColumnName, keyReturned); pUserColumn->insertCopyKey((wchar_t*)szColumnName, (wchar_t*)ptszChild, nullptr, CopyKey, DestroyKey); } } } SearchResults.insert((void*)pUserColumn); } } } else if (!mir_wstrcmp(type, L"error")) { const wchar_t *code = nullptr; const wchar_t *description = nullptr; wchar_t buff[255]; HXML errorNode = XmlGetChild(iqNode, "error"); if (errorNode) { code = XmlGetAttrValue(errorNode, L"code"); description = XmlGetText(errorNode); } mir_snwprintf(buff, TranslateT("Error %s %s\r\nTry to specify more detailed"), code ? code : L"", description ? description : L""); ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); if (searchHandleDlg) SetDlgItemText(searchHandleDlg, IDC_INSTRUCTIONS, buff); else MessageBox(nullptr, buff, TranslateT("Search error"), MB_OK | MB_ICONSTOP); return; } SearchReturnResults((HANDLE)id, (void*)&SearchResults, (U_TCHAR_MAP *)&mColumnsNames); for (auto &it : SearchResults) delete ((U_TCHAR_MAP*)it); //send success to finish searching ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)id, 0); } static BOOL CALLBACK DeleteChildWindowsProc(HWND hwnd, LPARAM) { DestroyWindow(hwnd); return TRUE; } static void JabberSearchFreeData(HWND hwndDlg, JabberSearchData * dat) { if (!dat->fSearchRequestIsXForm && dat->nJSInfCount && dat->pJSInf) { for (int i = 0; i < dat->nJSInfCount; i++) { if (dat->pJSInf[i].hwndValueItem) DestroyWindow(dat->pJSInf[i].hwndValueItem); if (dat->pJSInf[i].hwndCaptionItem) DestroyWindow(dat->pJSInf[i].hwndCaptionItem); if (dat->pJSInf[i].szFieldCaption) free(dat->pJSInf[i].szFieldCaption); if (dat->pJSInf[i].szFieldName) free(dat->pJSInf[i].szFieldName); } free(dat->pJSInf); dat->pJSInf = nullptr; } else EnumChildWindows(GetDlgItem(hwndDlg, IDC_FRAME), DeleteChildWindowsProc, 0); if (dat->xNode) xmlDestroyNode(dat->xNode); SendDlgItemMessage(hwndDlg, IDC_FRAME, WM_SETFONT, (WPARAM)SendMessage(hwndDlg, WM_GETFONT, 0, 0), 0); dat->nJSInfCount = 0; ShowWindow(GetDlgItem(hwndDlg, IDC_VSCROLL), SW_HIDE); SetDlgItemText(hwndDlg, IDC_INSTRUCTIONS, TranslateT("Select/type search service URL above and press ")); } static void JabberSearchRefreshFrameScroll(HWND hwndDlg, JabberSearchData *dat) { HWND hFrame = GetDlgItem(hwndDlg, IDC_FRAME); HWND hwndScroll = GetDlgItem(hwndDlg, IDC_VSCROLL); RECT rc; GetClientRect(hFrame, &rc); GetClientRect(hFrame, &dat->frameRect); dat->frameHeight = rc.bottom - rc.top; if (dat->frameHeight < dat->CurrentHeight) { ShowWindow(hwndScroll, SW_SHOW); EnableWindow(hwndScroll, TRUE); } else ShowWindow(hwndScroll, SW_HIDE); SetScrollRange(hwndScroll, SB_CTL, 0, dat->CurrentHeight - dat->frameHeight, FALSE); } int CJabberProto::SearchRenewFields(HWND hwndDlg, JabberSearchData *dat) { wchar_t szServerName[100]; EnableWindow(GetDlgItem(hwndDlg, IDC_GO), FALSE); GetDlgItemText(hwndDlg, IDC_SERVER, szServerName, _countof(szServerName)); dat->CurrentHeight = 0; dat->curPos = 0; SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE); JabberSearchFreeData(hwndDlg, dat); JabberSearchRefreshFrameScroll(hwndDlg, dat); SetDlgItemText(hwndDlg, IDC_INSTRUCTIONS, m_bJabberOnline ? TranslateT("Please wait...\r\nConnecting search server...") : TranslateT("You have to be connected to server")); if (!m_bJabberOnline) return 0; searchHandleDlg = hwndDlg; CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultGetSearchFields, JABBER_IQ_TYPE_GET, szServerName); m_ThreadInfo->send(XmlNodeIq(pInfo) << XQUERY(L"jabber:iq:search")); return pInfo->GetIqId(); } static void JabberSearchAddUrlToRecentCombo(HWND hwndDlg, const wchar_t *szAddr) { int lResult = SendDlgItemMessage(hwndDlg, IDC_SERVER, (UINT)CB_FINDSTRING, 0, (LPARAM)szAddr); if (lResult == -1) SendDlgItemMessage(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0, (LPARAM)szAddr); } void CJabberProto::SearchDeleteFromRecent(const wchar_t *szAddr, bool deleteLastFromDB) { // search in recent for (int i = 0; i < 10; i++) { char key[30]; mir_snprintf(key, "RecentlySearched_%d", i); ptrW szValue(getWStringA(key)); if (szValue == nullptr || mir_wstrcmpi(szAddr, szValue)) continue; for (int j = i; j < 10; j++) { mir_snprintf(key, "RecentlySearched_%d", j + 1); szValue = getWStringA(key); if (szValue != nullptr) { mir_snprintf(key, "RecentlySearched_%d", j); setWString(0, key, szValue); } else { if (deleteLastFromDB) { mir_snprintf(key, "RecentlySearched_%d", j); delSetting(0, key); } break; } } break; } } void CJabberProto::SearchAddToRecent(const wchar_t *szAddr, HWND hwndDialog) { char key[30]; SearchDeleteFromRecent(szAddr, true); for (int j = 9; j > 0; j--) { mir_snprintf(key, "RecentlySearched_%d", j - 1); ptrW szValue(getWStringA(key)); if (szValue != nullptr) { mir_snprintf(key, "RecentlySearched_%d", j); setWString(0, key, szValue); } } mir_snprintf(key, "RecentlySearched_%d", 0); setWString(key, szAddr); if (hwndDialog) JabberSearchAddUrlToRecentCombo(hwndDialog, szAddr); } static INT_PTR CALLBACK JabberSearchAdvancedDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { JabberSearchData* dat = (JabberSearchData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); switch (msg) { case WM_INITDIALOG: TranslateDialogDefault(hwndDlg); { dat = (JabberSearchData *)mir_calloc(sizeof(JabberSearchData)); dat->ppro = (CJabberProto*)lParam; SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); /* Server Combo box */ ptrA jud(dat->ppro->getStringA("Jud")); char *szServerName = (jud == nullptr) ? "users.jabber.org" : jud; SetDlgItemTextA(hwndDlg, IDC_SERVER, szServerName); SendDlgItemMessageA(hwndDlg, IDC_SERVER, CB_ADDSTRING, 0, (LPARAM)szServerName); //TO DO: Add Transports here for (auto &it : dat->ppro->m_lstTransports) if (it != nullptr) JabberSearchAddUrlToRecentCombo(hwndDlg, it); for (int i = 0; i < 10; i++) { char key[30]; mir_snprintf(key, "RecentlySearched_%d", i); ptrW szValue(dat->ppro->getWStringA(key)); if (szValue != nullptr) JabberSearchAddUrlToRecentCombo(hwndDlg, szValue); } //TO DO: Add 4 recently used dat->lastRequestIq = dat->ppro->SearchRenewFields(hwndDlg, dat); } return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDC_SERVER) { switch (HIWORD(wParam)) { case CBN_SETFOCUS: PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg); return TRUE; case CBN_EDITCHANGE: EnableWindow(GetDlgItem(hwndDlg, IDC_GO), TRUE); return TRUE; case CBN_EDITUPDATE: JabberSearchFreeData(hwndDlg, dat); EnableWindow(GetDlgItem(hwndDlg, IDC_GO), TRUE); return TRUE; case CBN_SELENDOK: EnableWindow(GetDlgItem(hwndDlg, IDC_GO), TRUE); PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_GO, BN_CLICKED), 0); return TRUE; } } else if (LOWORD(wParam) == IDC_GO && HIWORD(wParam) == BN_CLICKED) { dat->ppro->SearchRenewFields(hwndDlg, dat); return TRUE; } break; case WM_SIZE: { //Resize IDC_FRAME to take full size RECT rcForm; GetWindowRect(hwndDlg, &rcForm); RECT rcFrame; GetWindowRect(GetDlgItem(hwndDlg, IDC_FRAME), &rcFrame); rcFrame.bottom = rcForm.bottom; SetWindowPos(GetDlgItem(hwndDlg, IDC_FRAME), nullptr, 0, 0, rcFrame.right - rcFrame.left, rcFrame.bottom - rcFrame.top, SWP_NOZORDER | SWP_NOMOVE); GetWindowRect(GetDlgItem(hwndDlg, IDC_VSCROLL), &rcForm); SetWindowPos(GetDlgItem(hwndDlg, IDC_VSCROLL), nullptr, 0, 0, rcForm.right - rcForm.left, rcFrame.bottom - rcFrame.top, SWP_NOZORDER | SWP_NOMOVE); JabberSearchRefreshFrameScroll(hwndDlg, dat); } return TRUE; case WM_USER + 11: { dat->fSearchRequestIsXForm = TRUE; dat->xNode = (HXML)wParam; JabberFormCreateUI(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode, &dat->CurrentHeight, TRUE); ShowWindow(GetDlgItem(hwndDlg, IDC_FRAME), SW_SHOW); dat->nJSInfCount = 1; } return TRUE; case WM_USER + 10: { Data *MyDat = (Data*)lParam; if (MyDat) { dat->fSearchRequestIsXForm = (BOOL)wParam; dat->CurrentHeight = JabberSearchAddField(hwndDlg, MyDat); mir_free(MyDat->Label); mir_free(MyDat->Var); mir_free(MyDat->defValue); free(MyDat); } else { JabberSearchRefreshFrameScroll(hwndDlg, dat); ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - 0, nullptr, &(dat->frameRect)); SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, 0, FALSE); dat->curPos = 0; } } return TRUE; case WM_MOUSEWHEEL: { short zDelta = GET_WHEEL_DELTA_WPARAM(wParam); if (zDelta) { int nScrollLines = 0; SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, (void*)&nScrollLines, 0); for (int i = 0; i < (nScrollLines + 1) / 2; i++) SendMessage(hwndDlg, WM_VSCROLL, (zDelta < 0) ? SB_LINEDOWN : SB_LINEUP, 0); } } return TRUE; case WM_VSCROLL: { int pos; if (dat != nullptr) { pos = dat->curPos; switch (LOWORD(wParam)) { case SB_LINEDOWN: pos += 10; break; case SB_LINEUP: pos -= 10; break; case SB_PAGEDOWN: pos += (dat->CurrentHeight - 10); break; case SB_PAGEUP: pos -= (dat->CurrentHeight - 10); break; case SB_THUMBTRACK: pos = HIWORD(wParam); break; } if (pos > (dat->CurrentHeight - dat->frameHeight)) pos = dat->CurrentHeight - dat->frameHeight; if (pos < 0) pos = 0; if (dat->curPos != pos) { ScrollWindow(GetDlgItem(hwndDlg, IDC_FRAME), 0, dat->curPos - pos, nullptr, &(dat->frameRect)); SetScrollPos(GetDlgItem(hwndDlg, IDC_VSCROLL), SB_CTL, pos, TRUE); RECT Invalid = dat->frameRect; if (dat->curPos - pos >0) Invalid.bottom = Invalid.top + (dat->curPos - pos); else Invalid.top = Invalid.bottom + (dat->curPos - pos); RedrawWindow(GetDlgItem(hwndDlg, IDC_FRAME), nullptr, nullptr, RDW_UPDATENOW | RDW_ALLCHILDREN); dat->curPos = pos; } } } return TRUE; case WM_DESTROY: JabberSearchFreeData(hwndDlg, dat); JabberFormDestroyUI(GetDlgItem(hwndDlg, IDC_FRAME)); mir_free(dat); SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); return TRUE; } return FALSE; } HWND CJabberProto::CreateExtendedSearchUI(HWND parent) { if (parent && g_plugin.getInst()) { ptrW szServer(getWStringA("LoginServer")); if (szServer == nullptr || mir_wstrcmpi(szServer, L"S.ms")) return CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_SEARCHUSER), parent, JabberSearchAdvancedDlgProc, (LPARAM)this); } return nullptr; // Failure } ////////////////////////////////////////////////////////////////////////// // The function formats request to server HWND CJabberProto::SearchAdvanced(HWND hwndDlg) { if (!m_bJabberOnline || !hwndDlg) return nullptr; //error JabberSearchData * dat = (JabberSearchData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (!dat) return nullptr; //error // check if server connected (at least one field exists) if (dat->nJSInfCount == 0) return nullptr; // formating request BOOL fRequestNotEmpty = FALSE; // get server name wchar_t szServerName[100]; GetDlgItemText(hwndDlg, IDC_SERVER, szServerName, _countof(szServerName)); // formating query CJabberIqInfo *pInfo = AddIQ(&CJabberProto::OnIqResultAdvancedSearch, JABBER_IQ_TYPE_SET, szServerName); XmlNodeIq iq(pInfo); HXML query = iq << XQUERY(L"jabber:iq:search"); if (m_tszSelectedLang) iq << XATTR(L"xml:lang", m_tszSelectedLang); // i'm sure :) // next can be 2 cases: // Forms: XEP-0055 Example 7 if (dat->fSearchRequestIsXForm) { fRequestNotEmpty = TRUE; HXML n = JabberFormGetData(GetDlgItem(hwndDlg, IDC_FRAME), dat->xNode); XmlAddChild(query, n); xmlDestroyNode(n); } else { //and Simple fields: XEP-0055 Example 3 for (int i = 0; i < dat->nJSInfCount; i++) { wchar_t szFieldValue[100]; GetWindowText(dat->pJSInf[i].hwndValueItem, szFieldValue, _countof(szFieldValue)); if (szFieldValue[0] != 0) { XmlAddChild(query, dat->pJSInf[i].szFieldName, szFieldValue); fRequestNotEmpty = TRUE; } } } if (fRequestNotEmpty) { m_ThreadInfo->send(iq); return (HWND)pInfo->GetIqId(); } return nullptr; }