From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- src/modules/addcontact/addcontact.cpp | 271 +++ src/modules/autoaway/autoaway.cpp | 74 + src/modules/button/button.cpp | 614 ++++++ src/modules/clist/Docking.cpp | 392 ++++ src/modules/clist/clc.cpp | 1350 +++++++++++++ src/modules/clist/clc.h | 248 +++ src/modules/clist/clcfiledrop.cpp | 278 +++ src/modules/clist/clcidents.cpp | 201 ++ src/modules/clist/clcitems.cpp | 707 +++++++ src/modules/clist/clcmsgs.cpp | 472 +++++ src/modules/clist/clcutils.cpp | 879 +++++++++ src/modules/clist/clistcore.cpp | 224 +++ src/modules/clist/clistevents.cpp | 441 +++++ src/modules/clist/clistmenus.cpp | 1443 ++++++++++++++ src/modules/clist/clistmod.cpp | 569 ++++++ src/modules/clist/clistsettings.cpp | 331 ++++ src/modules/clist/clisttray.cpp | 980 +++++++++ src/modules/clist/clui.cpp | 1136 +++++++++++ src/modules/clist/cluiservices.cpp | 221 +++ src/modules/clist/contact.cpp | 193 ++ src/modules/clist/genmenu.cpp | 1294 ++++++++++++ src/modules/clist/genmenu.h | 146 ++ src/modules/clist/genmenuopt.cpp | 870 ++++++++ src/modules/clist/groups.cpp | 577 ++++++ src/modules/clist/keyboard.cpp | 173 ++ src/modules/clist/movetogroup.cpp | 160 ++ src/modules/clist/protocolorder.cpp | 351 ++++ src/modules/contacts/contacts.cpp | 513 +++++ src/modules/database/database.cpp | 563 ++++++ src/modules/database/dbini.cpp | 490 +++++ src/modules/database/dblists.cpp | 282 +++ src/modules/database/dblists.h | 39 + src/modules/database/dbutils.cpp | 365 ++++ src/modules/database/profilemanager.cpp | 850 ++++++++ src/modules/database/profilemanager.h | 43 + src/modules/findadd/findadd.cpp | 1033 ++++++++++ src/modules/findadd/findadd.h | 59 + src/modules/findadd/searchresults.cpp | 398 ++++ src/modules/fonts/FontOptions.cpp | 1523 ++++++++++++++ src/modules/fonts/FontService.cpp | 126 ++ src/modules/fonts/FontService.h | 112 ++ src/modules/fonts/services.cpp | 534 +++++ src/modules/help/about.cpp | 148 ++ src/modules/help/help.cpp | 117 ++ src/modules/history/history.cpp | 434 ++++ src/modules/icolib/IcoLib.h | 83 + src/modules/icolib/extracticon.cpp | 280 +++ src/modules/icolib/skin2icons.cpp | 1992 +++++++++++++++++++ src/modules/idle/idle.cpp | 520 +++++ src/modules/ignore/ignore.cpp | 481 +++++ src/modules/keybindings/keybindings.cpp | 616 ++++++ src/modules/keybindings/keybindings.h | 43 + src/modules/langpack/langpack.cpp | 569 ++++++ src/modules/langpack/lpservices.cpp | 168 ++ src/modules/netlib/netlib.cpp | 640 ++++++ src/modules/netlib/netlib.h | 209 ++ src/modules/netlib/netlibautoproxy.cpp | 460 +++++ src/modules/netlib/netlibbind.cpp | 287 +++ src/modules/netlib/netlibhttp.cpp | 1331 +++++++++++++ src/modules/netlib/netlibhttpproxy.cpp | 522 +++++ src/modules/netlib/netliblog.cpp | 637 ++++++ src/modules/netlib/netlibopenconn.cpp | 944 +++++++++ src/modules/netlib/netlibopts.cpp | 556 ++++++ src/modules/netlib/netlibpktrecver.cpp | 85 + src/modules/netlib/netlibsecurity.cpp | 570 ++++++ src/modules/netlib/netlibsock.cpp | 203 ++ src/modules/netlib/netlibssl.cpp | 981 +++++++++ src/modules/netlib/netlibupnp.cpp | 885 +++++++++ src/modules/options/descbutton.cpp | 332 ++++ src/modules/options/filter.cpp | 217 ++ src/modules/options/filter.h | 108 + src/modules/options/headerbar.cpp | 372 ++++ src/modules/options/iconheader.cpp | 545 +++++ src/modules/options/options.cpp | 1521 ++++++++++++++ src/modules/plugins/newplugins.cpp | 1204 ++++++++++++ src/modules/protocols/protoaccs.cpp | 638 ++++++ src/modules/protocols/protochains.cpp | 274 +++ src/modules/protocols/protocols.cpp | 844 ++++++++ src/modules/protocols/protodir.cpp | 248 +++ src/modules/protocols/protoint.cpp | 271 +++ src/modules/protocols/protoopts.cpp | 1084 ++++++++++ src/modules/skin/hotkeys.cpp | 1465 ++++++++++++++ src/modules/skin/skinicons.cpp | 489 +++++ src/modules/skin/sounds.cpp | 469 +++++ src/modules/srauth/auth.cpp | 129 ++ src/modules/srauth/authdialogs.cpp | 312 +++ src/modules/srawaymsg/awaymsg.cpp | 194 ++ src/modules/srawaymsg/sendmsg.cpp | 637 ++++++ src/modules/sremail/email.cpp | 89 + src/modules/srfile/file.cpp | 392 ++++ src/modules/srfile/file.h | 115 ++ src/modules/srfile/fileexistsdlg.cpp | 354 ++++ src/modules/srfile/fileopts.cpp | 247 +++ src/modules/srfile/filerecvdlg.cpp | 446 +++++ src/modules/srfile/filesenddlg.cpp | 363 ++++ src/modules/srfile/filexferdlg.cpp | 799 ++++++++ src/modules/srfile/ftmanager.cpp | 592 ++++++ src/modules/srurl/url.cpp | 182 ++ src/modules/srurl/url.h | 41 + src/modules/srurl/urldialogs.cpp | 664 +++++++ src/modules/updatenotify/updatenotify.cpp | 680 +++++++ src/modules/userinfo/contactinfo.cpp | 515 +++++ src/modules/userinfo/stdinfo.cpp | 613 ++++++ src/modules/userinfo/userinfo.cpp | 636 ++++++ src/modules/useronline/useronline.cpp | 117 ++ src/modules/utils/bmpfilter.cpp | 242 +++ src/modules/utils/colourpicker.cpp | 107 + src/modules/utils/hyperlink.cpp | 274 +++ src/modules/utils/imgconv.cpp | 152 ++ src/modules/utils/md5.cpp | 374 ++++ src/modules/utils/openurl.cpp | 228 +++ src/modules/utils/path.cpp | 600 ++++++ src/modules/utils/resizer.cpp | 152 ++ src/modules/utils/sha1.cpp | 175 ++ src/modules/utils/timeutils.cpp | 277 +++ src/modules/utils/timezones.cpp | 662 +++++++ src/modules/utils/utf.cpp | 413 ++++ src/modules/utils/utils.cpp | 587 ++++++ src/modules/utils/windowlist.cpp | 101 + src/modules/visibility/visibility.cpp | 297 +++ src/modules/xml/xmlApi.cpp | 446 +++++ src/modules/xml/xmlParser.cpp | 3061 +++++++++++++++++++++++++++++ src/modules/xml/xmlParser.h | 746 +++++++ 123 files changed, 62378 insertions(+) create mode 100644 src/modules/addcontact/addcontact.cpp create mode 100644 src/modules/autoaway/autoaway.cpp create mode 100644 src/modules/button/button.cpp create mode 100644 src/modules/clist/Docking.cpp create mode 100644 src/modules/clist/clc.cpp create mode 100644 src/modules/clist/clc.h create mode 100644 src/modules/clist/clcfiledrop.cpp create mode 100644 src/modules/clist/clcidents.cpp create mode 100644 src/modules/clist/clcitems.cpp create mode 100644 src/modules/clist/clcmsgs.cpp create mode 100644 src/modules/clist/clcutils.cpp create mode 100644 src/modules/clist/clistcore.cpp create mode 100644 src/modules/clist/clistevents.cpp create mode 100644 src/modules/clist/clistmenus.cpp create mode 100644 src/modules/clist/clistmod.cpp create mode 100644 src/modules/clist/clistsettings.cpp create mode 100644 src/modules/clist/clisttray.cpp create mode 100644 src/modules/clist/clui.cpp create mode 100644 src/modules/clist/cluiservices.cpp create mode 100644 src/modules/clist/contact.cpp create mode 100644 src/modules/clist/genmenu.cpp create mode 100644 src/modules/clist/genmenu.h create mode 100644 src/modules/clist/genmenuopt.cpp create mode 100644 src/modules/clist/groups.cpp create mode 100644 src/modules/clist/keyboard.cpp create mode 100644 src/modules/clist/movetogroup.cpp create mode 100644 src/modules/clist/protocolorder.cpp create mode 100644 src/modules/contacts/contacts.cpp create mode 100644 src/modules/database/database.cpp create mode 100644 src/modules/database/dbini.cpp create mode 100644 src/modules/database/dblists.cpp create mode 100644 src/modules/database/dblists.h create mode 100644 src/modules/database/dbutils.cpp create mode 100644 src/modules/database/profilemanager.cpp create mode 100644 src/modules/database/profilemanager.h create mode 100644 src/modules/findadd/findadd.cpp create mode 100644 src/modules/findadd/findadd.h create mode 100644 src/modules/findadd/searchresults.cpp create mode 100644 src/modules/fonts/FontOptions.cpp create mode 100644 src/modules/fonts/FontService.cpp create mode 100644 src/modules/fonts/FontService.h create mode 100644 src/modules/fonts/services.cpp create mode 100644 src/modules/help/about.cpp create mode 100644 src/modules/help/help.cpp create mode 100644 src/modules/history/history.cpp create mode 100644 src/modules/icolib/IcoLib.h create mode 100644 src/modules/icolib/extracticon.cpp create mode 100644 src/modules/icolib/skin2icons.cpp create mode 100644 src/modules/idle/idle.cpp create mode 100644 src/modules/ignore/ignore.cpp create mode 100644 src/modules/keybindings/keybindings.cpp create mode 100644 src/modules/keybindings/keybindings.h create mode 100644 src/modules/langpack/langpack.cpp create mode 100644 src/modules/langpack/lpservices.cpp create mode 100644 src/modules/netlib/netlib.cpp create mode 100644 src/modules/netlib/netlib.h create mode 100644 src/modules/netlib/netlibautoproxy.cpp create mode 100644 src/modules/netlib/netlibbind.cpp create mode 100644 src/modules/netlib/netlibhttp.cpp create mode 100644 src/modules/netlib/netlibhttpproxy.cpp create mode 100644 src/modules/netlib/netliblog.cpp create mode 100644 src/modules/netlib/netlibopenconn.cpp create mode 100644 src/modules/netlib/netlibopts.cpp create mode 100644 src/modules/netlib/netlibpktrecver.cpp create mode 100644 src/modules/netlib/netlibsecurity.cpp create mode 100644 src/modules/netlib/netlibsock.cpp create mode 100644 src/modules/netlib/netlibssl.cpp create mode 100644 src/modules/netlib/netlibupnp.cpp create mode 100644 src/modules/options/descbutton.cpp create mode 100644 src/modules/options/filter.cpp create mode 100644 src/modules/options/filter.h create mode 100644 src/modules/options/headerbar.cpp create mode 100644 src/modules/options/iconheader.cpp create mode 100644 src/modules/options/options.cpp create mode 100644 src/modules/plugins/newplugins.cpp create mode 100644 src/modules/protocols/protoaccs.cpp create mode 100644 src/modules/protocols/protochains.cpp create mode 100644 src/modules/protocols/protocols.cpp create mode 100644 src/modules/protocols/protodir.cpp create mode 100644 src/modules/protocols/protoint.cpp create mode 100644 src/modules/protocols/protoopts.cpp create mode 100644 src/modules/skin/hotkeys.cpp create mode 100644 src/modules/skin/skinicons.cpp create mode 100644 src/modules/skin/sounds.cpp create mode 100644 src/modules/srauth/auth.cpp create mode 100644 src/modules/srauth/authdialogs.cpp create mode 100644 src/modules/srawaymsg/awaymsg.cpp create mode 100644 src/modules/srawaymsg/sendmsg.cpp create mode 100644 src/modules/sremail/email.cpp create mode 100644 src/modules/srfile/file.cpp create mode 100644 src/modules/srfile/file.h create mode 100644 src/modules/srfile/fileexistsdlg.cpp create mode 100644 src/modules/srfile/fileopts.cpp create mode 100644 src/modules/srfile/filerecvdlg.cpp create mode 100644 src/modules/srfile/filesenddlg.cpp create mode 100644 src/modules/srfile/filexferdlg.cpp create mode 100644 src/modules/srfile/ftmanager.cpp create mode 100644 src/modules/srurl/url.cpp create mode 100644 src/modules/srurl/url.h create mode 100644 src/modules/srurl/urldialogs.cpp create mode 100644 src/modules/updatenotify/updatenotify.cpp create mode 100644 src/modules/userinfo/contactinfo.cpp create mode 100644 src/modules/userinfo/stdinfo.cpp create mode 100644 src/modules/userinfo/userinfo.cpp create mode 100644 src/modules/useronline/useronline.cpp create mode 100644 src/modules/utils/bmpfilter.cpp create mode 100644 src/modules/utils/colourpicker.cpp create mode 100644 src/modules/utils/hyperlink.cpp create mode 100644 src/modules/utils/imgconv.cpp create mode 100644 src/modules/utils/md5.cpp create mode 100644 src/modules/utils/openurl.cpp create mode 100644 src/modules/utils/path.cpp create mode 100644 src/modules/utils/resizer.cpp create mode 100644 src/modules/utils/sha1.cpp create mode 100644 src/modules/utils/timeutils.cpp create mode 100644 src/modules/utils/timezones.cpp create mode 100644 src/modules/utils/utf.cpp create mode 100644 src/modules/utils/utils.cpp create mode 100644 src/modules/utils/windowlist.cpp create mode 100644 src/modules/visibility/visibility.cpp create mode 100644 src/modules/xml/xmlApi.cpp create mode 100644 src/modules/xml/xmlParser.cpp create mode 100644 src/modules/xml/xmlParser.h (limited to 'src/modules') diff --git a/src/modules/addcontact/addcontact.cpp b/src/modules/addcontact/addcontact.cpp new file mode 100644 index 0000000000..0be08b93ba --- /dev/null +++ b/src/modules/addcontact/addcontact.cpp @@ -0,0 +1,271 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +INT_PTR CALLBACK AddContactDlgProc(HWND hdlg,UINT msg,WPARAM wparam,LPARAM lparam) +{ + ADDCONTACTSTRUCT *acs; + + switch(msg) { + case WM_INITDIALOG: + { + char szUin[10]; + acs=(ADDCONTACTSTRUCT *)lparam; + SetWindowLongPtr(hdlg,GWLP_USERDATA,(LONG_PTR)acs); + + TranslateDialogDefault(hdlg); + Window_SetIcon_IcoLib(hdlg, SKINICON_OTHER_ADDCONTACT); + if ( acs->handleType == HANDLE_EVENT ) { + DWORD dwUin; + DBEVENTINFO dbei = { 0 }; + dbei.cbSize=sizeof(dbei); + dbei.cbBlob=sizeof(DWORD); + dbei.pBlob=(PBYTE)&dwUin; + CallService(MS_DB_EVENT_GET,(WPARAM)acs->handle,(LPARAM)&dbei); + _ltoa(dwUin,szUin,10); + acs->szProto = dbei.szModule; + } + { + TCHAR *szName = NULL, *tmpStr = NULL; + if ( acs->handleType == HANDLE_CONTACT ) + szName = cli.pfnGetContactDisplayName( acs->handle, GCDNF_TCHAR ); + else { + int isSet = 0; + + if (acs->handleType == HANDLE_EVENT) { + DBEVENTINFO dbei; + HANDLE hcontact; + + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + dbei.cbBlob=CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)acs->handle,0); + dbei.pBlob=(PBYTE)mir_alloc(dbei.cbBlob); + CallService(MS_DB_EVENT_GET,(WPARAM)acs->handle,(LPARAM)&dbei); + hcontact=*((PHANDLE)(dbei.pBlob+sizeof(DWORD))); + mir_free(dbei.pBlob); + if (hcontact!=INVALID_HANDLE_VALUE) { + szName = cli.pfnGetContactDisplayName( hcontact, 0 ); + isSet = 1; + } + } + if (!isSet) { + szName = (acs->handleType == HANDLE_EVENT) ? (tmpStr = mir_a2t(szUin)) : + (acs->psr->id ? acs->psr->id : acs->psr->nick); + } } + + if ( szName && szName[0] ) { + TCHAR szTitle[128]; + mir_sntprintf( szTitle, SIZEOF(szTitle), TranslateT("Add %s"), szName ); + SetWindowText( hdlg, szTitle ); + } + else SetWindowText( hdlg, TranslateT("Add Contact")); + mir_free(tmpStr); + } } + + if ( acs->handleType == HANDLE_CONTACT && acs->handle ) + if ( acs->szProto == NULL || (acs->szProto != NULL && *acs->szProto == 0 )) + acs->szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)acs->handle,0); + + { + int groupId; + for ( groupId = 0; groupId < 999; groupId++ ) { + DBVARIANT dbv; + char idstr[4]; + int id; + _itoa(groupId,idstr,10); + if(DBGetContactSettingTString(NULL,"CListGroups",idstr,&dbv)) break; + id = SendDlgItemMessage(hdlg,IDC_GROUP,CB_ADDSTRING,0,(LPARAM)(dbv.ptszVal+1)); + SendDlgItemMessage(hdlg,IDC_GROUP,CB_SETITEMDATA ,id,groupId+1); + DBFreeVariant(&dbv); + } } + + SendDlgItemMessage(hdlg,IDC_GROUP,CB_INSERTSTRING,0,(LPARAM)TranslateT("None")); + SendDlgItemMessage(hdlg,IDC_GROUP,CB_SETCURSEL,0,0); + /* acs->szProto may be NULL don't expect it */ + { + // By default check both checkboxes + CheckDlgButton(hdlg,IDC_ADDED,BST_CHECKED); + CheckDlgButton(hdlg,IDC_AUTH,BST_CHECKED); + + DWORD flags = (acs->szProto) ? CallProtoService(acs->szProto,PS_GETCAPS,PFLAGNUM_4,0) : 0; + if (flags&PF4_FORCEADDED) { // force you were added requests for this protocol + EnableWindow(GetDlgItem(hdlg,IDC_ADDED),FALSE); + } + if (flags&PF4_FORCEAUTH) { // force auth requests for this protocol + EnableWindow(GetDlgItem(hdlg,IDC_AUTH),FALSE); + } + if (flags&PF4_NOCUSTOMAUTH) { + EnableWindow(GetDlgItem(hdlg,IDC_AUTHREQ),FALSE); + EnableWindow(GetDlgItem(hdlg,IDC_AUTHGB),FALSE); + } + else { + EnableWindow(GetDlgItem(hdlg,IDC_AUTHREQ),IsDlgButtonChecked(hdlg,IDC_AUTH)); + EnableWindow(GetDlgItem(hdlg,IDC_AUTHGB),IsDlgButtonChecked(hdlg,IDC_AUTH)); + SetDlgItemText(hdlg,IDC_AUTHREQ,TranslateT("Please authorize my request and add me to your contact list.")); + } + } + break; + + case WM_COMMAND: + acs = (ADDCONTACTSTRUCT *)GetWindowLongPtr(hdlg, GWLP_USERDATA); + + switch (LOWORD(wparam)) + { + case IDC_AUTH: + { + DWORD flags = CallProtoService(acs->szProto,PS_GETCAPS,PFLAGNUM_4,0); + if (flags & PF4_NOCUSTOMAUTH) { + EnableWindow(GetDlgItem(hdlg,IDC_AUTHREQ),FALSE); + EnableWindow(GetDlgItem(hdlg,IDC_AUTHGB),FALSE); + } + else { + EnableWindow(GetDlgItem(hdlg,IDC_AUTHREQ),IsDlgButtonChecked(hdlg,IDC_AUTH)); + EnableWindow(GetDlgItem(hdlg,IDC_AUTHGB),IsDlgButtonChecked(hdlg,IDC_AUTH)); + } + } + break; + case IDOK: + { + HANDLE hContact = INVALID_HANDLE_VALUE; + switch (acs->handleType) + { + case HANDLE_EVENT: + { + DBEVENTINFO dbei = { 0 }; + dbei.cbSize = sizeof(dbei); + CallService(MS_DB_EVENT_GET, (WPARAM)acs->handle, (LPARAM)&dbei); + hContact = (HANDLE)CallProtoService(dbei.szModule, PS_ADDTOLISTBYEVENT, 0, (LPARAM)acs->handle); + } + break; + + case HANDLE_SEARCHRESULT: + hContact = (HANDLE)CallProtoService(acs->szProto, PS_ADDTOLIST, 0, (LPARAM)acs->psr); + break; + + case HANDLE_CONTACT: + hContact = acs->handle; + break; + } + + if (hContact == NULL) + break; + + TCHAR szHandle[256]; + if (GetDlgItemText(hdlg, IDC_MYHANDLE, szHandle, SIZEOF(szHandle))) + DBWriteContactSettingTString(hContact, "CList", "MyHandle", szHandle); + + int item = SendDlgItemMessage(hdlg, IDC_GROUP, CB_GETCURSEL, 0, 0); + if (item > 0) + { + item = SendDlgItemMessage(hdlg, IDC_GROUP, CB_GETITEMDATA, item, 0); + CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)hContact, item); + } + + DBDeleteContactSetting(hContact, "CList", "NotOnList"); + + if (IsDlgButtonChecked(hdlg, IDC_ADDED)) + CallContactService(hContact, PSS_ADDED, 0, 0); + + if (IsDlgButtonChecked(hdlg, IDC_AUTH)) + { + DWORD flags = CallProtoService(acs->szProto, PS_GETCAPS, PFLAGNUM_4, 0); + if (flags & PF4_NOCUSTOMAUTH) + CallContactService(hContact, PSS_AUTHREQUESTT, 0, 0); + else + { + TCHAR szReason[512]; + GetDlgItemText(hdlg, IDC_AUTHREQ, szReason, SIZEOF(szReason)); + CallContactService(hContact, PSS_AUTHREQUESTT, 0, (LPARAM)szReason); + } + } + } + // fall through + case IDCANCEL: + if ( GetParent( hdlg ) == NULL) + DestroyWindow( hdlg ); + else + EndDialog( hdlg, 0 ); + break; + } + break; + + case WM_CLOSE: + /* if there is no parent for the dialog, its a modeless dialog and can't be killed using EndDialog() */ + if ( GetParent( hdlg ) == NULL ) + DestroyWindow(hdlg); + else + EndDialog( hdlg, 0 ); + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hdlg); + acs = ( ADDCONTACTSTRUCT* )GetWindowLongPtr(hdlg,GWLP_USERDATA); + if (acs) { + if (acs->psr) { + mir_free(acs->psr->nick); + mir_free(acs->psr->firstName); + mir_free(acs->psr->lastName); + mir_free(acs->psr->email); + mir_free(acs->psr); + } + mir_free(acs); + } + break; + } + + return FALSE; +} + +INT_PTR AddContactDialog(WPARAM wParam,LPARAM lParam) +{ + if (lParam) { + ADDCONTACTSTRUCT* acs = ( ADDCONTACTSTRUCT* )mir_alloc(sizeof(ADDCONTACTSTRUCT)); + memmove( acs, ( ADDCONTACTSTRUCT* )lParam, sizeof( ADDCONTACTSTRUCT )); + if ( acs->psr ) { + PROTOSEARCHRESULT *psr; + /* bad! structures that are bigger than psr will cause crashes if they define pointers within unreachable structural space */ + psr = (PROTOSEARCHRESULT *)mir_alloc(acs->psr->cbSize); + memmove(psr,acs->psr,acs->psr->cbSize); + psr->nick = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->nick) : mir_a2t((char*)psr->nick); + psr->firstName = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->firstName) : mir_a2t((char*)psr->firstName); + psr->lastName = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->lastName) : mir_a2t((char*)psr->lastName); + psr->email = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->email) : mir_a2t((char*)psr->email); + psr->flags = psr->flags & ~PSR_UNICODE | PSR_TCHAR; + acs->psr = psr; + /* copied the passed acs structure, the psr structure with, the pointers within that */ + } + + if ( wParam ) + DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(IDD_ADDCONTACT),(HWND)wParam,AddContactDlgProc,(LPARAM)acs); + else + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_ADDCONTACT),(HWND)wParam,AddContactDlgProc,(LPARAM)acs); + return 0; + } + return 1; +} + +int LoadAddContactModule(void) +{ + CreateServiceFunction(MS_ADDCONTACT_SHOW,AddContactDialog); + return 0; +} diff --git a/src/modules/autoaway/autoaway.cpp b/src/modules/autoaway/autoaway.cpp new file mode 100644 index 0000000000..9805535173 --- /dev/null +++ b/src/modules/autoaway/autoaway.cpp @@ -0,0 +1,74 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#define AA_MODULE "AutoAway" + +void Proto_SetStatus(const char* szProto, unsigned status); + +static int AutoAwayEvent(WPARAM, LPARAM lParam) +{ + int i; + + MIRANDA_IDLE_INFO mii; + mii.cbSize = sizeof( mii ); + CallService( MS_IDLE_GETIDLEINFO, 0, (LPARAM)&mii ); + if ( mii.aaStatus == 0 ) + return 0; + + for ( i=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + + if (!Proto_IsAccountEnabled( pa ) || Proto_IsAccountLocked( pa )) continue; + + int statusbits = CallProtoService( pa->szModuleName, PS_GETCAPS, PFLAGNUM_2, 0 ); + int currentstatus = CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0 ); + int status = mii.aaStatus; + if ( !(statusbits & Proto_Status2Flag(status)) ) { + // the protocol doesnt support the given status + if ( statusbits & Proto_Status2Flag( ID_STATUS_AWAY )) + status = ID_STATUS_AWAY; + // the proto doesnt support user mode or even away, bail. + else + continue; + } + if ( currentstatus >= ID_STATUS_ONLINE && currentstatus != ID_STATUS_INVISIBLE ) { + if ( (lParam&IDF_ISIDLE) && ( currentstatus == ID_STATUS_ONLINE || currentstatus == ID_STATUS_FREECHAT )) { + DBWriteContactSettingByte( NULL, AA_MODULE, pa->szModuleName, 1 ); + Proto_SetStatus( pa->szModuleName, status ); + } + else if ( !(lParam & IDF_ISIDLE) && DBGetContactSettingByte( NULL, AA_MODULE, pa->szModuleName, 0 )) { + // returning from idle and this proto was set away, set it back + DBWriteContactSettingByte( NULL, AA_MODULE, pa->szModuleName, 0 ); + if ( !mii.aaLock ) + Proto_SetStatus( pa->szModuleName, ID_STATUS_ONLINE); + } } } + + return 0; +} + +int LoadAutoAwayModule(void) +{ + HookEvent(ME_IDLE_CHANGED, AutoAwayEvent); + return 0; +} diff --git a/src/modules/button/button.cpp b/src/modules/button/button.cpp new file mode 100644 index 0000000000..b6265dfa7a --- /dev/null +++ b/src/modules/button/button.cpp @@ -0,0 +1,614 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include + +// TODO: +// - Support for bitmap buttons (simple call to DrawIconEx()) + +static LRESULT CALLBACK MButtonWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +typedef struct { + HWND hwnd; + int stateId; // button state + int focus; // has focus (1 or 0) + HFONT hFont; // font + HICON arrow; // uses down arrow + int defbutton; // default button + HICON hIcon; + HBITMAP hBitmap; + int pushBtn; + int pbState; + HTHEME hThemeButton; + HTHEME hThemeToolbar; + char cHot; + int flatBtn; + HWND hwndToolTips; + IAccPropServices* pAccPropServices; +} MButtonCtrl; + + +static CRITICAL_SECTION csTips; +static SortedList lToolTips; +static BOOL bModuleInitialized = FALSE; + +typedef struct +{ + DWORD ThreadId; + HWND hwnd; +} TTooltips; + +int LoadButtonModule(void) +{ + WNDCLASSEX wc = {0}; + + if ( bModuleInitialized ) return 0; + bModuleInitialized = TRUE; + + wc.cbSize = sizeof(wc); + wc.lpszClassName = MIRANDABUTTONCLASS; + wc.lpfnWndProc = MButtonWndProc; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.cbWndExtra = sizeof(MButtonCtrl*); + wc.hbrBackground = 0; + wc.style = CS_GLOBALCLASS; + RegisterClassEx(&wc); + + InitializeCriticalSection(&csTips); + lToolTips.increment = 1; + lToolTips.sortFunc = NumericKeySort; + return 0; +} + +void UnloadButtonModule() +{ + if ( !bModuleInitialized ) return; + EnterCriticalSection(&csTips); + List_Destroy(&lToolTips); + LeaveCriticalSection(&csTips); + DeleteCriticalSection(&csTips); +} + +// Used for our own cheap TrackMouseEvent +#define BUTTON_POLLID 100 +#define BUTTON_POLLDELAY 50 + +static void DestroyTheme(MButtonCtrl *ctl) { + if (closeThemeData) { + if (ctl->hThemeButton) { + closeThemeData(ctl->hThemeButton); + ctl->hThemeButton = NULL; + } + if (ctl->hThemeToolbar) { + closeThemeData(ctl->hThemeToolbar); + ctl->hThemeToolbar = NULL; + } + } +} + +static void LoadTheme(MButtonCtrl *ctl) +{ + if (openThemeData) { + DestroyTheme(ctl); + ctl->hThemeButton = openThemeData(ctl->hwnd, L"BUTTON"); + ctl->hThemeToolbar = openThemeData(ctl->hwnd, L"TOOLBAR"); + } +} + +static void SetHwndPropInt(MButtonCtrl* bct, DWORD idObject, DWORD idChild, MSAAPROPID idProp, int val) +{ + if (bct->pAccPropServices == NULL) return; + VARIANT var; + var.vt = VT_I4; + var.lVal = val; + bct->pAccPropServices->SetHwndProp(bct->hwnd, idObject, idChild, idProp, var); +} +static int TBStateConvert2Flat(int state) +{ + switch(state) { + case PBS_NORMAL: return TS_NORMAL; + case PBS_HOT: return TS_HOT; + case PBS_PRESSED: return TS_PRESSED; + case PBS_DISABLED: return TS_DISABLED; + case PBS_DEFAULTED: return TS_NORMAL; + } + return TS_NORMAL; +} + +#ifndef DFCS_HOT +#define DFCS_HOT 0x1000 +#endif + +static void PaintWorker(MButtonCtrl *ctl, HDC hdcPaint) +{ + if (hdcPaint) { + HDC hdcMem; + HBITMAP hbmMem; + HDC hOld; + RECT rcClient; + + GetClientRect(ctl->hwnd, &rcClient); + hdcMem = CreateCompatibleDC(hdcPaint); + hbmMem = CreateCompatibleBitmap(hdcPaint, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top); + hOld = ( HDC )SelectObject(hdcMem, hbmMem); + + // If its a push button, check to see if it should stay pressed + if (ctl->pushBtn && ctl->pbState) ctl->stateId = PBS_PRESSED; + + // Draw the flat button + if (ctl->flatBtn) { + if (ctl->hThemeToolbar) { + int state = IsWindowEnabled(ctl->hwnd)?(ctl->stateId==PBS_NORMAL&&ctl->defbutton?PBS_DEFAULTED:ctl->stateId):PBS_DISABLED; + if (isThemeBackgroundPartiallyTransparent(ctl->hThemeToolbar, TP_BUTTON, TBStateConvert2Flat(state))) { + drawThemeParentBackground(ctl->hwnd, hdcMem, &rcClient); + } + drawThemeBackground(ctl->hThemeToolbar, hdcMem, TP_BUTTON, TBStateConvert2Flat(state), &rcClient, &rcClient); + } + else { + HBRUSH hbr; + + if (ctl->stateId==PBS_PRESSED||ctl->stateId==PBS_HOT) + hbr = GetSysColorBrush(COLOR_3DLIGHT); + else { + HWND hwndParent = GetParent(ctl->hwnd); + HDC dc = GetDC(hwndParent); + HBRUSH oldBrush = (HBRUSH)GetCurrentObject( dc, OBJ_BRUSH ); + hbr = (HBRUSH)SendMessage(hwndParent, WM_CTLCOLORDLG, (WPARAM)dc, (LPARAM)hwndParent); + SelectObject(dc,oldBrush); + ReleaseDC(hwndParent,dc); + } + if (hbr) { + FillRect(hdcMem, &rcClient, hbr); + DeleteObject(hbr); + } + if (ctl->stateId==PBS_HOT||ctl->focus) { + if (ctl->pbState) + DrawEdge(hdcMem,&rcClient, EDGE_ETCHED,BF_RECT|BF_SOFT); + else DrawEdge(hdcMem,&rcClient, BDR_RAISEDOUTER,BF_RECT|BF_SOFT|BF_FLAT); + } + else if (ctl->stateId==PBS_PRESSED) + DrawEdge(hdcMem, &rcClient, BDR_SUNKENOUTER,BF_RECT|BF_SOFT); + } + } + else { + // Draw background/border + if (ctl->hThemeButton) { + int state = IsWindowEnabled(ctl->hwnd)?(ctl->stateId==PBS_NORMAL&&ctl->defbutton?PBS_DEFAULTED:ctl->stateId):PBS_DISABLED; + if (isThemeBackgroundPartiallyTransparent(ctl->hThemeButton, BP_PUSHBUTTON, state)) { + drawThemeParentBackground(ctl->hwnd, hdcMem, &rcClient); + } + drawThemeBackground(ctl->hThemeButton, hdcMem, BP_PUSHBUTTON, state, &rcClient, &rcClient); + } + else { + UINT uState = DFCS_BUTTONPUSH|((ctl->stateId==PBS_HOT)?DFCS_HOT:0)|((ctl->stateId == PBS_PRESSED)?DFCS_PUSHED:0); + if (ctl->defbutton&&ctl->stateId==PBS_NORMAL) uState |= DLGC_DEFPUSHBUTTON; + DrawFrameControl(hdcMem, &rcClient, DFC_BUTTON, uState); + } + + // Draw focus rectangle if button has focus + if (ctl->focus) { + RECT focusRect = rcClient; + InflateRect(&focusRect, -3, -3); + DrawFocusRect(hdcMem, &focusRect); + } + } + + // If we have an icon or a bitmap, ignore text and only draw the image on the button + if (ctl->hIcon) { + int ix = (rcClient.right-rcClient.left)/2 - (GetSystemMetrics(SM_CXSMICON)/2); + int iy = (rcClient.bottom-rcClient.top)/2 - (GetSystemMetrics(SM_CYSMICON)/2); + if (ctl->stateId == PBS_PRESSED) { + ix++; + iy++; + } + { + HIMAGELIST hImageList; + HICON hIconNew; + + hImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON), IsWinVerXPPlus()? ILC_COLOR32 | ILC_MASK : ILC_COLOR16 | ILC_MASK, 1, 0); + ImageList_AddIcon(hImageList, ctl->hIcon); + hIconNew = ImageList_GetIcon(hImageList, 0, ILD_NORMAL); + DrawState(hdcMem,NULL,NULL,(LPARAM)hIconNew,0,ix,iy,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),IsWindowEnabled(ctl->hwnd)?DST_ICON|DSS_NORMAL:DST_ICON|DSS_DISABLED); + ImageList_RemoveAll(hImageList); + ImageList_Destroy(hImageList); + DestroyIcon(hIconNew); + } + } + else if (ctl->hBitmap) { + BITMAP bminfo; + int ix,iy; + + GetObject(ctl->hBitmap, sizeof(bminfo), &bminfo); + ix = (rcClient.right-rcClient.left)/2 - (bminfo.bmWidth/2); + iy = (rcClient.bottom-rcClient.top)/2 - (bminfo.bmHeight/2); + if (ctl->stateId == PBS_PRESSED) { + ix++; + iy++; + } + DrawState(hdcMem,NULL,NULL,(LPARAM)ctl->hBitmap,0,ix,iy,bminfo.bmWidth,bminfo.bmHeight,IsWindowEnabled(ctl->hwnd)?DST_BITMAP:DST_BITMAP|DSS_DISABLED); + } + else if (GetWindowTextLength(ctl->hwnd)) { + // Draw the text and optinally the arrow + TCHAR szText[MAX_PATH]; + SIZE sz; + RECT rcText; + HFONT hOldFont; + + CopyRect(&rcText, &rcClient); + GetWindowText(ctl->hwnd, szText, SIZEOF(szText)); + SetBkMode(hdcMem, TRANSPARENT); + hOldFont = (HFONT)SelectObject(hdcMem, ctl->hFont); + // XP w/themes doesn't used the glossy disabled text. Is it always using COLOR_GRAYTEXT? Seems so. + SetTextColor(hdcMem, IsWindowEnabled(ctl->hwnd)||!ctl->hThemeButton?GetSysColor(COLOR_BTNTEXT):GetSysColor(COLOR_GRAYTEXT)); + GetTextExtentPoint32(hdcMem, szText, lstrlen(szText), &sz); + if (ctl->cHot) { + SIZE szHot; + + GetTextExtentPoint32 (hdcMem, _T("&"), 1, &szHot); + sz.cx -= szHot.cx; + } + if (ctl->arrow) { + DrawState(hdcMem,NULL,NULL,(LPARAM)ctl->arrow,0,rcClient.right-rcClient.left-5-GetSystemMetrics(SM_CXSMICON)+(!ctl->hThemeButton&&ctl->stateId==PBS_PRESSED?1:0),(rcClient.bottom-rcClient.top)/2-GetSystemMetrics(SM_CYSMICON)/2+(!ctl->hThemeButton&&ctl->stateId==PBS_PRESSED?1:0),GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),IsWindowEnabled(ctl->hwnd)?DST_ICON:DST_ICON|DSS_DISABLED); + } + SelectObject(hdcMem, ctl->hFont); + DrawState(hdcMem,NULL,NULL,(LPARAM)szText,0,(rcText.right-rcText.left-sz.cx)/2+(!ctl->hThemeButton&&ctl->stateId==PBS_PRESSED?1:0),ctl->hThemeButton?(rcText.bottom-rcText.top-sz.cy)/2:(rcText.bottom-rcText.top-sz.cy)/2-(ctl->stateId==PBS_PRESSED?0:1),sz.cx,sz.cy,IsWindowEnabled(ctl->hwnd)||ctl->hThemeButton?DST_PREFIXTEXT|DSS_NORMAL:DST_PREFIXTEXT|DSS_DISABLED); + SelectObject(hdcMem, hOldFont); + } + BitBlt(hdcPaint, 0, 0, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top, hdcMem, 0, 0, SRCCOPY); + SelectObject(hdcMem, hOld); + DeleteObject(hbmMem); + DeleteDC(hdcMem); + + } +} + +static LRESULT CALLBACK MButtonWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + MButtonCtrl* bct = (MButtonCtrl *)GetWindowLongPtr(hwndDlg, 0); + switch(msg) { + case WM_NCCREATE: + SetWindowLongPtr(hwndDlg, GWL_STYLE, GetWindowLongPtr(hwndDlg, GWL_STYLE) | BS_OWNERDRAW); + bct = ( MButtonCtrl* )mir_calloc(sizeof(MButtonCtrl)); + if (bct==NULL) return FALSE; + bct->hwnd = hwndDlg; + bct->stateId = PBS_NORMAL; + bct->hFont = ( HFONT )GetStockObject(DEFAULT_GUI_FONT); + LoadTheme(bct); + if (SUCCEEDED(CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, + IID_IAccPropServices, (void**)&bct->pAccPropServices))) + { + // Annotating the Role of this object to be PushButton + SetHwndPropInt(bct, OBJID_CLIENT, CHILDID_SELF, PROPID_ACC_ROLE, ROLE_SYSTEM_PUSHBUTTON); + } + else + bct->pAccPropServices = NULL; + SetWindowLongPtr(hwndDlg, 0, (LONG_PTR)bct); + if (((CREATESTRUCT *)lParam)->lpszName) SetWindowText(hwndDlg, ((CREATESTRUCT *)lParam)->lpszName); + return TRUE; + + case WM_DESTROY: + if (bct) { + if (bct->pAccPropServices) { + bct->pAccPropServices->Release(); + bct->pAccPropServices = NULL; + } + if (bct->hwndToolTips) { + TOOLINFO ti = {0}; + ti.cbSize = sizeof(ti); + ti.uFlags = TTF_IDISHWND; + ti.hwnd = bct->hwnd; + ti.uId = (UINT_PTR)bct->hwnd; + if (SendMessage(bct->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti)) { + SendMessage(bct->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)&ti); + } + if ( SendMessage(bct->hwndToolTips, TTM_GETTOOLCOUNT, 0, (LPARAM)&ti) == 0 ) { + int idx; + TTooltips tt; + tt.ThreadId = GetCurrentThreadId(); + + EnterCriticalSection(&csTips); + if ( List_GetIndex( &lToolTips, &tt, &idx ) ) { + mir_free( lToolTips.items[idx] ); + List_Remove( &lToolTips, idx ); + DestroyWindow( bct->hwndToolTips ); + } + LeaveCriticalSection(&csTips); + + bct->hwndToolTips = NULL; + } + } + if (bct->arrow) IconLib_ReleaseIcon(bct->arrow, 0); + DestroyTheme(bct); + } + break; // DONT! fall thru + + case WM_NCDESTROY: + mir_free(bct); + break; + + case WM_SETTEXT: + bct->cHot = 0; + if ( lParam != 0 ) { + TCHAR *tmp = ( TCHAR* )lParam; + while (*tmp) { + if (*tmp=='&' && *(tmp+1)) { + bct->cHot = _tolower(*(tmp+1)); + break; + } + tmp++; + } + InvalidateRect(bct->hwnd, NULL, TRUE); + } + break; + + case WM_KEYUP: + if (bct->stateId!=PBS_DISABLED && wParam == VK_SPACE) { + if (bct->pushBtn) { + if (bct->pbState) { + bct->pbState = 0; + bct->stateId = PBS_NORMAL; + } + else { + bct->pbState = 1; + bct->stateId = PBS_PRESSED; + } + InvalidateRect(bct->hwnd, NULL, TRUE); + } + SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwndDlg), BN_CLICKED), (LPARAM)hwndDlg); + return 0; + } + break; + + case WM_SYSKEYUP: + if (bct->stateId!=PBS_DISABLED && bct->cHot && bct->cHot == tolower((int)wParam)) { + if (bct->pushBtn) { + if (bct->pbState) { + bct->pbState = 0; + bct->stateId = PBS_NORMAL; + } + else { + bct->pbState = 1; + bct->stateId = PBS_PRESSED; + } + InvalidateRect(bct->hwnd, NULL, TRUE); + } + SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwndDlg), BN_CLICKED), (LPARAM)hwndDlg); + return 0; + } + break; + + case WM_THEMECHANGED: + // themed changed, reload theme object + LoadTheme(bct); + InvalidateRect(bct->hwnd, NULL, TRUE); // repaint it + break; + + case WM_SETFONT: // remember the font so we can use it later + bct->hFont = (HFONT)wParam; // maybe we should redraw? + break; + + case WM_NCPAINT: + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdcPaint; + + hdcPaint = BeginPaint(hwndDlg, &ps); + if (hdcPaint) { + PaintWorker(bct, hdcPaint); + EndPaint(hwndDlg, &ps); + } + break; + } + case BM_SETIMAGE: + { + HGDIOBJ hnd = NULL; + if (bct->hIcon) hnd = bct->hIcon; + else if (bct->hBitmap) hnd = bct->hBitmap; + + if (wParam == IMAGE_ICON) { + bct->hIcon = (HICON)lParam; + bct->hBitmap = NULL; + InvalidateRect(bct->hwnd, NULL, TRUE); + } + else if (wParam == IMAGE_BITMAP) { + bct->hBitmap = (HBITMAP)lParam; + bct->hIcon = NULL; + InvalidateRect(bct->hwnd, NULL, TRUE); + } + return (LRESULT)hnd; + } + case BM_GETIMAGE: + if (bct->hIcon) return (LRESULT)bct->hIcon; + else if (bct->hBitmap) return (LRESULT)bct->hBitmap; + else return 0; + case BM_SETCHECK: + if (!bct->pushBtn) break; + if (wParam == BST_CHECKED) { + bct->pbState = 1; + bct->stateId = PBS_PRESSED; + } + else if (wParam == BST_UNCHECKED) { + bct->pbState = 0; + bct->stateId = PBS_NORMAL; + } + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + case BM_GETCHECK: + if (bct->pushBtn) { + return bct->pbState?BST_CHECKED:BST_UNCHECKED; + } + return 0; + case BUTTONSETARROW: // turn arrow on/off + if (wParam) { + if (!bct->arrow) { + bct->arrow = LoadSkinIcon(SKINICON_OTHER_DOWNARROW); + SetHwndPropInt(bct, OBJID_CLIENT, CHILDID_SELF, PROPID_ACC_ROLE, ROLE_SYSTEM_BUTTONDROPDOWN); + } + } + else { + if (bct->arrow) { + IconLib_ReleaseIcon(bct->arrow, 0); + bct->arrow = NULL; + SetHwndPropInt(bct, OBJID_CLIENT, CHILDID_SELF, PROPID_ACC_ROLE, ROLE_SYSTEM_PUSHBUTTON); + } + } + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + case BUTTONSETDEFAULT: + bct->defbutton = wParam?1:0; + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + case BUTTONSETASPUSHBTN: + bct->pushBtn = 1; + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + case BUTTONSETASFLATBTN: + bct->flatBtn = 1; + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + case BUTTONADDTOOLTIP: + if ( wParam ) { + TOOLINFO ti = {0}; + if ( !bct->hwndToolTips ) { + int idx; + TTooltips tt; + tt.ThreadId = GetCurrentThreadId(); + + EnterCriticalSection(&csTips); + if ( List_GetIndex( &lToolTips, &tt, &idx )) { + bct->hwndToolTips = ((TTooltips*)lToolTips.items[idx])->hwnd; + } else { + TTooltips *ptt = ( TTooltips* )mir_alloc( sizeof(TTooltips) ); + ptt->ThreadId = tt.ThreadId; + ptt->hwnd = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, _T(""), TTS_ALWAYSTIP, 0, 0, 0, 0, NULL, NULL, hMirandaInst, NULL); + List_Insert( &lToolTips, ptt, idx ); + bct->hwndToolTips = ptt->hwnd; + } + LeaveCriticalSection(&csTips); + } + ti.cbSize = sizeof(ti); + ti.uFlags = TTF_IDISHWND; + ti.hwnd = bct->hwnd; + ti.uId = (UINT_PTR)bct->hwnd; + if (SendMessage(bct->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti)) + SendMessage(bct->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)&ti); + ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS; + ti.uId = (UINT_PTR)bct->hwnd; + #if defined( _UNICODE ) + if ( lParam & BATF_UNICODE ) + ti.lpszText = mir_wstrdup( TranslateW(( WCHAR* )wParam )); + else + ti.lpszText = LangPackPcharToTchar(( char* )wParam ); + #else + ti.lpszText = Translate(( char* )wParam ); + #endif + if (bct->pAccPropServices) { + wchar_t *tmpstr = mir_t2u(ti.lpszText); + bct->pAccPropServices->SetHwndPropStr(bct->hwnd, OBJID_CLIENT, + CHILDID_SELF, PROPID_ACC_DESCRIPTION, tmpstr); + mir_free(tmpstr); + } + SendMessage( bct->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)&ti); + #if defined( _UNICODE ) + mir_free( ti.lpszText ); + #endif + } + break; + case WM_SETFOCUS: // set keybord focus and redraw + bct->focus = 1; + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + + case WM_KILLFOCUS: // kill focus and redraw + bct->focus = 0; + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + + case WM_WINDOWPOSCHANGED: + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + + case WM_ENABLE: // windows tells us to enable/disable + bct->stateId = wParam?PBS_NORMAL:PBS_DISABLED; + InvalidateRect(bct->hwnd, NULL, TRUE); + break; + + case WM_MOUSELEAVE: // faked by the WM_TIMER + if (bct->stateId!=PBS_DISABLED) { // don't change states if disabled + bct->stateId = PBS_NORMAL; + InvalidateRect(bct->hwnd, NULL, TRUE); + } + break; + + case WM_LBUTTONDOWN: + if (bct->stateId!=PBS_DISABLED) { // don't change states if disabled + bct->stateId = PBS_PRESSED; + InvalidateRect(bct->hwnd, NULL, TRUE); + } + break; + + case WM_LBUTTONUP: + { + int showClick = 0; + if (bct->pushBtn) { + if (bct->pbState) bct->pbState = 0; + else bct->pbState = 1; + } + if (bct->stateId!=PBS_DISABLED) { // don't change states if disabled + if (bct->stateId==PBS_PRESSED) + showClick = 1; + if (msg==WM_LBUTTONUP) bct->stateId = PBS_HOT; + else bct->stateId = PBS_NORMAL; + InvalidateRect(bct->hwnd, NULL, TRUE); + } + if (showClick) // Tell your daddy you got clicked. + SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwndDlg), BN_CLICKED), (LPARAM)hwndDlg); + break; + } + case WM_MOUSEMOVE: + if (bct->stateId == PBS_NORMAL) { + bct->stateId = PBS_HOT; + InvalidateRect(bct->hwnd, NULL, TRUE); + } + // Call timer, used to start cheesy TrackMouseEvent faker + SetTimer(hwndDlg,BUTTON_POLLID,BUTTON_POLLDELAY,NULL); + break; + case WM_TIMER: // use a timer to check if they have did a mouseout + if (wParam == BUTTON_POLLID) { + RECT rc; + POINT pt; + GetWindowRect(hwndDlg,&rc); + GetCursorPos(&pt); + if(!PtInRect(&rc,pt)) { // mouse must be gone, trigger mouse leave + PostMessage(hwndDlg,WM_MOUSELEAVE,0,0L); + KillTimer(hwndDlg,BUTTON_POLLID); + } } + break; + + case WM_ERASEBKGND: + return 1; + } + return DefWindowProc(hwndDlg, msg, wParam, lParam); +} diff --git a/src/modules/clist/Docking.cpp b/src/modules/clist/Docking.cpp new file mode 100644 index 0000000000..8dd61cdc37 --- /dev/null +++ b/src/modules/clist/Docking.cpp @@ -0,0 +1,392 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +#define WM_DOCKCALLBACK (WM_USER+121) +#define EDGESENSITIVITY 3 + +#define DOCKED_NONE 0 +#define DOCKED_LEFT 1 +#define DOCKED_RIGHT 2 + +static char docked; +static POINT dockPos; + +static void Docking_GetMonitorRectFromPoint(LPPOINT pt, LPRECT rc) +{ + if (MyMonitorFromPoint) + { + MONITORINFO monitorInfo; + HMONITOR hMonitor = MyMonitorFromPoint(*pt, MONITOR_DEFAULTTONEAREST); // always returns a valid value + monitorInfo.cbSize = sizeof(monitorInfo); + + if (MyGetMonitorInfo(hMonitor, &monitorInfo)) + { + *rc = monitorInfo.rcMonitor; + return; + } + } + + // "generic" win95/NT support, also serves as failsafe + rc->left = 0; + rc->top = 0; + rc->bottom = GetSystemMetrics(SM_CYSCREEN); + rc->right = GetSystemMetrics(SM_CXSCREEN); +} + +static void Docking_RectToDock(LPRECT rc) +{ + rc->right += dockPos.x - rc->left; + rc->left = dockPos.x; + rc->bottom += dockPos.y - rc->top; + rc->top = dockPos.y; +} + +static void Docking_PosCommand(HWND hwnd, LPRECT rc, bool query) +{ + APPBARDATA abd = {0}; + + abd.cbSize = sizeof(abd); + abd.hWnd = hwnd; + abd.uEdge = docked == DOCKED_LEFT ? ABE_LEFT : ABE_RIGHT; + abd.rc = *rc; + SHAppBarMessage(query ? ABM_QUERYPOS : ABM_SETPOS, &abd); + *rc = abd.rc; +} + +static UINT_PTR Docking_Command(HWND hwnd, int cmd) +{ + APPBARDATA abd = {0}; + + abd.cbSize = sizeof(abd); + abd.hWnd = hwnd; + abd.uCallbackMessage = WM_DOCKCALLBACK; + return SHAppBarMessage(cmd, &abd); +} + +static void Docking_AdjustPosition(HWND hwnd, LPRECT rcDisplay, LPRECT rc, bool query, bool move) +{ + int cx = rc->right - rc->left; + + rc->top = rcDisplay->top; + rc->bottom = rcDisplay->bottom; + if (docked == DOCKED_LEFT) + { + rc->right = rcDisplay->left + (rc->right - rc->left); + rc->left = rcDisplay->left; + } + else + { + rc->left = rcDisplay->right - (rc->right - rc->left); + rc->right = rcDisplay->right; + } + Docking_PosCommand(hwnd, rc, true); + + if (docked == DOCKED_LEFT) + rc->right = rc->left + cx; + else + rc->left = rc->right - cx; + + if (!query) + { + Docking_PosCommand(hwnd, rc, false); + dockPos = *(LPPOINT)rc; + } + + if (move) + { + MoveWindow(hwnd, rc->left, rc->top, rc->right - rc->left, + rc->bottom - rc->top, TRUE); + } +} + +static void Docking_SetSize(HWND hwnd, LPRECT rc, bool query, bool move) +{ + RECT rcMonitor; + Docking_GetMonitorRectFromPoint( + docked == DOCKED_LEFT && !query ? (LPPOINT)&rc->right : (LPPOINT)rc, &rcMonitor); + Docking_AdjustPosition(hwnd, &rcMonitor, rc, query, move); +} + +static bool Docking_IsWindowVisible(HWND hwnd) +{ + LONG style = GetWindowLong(hwnd, GWL_STYLE); + return style & WS_VISIBLE && !(style & WS_MINIMIZE); +} + +INT_PTR Docking_IsDocked(WPARAM, LPARAM) +{ + return docked; +} + +int fnDocking_ProcessWindowMessage(WPARAM wParam, LPARAM lParam) +{ + static int draggingTitle; + MSG *msg = (MSG *) wParam; + + if (msg->message == WM_DESTROY) + { + if (docked) + { + DBWriteContactSettingByte(NULL, "CList", "Docked", (BYTE) docked); + DBWriteContactSettingDword(NULL, "CList", "DockX", (DWORD) dockPos.x); + DBWriteContactSettingDword(NULL, "CList", "DockY", (DWORD) dockPos.y); + } + else + { + DBDeleteContactSetting(NULL, "CList", "Docked"); + DBDeleteContactSetting(NULL, "CList", "DockX"); + DBDeleteContactSetting(NULL, "CList", "DockY"); + } + } + + if (!docked && msg->message != WM_CREATE && msg->message != WM_MOVING) + return 0; + + switch (msg->message) + { + case WM_CREATE: + draggingTitle = 0; + docked = DBGetContactSettingByte(NULL, "CLUI", "DockToSides", 1) ? + (char) DBGetContactSettingByte(NULL, "CList", "Docked", 0) : 0; + dockPos.x = (int) DBGetContactSettingDword(NULL, "CList", "DockX", 0); + dockPos.y = (int) DBGetContactSettingDword(NULL, "CList", "DockY", 0); + break; + + case WM_ACTIVATE: + Docking_Command(msg->hwnd, ABM_ACTIVATE); + break; + + case WM_WINDOWPOSCHANGING: + { + LPWINDOWPOS wp = (LPWINDOWPOS)msg->lParam; + + bool vis = Docking_IsWindowVisible(msg->hwnd); + if (wp->flags & SWP_SHOWWINDOW) + vis = !IsIconic(msg->hwnd); + if (wp->flags & SWP_HIDEWINDOW) + vis = false; + + if (vis) + { + if (!(wp->flags & (SWP_NOMOVE | SWP_NOSIZE))) + { + bool addbar = Docking_Command(msg->hwnd, ABM_NEW) != 0; + + RECT rc = {0}; + GetWindowRect(msg->hwnd, &rc); + + int cx = rc.right - rc.left; + if (!(wp->flags & SWP_NOMOVE)) { rc.left = wp->x; rc.top = wp->y; } + + if (addbar) + Docking_RectToDock(&rc); + + if (!(wp->flags & SWP_NOSIZE)) + { + rc.right = rc.left + wp->cx; + rc.bottom = rc.top + wp->cy; + addbar |= (cx != wp->cx); + } + + Docking_SetSize(msg->hwnd, &rc, !addbar, false); + + if (!(wp->flags & SWP_NOMOVE)) { wp->x = rc.left; wp->y = rc.top; } + if (!(wp->flags & SWP_NOSIZE)) wp->cy = rc.bottom - rc.top; + + *((LRESULT *) lParam) = TRUE; + return TRUE; + } + else + { + if ((wp->flags & SWP_SHOWWINDOW) && Docking_Command(msg->hwnd, ABM_NEW)) + { + RECT rc = {0}; + GetWindowRect(msg->hwnd, &rc); + Docking_RectToDock(&rc); + + Docking_SetSize(msg->hwnd, &rc, false, false); + + wp->x = rc.left; + wp->y = rc.top; + wp->cy = rc.bottom - rc.top; + wp->cx = rc.right - rc.left; + wp->flags &= ~(SWP_NOSIZE | SWP_NOMOVE); + } + } + } + break; + } + + case WM_WINDOWPOSCHANGED: + { + LPWINDOWPOS wp = (LPWINDOWPOS)msg->lParam; + bool vis = Docking_IsWindowVisible(msg->hwnd); + if (wp->flags & SWP_SHOWWINDOW) + vis = !IsIconic(msg->hwnd); + if (wp->flags & SWP_HIDEWINDOW) + vis = false; + + if (!vis) + Docking_Command(msg->hwnd, ABM_REMOVE); + else + Docking_Command(msg->hwnd, ABM_WINDOWPOSCHANGED); + break; + } + + case WM_DISPLAYCHANGE: + if (Docking_IsWindowVisible(msg->hwnd)) + { + RECT rc = {0}; + GetWindowRect(msg->hwnd, &rc); + Docking_RectToDock(&rc); + Docking_SetSize(msg->hwnd, &rc, false, true); + } + break; + + case WM_MOVING: + if (!docked) + { + RECT rcMonitor; + POINT ptCursor; + + // stop early + if (GetAsyncKeyState(VK_CONTROL) & 0x8000) + return 0; + + // GetMessagePos() is no good, position is always unsigned +// GetCursorPos(&ptCursor); + DWORD pos = GetMessagePos(); + ptCursor.x = GET_X_LPARAM(pos); + ptCursor.y = GET_Y_LPARAM(pos); + Docking_GetMonitorRectFromPoint(&ptCursor, &rcMonitor); + + if (((ptCursor.x < rcMonitor.left + EDGESENSITIVITY) || + (ptCursor.x >= rcMonitor.right - EDGESENSITIVITY)) && + DBGetContactSettingByte(NULL, "CLUI", "DockToSides", 1)) + { + docked = (ptCursor.x < rcMonitor.left + EDGESENSITIVITY) ? DOCKED_LEFT : DOCKED_RIGHT; + PostMessage(msg->hwnd, WM_LBUTTONUP, 0, MAKELPARAM(ptCursor.x, ptCursor.y)); + + Docking_Command(msg->hwnd, ABM_NEW); + Docking_AdjustPosition(msg->hwnd, &rcMonitor, (LPRECT)msg->lParam, false, true); + + *((LRESULT *) lParam) = TRUE; + return TRUE; + } + } + break; + + case WM_NCHITTEST: + switch (DefWindowProc(msg->hwnd, WM_NCHITTEST, msg->wParam, msg->lParam)) + { + case HTSIZE: case HTTOP: case HTTOPLEFT: case HTTOPRIGHT: + case HTBOTTOM: case HTBOTTOMRIGHT: case HTBOTTOMLEFT: + *((LRESULT *) lParam) = HTCLIENT; + return TRUE; + + case HTLEFT: + if (docked == DOCKED_LEFT) + { + *((LRESULT *) lParam) = HTCLIENT; + return TRUE; + } + break; + + case HTRIGHT: + if (docked == DOCKED_RIGHT) + { + *((LRESULT *) lParam) = HTCLIENT; + return TRUE; + } + break; + } + break; + + case WM_SYSCOMMAND: + if ((msg->wParam & 0xFFF0) != SC_MOVE) + return 0; + + SetActiveWindow(msg->hwnd); + SetCapture(msg->hwnd); + draggingTitle = 1; + *((LRESULT *) lParam) = 0; + return 1; + + case WM_MOUSEMOVE: + if (draggingTitle) + { + RECT rc; + POINT pt; + GetClientRect(msg->hwnd, &rc); + if ((docked == DOCKED_LEFT && (short) LOWORD(msg->lParam) > rc.right) || + (docked == DOCKED_RIGHT && (short) LOWORD(msg->lParam) < 0)) + { + ReleaseCapture(); + draggingTitle = 0; + docked = 0; + GetCursorPos(&pt); + PostMessage(msg->hwnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(pt.x, pt.y)); + SetWindowPos(msg->hwnd, 0, pt.x - rc.right / 2, + pt.y - GetSystemMetrics(SM_CYFRAME) - GetSystemMetrics(SM_CYSMCAPTION) / 2, + DBGetContactSettingDword(NULL, "CList", "Width", 0), + DBGetContactSettingDword(NULL, "CList", "Height", 0), + SWP_NOZORDER); + Docking_Command(msg->hwnd, ABM_REMOVE); + } + return 1; + } + break; + + case WM_LBUTTONUP: + if (draggingTitle) + { + ReleaseCapture(); + draggingTitle = 0; + } + break; + + case WM_DOCKCALLBACK: + switch (msg->wParam) + { + case ABN_WINDOWARRANGE: + ShowWindow(msg->hwnd, msg->lParam ? SW_HIDE : SW_SHOW); + break; + + case ABN_POSCHANGED: + { + RECT rc = {0}; + GetWindowRect(msg->hwnd, &rc); + Docking_SetSize(msg->hwnd, &rc, false, true); + } + break; + } + return 1; + + case WM_DESTROY: + Docking_Command(msg->hwnd, ABM_REMOVE); + break; + } + return 0; +} diff --git a/src/modules/clist/clc.cpp b/src/modules/clist/clc.cpp new file mode 100644 index 0000000000..1994706992 --- /dev/null +++ b/src/modules/clist/clc.cpp @@ -0,0 +1,1350 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +int InitGenMenu( void ); +int UnitGenMenu( void ); + +void InitCustomMenus( void ); +void UninitCustomMenus( void ); + +void MTG_OnmodulesLoad( void ); + +static BOOL bModuleInitialized = FALSE; +static HANDLE hClcWindowList; +static HANDLE hShowInfoTipEvent; +HANDLE hHideInfoTipEvent; +static HANDLE hAckHook; +static HANDLE hClcSettingsChanged; + +int g_IconWidth, g_IconHeight; + +void FreeDisplayNameCache(void); + +void fnClcBroadcast( int msg, WPARAM wParam, LPARAM lParam ) +{ + WindowList_Broadcast(hClcWindowList, msg, wParam, lParam); +} + +void fnClcOptionsChanged(void) +{ + cli.pfnClcBroadcast( INTM_RELOADOPTIONS, 0, 0); +} + +HMENU fnBuildGroupPopupMenu( struct ClcGroup* group ) +{ + HMENU hMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT)); + HMENU hGroupMenu = GetSubMenu(hMenu, 2); + RemoveMenu(hMenu, 2, MF_BYPOSITION); + DestroyMenu(hMenu); + CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) hGroupMenu, 0); + + CheckMenuItem(hGroupMenu, POPUP_GROUPHIDEOFFLINE, group->hideOffline ? MF_CHECKED : MF_UNCHECKED); + return hGroupMenu; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// standard CLC services + +static int ClcSettingChanged(WPARAM wParam, LPARAM lParam) +{ + char *szProto; + DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam; + if ( (HANDLE)wParam != NULL && !strcmp(cws->szModule, "CList")) { + if (!strcmp(cws->szSetting, "MyHandle")) { + cli.pfnInvalidateDisplayNameCacheEntry((HANDLE) wParam); + cli.pfnClcBroadcast( INTM_NAMECHANGED, wParam, lParam); + } + else if (!strcmp(cws->szSetting, "Group")) + cli.pfnClcBroadcast( INTM_GROUPCHANGED, wParam, lParam); + else if (!strcmp(cws->szSetting, "Hidden")) + cli.pfnClcBroadcast( INTM_HIDDENCHANGED, wParam, lParam); + else if (!strcmp(cws->szSetting, "NotOnList")) + cli.pfnClcBroadcast( INTM_NOTONLISTCHANGED, wParam, lParam); + else if (!strcmp(cws->szSetting, "Status")) + cli.pfnClcBroadcast( INTM_INVALIDATE, 0, 0); + else if (!strcmp(cws->szSetting, "NameOrder")) + cli.pfnClcBroadcast( INTM_NAMEORDERCHANGED, 0, 0); + } + else if (!strcmp(cws->szModule, "CListGroups")) { + cli.pfnClcBroadcast( INTM_GROUPSCHANGED, wParam, lParam); + } + else { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto != NULL && (HANDLE) wParam != NULL) { + char *id = NULL; + if (!strcmp(cws->szModule, "Protocol") && !strcmp(cws->szSetting, "p")) { + cli.pfnClcBroadcast( INTM_PROTOCHANGED, wParam, lParam); + } + // something is being written to a protocol module + if (!strcmp(szProto, cws->szModule)) { + // was a unique setting key written? + id = (char *) CallProtoService(szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0); + if ((INT_PTR) id != CALLSERVICE_NOTFOUND && id != NULL && !strcmp(id, cws->szSetting)) { + cli.pfnClcBroadcast( INTM_PROTOCHANGED, wParam, lParam); + } + } + } + if (szProto == NULL || strcmp(szProto, cws->szModule)) + return 0; + if (!strcmp(cws->szSetting, "Nick") || !strcmp(cws->szSetting, "FirstName") || !strcmp(cws->szSetting, "e-mail") + || !strcmp(cws->szSetting, "LastName") || !strcmp(cws->szSetting, "UIN")) + cli.pfnClcBroadcast( INTM_NAMECHANGED, wParam, lParam); + else if (!strcmp(cws->szSetting, "ApparentMode")) + cli.pfnClcBroadcast( INTM_APPARENTMODECHANGED, wParam, lParam); + else if (!strcmp(cws->szSetting, "IdleTS")) + cli.pfnClcBroadcast( INTM_IDLECHANGED, wParam, lParam); + } + return 0; +} + +static int ClcAccountsChanged(WPARAM, LPARAM) +{ + int i, cnt; + for (i = 0, cnt = 0; i < accounts.getCount(); ++i) + if (Proto_IsAccountEnabled(accounts[i])) ++cnt; + + cli.hClcProtoCount = cnt; + cli.clcProto = (ClcProtoStatus *) mir_realloc(cli.clcProto, sizeof(ClcProtoStatus) * cli.hClcProtoCount); + + for (i = 0, cnt = 0; i < accounts.getCount(); ++i) { + if (Proto_IsAccountEnabled(accounts[i])) { + cli.clcProto[cnt].szProto = accounts[i]->szModuleName; + cli.clcProto[cnt].dwStatus = CallProtoService(accounts[i]->szModuleName, PS_GETSTATUS, 0, 0); + ++cnt; + } + } + return 0; +} + +static int ClcModulesLoaded(WPARAM, LPARAM) +{ + ClcAccountsChanged(0, 0); + MTG_OnmodulesLoad(); + return 0; +} + +static int ClcProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA *) lParam; + int i; + + if (ack->type == ACKTYPE_STATUS) { + WindowList_BroadcastAsync(hClcWindowList,INTM_INVALIDATE,0,0); + if (ack->result == ACKRESULT_SUCCESS) { + for (i = 0; i < cli.hClcProtoCount; i++) { + if (!lstrcmpA(cli.clcProto[i].szProto, ack->szModule)) { + cli.clcProto[i].dwStatus = (WORD) ack->lParam; + break; + } + } + } + } + return 0; +} + +static int ClcContactAdded(WPARAM wParam, LPARAM lParam) +{ + WindowList_BroadcastAsync(hClcWindowList,INTM_CONTACTADDED,wParam,lParam); + return 0; +} + +static int ClcContactDeleted(WPARAM wParam, LPARAM lParam) +{ + WindowList_BroadcastAsync(hClcWindowList,INTM_CONTACTDELETED,wParam,lParam); + return 0; +} + +static int ClcContactIconChanged(WPARAM wParam, LPARAM lParam) +{ + WindowList_BroadcastAsync(hClcWindowList,INTM_ICONCHANGED,wParam,lParam); + return 0; +} + +static int ClcIconsChanged(WPARAM, LPARAM) +{ + WindowList_BroadcastAsync(hClcWindowList,INTM_INVALIDATE,0,0); + return 0; +} + +static INT_PTR SetInfoTipHoverTime(WPARAM wParam, LPARAM) +{ + DBWriteContactSettingWord(NULL, "CLC", "InfoTipHoverTime", (WORD) wParam); + cli.pfnClcBroadcast( INTM_SETINFOTIPHOVERTIME, wParam, 0); + return 0; +} + +static INT_PTR GetInfoTipHoverTime(WPARAM, LPARAM) +{ + return DBGetContactSettingWord(NULL, "CLC", "InfoTipHoverTime", 750); +} + +static void SortClcByTimer( HWND hwnd ) +{ + KillTimer( hwnd, TIMERID_DELAYEDRESORTCLC ); + SetTimer( hwnd, TIMERID_DELAYEDRESORTCLC, 200, NULL ); +} + +int LoadCLCModule(void) +{ + bModuleInitialized = TRUE; + + g_IconWidth = GetSystemMetrics(SM_CXSMICON); + g_IconHeight = GetSystemMetrics(SM_CYSMICON); + + hClcWindowList = (HANDLE) CallService(MS_UTILS_ALLOCWINDOWLIST, 0, 0); + hShowInfoTipEvent = CreateHookableEvent(ME_CLC_SHOWINFOTIP); + hHideInfoTipEvent = CreateHookableEvent(ME_CLC_HIDEINFOTIP); + CreateServiceFunction(MS_CLC_SETINFOTIPHOVERTIME, SetInfoTipHoverTime); + CreateServiceFunction(MS_CLC_GETINFOTIPHOVERTIME, GetInfoTipHoverTime); + + InitFileDropping(); + + HookEvent(ME_SYSTEM_MODULESLOADED, ClcModulesLoaded); + HookEvent(ME_PROTO_ACCLISTCHANGED, ClcAccountsChanged); + hClcSettingsChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ClcSettingChanged); + HookEvent(ME_DB_CONTACT_ADDED, ClcContactAdded); + HookEvent(ME_DB_CONTACT_DELETED, ClcContactDeleted); + HookEvent(ME_CLIST_CONTACTICONCHANGED, ClcContactIconChanged); + HookEvent(ME_SKIN_ICONSCHANGED, ClcIconsChanged); + hAckHook = (HANDLE) HookEvent(ME_PROTO_ACK, ClcProtoAck); + + InitCustomMenus(); + return 0; +} + +void UnloadClcModule() +{ + if ( !bModuleInitialized ) return; + + UnhookEvent(hAckHook); + UnhookEvent(hClcSettingsChanged); + + mir_free(cli.clcProto); + + FreeDisplayNameCache(); + + UninitCustomMenus(); + UnitGenMenu(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// default contact list control window procedure + +LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct ClcData *dat; + + dat = (struct ClcData *) GetWindowLongPtr(hwnd, 0); + if (msg >= CLM_FIRST && msg < CLM_LAST) + return cli.pfnProcessExternalMessages(hwnd, dat, msg, wParam, lParam); + + switch (msg) { + case WM_CREATE: + WindowList_Add(hClcWindowList, hwnd, NULL); + cli.pfnRegisterFileDropping(hwnd); + if ( dat == NULL ) { + dat = (struct ClcData *) mir_calloc(sizeof(struct ClcData)); + SetWindowLongPtr(hwnd, 0, (LONG_PTR) dat); + } + { + int i; + for (i = 0; i <= FONTID_MAX; i++) + dat->fontInfo[i].changed = 1; + } + dat->selection = -1; + dat->iconXSpace = 20; + dat->checkboxSize = 13; + dat->dragAutoScrollHeight = 30; + dat->iDragItem = -1; + dat->iInsertionMark = -1; + dat->insertionMarkHitHeight = 5; + dat->iHotTrack = -1; + dat->infoTipTimeout = DBGetContactSettingWord(NULL, "CLC", "InfoTipHoverTime", 750); + dat->extraColumnSpacing = 20; + dat->list.cl.increment = 30; + dat->needsResort = 1; + cli.pfnLoadClcOptions(hwnd, dat); + if (!IsWindowVisible(hwnd)) + SetTimer(hwnd,TIMERID_REBUILDAFTER,10,NULL); + else + { + cli.pfnRebuildEntireList(hwnd,dat); + NMCLISTCONTROL nm; + nm.hdr.code = CLN_LISTREBUILT; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + } + break; + case INTM_SCROLLBARCHANGED: + if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CONTACTLIST) { + if (dat->noVScrollbar) + ShowScrollBar(hwnd, SB_VERT, FALSE); + else + cli.pfnRecalcScrollBar(hwnd, dat); + } + break; + + case INTM_RELOADOPTIONS: + cli.pfnLoadClcOptions(hwnd, dat); + cli.pfnSaveStateAndRebuildList(hwnd, dat); + break; + case WM_THEMECHANGED: + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + case WM_SIZE: + cli.pfnEndRename(hwnd, dat, 1); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + cli.pfnRecalcScrollBar(hwnd, dat); + { // creating imagelist containing blue line for highlight + HBITMAP hBmp, hBmpMask, hoBmp, hoMaskBmp; + HDC hdc,hdcMem; + RECT rc; + int depth; + HBRUSH hBrush; + + GetClientRect(hwnd, &rc); + if (rc.right == 0) + break; + rc.bottom = dat->rowHeight; + hdc = GetDC(hwnd); + depth = GetDeviceCaps(hdc, BITSPIXEL); + if (depth < 16) + depth = 16; + hBmp = CreateBitmap(rc.right, rc.bottom, 1, depth, NULL); + hBmpMask = CreateBitmap(rc.right, rc.bottom, 1, 1, NULL); + hdcMem = CreateCompatibleDC(hdc); + hoBmp = (HBITMAP) SelectObject(hdcMem, hBmp); + hBrush = CreateSolidBrush(dat->useWindowsColours ? GetSysColor(COLOR_HIGHLIGHT) : dat->selBkColour); + FillRect(hdcMem, &rc, hBrush); + DeleteObject(hBrush); + + hoMaskBmp = ( HBITMAP )SelectObject(hdcMem, hBmpMask); + FillRect(hdcMem, &rc, ( HBRUSH )GetStockObject(BLACK_BRUSH)); + SelectObject(hdcMem, hoMaskBmp); + SelectObject(hdcMem, hoBmp); + DeleteDC(hdcMem); + ReleaseDC(hwnd, hdc); + if (dat->himlHighlight) + ImageList_Destroy(dat->himlHighlight); + dat->himlHighlight = ImageList_Create(rc.right, rc.bottom, (IsWinVerXPPlus()? ILC_COLOR32 : ILC_COLOR16) | ILC_MASK, 1, 1); + ImageList_Add(dat->himlHighlight, hBmp, hBmpMask); + DeleteObject(hBmpMask); + DeleteObject(hBmp); + } + break; + + case WM_SYSCOLORCHANGE: + SendMessage(hwnd, WM_SIZE, 0, 0); + break; + + case WM_GETDLGCODE: + if (lParam) { + MSG *msg = (MSG *) lParam; + if (msg->message == WM_KEYDOWN) { + if (msg->wParam == VK_TAB) + return 0; + if (msg->wParam == VK_ESCAPE && dat->hwndRenameEdit == NULL && dat->szQuickSearch[0] == 0) + return 0; + } + if (msg->message == WM_CHAR) { + if (msg->wParam == '\t') + return 0; + if (msg->wParam == 27 && dat->hwndRenameEdit == NULL && dat->szQuickSearch[0] == 0) + return 0; + } + } + return DLGC_WANTMESSAGE; + + case WM_KILLFOCUS: + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + case WM_SETFOCUS: + case WM_ENABLE: + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case WM_GETFONT: + return (LRESULT) dat->fontInfo[FONTID_CONTACTS].hFont; + + case INTM_GROUPSCHANGED: + { + DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam; + if (dbcws->value.type == DBVT_ASCIIZ || dbcws->value.type == DBVT_UTF8) { + int groupId = atoi(dbcws->szSetting) + 1; + struct ClcContact *contact; + struct ClcGroup *group; + TCHAR szFullName[512]; + int i, nameLen, eq; + //check name of group and ignore message if just being expanded/collapsed + if (cli.pfnFindItem(hwnd, dat, (HANDLE) (groupId | HCONTACT_ISGROUP), &contact, &group, NULL)) { + lstrcpy(szFullName, contact->szText); + while (group->parent) { + for (i = 0; i < group->parent->cl.count; i++) + if (group->parent->cl.items[i]->group == group) + break; + if (i == group->parent->cl.count) { + szFullName[0] = '\0'; + break; + } + group = group->parent; + nameLen = lstrlen(group->cl.items[i]->szText); + if (lstrlen(szFullName) + 1 + nameLen > SIZEOF(szFullName)) { + szFullName[0] = '\0'; + break; + } + memmove(szFullName + 1 + nameLen, szFullName, sizeof( TCHAR )*( lstrlen(szFullName) + 1)); + memcpy(szFullName, group->cl.items[i]->szText, sizeof( TCHAR )*nameLen); + szFullName[nameLen] = '\\'; + } + + if ( dbcws->value.type == DBVT_ASCIIZ ) { + #if defined( UNICODE ) + WCHAR* wszGrpName = mir_a2u(dbcws->value.pszVal+1); + eq = !lstrcmp( szFullName, wszGrpName ); + mir_free( wszGrpName ); + #else + eq = !lstrcmp( szFullName, dbcws->value.pszVal+1 ); + #endif + } + else { + char* szGrpName = NEWSTR_ALLOCA(dbcws->value.pszVal+1); + #if defined( UNICODE ) + WCHAR* wszGrpName; + Utf8Decode(szGrpName, &wszGrpName ); + eq = !lstrcmp( szFullName, wszGrpName ); + mir_free( wszGrpName ); + #else + Utf8Decode(szGrpName, NULL); + eq = !lstrcmp( szFullName, szGrpName ); + #endif + } + if ( eq && (contact->group->hideOffline != 0) == ((dbcws->value.pszVal[0] & GROUPF_HIDEOFFLINE) != 0)) + break; //only expanded has changed: no action reqd + } + } + cli.pfnSaveStateAndRebuildList(hwnd, dat); + break; + } + case INTM_NAMEORDERCHANGED: + PostMessage(hwnd, CLM_AUTOREBUILD, 0, 0); + break; + + case INTM_CONTACTADDED: + cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 1); + cli.pfnNotifyNewContact(hwnd, (HANDLE) wParam); + SortClcByTimer(hwnd); + break; + + case INTM_CONTACTDELETED: + cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam); + SortClcByTimer(hwnd); + break; + + case INTM_HIDDENCHANGED: + { + DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam; + if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN) + break; + if (dbcws->value.type == DBVT_DELETED || dbcws->value.bVal == 0) { + if (cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, NULL, NULL, NULL)) + break; + cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 1); + cli.pfnNotifyNewContact(hwnd, (HANDLE) wParam); + } + else cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam); + + dat->needsResort = 1; + SortClcByTimer(hwnd); + break; + } + case INTM_GROUPCHANGED: + { + struct ClcContact *contact; + BYTE iExtraImage[MAXEXTRACOLUMNS]; + BYTE flags = 0; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + memset(iExtraImage, 0xFF, SIZEOF(iExtraImage)); + else { + CopyMemory(iExtraImage, contact->iExtraImage, SIZEOF(iExtraImage)); + flags = contact->flags; + } + cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam); + if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN || !DBGetContactSettingByte((HANDLE) wParam, "CList", "Hidden", 0)) { + NMCLISTCONTROL nm; + cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 1); + if (cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) { + CopyMemory(contact->iExtraImage, iExtraImage, SIZEOF(iExtraImage)); + if(flags & CONTACTF_CHECKED) + contact->flags |= CONTACTF_CHECKED; + } + nm.hdr.code = CLN_CONTACTMOVED; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = (HANDLE) wParam; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + dat->needsResort = 1; + } + SetTimer(hwnd,TIMERID_REBUILDAFTER,1,NULL); + break; + } + case INTM_ICONCHANGED: + { + struct ClcContact *contact = NULL; + struct ClcGroup *group = NULL; + int recalcScrollBar = 0, shouldShow; + WORD status; + char *szProto; + HANDLE hSelItem = NULL; + struct ClcContact *selcontact = NULL; + + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto == NULL) + status = ID_STATUS_OFFLINE; + else + status = DBGetContactSettingWord((HANDLE) wParam, szProto, "Status", ID_STATUS_OFFLINE); + + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + shouldShow = (style & CLS_SHOWHIDDEN || !DBGetContactSettingByte((HANDLE) wParam, "CList", "Hidden", 0)) + && (!cli.pfnIsHiddenMode(dat, status) + || CallService(MS_CLIST_GETCONTACTICON, wParam, 0) != lParam); // this means an offline msg is flashing, so the contact should be shown + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, &group, NULL)) { + if (shouldShow && CallService(MS_DB_CONTACT_IS, wParam, 0)) { + if (dat->selection >= 0 && cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) != -1) + hSelItem = cli.pfnContactToHItem(selcontact); + cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, (style & CLS_CONTACTLIST) == 0, 0); + recalcScrollBar = 1; + cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL); + if (contact) { + contact->iImage = (WORD) lParam; + cli.pfnNotifyNewContact(hwnd, (HANDLE) wParam); + dat->needsResort = 1; + } } + } + else { // item in list already + if (contact->iImage == (WORD) lParam) + break; + if (!shouldShow && !(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { + if (dat->selection >= 0 && cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) != -1) + hSelItem = cli.pfnContactToHItem(selcontact); + cli.pfnRemoveItemFromGroup(hwnd, group, contact, (style & CLS_CONTACTLIST) == 0); + recalcScrollBar = 1; + } + else { + contact->iImage = (WORD) lParam; + if (!cli.pfnIsHiddenMode(dat, status)) + contact->flags |= CONTACTF_ONLINE; + else + contact->flags &= ~CONTACTF_ONLINE; + } + dat->needsResort = 1; + } + if (hSelItem) { + struct ClcGroup *selgroup; + if (cli.pfnFindItem(hwnd, dat, hSelItem, &selcontact, &selgroup, NULL)) + dat->selection = cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf(( SortedList* )&selgroup->cl, selcontact)); + else + dat->selection = -1; + } + SortClcByTimer(hwnd); + break; + } + case INTM_NAMECHANGED: + { + struct ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + + lstrcpyn(contact->szText, cli.pfnGetContactDisplayName((HANDLE)wParam,0), SIZEOF(contact->szText)); + dat->needsResort = 1; + SortClcByTimer(hwnd); + break; + } + case INTM_PROTOCHANGED: + { + struct ClcContact *contact = NULL; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + contact->proto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + cli.pfnInvalidateDisplayNameCacheEntry((HANDLE)wParam); + lstrcpyn(contact->szText, cli.pfnGetContactDisplayName((HANDLE)wParam,0), SIZEOF(contact->szText)); + SortClcByTimer(hwnd); + break; + } + case INTM_NOTONLISTCHANGED: + { + DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam; + struct ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + if (contact->type != CLCIT_CONTACT) + break; + if (dbcws->value.type == DBVT_DELETED || dbcws->value.bVal == 0) + contact->flags &= ~CONTACTF_NOTONLIST; + else + contact->flags |= CONTACTF_NOTONLIST; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + } + case INTM_INVALIDATE: + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case INTM_APPARENTMODECHANGED: + { + WORD apparentMode; + char *szProto; + struct ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto == NULL) + break; + apparentMode = DBGetContactSettingWord((HANDLE) wParam, szProto, "ApparentMode", 0); + contact->flags &= ~(CONTACTF_INVISTO | CONTACTF_VISTO); + if (apparentMode == ID_STATUS_OFFLINE) + contact->flags |= CONTACTF_INVISTO; + else if (apparentMode == ID_STATUS_ONLINE) + contact->flags |= CONTACTF_VISTO; + else if (apparentMode) + contact->flags |= CONTACTF_VISTO | CONTACTF_INVISTO; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + } + case INTM_SETINFOTIPHOVERTIME: + dat->infoTipTimeout = wParam; + break; + + case INTM_IDLECHANGED: + { + char *szProto; + struct ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto == NULL) + break; + contact->flags &= ~CONTACTF_IDLE; + if (DBGetContactSettingDword((HANDLE) wParam, szProto, "IdleTS", 0)) { + contact->flags |= CONTACTF_IDLE; + } + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + } + case WM_PRINTCLIENT: + cli.pfnPaintClc(hwnd, dat, (HDC) wParam, NULL); + break; + + case WM_NCPAINT: + if (wParam == 1) + break; + { + POINT ptTopLeft = { 0, 0 }; + HRGN hClientRgn; + ClientToScreen(hwnd, &ptTopLeft); + hClientRgn = CreateRectRgn(0, 0, 1, 1); + CombineRgn(hClientRgn, (HRGN) wParam, NULL, RGN_COPY); + OffsetRgn(hClientRgn, -ptTopLeft.x, -ptTopLeft.y); + InvalidateRgn(hwnd, hClientRgn, FALSE); + DeleteObject(hClientRgn); + UpdateWindow(hwnd); + } + break; + + case WM_PAINT: + { + HDC hdc; + PAINTSTRUCT ps; + hdc = BeginPaint(hwnd, &ps); + /* we get so many cli.pfnInvalidateRect()'s that there is no point painting, + Windows in theory shouldn't queue up WM_PAINTs in this case but it does so + we'll just ignore them */ + if (IsWindowVisible(hwnd)) + cli.pfnPaintClc(hwnd, dat, hdc, &ps.rcPaint); + EndPaint(hwnd, &ps); + break; + } + case WM_VSCROLL: + { + int desty; + RECT clRect; + int noSmooth = 0; + + cli.pfnEndRename(hwnd, dat, 1); + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + desty = dat->yScroll; + GetClientRect(hwnd, &clRect); + switch (LOWORD(wParam)) { + case SB_LINEUP: desty -= dat->rowHeight; break; + case SB_LINEDOWN: desty += dat->rowHeight; break; + case SB_PAGEUP: desty -= clRect.bottom - dat->rowHeight; break; + case SB_PAGEDOWN: desty += clRect.bottom - dat->rowHeight; break; + case SB_BOTTOM: desty = 0x7FFFFFFF; break; + case SB_TOP: desty = 0; break; + case SB_THUMBTRACK: desty = HIWORD(wParam); noSmooth = 1; break; //noone has more than 4000 contacts, right? + default: return 0; + } + cli.pfnScrollTo(hwnd, dat, desty, noSmooth); + break; + } + case WM_MOUSEWHEEL: + { + UINT scrollLines; + cli.pfnEndRename(hwnd, dat, 1); + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, FALSE)) + scrollLines = 3; + cli.pfnScrollTo(hwnd, dat, dat->yScroll - (short) HIWORD(wParam) * dat->rowHeight * (signed) scrollLines / WHEEL_DELTA, 0); + return 0; + } + case WM_KEYDOWN: + { + int selMoved = 0; + int changeGroupExpand = 0; + int pageSize; + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + if (CallService(MS_CLIST_MENUPROCESSHOTKEY, wParam, MPCF_CONTACTMENU)) + break; + { + RECT clRect; + GetClientRect(hwnd, &clRect); + pageSize = clRect.bottom / dat->rowHeight; + } + switch (wParam) { + case VK_DOWN: dat->selection++; selMoved = 1; break; + case VK_UP: dat->selection--; selMoved = 1; break; + case VK_PRIOR: dat->selection -= pageSize; selMoved = 1; break; + case VK_NEXT: dat->selection += pageSize; selMoved = 1; break; + case VK_HOME: dat->selection = 0; selMoved = 1; break; + case VK_END: dat->selection = cli.pfnGetGroupContentsCount(&dat->list, 1) - 1; selMoved = 1; break; + case VK_LEFT: changeGroupExpand = 1; break; + case VK_RIGHT: changeGroupExpand = 2; break; + case VK_RETURN: cli.pfnDoSelectionDefaultAction(hwnd, dat); return 0; + case VK_F2: cli.pfnBeginRenameSelection(hwnd, dat); return 0; + case VK_DELETE: cli.pfnDeleteFromContactList(hwnd, dat); return 0; + default: + { + NMKEY nmkey; + nmkey.hdr.hwndFrom = hwnd; + nmkey.hdr.idFrom = GetDlgCtrlID(hwnd); + nmkey.hdr.code = NM_KEYDOWN; + nmkey.nVKey = wParam; + nmkey.uFlags = HIWORD(lParam); + if (SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nmkey)) + return 0; + } + } + if (changeGroupExpand) { + int hit; + struct ClcContact *contact; + struct ClcGroup *group; + dat->szQuickSearch[0] = 0; + hit = cli.pfnGetRowByIndex(dat, dat->selection, &contact, &group); + if (hit != -1) { + if (changeGroupExpand == 1 && contact->type == CLCIT_CONTACT) { + if (group == &dat->list) + return 0; + dat->selection = cli.pfnGetRowsPriorTo(&dat->list, group, -1); + selMoved = 1; + } + else { + if (contact->type == CLCIT_GROUP) + cli.pfnSetGroupExpand(hwnd, dat, contact->group, changeGroupExpand == 2); + return 0; + } + } + else + return 0; + } + if (selMoved) { + dat->szQuickSearch[0] = 0; + if (dat->selection >= cli.pfnGetGroupContentsCount(&dat->list, 1)) + dat->selection = cli.pfnGetGroupContentsCount(&dat->list, 1) - 1; + if (dat->selection < 0) + dat->selection = 0; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); + UpdateWindow(hwnd); + return 0; + } + break; + } + case WM_CHAR: + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + if (wParam == 27) //escape + dat->szQuickSearch[0] = 0; + else if (wParam == '\b' && dat->szQuickSearch[0]) + dat->szQuickSearch[lstrlen(dat->szQuickSearch) - 1] = '\0'; + else if (wParam < ' ') + break; + else if (wParam == ' ' && dat->szQuickSearch[0] == '\0' && GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CHECKBOXES) { + struct ClcContact *contact; + NMCLISTCONTROL nm; + if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1) + break; + if (contact->type != CLCIT_CONTACT) + break; + contact->flags ^= CONTACTF_CHECKED; + if (contact->type == CLCIT_GROUP) + cli.pfnSetGroupChildCheckboxes(contact->group, contact->flags & CONTACTF_CHECKED); + cli.pfnRecalculateGroupCheckboxes(hwnd, dat); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + nm.hdr.code = CLN_CHECKCHANGED; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + } + else { + TCHAR szNew[2]; + szNew[0] = (TCHAR) wParam; + szNew[1] = '\0'; + if (lstrlen(dat->szQuickSearch) >= SIZEOF(dat->szQuickSearch) - 1) { + MessageBeep(MB_OK); + break; + } + _tcscat(dat->szQuickSearch, szNew); + } + if (dat->szQuickSearch[0]) { + int index; + index = cli.pfnFindRowByText(hwnd, dat, dat->szQuickSearch, 1); + if (index != -1) + dat->selection = index; + else { + MessageBeep(MB_OK); + dat->szQuickSearch[ lstrlen(dat->szQuickSearch) - 1] = '\0'; + } + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); + } + else + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case WM_SYSKEYDOWN: + cli.pfnEndRename(hwnd, dat, 1); + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + dat->iHotTrack = -1; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + ReleaseCapture(); + if (wParam == VK_F10 && GetKeyState(VK_SHIFT) & 0x8000) + break; + SendMessage(GetParent(hwnd), msg, wParam, lParam); + return 0; + + case WM_TIMER: + switch( wParam ) { + case TIMERID_RENAME: + cli.pfnBeginRenameSelection(hwnd, dat); + break; + case TIMERID_DRAGAUTOSCROLL: + cli.pfnScrollTo(hwnd, dat, dat->yScroll + dat->dragAutoScrolling * dat->rowHeight * 2, 0); + break; + case TIMERID_INFOTIP: + { CLCINFOTIP it; + struct ClcContact *contact; + int hit; + RECT clRect; + POINT ptClientOffset = { 0 }; + + KillTimer(hwnd, wParam); + GetCursorPos(&it.ptCursor); + ScreenToClient(hwnd, &it.ptCursor); + if (it.ptCursor.x != dat->ptInfoTip.x || it.ptCursor.y != dat->ptInfoTip.y) + break; + GetClientRect(hwnd, &clRect); + it.rcItem.left = 0; + it.rcItem.right = clRect.right; + hit = cli.pfnHitTest(hwnd, dat, it.ptCursor.x, it.ptCursor.y, &contact, NULL, NULL); + if (hit == -1) + break; + if (contact->type != CLCIT_GROUP && contact->type != CLCIT_CONTACT) + break; + ClientToScreen(hwnd, &it.ptCursor); + ClientToScreen(hwnd, &ptClientOffset); + it.isTreeFocused = GetFocus() == hwnd; + it.rcItem.top = cli.pfnGetRowTopY(dat, hit) - dat->yScroll; + it.rcItem.bottom = it.rcItem.top + cli.pfnGetRowHeight(dat, hit); + OffsetRect(&it.rcItem, ptClientOffset.x, ptClientOffset.y); + it.isGroup = contact->type == CLCIT_GROUP; + it.hItem = contact->type == CLCIT_GROUP ? (HANDLE) contact->groupId : contact->hContact; + it.cbSize = sizeof(it); + dat->hInfoTipItem = cli.pfnContactToHItem(contact); + NotifyEventHooks(hShowInfoTipEvent, 0, (LPARAM) & it); + break; + } + case TIMERID_REBUILDAFTER: + KillTimer(hwnd,TIMERID_REBUILDAFTER); + cli.pfnInvalidateRect(hwnd,NULL,FALSE); + cli.pfnSaveStateAndRebuildList(hwnd,dat); + break; + + case TIMERID_DELAYEDRESORTCLC: + KillTimer(hwnd,TIMERID_DELAYEDRESORTCLC); + cli.pfnInvalidateRect(hwnd,NULL,FALSE); + cli.pfnSortCLC(hwnd,dat,1); + cli.pfnRecalcScrollBar(hwnd,dat); + break; + } + break; + + case WM_MBUTTONDOWN: + case WM_LBUTTONDOWN: + { + struct ClcContact *contact; + struct ClcGroup *group; + int hit; + DWORD hitFlags; + + if (GetFocus() != hwnd) + SetFocus(hwnd); + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + cli.pfnEndRename(hwnd, dat, 1); + dat->ptDragStart.x = (short) LOWORD(lParam); + dat->ptDragStart.y = (short) HIWORD(lParam); + dat->szQuickSearch[0] = 0; + hit = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, &group, &hitFlags); + if (hit != -1) { + if (hit == dat->selection && hitFlags & CLCHT_ONITEMLABEL && dat->exStyle & CLS_EX_EDITLABELS) { + SetCapture(hwnd); + dat->iDragItem = dat->selection; + dat->dragStage = DRAGSTAGE_NOTMOVED | DRAGSTAGEF_MAYBERENAME; + dat->dragAutoScrolling = 0; + break; + } } + + if (hit != -1 && contact->type == CLCIT_GROUP) + if (hitFlags & CLCHT_ONITEMICON) { + struct ClcGroup *selgroup; + struct ClcContact *selcontact; + dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, &selgroup); + cli.pfnSetGroupExpand(hwnd, dat, contact->group, -1); + if (dat->selection != -1) { + dat->selection = + cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf((SortedList*)&selgroup->cl,selcontact)); + if (dat->selection == -1) + dat->selection = cli.pfnGetRowsPriorTo(&dat->list, contact->group, -1); + } + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + UpdateWindow(hwnd); + break; + } + if (hit != -1 && hitFlags & CLCHT_ONITEMCHECK) { + NMCLISTCONTROL nm; + contact->flags ^= CONTACTF_CHECKED; + if (contact->type == CLCIT_GROUP) + cli.pfnSetGroupChildCheckboxes(contact->group, contact->flags & CONTACTF_CHECKED); + cli.pfnRecalculateGroupCheckboxes(hwnd, dat); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + nm.hdr.code = CLN_CHECKCHANGED; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + } + if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL | CLCHT_ONITEMCHECK))) { + NMCLISTCONTROL nm; + nm.hdr.code = NM_CLICK; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + if (hit == -1) + nm.hItem = NULL; + else + nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); + nm.iColumn = hitFlags & CLCHT_ONITEMEXTRA ? HIBYTE(HIWORD(hitFlags)) : -1; + nm.pt = dat->ptDragStart; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + } + if (hitFlags & (CLCHT_ONITEMCHECK | CLCHT_ONITEMEXTRA)) + break; + dat->selection = hit; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + if (dat->selection != -1) + cli.pfnEnsureVisible(hwnd, dat, hit, 0); + UpdateWindow(hwnd); + if (dat->selection != -1 && (contact->type == CLCIT_CONTACT || contact->type == CLCIT_GROUP) + && !(hitFlags & (CLCHT_ONITEMEXTRA | CLCHT_ONITEMCHECK))) { + SetCapture(hwnd); + dat->iDragItem = dat->selection; + dat->dragStage = DRAGSTAGE_NOTMOVED; + dat->dragAutoScrolling = 0; + } + break; + } + case WM_MOUSEMOVE: + if (dat->iDragItem == -1) { + int iOldHotTrack = dat->iHotTrack; + if (dat->hwndRenameEdit != NULL) + break; + if (GetKeyState(VK_MENU) & 0x8000 || GetKeyState(VK_F10) & 0x8000) + break; + dat->iHotTrack = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), NULL, NULL, NULL); + if (iOldHotTrack != dat->iHotTrack) { + if (iOldHotTrack == -1) + SetCapture(hwnd); + else if (dat->iHotTrack == -1) + ReleaseCapture(); + if (dat->exStyle & CLS_EX_TRACKSELECT) { + cli.pfnInvalidateItem(hwnd, dat, iOldHotTrack); + cli.pfnInvalidateItem(hwnd, dat, dat->iHotTrack); + } + cli.pfnHideInfoTip(hwnd, dat); + } + KillTimer(hwnd, TIMERID_INFOTIP); + if (wParam == 0 && dat->hInfoTipItem == NULL) { + dat->ptInfoTip.x = (short) LOWORD(lParam); + dat->ptInfoTip.y = (short) HIWORD(lParam); + SetTimer(hwnd, TIMERID_INFOTIP, dat->infoTipTimeout, NULL); + } + break; + } + if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_NOTMOVED && !(dat->exStyle & CLS_EX_DISABLEDRAGDROP)) { + if (abs((short) LOWORD(lParam) - dat->ptDragStart.x) >= GetSystemMetrics(SM_CXDRAG) + || abs((short) HIWORD(lParam) - dat->ptDragStart.y) >= GetSystemMetrics(SM_CYDRAG)) + dat->dragStage = (dat->dragStage & ~DRAGSTAGEM_STAGE) | DRAGSTAGE_ACTIVE; + } + if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_ACTIVE) { + HCURSOR hNewCursor; + RECT clRect; + POINT pt; + int target; + + GetClientRect(hwnd, &clRect); + pt.x = (short) LOWORD(lParam); + pt.y = (short) HIWORD(lParam); + hNewCursor = LoadCursor(NULL, IDC_NO); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + if (dat->dragAutoScrolling) { + KillTimer(hwnd, TIMERID_DRAGAUTOSCROLL); + dat->dragAutoScrolling = 0; + } + target = cli.pfnGetDropTargetInformation(hwnd, dat, pt); + if (dat->dragStage & DRAGSTAGEF_OUTSIDE && target != DROPTARGET_OUTSIDE) { + NMCLISTCONTROL nm; + struct ClcContact *contact; + cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); + nm.hdr.code = CLN_DRAGSTOP; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + dat->dragStage &= ~DRAGSTAGEF_OUTSIDE; + } + switch (target) { + case DROPTARGET_ONSELF: + case DROPTARGET_ONCONTACT: + break; + case DROPTARGET_ONGROUP: + hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)); + break; + case DROPTARGET_INSERTION: + hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)); + break; + case DROPTARGET_OUTSIDE: + { + NMCLISTCONTROL nm; + struct ClcContact *contact; + + if (pt.x >= 0 && pt.x < clRect.right + && ((pt.y < 0 && pt.y > -dat->dragAutoScrollHeight) + || (pt.y >= clRect.bottom && pt.y < clRect.bottom + dat->dragAutoScrollHeight))) { + if (!dat->dragAutoScrolling) { + if (pt.y < 0) + dat->dragAutoScrolling = -1; + else + dat->dragAutoScrolling = 1; + SetTimer(hwnd, TIMERID_DRAGAUTOSCROLL, dat->scrollTime, NULL); + } + SendMessage(hwnd, WM_TIMER, TIMERID_DRAGAUTOSCROLL, 0); + } + + dat->dragStage |= DRAGSTAGEF_OUTSIDE; + cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); + nm.hdr.code = CLN_DRAGGING; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); + nm.pt = pt; + if (SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm)) + return 0; + break; + } + default: + { + struct ClcGroup *group; + cli.pfnGetRowByIndex(dat, dat->iDragItem, NULL, &group); + if (group->parent) + hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)); + break; + } + } + SetCursor(hNewCursor); + } + break; + + case WM_LBUTTONUP: + if (dat->iDragItem == -1) + break; + SetCursor((HCURSOR) GetClassLongPtr(hwnd, GCLP_HCURSOR)); + if (dat->exStyle & CLS_EX_TRACKSELECT) { + dat->iHotTrack = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), NULL, NULL, NULL); + if (dat->iHotTrack == -1) + ReleaseCapture(); + } + else ReleaseCapture(); + KillTimer(hwnd, TIMERID_DRAGAUTOSCROLL); + if (dat->dragStage == (DRAGSTAGE_NOTMOVED | DRAGSTAGEF_MAYBERENAME)) + SetTimer(hwnd, TIMERID_RENAME, GetDoubleClickTime(), NULL); + else if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_ACTIVE) { + POINT pt; + int target; + + pt.x = (short) LOWORD(lParam); + pt.y = (short) HIWORD(lParam); + target = cli.pfnGetDropTargetInformation(hwnd, dat, pt); + switch (target) { + case DROPTARGET_ONSELF: + break; + case DROPTARGET_ONCONTACT: + break; + case DROPTARGET_ONGROUP: + { + struct ClcContact *contactn, *contacto; + cli.pfnGetRowByIndex(dat, dat->selection, &contactn, NULL); + cli.pfnGetRowByIndex(dat, dat->iDragItem, &contacto, NULL); + if (contacto->type == CLCIT_CONTACT) //dropee is a contact + CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contacto->hContact, contactn->groupId); + else if (contacto->type == CLCIT_GROUP) { //dropee is a group + TCHAR szNewName[120]; + TCHAR* szGroup = cli.pfnGetGroupName(contactn->groupId, NULL); + mir_sntprintf(szNewName, SIZEOF(szNewName), _T("%s\\%s"), szGroup, contacto->szText); + cli.pfnRenameGroup( contacto->groupId, szNewName ); + } + break; + } + case DROPTARGET_INSERTION: + { + struct ClcContact *contact, *destcontact; + struct ClcGroup *destgroup; + cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); + if (cli.pfnGetRowByIndex(dat, dat->iInsertionMark, &destcontact, &destgroup) == -1 || destgroup != contact->group->parent) + CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, 0); + else { + if (destcontact->type == CLCIT_GROUP) + destgroup = destcontact->group; + else + destgroup = destgroup; + CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, destgroup->groupId); + } + break; + } + case DROPTARGET_OUTSIDE: + { + NMCLISTCONTROL nm; + struct ClcContact *contact; + cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); + nm.hdr.code = CLN_DROPPED; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); + nm.pt = pt; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); + break; + } + default: + { + struct ClcGroup *group; + struct ClcContact *contact; + cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, &group); + if (group->parent) { //move to root + if (contact->type == CLCIT_CONTACT) //dropee is a contact + CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contact->hContact, 0); + else if (contact->type == CLCIT_GROUP) { //dropee is a group + TCHAR szNewName[120]; + lstrcpyn(szNewName, contact->szText, SIZEOF(szNewName)); + cli.pfnRenameGroup( contact->groupId, szNewName ); + } } } } } + + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + dat->iDragItem = -1; + dat->iInsertionMark = -1; + break; + + case WM_LBUTTONDBLCLK: + { + struct ClcContact *contact; + DWORD hitFlags; + ReleaseCapture(); + dat->iHotTrack = -1; + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_RENAME); + KillTimer(hwnd, TIMERID_INFOTIP); + dat->szQuickSearch[0] = 0; + dat->selection = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, NULL, &hitFlags); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + if (dat->selection != -1) + cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); + if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL))) + break; + UpdateWindow(hwnd); + cli.pfnDoSelectionDefaultAction(hwnd, dat); + break; + } + case WM_CONTEXTMENU: + { + struct ClcContact *contact; + HMENU hMenu = NULL; + POINT pt; + DWORD hitFlags; + + cli.pfnEndRename(hwnd, dat, 1); + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_RENAME); + KillTimer(hwnd, TIMERID_INFOTIP); + if (GetFocus() != hwnd) + SetFocus(hwnd); + dat->iHotTrack = -1; + dat->szQuickSearch[0] = 0; + pt.x = (short) LOWORD(lParam); + pt.y = (short) HIWORD(lParam); + if (pt.x == -1 && pt.y == -1) { + dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL); + if (dat->selection != -1) + cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); + pt.x = dat->iconXSpace + 15; + pt.y = cli.pfnGetRowTopY(dat, dat->selection) - dat->yScroll + (int)(cli.pfnGetRowHeight(dat, dat->selection) * .7); + hitFlags = dat->selection == -1 ? CLCHT_NOWHERE : CLCHT_ONITEMLABEL; + } + else { + ScreenToClient(hwnd, &pt); + dat->selection = cli.pfnHitTest(hwnd, dat, pt.x, pt.y, &contact, NULL, &hitFlags); + } + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + if (dat->selection != -1) + cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); + UpdateWindow(hwnd); + + if (dat->selection != -1 && hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMCHECK | CLCHT_ONITEMLABEL)) { + if (contact->type == CLCIT_GROUP) { + hMenu = cli.pfnBuildGroupPopupMenu(contact->group); + ClientToScreen(hwnd, &pt); + TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL); + DestroyMenu(hMenu); + return 0; + } + else if (contact->type == CLCIT_CONTACT) + hMenu = (HMENU) CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM) contact->hContact, 0); + } + else { + //call parent for new group/hide offline menu + SendMessage(GetParent(hwnd), WM_CONTEXTMENU, wParam, lParam); + } + if (hMenu != NULL) { + ClientToScreen(hwnd, &pt); + TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL); + DestroyMenu(hMenu); + } + return 0; + } + case WM_MEASUREITEM: + return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam); + + case WM_DRAWITEM: + return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam); + + case WM_COMMAND: + { + struct ClcContact *contact; + int hit = cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL); + if (hit == -1) + break; + if (contact->type == CLCIT_CONTACT) + if (CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_CONTACTMENU), (LPARAM) contact->hContact)) + break; + switch (LOWORD(wParam)) { + case POPUP_NEWSUBGROUP: + if (contact->type != CLCIT_GROUP) + break; + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS); + CallService(MS_CLIST_GROUPCREATE, contact->groupId, 0); + break; + case POPUP_RENAMEGROUP: + cli.pfnBeginRenameSelection(hwnd, dat); + break; + case POPUP_DELETEGROUP: + if (contact->type != CLCIT_GROUP) + break; + CallService(MS_CLIST_GROUPDELETE, contact->groupId, 0); + break; + case POPUP_GROUPHIDEOFFLINE: + if (contact->type != CLCIT_GROUP) + break; + CallService(MS_CLIST_GROUPSETFLAGS, contact->groupId, + MAKELPARAM(contact->group->hideOffline ? 0 : GROUPF_HIDEOFFLINE, GROUPF_HIDEOFFLINE)); + break; + } + break; + } + case WM_DESTROY: + cli.pfnHideInfoTip(hwnd, dat); + { + int i; + for (i = 0; i <= FONTID_MAX; i++) + if (!dat->fontInfo[i].changed) + DeleteObject(dat->fontInfo[i].hFont); + } + if (dat->himlHighlight) + ImageList_Destroy(dat->himlHighlight); + if (dat->hwndRenameEdit) + DestroyWindow(dat->hwndRenameEdit); + if (!dat->bkChanged && dat->hBmpBackground) + DeleteObject(dat->hBmpBackground); + cli.pfnFreeGroup(&dat->list); + mir_free(dat); + cli.pfnUnregisterFileDropping(hwnd); + WindowList_Remove(hClcWindowList, hwnd); + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} diff --git a/src/modules/clist/clc.h b/src/modules/clist/clc.h new file mode 100644 index 0000000000..0bbf197b44 --- /dev/null +++ b/src/modules/clist/clc.h @@ -0,0 +1,248 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +struct ClcContact { + BYTE type; + BYTE flags; + union { + struct { + WORD iImage; + HANDLE hContact; + }; + struct { + WORD groupId; + struct ClcGroup *group; + }; + }; + BYTE iExtraImage[MAXEXTRACOLUMNS]; + TCHAR szText[120-MAXEXTRACOLUMNS]; + char * proto; // MS_PROTO_GETBASEPROTO +}; + +struct ClcData { + struct ClcGroup list; + int rowHeight; + int yScroll; + int selection; + struct ClcFontInfo fontInfo[FONTID_MAX + 1]; + int scrollTime; + HIMAGELIST himlHighlight; + int groupIndent; + TCHAR szQuickSearch[128]; + int iconXSpace; + HWND hwndRenameEdit; + COLORREF bkColour, selBkColour, selTextColour, hotTextColour, quickSearchColour; + int iDragItem, iInsertionMark; + int dragStage; + POINT ptDragStart; + int dragAutoScrolling; + int dragAutoScrollHeight; + int leftMargin; + int insertionMarkHitHeight; + HBITMAP hBmpBackground; + int backgroundBmpUse, bkChanged; + int iHotTrack; + int gammaCorrection; + DWORD greyoutFlags; //see m_clc.h + DWORD offlineModes; + DWORD exStyle; + POINT ptInfoTip; + int infoTipTimeout; + HANDLE hInfoTipItem; + HIMAGELIST himlExtraColumns; + int extraColumnsCount; + int extraColumnSpacing; + int checkboxSize; + int showSelAlways; + int showIdle; + int noVScrollbar; + int useWindowsColours; + int needsResort; +}; + +/* clc.c */ +extern int g_IconWidth, g_IconHeight; + +void fnClcOptionsChanged( void ); +void fnClcBroadcast( int msg, WPARAM wParam, LPARAM lParam ); +HMENU fnBuildGroupPopupMenu( struct ClcGroup* group ); + +LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +/* clcidents.c */ +int fnGetRowsPriorTo( struct ClcGroup *group, struct ClcGroup *subgroup, int contactIndex ); +int fnFindItem( HWND hwnd, struct ClcData *dat, HANDLE hItem, struct ClcContact **contact, struct ClcGroup **subgroup, int *isVisible ); +int fnGetRowByIndex( struct ClcData *dat, int testindex, struct ClcContact **contact, struct ClcGroup **subgroup ); +HANDLE fnContactToHItem( struct ClcContact* contact ); +HANDLE fnContactToItemHandle( struct ClcContact * contact, DWORD * nmFlags ); + +/* clcitems.c */ +struct ClcGroup* fnAddGroup( HWND hwnd, struct ClcData *dat, const TCHAR *szName, DWORD flags, int groupId, int calcTotalMembers ); +struct ClcGroup* fnRemoveItemFromGroup(HWND hwnd, struct ClcGroup *group, struct ClcContact *contact, int updateTotalCount); + +void fnFreeContact( struct ClcContact *p ); +void fnFreeGroup( struct ClcGroup *group ); +int fnAddInfoItemToGroup(struct ClcGroup *group, int flags, const TCHAR *pszText); +int fnAddItemToGroup( struct ClcGroup *group,int iAboveItem ); +void fnAddContactToTree( HWND hwnd, struct ClcData *dat, HANDLE hContact, int updateTotalCount, int checkHideOffline); +int fnAddContactToGroup( struct ClcData *dat, struct ClcGroup *group, HANDLE hContact); +void fnDeleteItemFromTree( HWND hwnd, HANDLE hItem ); +void fnRebuildEntireList( HWND hwnd, struct ClcData *dat ); +int fnGetGroupContentsCount( struct ClcGroup *group, int visibleOnly ); +void fnSortCLC( HWND hwnd, struct ClcData *dat, int useInsertionSort ); +void fnSaveStateAndRebuildList(HWND hwnd, struct ClcData *dat); + +/* clcmsgs.c */ +LRESULT fnProcessExternalMessages(HWND hwnd, struct ClcData *dat, UINT msg, WPARAM wParam, LPARAM lParam ); + +/* clcutils.c */ +char* fnGetGroupCountsText(struct ClcData *dat, struct ClcContact *contact ); +int fnHitTest( HWND hwnd, struct ClcData *dat, int testx, int testy, struct ClcContact **contact, struct ClcGroup **group, DWORD * flags ); +void fnScrollTo( HWND hwnd, struct ClcData *dat, int desty, int noSmooth ); +void fnEnsureVisible(HWND hwnd, struct ClcData *dat, int iItem, int partialOk ); +void fnRecalcScrollBar( HWND hwnd, struct ClcData *dat ); +void fnSetGroupExpand( HWND hwnd, struct ClcData *dat, struct ClcGroup *group, int newState ); +void fnDoSelectionDefaultAction( HWND hwnd, struct ClcData *dat ); +int fnFindRowByText(HWND hwnd, struct ClcData *dat, const TCHAR *text, int prefixOk ); +void fnEndRename(HWND hwnd, struct ClcData *dat, int save ); +void fnDeleteFromContactList( HWND hwnd, struct ClcData *dat ); +void fnBeginRenameSelection( HWND hwnd, struct ClcData *dat ); +void fnCalcEipPosition( struct ClcData *dat, struct ClcContact *contact, struct ClcGroup *group, POINT *result); +int fnGetDropTargetInformation( HWND hwnd, struct ClcData *dat, POINT pt ); +int fnClcStatusToPf2( int status ); +int fnIsHiddenMode( struct ClcData *dat, int status ); +void fnHideInfoTip( HWND hwnd, struct ClcData *dat ); +void fnNotifyNewContact( HWND hwnd, HANDLE hContact ); +DWORD fnGetDefaultExStyle( void ); +void fnGetSetting( int i, LOGFONT* lf, COLORREF* colour ); +void fnGetDefaultFontSetting(int i, LOGFONT* lf, COLORREF* colour); +void fnGetFontSetting( int i, LOGFONT* lf, COLORREF* colour ); +void fnLoadClcOptions( HWND hwnd, struct ClcData *dat ); +void fnRecalculateGroupCheckboxes( HWND hwnd, struct ClcData *dat ); +void fnSetGroupChildCheckboxes( struct ClcGroup *group, int checked ); +void fnInvalidateItem( HWND hwnd, struct ClcData *dat, int iItem ); + +int fnGetRowBottomY(struct ClcData *dat, int item); +int fnGetRowHeight(struct ClcData *dat, int item); +int fnGetRowTopY(struct ClcData *dat, int item); +int fnGetRowTotalHeight(struct ClcData *dat); +int fnRowHitTest(struct ClcData *dat, int y); + +/* clcopts.c */ +int ClcOptInit(WPARAM wParam,LPARAM lParam); +DWORD GetDefaultExStyle(void); +void GetFontSetting(int i,LOGFONTA *lf,COLORREF *colour); + +/* clistmenus.c */ +HGENMENU fnGetProtocolMenu( const char* ); +int fnGetProtocolVisibility( const char* accName ); + +int fnGetAccountIndexByPos(int Pos); +int fnGetProtoIndexByPos(PROTOCOLDESCRIPTOR ** proto, int protoCnt, int Pos); +void RebuildMenuOrder( void ); + +INT_PTR MenuProcessCommand(WPARAM wParam, LPARAM lParam); + +/* clistsettings.c */ +TCHAR* fnGetContactDisplayName( HANDLE hContact, int mode ); +void fnGetDefaultFontSetting( int i, LOGFONT* lf, COLORREF * colour); +void fnInvalidateDisplayNameCacheEntry( HANDLE hContact ); + +ClcCacheEntryBase* fnGetCacheEntry( HANDLE hContact ); +ClcCacheEntryBase* fnCreateCacheItem ( HANDLE hContact ); +void fnCheckCacheItem( ClcCacheEntryBase* p ); +void fnFreeCacheItem( ClcCacheEntryBase* p ); + +/* clcfiledrop.c */ +void InitFileDropping(void); + +void fnRegisterFileDropping ( HWND hwnd ); +void fnUnregisterFileDropping ( HWND hwnd ); + +/* clistevents.c */ +struct CListEvent* fnAddEvent( CLISTEVENT *cle ); +CLISTEVENT* fnGetEvent( HANDLE hContact, int idx ); + +struct CListEvent* fnCreateEvent( void ); +void fnFreeEvent( struct CListEvent* p ); + +int fnEventsProcessContactDoubleClick( HANDLE hContact ); +int fnEventsProcessTrayDoubleClick( int ); +int fnGetImlIconIndex(HICON hIcon); +int fnRemoveEvent( HANDLE hContact, HANDLE dbEvent ); + +/* clistmod.c */ +int fnIconFromStatusMode(const char *szProto, int status, HANDLE hContact); +int fnShowHide( WPARAM wParam, LPARAM lParam ); +HICON fnGetIconFromStatusMode( HANDLE hContact, const char *szProto, int status ); +TCHAR* fnGetStatusModeDescription( int wParam, int lParam); +int fnGetWindowVisibleState(HWND hWnd, int iStepX, int iStepY); + +/* clisttray.c */ +void fnInitTray( void ); +void fnUninitTray( void ); +void fnLockTray( void ); +void fnUnlockTray( void ); +int fnCListTrayNotify(MIRANDASYSTRAYNOTIFY *msn); +int fnTrayIconAdd(HWND hwnd, const char *szProto, const char *szIconProto, int status); +int fnTrayIconDestroy( HWND hwnd ); +void fnTrayIconIconsChanged ( void ); +int fnTrayIconInit( HWND hwnd ); +TCHAR* fnTrayIconMakeTooltip( const TCHAR *szPrefix, const char *szProto ); +int fnTrayIconPauseAutoHide ( WPARAM wParam, LPARAM lParam ); +INT_PTR fnTrayIconProcessMessage ( WPARAM wParam, LPARAM lParam ); +void fnTrayIconRemove(HWND hwnd, const char *szProto); +int fnTrayIconSetBaseInfo(HICON hIcon, const char *szPreferredProto); +void fnTrayIconSetToBase ( char *szPreferredProto ); +void fnTrayIconTaskbarCreated( HWND hwnd ); +int fnTrayIconUpdate( HICON hNewIcon, const TCHAR *szNewTip, const char *szPreferredProto, int isBase ); +void fnTrayIconUpdateBase ( const char *szChangedProto ); +void fnTrayIconUpdateWithImageList ( int iImage, const TCHAR *szNewTip, char *szPreferredProto ); + +VOID CALLBACK fnTrayCycleTimerProc(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); + +/* clui.c */ +LRESULT CALLBACK fnContactListWndProc ( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ); +void fnLoadCluiGlobalOpts( void ); +void fnCluiProtocolStatusChanged(int,const char*); +void fnDrawMenuItem(DRAWITEMSTRUCT *dis, HICON hIcon, HICON eventIcon); + +/* contact.c */ +void fnChangeContactIcon ( HANDLE hContact, int iIcon, int add ); +void fnLoadContactTree ( void ); +int fnCompareContacts ( const struct ClcContact *contact1, const struct ClcContact *contact2); +void fnSortContacts ( void ); +int fnSetHideOffline ( WPARAM wParam, LPARAM lParam ); + +/* docking.c */ +int fnDocking_ProcessWindowMessage ( WPARAM wParam, LPARAM lParam ); + +/* group.c */ +TCHAR* fnGetGroupName ( int idx, DWORD* pdwFlags ); +int fnRenameGroup ( int groupID, TCHAR* newName ); + +/* keyboard.c */ +int fnHotKeysRegister ( HWND hwnd ); +void fnHotKeysUnregister ( HWND hwnd ); +int fnHotKeysProcess ( HWND hwnd, WPARAM wParam, LPARAM lParam ); +int fnHotkeysProcessMessage ( WPARAM wParam, LPARAM lParam ); diff --git a/src/modules/clist/clcfiledrop.cpp b/src/modules/clist/clcfiledrop.cpp new file mode 100644 index 0000000000..aae299d7b8 --- /dev/null +++ b/src/modules/clist/clcfiledrop.cpp @@ -0,0 +1,278 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" +#include + +struct CDropTarget : IDropTarget +{ + LONG refCount; + IDropTargetHelper *pDropTargetHelper; + + ULONG STDMETHODCALLTYPE AddRef(void); + ULONG STDMETHODCALLTYPE Release(void); + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject); + + HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); + HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); + HRESULT STDMETHODCALLTYPE DragLeave(void); + HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); +} +static dropTarget; + +static HWND hwndCurrentDrag = NULL; +static int originalSelection; + +HRESULT CDropTarget::QueryInterface(REFIID riid, LPVOID * ppvObj) +{ + if (riid == IID_IDropTarget) { + *ppvObj = this; + AddRef(); + return S_OK; + } + *ppvObj = NULL; + return E_NOINTERFACE; +} + +ULONG CDropTarget::AddRef(void) +{ + return InterlockedIncrement(&refCount); +} + +ULONG CDropTarget::Release(void) +{ + if (refCount == 1) { + if (pDropTargetHelper) + pDropTargetHelper->Release(); + } + return InterlockedDecrement(&refCount); +} + +static HANDLE HContactFromPoint(HWND hwnd, struct ClcData *dat, int x, int y, int *hitLine) +{ + int hit; + struct ClcContact *contact; + DWORD hitFlags; + char *szProto; + DWORD protoCaps; + + hit = cli.pfnHitTest(hwnd, dat, x, y, &contact, NULL, &hitFlags); + if (hit == -1 || !(hitFlags & (CLCHT_ONITEMLABEL | CLCHT_ONITEMICON)) || contact->type != CLCIT_CONTACT) + return NULL; + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) contact->hContact, 0); + if (szProto == NULL) + return NULL; + protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); + if (!(protoCaps & PF1_FILESEND)) + return NULL; + if (ID_STATUS_OFFLINE == DBGetContactSettingWord(contact->hContact, szProto, "Status", ID_STATUS_OFFLINE)) + return NULL; + if (hitLine) + *hitLine = hit; + return contact->hContact; +} + +HRESULT CDropTarget::DragOver(DWORD /*grfKeyState*/, POINTL pt, DWORD * pdwEffect) +{ + POINT shortPt; + struct ClcData *dat; + RECT clRect; + int hit; + HANDLE hContact; + + if (pDropTargetHelper && hwndCurrentDrag) + pDropTargetHelper->DragOver((POINT*)&pt, *pdwEffect); + + *pdwEffect = 0; + if (hwndCurrentDrag == NULL) { + *pdwEffect = DROPEFFECT_NONE; + return S_OK; + } + CallService(MS_CLIST_PAUSEAUTOHIDE, 0, 0); + dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0); + shortPt.x = pt.x; + shortPt.y = pt.y; + ScreenToClient(hwndCurrentDrag, &shortPt); + GetClientRect(hwndCurrentDrag, &clRect); + + if (shortPt.y < dat->dragAutoScrollHeight || shortPt.y >= clRect.bottom - dat->dragAutoScrollHeight) { + *pdwEffect |= DROPEFFECT_SCROLL; + cli.pfnScrollTo(hwndCurrentDrag, dat, dat->yScroll + (shortPt.y < dat->dragAutoScrollHeight ? -1 : 1) * dat->rowHeight * 2, 0); + } + hContact = HContactFromPoint(hwndCurrentDrag, dat, shortPt.x, shortPt.y, &hit); + if (hContact == NULL) { + hit = -1; + *pdwEffect |= DROPEFFECT_NONE; + } + else + *pdwEffect |= DROPEFFECT_COPY; + + if (dat->selection != hit) { + dat->selection = hit; + cli.pfnInvalidateRect(hwndCurrentDrag, NULL, FALSE); + if (pDropTargetHelper) pDropTargetHelper->Show(FALSE); + UpdateWindow(hwndCurrentDrag); + if (pDropTargetHelper) pDropTargetHelper->Show(TRUE); + } + + return S_OK; +} + +HRESULT CDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + HWND hwnd; + TCHAR szWindowClass[64]; + POINT shortPt; + + shortPt.x = pt.x; + shortPt.y = pt.y; + hwnd = WindowFromPoint(shortPt); + GetClassName(hwnd, szWindowClass, SIZEOF(szWindowClass)); + if (!lstrcmp(szWindowClass, CLISTCONTROL_CLASS)) { + struct ClcData *dat; + hwndCurrentDrag = hwnd; + dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0); + originalSelection = dat->selection; + dat->showSelAlways = 1; + } + if (pDropTargetHelper && hwndCurrentDrag) + pDropTargetHelper->DragEnter(hwndCurrentDrag, pDataObj, (POINT*)&pt, *pdwEffect); + return DragOver(grfKeyState, pt, pdwEffect); +} + +HRESULT CDropTarget::DragLeave(void) +{ + if (hwndCurrentDrag) { + struct ClcData *dat; + if (pDropTargetHelper) + pDropTargetHelper->DragLeave(); + dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0); + dat->showSelAlways = 0; + dat->selection = originalSelection; + cli.pfnInvalidateRect(hwndCurrentDrag, NULL, FALSE); + } + hwndCurrentDrag = NULL; + return S_OK; +} + +static void AddToFileList(TCHAR ***pppFiles, int *totalCount, const TCHAR *szFilename) +{ + *pppFiles = (TCHAR **) mir_realloc(*pppFiles, (++*totalCount + 1) * sizeof(TCHAR *)); + (*pppFiles)[*totalCount] = NULL; + (*pppFiles)[*totalCount - 1] = mir_tstrdup(szFilename); + if (GetFileAttributes(szFilename) & FILE_ATTRIBUTE_DIRECTORY) { + WIN32_FIND_DATA fd; + HANDLE hFind; + TCHAR szPath[MAX_PATH]; + lstrcpy(szPath, szFilename); + lstrcat(szPath, _T("\\*")); + if (hFind = FindFirstFile(szPath, &fd)) { + do { + if (!lstrcmp(fd.cFileName, _T(".")) || !lstrcmp(fd.cFileName, _T(".."))) + continue; + lstrcpy(szPath, szFilename); + lstrcat(szPath, _T("\\")); + lstrcat(szPath, fd.cFileName); + AddToFileList(pppFiles, totalCount, szPath); + } while (FindNextFile(hFind, &fd)); + FindClose(hFind); + } + } +} + +HRESULT CDropTarget::Drop(IDataObject * pDataObj, DWORD /*fKeyState*/, POINTL pt, DWORD * pdwEffect) +{ + FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stg; + HDROP hDrop; + POINT shortPt; + struct ClcData *dat; + HANDLE hContact; + + if (pDropTargetHelper && hwndCurrentDrag) + pDropTargetHelper->Drop(pDataObj, (POINT*)&pt, *pdwEffect); + + *pdwEffect = DROPEFFECT_NONE; + if (hwndCurrentDrag == NULL || S_OK != pDataObj->GetData(&fe, &stg)) + return S_OK; + hDrop = (HDROP) stg.hGlobal; + dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0); + + shortPt.x = pt.x; + shortPt.y = pt.y; + ScreenToClient(hwndCurrentDrag, &shortPt); + hContact = HContactFromPoint(hwndCurrentDrag, dat, shortPt.x, shortPt.y, NULL); + if (hContact != NULL) { + TCHAR **ppFiles = NULL; + TCHAR szFilename[MAX_PATH]; + int fileCount, totalCount = 0, i; + + fileCount = DragQueryFile(hDrop, -1, NULL, 0); + ppFiles = NULL; + for (i = 0; i < fileCount; i++) { + DragQueryFile(hDrop, i, szFilename, SIZEOF(szFilename)); + AddToFileList(&ppFiles, &totalCount, szFilename); + } + + if (!CallService(MS_FILE_SENDSPECIFICFILEST, (WPARAM) hContact, (LPARAM) ppFiles)) + *pdwEffect = DROPEFFECT_COPY; + + for (i = 0; ppFiles[i]; i++) + mir_free(ppFiles[i]); + mir_free(ppFiles); + } + + if (stg.pUnkForRelease) + stg.pUnkForRelease->Release(); + else + GlobalFree(stg.hGlobal); + + DragLeave(); + return S_OK; +} + +static VOID CALLBACK CreateDropTargetHelperTimerProc(HWND hwnd, UINT, UINT_PTR idEvent, DWORD) +{ + KillTimer(hwnd, idEvent); + //This is a ludicrously slow function (~200ms) so we delay load it a bit. + if (S_OK != CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, + IID_IDropTargetHelper, (LPVOID*)&dropTarget.pDropTargetHelper)) + dropTarget.pDropTargetHelper = NULL; +} + +void InitFileDropping(void) +{ + // Disabled as this function loads tons of dlls for no apparenet reason + // we will se what the reaction will be +// SetTimer(NULL, 1, 1000, CreateDropTargetHelperTimerProc); +} + +void fnRegisterFileDropping(HWND hwnd) +{ + RegisterDragDrop(hwnd, (IDropTarget *) & dropTarget); +} + +void fnUnregisterFileDropping(HWND hwnd) +{ + RevokeDragDrop(hwnd); +} diff --git a/src/modules/clist/clcidents.cpp b/src/modules/clist/clcidents.cpp new file mode 100644 index 0000000000..7cb18760fe --- /dev/null +++ b/src/modules/clist/clcidents.cpp @@ -0,0 +1,201 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +/* the CLC uses 3 different ways to identify elements in its list, this file +contains routines to convert between them. + +1) struct ClcContact/struct ClcGroup pair. Only ever used within the duration + of a single operation, but used at some point in nearly everything +2) index integer. The 0-based number of the item from the top. Only visible + items are counted (ie not closed groups). Used for saving selection and drag + highlight +3) hItem handle. Either the hContact or (hGroup|HCONTACT_ISGROUP). Used + exclusively externally + +1->2: GetRowsPriorTo() +1->3: ContactToHItem() +3->1: FindItem() +2->1: GetRowByIndex() +*/ + +int fnGetRowsPriorTo(struct ClcGroup *group, struct ClcGroup *subgroup, int contactIndex) +{ + int count = 0; + + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + group->scanIndex++; + continue; + } + if (group == subgroup && contactIndex == group->scanIndex) + return count; + count++; + if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + if (group->cl.items[group->scanIndex]->group == subgroup && contactIndex == -1) + return count - 1; + if (group->cl.items[group->scanIndex]->group->expanded) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + continue; + } + } + group->scanIndex++; + } + return -1; +} + +int fnFindItem(HWND hwnd, struct ClcData *dat, HANDLE hItem, struct ClcContact **contact, struct ClcGroup **subgroup, int *isVisible) +{ + int index = 0; + int nowVisible = 1; + struct ClcGroup *group = &dat->list; + + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + struct ClcGroup *tgroup; + group = group->parent; + if (group == NULL) + break; + nowVisible = 1; + for (tgroup = group; tgroup; tgroup = tgroup->parent) + if (!group->expanded) { + nowVisible = 0; + break; + } + group->scanIndex++; + continue; + } + if (nowVisible) + index++; + if ((IsHContactGroup(hItem) && group->cl.items[group->scanIndex]->type == CLCIT_GROUP + && ((unsigned) hItem & ~HCONTACT_ISGROUP) == group->cl.items[group->scanIndex]->groupId) || (IsHContactContact(hItem) + && group->cl.items[group->scanIndex]->type == CLCIT_CONTACT + && group->cl.items[group->scanIndex]->hContact == hItem) || (IsHContactInfo(hItem) + && group->cl.items[group->scanIndex]->type == CLCIT_INFO + && group->cl.items[group->scanIndex]->hContact == (HANDLE) ((UINT_PTR)hItem & ~HCONTACT_ISINFO))) + { + if (isVisible) { + if (!nowVisible) + *isVisible = 0; + else { + int posY = cli.pfnGetRowTopY(dat, index+1); + if (posY < dat->yScroll) + *isVisible = 0; + else { + RECT clRect; + GetClientRect(hwnd, &clRect); + if (posY >= dat->yScroll + clRect.bottom) + *isVisible = 0; + else + *isVisible = 1; + } + } + } + if (contact) + *contact = group->cl.items[group->scanIndex]; + if (subgroup) + *subgroup = group; + return 1; + } + if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + nowVisible &= group->expanded; + continue; + } + group->scanIndex++; + } + return 0; +} + +int fnGetRowByIndex(struct ClcData *dat, int testindex, struct ClcContact **contact, struct ClcGroup **subgroup) +{ + int index = 0; + struct ClcGroup *group = &dat->list; + + if (testindex<0) + return (-1); + + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + group->scanIndex++; + continue; + } + if (testindex == index) { + if (contact) + *contact = group->cl.items[group->scanIndex]; + if (subgroup) + *subgroup = group; + return index; + } + index++; + if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP && group->cl.items[group->scanIndex]->group->expanded) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + continue; + } + group->scanIndex++; + } + return -1; +} + +HANDLE fnContactToHItem(struct ClcContact * contact) +{ + switch (contact->type) { + case CLCIT_CONTACT: + return contact->hContact; + case CLCIT_GROUP: + return (HANDLE) (contact->groupId | HCONTACT_ISGROUP); + case CLCIT_INFO: + return (HANDLE) ((UINT_PTR) contact->hContact | HCONTACT_ISINFO); + } + return NULL; +} + +HANDLE fnContactToItemHandle(struct ClcContact * contact, DWORD * nmFlags) +{ + switch (contact->type) { + case CLCIT_CONTACT: + return contact->hContact; + case CLCIT_GROUP: + if (nmFlags) + *nmFlags |= CLNF_ISGROUP; + return (HANDLE) contact->groupId; + case CLCIT_INFO: + if (nmFlags) + *nmFlags |= CLNF_ISINFO; + return (HANDLE) ((UINT_PTR) contact->hContact | HCONTACT_ISINFO); + } + return NULL; +} diff --git a/src/modules/clist/clcitems.cpp b/src/modules/clist/clcitems.cpp new file mode 100644 index 0000000000..cc338d5ec9 --- /dev/null +++ b/src/modules/clist/clcitems.cpp @@ -0,0 +1,707 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +//routines for managing adding/removal of items in the list, including sorting + +int fnAddItemToGroup(struct ClcGroup *group, int iAboveItem) +{ + struct ClcContact* newItem = cli.pfnCreateClcContact(); + newItem->type = CLCIT_DIVIDER; + newItem->flags = 0; + newItem->szText[0] = '\0'; + memset( newItem->iExtraImage, 0xFF, SIZEOF(newItem->iExtraImage)); + + List_Insert(( SortedList* )&group->cl, newItem, iAboveItem ); + return iAboveItem; +} + +struct ClcGroup* fnAddGroup(HWND hwnd, struct ClcData *dat, const TCHAR *szName, DWORD flags, int groupId, int calcTotalMembers) +{ + TCHAR *pBackslash, *pNextField, szThisField[ SIZEOF(dat->list.cl.items[0]->szText) ]; + struct ClcGroup *group = &dat->list; + int i, compareResult; + + dat->needsResort = 1; + if (!(GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_USEGROUPS)) + return &dat->list; + + pNextField = ( TCHAR* )szName; + do { + pBackslash = _tcschr(pNextField, '\\'); + if (pBackslash == NULL) { + lstrcpyn(szThisField, pNextField, SIZEOF(szThisField)); + pNextField = NULL; + } + else { + lstrcpyn(szThisField, pNextField, min( SIZEOF(szThisField), pBackslash - pNextField + 1 )); + pNextField = pBackslash + 1; + } + compareResult = 1; + for (i = 0; i < group->cl.count; i++) { + if (group->cl.items[i]->type == CLCIT_CONTACT) + break; + if (group->cl.items[i]->type != CLCIT_GROUP) + continue; + compareResult = lstrcmp(szThisField, group->cl.items[i]->szText); + if (compareResult == 0) { + if (pNextField == NULL && flags != (DWORD) - 1) { + group->cl.items[i]->groupId = (WORD) groupId; + group = group->cl.items[i]->group; + group->expanded = (flags & GROUPF_EXPANDED) != 0; + group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0; + group->groupId = groupId; + } + else + group = group->cl.items[i]->group; + break; + } + if (pNextField == NULL && group->cl.items[i]->groupId == 0) + break; + if (!(dat->exStyle & CLS_EX_SORTGROUPSALPHA) && groupId && group->cl.items[i]->groupId > groupId) + break; + } + if (compareResult) { + if (groupId == 0) + return NULL; + i = cli.pfnAddItemToGroup(group, i); + group->cl.items[i]->type = CLCIT_GROUP; + lstrcpyn(group->cl.items[i]->szText, szThisField, SIZEOF( group->cl.items[i]->szText )); + group->cl.items[i]->groupId = (WORD) (pNextField ? 0 : groupId); + group->cl.items[i]->group = (struct ClcGroup *) mir_alloc(sizeof(struct ClcGroup)); + group->cl.items[i]->group->parent = group; + group = group->cl.items[i]->group; + memset( &group->cl, 0, sizeof( group->cl )); + group->cl.increment = 10; + if (flags == (DWORD) - 1 || pNextField != NULL) { + group->expanded = 0; + group->hideOffline = 0; + } + else { + group->expanded = (flags & GROUPF_EXPANDED) != 0; + group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0; + } + group->groupId = pNextField ? 0 : groupId; + group->totalMembers = 0; + if (flags != (DWORD) - 1 && pNextField == NULL && calcTotalMembers) { + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while (hContact) { + ClcCacheEntryBase* cache = cli.pfnGetCacheEntry( hContact ); + if ( !lstrcmp( cache->group, szName) && (style & CLS_SHOWHIDDEN || !cache->isHidden )) + group->totalMembers++; + + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0); + } + } + } + } while (pNextField); + return group; +} + +void fnFreeContact(struct ClcContact* p) +{ + if (p->type == CLCIT_GROUP) { + cli.pfnFreeGroup(p->group); + mir_free(p->group); +} } + +void fnFreeGroup(struct ClcGroup *group) +{ + int i; + for (i = 0; i < group->cl.count; i++) { + cli.pfnFreeContact(group->cl.items[i]); + mir_free(group->cl.items[i]); + } + if (group->cl.items) + mir_free(group->cl.items); + group->cl.limit = group->cl.count = 0; + group->cl.items = NULL; +} + +static int iInfoItemUniqueHandle = 0; +int fnAddInfoItemToGroup(struct ClcGroup *group, int flags, const TCHAR *pszText) +{ + int i = 0; + + if (flags & CLCIIF_BELOWCONTACTS) + i = group->cl.count; + else if (flags & CLCIIF_BELOWGROUPS) { + for (; i < group->cl.count; i++) + if (group->cl.items[i]->type == CLCIT_CONTACT) + break; + } + else + for (; i < group->cl.count; i++) + if (group->cl.items[i]->type != CLCIT_INFO) + break; + i = cli.pfnAddItemToGroup(group, i); + iInfoItemUniqueHandle = (iInfoItemUniqueHandle + 1) & 0xFFFF; + if (iInfoItemUniqueHandle == 0) + ++iInfoItemUniqueHandle; + group->cl.items[i]->type = CLCIT_INFO; + group->cl.items[i]->flags = (BYTE) flags; + group->cl.items[i]->hContact = (HANDLE)++ iInfoItemUniqueHandle; + lstrcpyn(group->cl.items[i]->szText, pszText, SIZEOF( group->cl.items[i]->szText )); + return i; +} + +int fnAddContactToGroup(struct ClcData *dat, struct ClcGroup *group, HANDLE hContact) +{ + char *szProto; + WORD apparentMode; + DWORD idleMode; + + int i, index = -1; + + dat->needsResort = 1; + for (i = group->cl.count - 1; i >= 0; i--) { + if (group->cl.items[i]->hContact == hContact ) + return i; + + if ( index == -1 ) + if (group->cl.items[i]->type != CLCIT_INFO || !(group->cl.items[i]->flags & CLCIIF_BELOWCONTACTS)) + index = i; + } + + i = cli.pfnAddItemToGroup(group, index + 1); + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + group->cl.items[i]->type = CLCIT_CONTACT; + group->cl.items[i]->iImage = CallService(MS_CLIST_GETCONTACTICON, (WPARAM) hContact, 0); + group->cl.items[i]->hContact = hContact; + group->cl.items[i]->proto = szProto; + if (szProto != NULL && !cli.pfnIsHiddenMode(dat, DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE))) + group->cl.items[i]->flags |= CONTACTF_ONLINE; + apparentMode = szProto != NULL ? DBGetContactSettingWord(hContact, szProto, "ApparentMode", 0) : 0; + if (apparentMode == ID_STATUS_OFFLINE) + group->cl.items[i]->flags |= CONTACTF_INVISTO; + else if (apparentMode == ID_STATUS_ONLINE) + group->cl.items[i]->flags |= CONTACTF_VISTO; + else if (apparentMode) + group->cl.items[i]->flags |= CONTACTF_VISTO | CONTACTF_INVISTO; + if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + group->cl.items[i]->flags |= CONTACTF_NOTONLIST; + idleMode = szProto != NULL ? DBGetContactSettingDword(hContact, szProto, "IdleTS", 0) : 0; + if (idleMode) + group->cl.items[i]->flags |= CONTACTF_IDLE; + lstrcpyn(group->cl.items[i]->szText, cli.pfnGetContactDisplayName(hContact,0), SIZEOF(group->cl.items[i]->szText)); + + { ClcCacheEntryBase* p = cli.pfnGetCacheEntry(hContact); + if ( p != NULL ) { + if ( p->group ) mir_free( p->group ); + p->group = NULL; + } } + + return i; +} + +void fnAddContactToTree(HWND hwnd, struct ClcData *dat, HANDLE hContact, int updateTotalCount, int checkHideOffline) +{ + struct ClcGroup *group; + DBVARIANT dbv; + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + WORD status = ID_STATUS_OFFLINE; + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + + dat->needsResort = 1; + if (style & CLS_NOHIDEOFFLINE) + checkHideOffline = 0; + if (checkHideOffline) + if (szProto != NULL) + status = DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE); + + if ( DBGetContactSettingTString(hContact, "CList", "Group", &dbv)) + group = &dat->list; + else { + group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, (DWORD) - 1, 0, 0); + if (group == NULL) { + int i, len; + DWORD groupFlags; + TCHAR *szGroupName; + if (!(style & CLS_HIDEEMPTYGROUPS)) { + mir_free(dbv.ptszVal); + return; + } + if (checkHideOffline && cli.pfnIsHiddenMode(dat, status)) { + for (i = 1;; i++) { + szGroupName = cli.pfnGetGroupName(i, &groupFlags); + if (szGroupName == NULL) { + mir_free(dbv.ptszVal); + return; + } //never happens + if (!lstrcmp(szGroupName, dbv.ptszVal)) + break; + } + if (groupFlags & GROUPF_HIDEOFFLINE) { + mir_free(dbv.ptszVal); + return; + } + } + for (i = 1;; i++) { + szGroupName = cli.pfnGetGroupName(i, &groupFlags); + if (szGroupName == NULL) { + mir_free(dbv.ptszVal); + return; + } //never happens + if (!lstrcmp(szGroupName, dbv.ptszVal)) + break; + len = lstrlen(szGroupName); + if (!_tcsncmp(szGroupName, dbv.ptszVal, len) && dbv.ptszVal[len] == '\\') + cli.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 1); + } + group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, groupFlags, i, 1); + } + mir_free(dbv.ptszVal); + } + if (checkHideOffline) { + if (cli.pfnIsHiddenMode(dat, status) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { + if (updateTotalCount) + group->totalMembers++; + return; + } + } + cli.pfnAddContactToGroup(dat, group, hContact); + if (updateTotalCount) + group->totalMembers++; +} + +struct ClcGroup* fnRemoveItemFromGroup(HWND hwnd, struct ClcGroup *group, struct ClcContact *contact, int updateTotalCount) +{ + int iContact; + if (( iContact = List_IndexOf(( SortedList* )&group->cl, contact )) == -1 ) + return group; + + if (updateTotalCount && contact->type == CLCIT_CONTACT) + group->totalMembers--; + + { ClcCacheEntryBase* p = cli.pfnGetCacheEntry(contact->hContact); + if ( p != NULL ) { + if ( p->group ) mir_free( p->group ); + p->group = NULL; + } } + + cli.pfnFreeContact( group->cl.items[iContact] ); + mir_free( group->cl.items[iContact] ); + List_Remove(( SortedList* )&group->cl, iContact ); + + if ((GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) && group->cl.count == 0) { + int i; + if (group->parent == NULL) + return group; + for (i = 0; i < group->parent->cl.count; i++) + if (group->parent->cl.items[i]->type == CLCIT_GROUP && group->parent->cl.items[i]->groupId == group->groupId) + break; + if (i == group->parent->cl.count) + return group; //never happens + return cli.pfnRemoveItemFromGroup(hwnd, group->parent, group->parent->cl.items[i], 0); + } + return group; +} + +void fnDeleteItemFromTree(HWND hwnd, HANDLE hItem) +{ + struct ClcContact *contact; + struct ClcGroup *group; + struct ClcData *dat = (struct ClcData *) GetWindowLongPtr(hwnd, 0); + + dat->needsResort = 1; + if (!cli.pfnFindItem(hwnd, dat, hItem, &contact, &group, NULL)) { + DBVARIANT dbv; + int i, nameOffset; + if (!IsHContactContact(hItem)) + return; + if (DBGetContactSettingTString(hItem, "CList", "Group", &dbv)) + return; + + //decrease member counts of all parent groups too + group = &dat->list; + nameOffset = 0; + for (i = 0;; i++) { + if (group->scanIndex == group->cl.count) + break; + if (group->cl.items[i]->type == CLCIT_GROUP) { + int len = lstrlen(group->cl.items[i]->szText); + if (!_tcsncmp(group->cl.items[i]->szText, dbv.ptszVal + nameOffset, len) && + (dbv.ptszVal[nameOffset + len] == '\\' || dbv.ptszVal[nameOffset + len] == '\0')) { + group->totalMembers--; + if (dbv.ptszVal[nameOffset + len] == '\0') + break; + } + } + } + mir_free(dbv.ptszVal); + } + else + cli.pfnRemoveItemFromGroup(hwnd, group, contact, 1); +} + +void fnRebuildEntireList(HWND hwnd, struct ClcData *dat) +{ + char *szProto; + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + HANDLE hContact; + struct ClcGroup *group; + DBVARIANT dbv; + + dat->list.expanded = 1; + dat->list.hideOffline = DBGetContactSettingByte(NULL, "CLC", "HideOfflineRoot", 0) && style&CLS_USEGROUPS; + dat->list.cl.count = dat->list.cl.limit = 0; + dat->selection = -1; + { + int i; + TCHAR *szGroupName; + DWORD groupFlags; + + for (i = 1;; i++) { + szGroupName = cli.pfnGetGroupName(i, &groupFlags); + if (szGroupName == NULL) + break; + cli.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 0); + } + } + + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while (hContact) { + if (style & CLS_SHOWHIDDEN || !DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) { + if (DBGetContactSettingTString(hContact, "CList", "Group", &dbv)) + group = &dat->list; + else { + group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, (DWORD) - 1, 0, 0); + if (group == NULL && style & CLS_SHOWHIDDEN) group = &dat->list; + mir_free(dbv.ptszVal); + } + + if (group != NULL) { + group->totalMembers++; + if (!(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto == NULL) { + if (!cli.pfnIsHiddenMode(dat, ID_STATUS_OFFLINE)) + cli.pfnAddContactToGroup(dat, group, hContact); + } + else if (!cli.pfnIsHiddenMode(dat, DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE))) + cli.pfnAddContactToGroup(dat, group, hContact); + } + else cli.pfnAddContactToGroup(dat, group, hContact); + } + } + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0); + } + + if (style & CLS_HIDEEMPTYGROUPS) { + group = &dat->list; + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + if (group->cl.items[group->scanIndex]->group->cl.count == 0) { + group = cli.pfnRemoveItemFromGroup(hwnd, group, group->cl.items[group->scanIndex], 0); + } + else { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + } + continue; + } + group->scanIndex++; + } + } + + cli.pfnSortCLC(hwnd, dat, 0); +} + +int fnGetGroupContentsCount(struct ClcGroup *group, int visibleOnly) +{ + int count = group->cl.count; + struct ClcGroup *topgroup = group; + + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + if (group == topgroup) + break; + group = group->parent; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP && (!visibleOnly || group->cl.items[group->scanIndex]->group->expanded)) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + count += group->cl.count; + continue; + } + group->scanIndex++; + } + return count; +} + +static int __cdecl GroupSortProc(const void* p1, const void* p2) +{ + ClcContact **contact1 = ( ClcContact** )p1, **contact2 = ( ClcContact** )p2; + + return lstrcmpi(contact1[0]->szText, contact2[0]->szText); +} + +static int __cdecl ContactSortProc(const void* p1, const void* p2) +{ + ClcContact **contact1 = ( ClcContact** )p1, **contact2 = ( ClcContact** )p2; + + int result = cli.pfnCompareContacts( contact1[0], contact2[0] ); + if (result) + return result; + //nothing to distinguish them, so make sure they stay in the same order + return (int)((INT_PTR) contact2[0]->hContact - (INT_PTR) contact1[0]->hContact); +} + +static void InsertionSort(struct ClcContact **pContactArray, int nArray, int (*CompareProc) (const void *, const void *)) +{ + int i, j; + struct ClcContact* testElement; + + for (i = 1; i < nArray; i++) { + if (CompareProc(&pContactArray[i - 1], &pContactArray[i]) > 0) { + testElement = pContactArray[i]; + for (j = i - 2; j >= 0; j--) + if (CompareProc(&pContactArray[j], &testElement) <= 0) + break; + j++; + memmove(&pContactArray[j + 1], &pContactArray[j], sizeof(void*) * (i - j)); + pContactArray[j] = testElement; +} } } + +static void SortGroup(struct ClcData *dat, struct ClcGroup *group, int useInsertionSort) +{ + int i, sortCount; + + for (i = group->cl.count - 1; i >= 0; i--) { + if (group->cl.items[i]->type == CLCIT_DIVIDER) { + mir_free( group->cl.items[i] ); + List_Remove(( SortedList* )&group->cl, i ); + } } + + for (i = 0; i < group->cl.count; i++) + if (group->cl.items[i]->type != CLCIT_INFO) + break; + if (i > group->cl.count - 2) + return; + if (group->cl.items[i]->type == CLCIT_GROUP) { + if (dat->exStyle & CLS_EX_SORTGROUPSALPHA) { + for (sortCount = 0; i + sortCount < group->cl.count; sortCount++) + if (group->cl.items[i + sortCount]->type != CLCIT_GROUP) + break; + qsort(group->cl.items + i, sortCount, sizeof(void*), GroupSortProc); + i = i + sortCount; + } + for (; i < group->cl.count; i++) + if (group->cl.items[i]->type == CLCIT_CONTACT) + break; + if (group->cl.count - i < 2) + return; + } + for (sortCount = 0; i + sortCount < group->cl.count; sortCount++) + if (group->cl.items[i + sortCount]->type != CLCIT_CONTACT) + break; + if (useInsertionSort) + InsertionSort(group->cl.items + i, sortCount, ContactSortProc); + else + qsort(group->cl.items + i, sortCount, sizeof(void*), ContactSortProc); + if (dat->exStyle & CLS_EX_DIVIDERONOFF) { + int prevContactOnline = 0; + for (i = 0; i < group->cl.count; i++) { + if (group->cl.items[i]->type != CLCIT_CONTACT) + continue; + if (group->cl.items[i]->flags & CONTACTF_ONLINE) + prevContactOnline = 1; + else { + if (prevContactOnline) { + i = cli.pfnAddItemToGroup(group, i); + group->cl.items[i]->type = CLCIT_DIVIDER; + lstrcpy(group->cl.items[i]->szText, TranslateT("Offline")); + } + break; +} } } } + +void fnSortCLC(HWND hwnd, struct ClcData *dat, int useInsertionSort) +{ + struct ClcContact *selcontact; + struct ClcGroup *group = &dat->list, *selgroup; + HANDLE hSelItem; + + if ( dat->needsResort ) { + if (cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) == -1) + hSelItem = NULL; + else + hSelItem = cli.pfnContactToHItem(selcontact); + group->scanIndex = 0; + SortGroup(dat, group, useInsertionSort); + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + SortGroup(dat, group, useInsertionSort); + continue; + } + group->scanIndex++; + } + if (hSelItem) + if (cli.pfnFindItem(hwnd, dat, hSelItem, &selcontact, &selgroup, NULL)) + dat->selection = cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf((SortedList*)&selgroup->cl,selcontact)); + + cli.pfnRecalcScrollBar(hwnd, dat); + } + dat->needsResort = 0; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); +} + +struct SavedContactState_t +{ + HANDLE hContact; + BYTE iExtraImage[MAXEXTRACOLUMNS]; + int checked; +}; + +struct SavedGroupState_t +{ + int groupId, expanded; +}; + +struct SavedInfoState_t +{ + int parentId; + ClcContact contact; +}; + +void fnSaveStateAndRebuildList(HWND hwnd, struct ClcData *dat) +{ + NMCLISTCONTROL nm; + int i, j; + ClcGroup *group; + ClcContact *contact; + + cli.pfnHideInfoTip(hwnd, dat); + KillTimer(hwnd, TIMERID_INFOTIP); + KillTimer(hwnd, TIMERID_RENAME); + cli.pfnEndRename(hwnd, dat, 1); + + OBJLIST saveContact( 10, HandleKeySortT ); + OBJLIST saveGroup( 100, NumericKeySortT ); + OBJLIST saveInfo( 10, NumericKeySortT ); + + dat->needsResort = 1; + group = &dat->list; + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + + SavedGroupState_t* p = new SavedGroupState_t; + p->groupId = group->groupId; + p->expanded = group->expanded; + saveGroup.insert( p ); + continue; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_CONTACT) { + SavedContactState_t* p = new SavedContactState_t; + p->hContact = group->cl.items[group->scanIndex]->hContact; + CopyMemory( p->iExtraImage, group->cl.items[group->scanIndex]->iExtraImage, + sizeof(group->cl.items[group->scanIndex]->iExtraImage)); + p->checked = group->cl.items[group->scanIndex]->flags & CONTACTF_CHECKED; + saveContact.insert( p ); + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_INFO) { + SavedInfoState_t* p = new SavedInfoState_t; + p->parentId = (group->parent == NULL) ? -1 : group->groupId; + p->contact = *group->cl.items[group->scanIndex]; + saveInfo.insert( p ); + } + group->scanIndex++; + } + + cli.pfnFreeGroup(&dat->list); + cli.pfnRebuildEntireList(hwnd, dat); + + group = &dat->list; + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + + SavedGroupState_t tmp, *p; + tmp.groupId = group->groupId; + if (( p = saveGroup.find( &tmp )) != NULL ) + group->expanded = p->expanded; + continue; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_CONTACT) { + SavedContactState_t tmp, *p; + tmp.hContact = group->cl.items[group->scanIndex]->hContact; + if (( p = saveContact.find( &tmp )) != NULL ) { + CopyMemory(group->cl.items[group->scanIndex]->iExtraImage, p->iExtraImage, + SIZEOF(group->cl.items[group->scanIndex]->iExtraImage)); + if (p->checked) + group->cl.items[group->scanIndex]->flags |= CONTACTF_CHECKED; + } } + + group->scanIndex++; + } + + for (i = 0; i < saveInfo.getCount(); i++) { + if (saveInfo[i].parentId == -1) + group = &dat->list; + else { + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) (saveInfo[i].parentId | HCONTACT_ISGROUP), &contact, NULL, NULL)) + continue; + group = contact->group; + } + j = cli.pfnAddInfoItemToGroup(group, saveInfo[i].contact.flags, _T("")); + *group->cl.items[j] = saveInfo[i].contact; + } + + cli.pfnRecalculateGroupCheckboxes(hwnd, dat); + + cli.pfnRecalcScrollBar(hwnd, dat); + nm.hdr.code = CLN_LISTREBUILT; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); +} diff --git a/src/modules/clist/clcmsgs.cpp b/src/modules/clist/clcmsgs.cpp new file mode 100644 index 0000000000..a46777939d --- /dev/null +++ b/src/modules/clist/clcmsgs.cpp @@ -0,0 +1,472 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +//processing of all the CLM_ messages incoming + +LRESULT fnProcessExternalMessages(HWND hwnd, struct ClcData *dat, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case CLM_ADDCONTACT: + cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 0); + cli.pfnRecalcScrollBar(hwnd, dat); + cli.pfnSortCLC(hwnd, dat, 1); + break; + + case CLM_ADDGROUP: + { + DWORD groupFlags; + TCHAR *szName = cli.pfnGetGroupName(wParam, &groupFlags); + if (szName == NULL) + break; + cli.pfnAddGroup(hwnd, dat, szName, groupFlags, wParam, 0); + cli.pfnRecalcScrollBar(hwnd, dat); + break; + } + + case CLM_ADDINFOITEMA: + case CLM_ADDINFOITEMW: + { + int i; + ClcContact *groupContact; + ClcGroup *group; + CLCINFOITEM *cii = (CLCINFOITEM *) lParam; + if (cii==NULL || cii->cbSize != sizeof(CLCINFOITEM)) + return (LRESULT) (HANDLE) NULL; + if (cii->hParentGroup == NULL) + group = &dat->list; + else { + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) ((UINT_PTR) cii->hParentGroup | HCONTACT_ISGROUP), &groupContact, NULL, NULL)) + return (LRESULT) (HANDLE) NULL; + group = groupContact->group; + } + #if defined( _UNICODE ) + if ( msg == CLM_ADDINFOITEMA ) + { WCHAR* wszText = mir_a2u(( char* )cii->pszText ); + i = cli.pfnAddInfoItemToGroup(group, cii->flags, wszText); + mir_free( wszText ); + } + else i = cli.pfnAddInfoItemToGroup(group, cii->flags, cii->pszText); + #else + i = cli.pfnAddInfoItemToGroup(group, cii->flags, cii->pszText); + #endif + cli.pfnRecalcScrollBar(hwnd, dat); + return (LRESULT) group->cl.items[i]->hContact | HCONTACT_ISINFO; + } + + case CLM_AUTOREBUILD: + KillTimer(hwnd,TIMERID_REBUILDAFTER); + cli.pfnSaveStateAndRebuildList(hwnd, dat); + break; + + case CLM_DELETEITEM: + cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam); + cli.pfnSortCLC(hwnd, dat, 1); + cli.pfnRecalcScrollBar(hwnd, dat); + break; + + case CLM_EDITLABEL: + SendMessage(hwnd, CLM_SELECTITEM, wParam, 0); + cli.pfnBeginRenameSelection(hwnd, dat); + break; + + case CLM_ENDEDITLABELNOW: + cli.pfnEndRename(hwnd, dat, wParam); + break; + + case CLM_ENSUREVISIBLE: + { + ClcContact *contact; + ClcGroup *group, *tgroup; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, &group, NULL)) + break; + for (tgroup = group; tgroup; tgroup = tgroup->parent) + cli.pfnSetGroupExpand(hwnd, dat, tgroup, 1); + cli.pfnEnsureVisible(hwnd, dat, cli.pfnGetRowsPriorTo(&dat->list, group, List_IndexOf((SortedList*)&group->cl,contact)), 0); + break; + } + + case CLM_EXPAND: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + if (contact->type != CLCIT_GROUP) + break; + cli.pfnSetGroupExpand(hwnd, dat, contact->group, lParam); + break; + } + + case CLM_FINDCONTACT: + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, NULL, NULL, NULL)) + return (LRESULT) (HANDLE) NULL; + return wParam; + + case CLM_FINDGROUP: + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) (wParam | HCONTACT_ISGROUP), NULL, NULL, NULL)) + return (LRESULT) (HANDLE) NULL; + return wParam | HCONTACT_ISGROUP; + + case CLM_GETBKCOLOR: + return dat->bkColour; + + case CLM_GETCHECKMARK: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return 0; + return (contact->flags & CONTACTF_CHECKED) != 0; + } + + case CLM_GETCOUNT: + return cli.pfnGetGroupContentsCount(&dat->list, 0); + + case CLM_GETEDITCONTROL: + return (LRESULT) dat->hwndRenameEdit; + + case CLM_GETEXPAND: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return CLE_INVALID; + if (contact->type != CLCIT_GROUP) + return CLE_INVALID; + return contact->group->expanded; + } + + case CLM_GETEXTRACOLUMNS: + return dat->extraColumnsCount; + + case CLM_GETEXTRAIMAGE: + { + ClcContact *contact; + if (LOWORD(lParam) >= dat->extraColumnsCount) + return 0xFF; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return 0xFF; + return contact->iExtraImage[LOWORD(lParam)]; + } + + case CLM_GETEXTRAIMAGELIST: + return (LRESULT) dat->himlExtraColumns; + + case CLM_GETFONT: + if (wParam < 0 || wParam > FONTID_MAX) + return 0; + return (LRESULT) dat->fontInfo[wParam].hFont; + + case CLM_GETHIDEOFFLINEROOT: + return DBGetContactSettingByte(NULL, "CLC", "HideOfflineRoot", 0); + + case CLM_GETINDENT: + return dat->groupIndent; + + case CLM_GETISEARCHSTRING: + lstrcpy(( TCHAR* ) lParam, dat->szQuickSearch); + return lstrlen(dat->szQuickSearch); + + case CLM_GETITEMTEXT: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return 0; + lstrcpy(( TCHAR* ) lParam, contact->szText); + return lstrlen(contact->szText); + } + + case CLM_GETITEMTYPE: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return CLCIT_INVALID; + return contact->type; + } + + case CLM_GETLEFTMARGIN: + return dat->leftMargin; + + case CLM_GETNEXTITEM: + { + if (wParam == CLGN_ROOT) { + if (dat->list.cl.count) + return (LRESULT) cli.pfnContactToHItem(dat->list.cl.items[0]); + return NULL; + } + + ClcContact *contact; + ClcGroup *group; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) lParam, &contact, &group, NULL)) + return NULL; + + int i = List_IndexOf((SortedList*)&group->cl,contact); + switch (wParam) { + case CLGN_CHILD: + if (contact->type != CLCIT_GROUP) + return (LRESULT) (HANDLE) NULL; + group = contact->group; + if (group->cl.count == 0) + return NULL; + return (LRESULT) cli.pfnContactToHItem(group->cl.items[0]); + + case CLGN_PARENT: + return group->groupId | HCONTACT_ISGROUP; + + case CLGN_NEXT: + do { + if (++i >= group->cl.count) + return NULL; + } + while (group->cl.items[i]->type == CLCIT_DIVIDER); + return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]); + + case CLGN_PREVIOUS: + do { + if (--i < 0) + return NULL; + } + while (group->cl.items[i]->type == CLCIT_DIVIDER); + return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]); + + case CLGN_NEXTCONTACT: + for (i++; i < group->cl.count; i++) + if (group->cl.items[i]->type == CLCIT_CONTACT) + break; + if (i >= group->cl.count) + return NULL; + return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]); + + case CLGN_PREVIOUSCONTACT: + if (i >= group->cl.count) + return NULL; + for (i--; i >= 0; i--) + if (group->cl.items[i]->type == CLCIT_CONTACT) + break; + if (i < 0) + return NULL; + return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]); + + case CLGN_NEXTGROUP: + for (i++; i < group->cl.count; i++) + if (group->cl.items[i]->type == CLCIT_GROUP) + break; + if (i >= group->cl.count) + return NULL; + return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]); + + case CLGN_PREVIOUSGROUP: + if (i >= group->cl.count) + return NULL; + for (i--; i >= 0; i--) + if (group->cl.items[i]->type == CLCIT_GROUP) + break; + if (i < 0) + return NULL; + return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]); + } + return NULL; + } + + case CLM_GETSCROLLTIME: + return dat->scrollTime; + + case CLM_GETSELECTION: + { + ClcContact *contact; + if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1) + return NULL; + return (LRESULT) cli.pfnContactToHItem(contact); + } + + case CLM_GETTEXTCOLOR: + if (wParam < 0 || wParam > FONTID_MAX) + return 0; + return (LRESULT) dat->fontInfo[wParam].colour; + + case CLM_HITTEST: + { + ClcContact *contact; + DWORD hitFlags; + int hit; + hit = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, NULL, &hitFlags); + if (wParam) + *(PDWORD) wParam = hitFlags; + if (hit == -1) + return NULL; + return (LRESULT) cli.pfnContactToHItem(contact); + } + + case CLM_SELECTITEM: + { + ClcContact *contact; + ClcGroup *group, *tgroup; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, &group, NULL)) + break; + for (tgroup = group; tgroup; tgroup = tgroup->parent) + cli.pfnSetGroupExpand(hwnd, dat, tgroup, 1); + dat->selection = cli.pfnGetRowsPriorTo(&dat->list, group, List_IndexOf((SortedList*)&group->cl,contact)); + cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); + break; + } + + case CLM_SETBKBITMAP: + if (!dat->bkChanged && dat->hBmpBackground) { + DeleteObject(dat->hBmpBackground); + dat->hBmpBackground = NULL; + } + dat->hBmpBackground = (HBITMAP) lParam; + dat->backgroundBmpUse = wParam; + dat->bkChanged = 1; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETBKCOLOR: + if (!dat->bkChanged && dat->hBmpBackground) { + DeleteObject(dat->hBmpBackground); + dat->hBmpBackground = NULL; + } + dat->bkColour = wParam; + dat->bkChanged = 1; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETCHECKMARK: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return 0; + if (lParam) + contact->flags |= CONTACTF_CHECKED; + else + contact->flags &= ~CONTACTF_CHECKED; + cli.pfnRecalculateGroupCheckboxes(hwnd, dat); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + } + + case CLM_SETEXTRACOLUMNS: + if (wParam > MAXEXTRACOLUMNS) + return 0; + dat->extraColumnsCount = wParam; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETEXTRAIMAGE: + { + ClcContact *contact; + if (LOWORD(lParam) >= dat->extraColumnsCount) + return 0; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + return 0; + contact->iExtraImage[LOWORD(lParam)] = (BYTE) HIWORD(lParam); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + } + + case CLM_SETEXTRAIMAGELIST: + dat->himlExtraColumns = (HIMAGELIST) lParam; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETFONT: + if (HIWORD(lParam) < 0 || HIWORD(lParam) > FONTID_MAX) + return 0; + dat->fontInfo[HIWORD(lParam)].hFont = (HFONT) wParam; + dat->fontInfo[HIWORD(lParam)].changed = 1; + { + SIZE fontSize; + HDC hdc = GetDC(hwnd); + SelectObject(hdc, (HFONT) wParam); + GetTextExtentPoint32A(hdc, "x", 1, &fontSize); + dat->fontInfo[HIWORD(lParam)].fontHeight = fontSize.cy; + if (dat->rowHeight < fontSize.cy + 2) + dat->rowHeight = fontSize.cy + 2; + ReleaseDC(hwnd, hdc); + } + if (LOWORD(lParam)) + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETGREYOUTFLAGS: + dat->greyoutFlags = wParam; + break; + + case CLM_SETHIDEEMPTYGROUPS: + if (wParam) + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | CLS_HIDEEMPTYGROUPS); + else + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS); + SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0); + break; + + case CLM_SETHIDEOFFLINEROOT: + DBWriteContactSettingByte(NULL, "CLC", "HideOfflineRoot", (BYTE) wParam); + SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0); + break; + + case CLM_SETINDENT: + dat->groupIndent = wParam; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETITEMTEXT: + { + ClcContact *contact; + if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) + break; + lstrcpyn(contact->szText, ( TCHAR* )lParam, SIZEOF( contact->szText )); + cli.pfnSortCLC(hwnd, dat, 1); + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + } + + case CLM_SETLEFTMARGIN: + dat->leftMargin = wParam; + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + break; + + case CLM_SETOFFLINEMODES: + dat->offlineModes = wParam; + SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0); + break; + + case CLM_SETSCROLLTIME: + dat->scrollTime = wParam; + break; + + case CLM_SETTEXTCOLOR: + if (wParam < 0 || wParam > FONTID_MAX) + break; + dat->fontInfo[wParam].colour = lParam; + break; + + case CLM_SETUSEGROUPS: + if (wParam) + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | CLS_USEGROUPS); + else + SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_USEGROUPS); + SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0); + break; + } + return 0; +} diff --git a/src/modules/clist/clcutils.cpp b/src/modules/clist/clcutils.cpp new file mode 100644 index 0000000000..3205066e2f --- /dev/null +++ b/src/modules/clist/clcutils.cpp @@ -0,0 +1,879 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +//loads of stuff that didn't really fit anywhere else + +extern HANDLE hHideInfoTipEvent; + +char* fnGetGroupCountsText(struct ClcData *dat, struct ClcContact *contact) +{ + static char szName[32]; + int onlineCount, totalCount; + struct ClcGroup *group, *topgroup; + + if (contact->type != CLCIT_GROUP || !(dat->exStyle & CLS_EX_SHOWGROUPCOUNTS)) + return ""; + + group = topgroup = contact->group; + onlineCount = 0; + totalCount = group->totalMembers; + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + if (group == topgroup) + break; + group = group->parent; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + totalCount += group->totalMembers; + continue; + } + else if (group->cl.items[group->scanIndex]->type == CLCIT_CONTACT) + if (group->cl.items[group->scanIndex]->flags & CONTACTF_ONLINE) + onlineCount++; + group->scanIndex++; + } + if (onlineCount == 0 && dat->exStyle & CLS_EX_HIDECOUNTSWHENEMPTY) + return ""; + mir_snprintf(szName, SIZEOF(szName), "(%u/%u)", onlineCount, totalCount); + return szName; +} + +int fnHitTest(HWND hwnd, struct ClcData *dat, int testx, int testy, struct ClcContact **contact, struct ClcGroup **group, DWORD * flags) +{ + ClcContact *hitcontact = NULL; + ClcGroup *hitgroup = NULL; + int hit, indent, width, i; + int checkboxWidth; + SIZE textSize; + HDC hdc; + RECT clRect; + HFONT hFont; + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + POINT pt; + + if ( flags ) + *flags = 0; + + pt.x = testx; + pt.y = testy; + ClientToScreen(hwnd, &pt); + + HWND hwndParent = hwnd, hwndTemp; + do + { + hwndTemp = hwndParent; + hwndParent = (HWND)GetWindowLongPtr(hwndTemp, GWLP_HWNDPARENT); + + POINT pt1 = pt; + ScreenToClient(hwndParent, &pt1); + HWND h = ChildWindowFromPointEx(hwndParent ? hwndParent : GetDesktopWindow(), + pt1, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT); + if (h != hwndTemp) + { + if (!hwndParent || !(GetWindowLong(hwndTemp, GWL_STYLE) & BS_GROUPBOX)) + return -1; + } + } + while (hwndParent); + + GetClientRect(hwnd, &clRect); + if ( testx < 0 || testy < 0 || testy >= clRect.bottom || testx >= clRect.right ) { + if ( flags ) { + if (testx < 0) + *flags |= CLCHT_TOLEFT; + else if (testx >= clRect.right) + *flags |= CLCHT_TORIGHT; + if (testy < 0) + *flags |= CLCHT_ABOVE; + else if (testy >= clRect.bottom) + *flags |= CLCHT_BELOW; + } + return -1; + } + if (testx < dat->leftMargin) { + if (flags) + *flags |= CLCHT_INLEFTMARGIN | CLCHT_NOWHERE; + return -1; + } + hit = cli.pfnRowHitTest(dat, dat->yScroll + testy); + if ( hit != -1 ) + hit = cli.pfnGetRowByIndex(dat, hit, &hitcontact, &hitgroup); + if (hit == -1) { + if (flags) + *flags |= CLCHT_NOWHERE | CLCHT_BELOWITEMS; + return -1; + } + if (contact) + *contact = hitcontact; + if (group) + *group = hitgroup; + for (indent = 0; hitgroup->parent; indent++, hitgroup = hitgroup->parent); + if (testx < dat->leftMargin + indent * dat->groupIndent) { + if (flags) + *flags |= CLCHT_ONITEMINDENT; + return hit; + } + checkboxWidth = 0; + if (style & CLS_CHECKBOXES && hitcontact->type == CLCIT_CONTACT) + checkboxWidth = dat->checkboxSize + 2; + if (style & CLS_GROUPCHECKBOXES && hitcontact->type == CLCIT_GROUP) + checkboxWidth = dat->checkboxSize + 2; + if (hitcontact->type == CLCIT_INFO && hitcontact->flags & CLCIIF_CHECKBOX) + checkboxWidth = dat->checkboxSize + 2; + if (testx < dat->leftMargin + indent * dat->groupIndent + checkboxWidth) { + if (flags) + *flags |= CLCHT_ONITEMCHECK; + return hit; + } + if (testx < dat->leftMargin + indent * dat->groupIndent + checkboxWidth + dat->iconXSpace) { + if (flags) + *flags |= CLCHT_ONITEMICON; + return hit; + } + + for (i = 0; i < dat->extraColumnsCount; i++) { + if (hitcontact->iExtraImage[i] == 0xFF) + continue; + if (testx >= clRect.right - dat->extraColumnSpacing * (dat->extraColumnsCount - i) && + testx < clRect.right - dat->extraColumnSpacing * (dat->extraColumnsCount - i) + g_IconWidth ) { + if (flags) + *flags |= CLCHT_ONITEMEXTRA | (i << 24); + return hit; + } + } + hdc = GetDC(hwnd); + if (hitcontact->type == CLCIT_GROUP) + hFont = ( HFONT )SelectObject(hdc, dat->fontInfo[FONTID_GROUPS].hFont); + else + hFont = ( HFONT )SelectObject(hdc, dat->fontInfo[FONTID_CONTACTS].hFont); + GetTextExtentPoint32(hdc, hitcontact->szText, lstrlen(hitcontact->szText), &textSize); + width = textSize.cx; + if (hitcontact->type == CLCIT_GROUP) { + char *szCounts; + szCounts = cli.pfnGetGroupCountsText(dat, hitcontact); + if (szCounts[0]) { + GetTextExtentPoint32A(hdc, " ", 1, &textSize); + width += textSize.cx; + SelectObject(hdc, dat->fontInfo[FONTID_GROUPCOUNTS].hFont); + GetTextExtentPoint32A(hdc, szCounts, lstrlenA(szCounts), &textSize); + width += textSize.cx; + } + } + SelectObject(hdc, hFont); + ReleaseDC(hwnd, hdc); + if (testx < dat->leftMargin + indent * dat->groupIndent + checkboxWidth + dat->iconXSpace + width + 4) { + if (flags) + *flags |= CLCHT_ONITEMLABEL; + return hit; + } + if (flags) + *flags |= CLCHT_NOWHERE; + return -1; +} + +void fnScrollTo(HWND hwnd, struct ClcData *dat, int desty, int noSmooth) +{ + DWORD startTick, nowTick; + int oldy = dat->yScroll; + RECT clRect, rcInvalidate; + int maxy, previousy; + + if (dat->iHotTrack != -1 && dat->yScroll != desty) { + cli.pfnInvalidateItem(hwnd, dat, dat->iHotTrack); + dat->iHotTrack = -1; + ReleaseCapture(); + } + GetClientRect(hwnd, &clRect); + rcInvalidate = clRect; + maxy = cli.pfnGetRowTotalHeight(dat) - clRect.bottom; + if (desty > maxy) + desty = maxy; + if (desty < 0) + desty = 0; + if (abs(desty - dat->yScroll) < 4) + noSmooth = 1; + if (!noSmooth && dat->exStyle & CLS_EX_NOSMOOTHSCROLLING) + noSmooth = 1; + previousy = dat->yScroll; + if (!noSmooth) { + startTick = GetTickCount(); + for (;;) { + nowTick = GetTickCount(); + if (nowTick >= startTick + dat->scrollTime) + break; + dat->yScroll = oldy + (desty - oldy) * (int) (nowTick - startTick) / dat->scrollTime; + if (dat->backgroundBmpUse & CLBF_SCROLL || dat->hBmpBackground == NULL) + ScrollWindowEx(hwnd, 0, previousy - dat->yScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE); + else + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + previousy = dat->yScroll; + SetScrollPos(hwnd, SB_VERT, dat->yScroll, TRUE); + UpdateWindow(hwnd); + } + } + dat->yScroll = desty; + if (dat->backgroundBmpUse & CLBF_SCROLL || dat->hBmpBackground == NULL) + ScrollWindowEx(hwnd, 0, previousy - dat->yScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE); + else + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + SetScrollPos(hwnd, SB_VERT, dat->yScroll, TRUE); +} + +void fnEnsureVisible(HWND hwnd, struct ClcData *dat, int iItem, int partialOk) +{ + int itemy = cli.pfnGetRowTopY(dat, iItem), itemh = cli.pfnGetRowHeight(dat, iItem), newY = 0; + int moved = 0; + RECT clRect; + + GetClientRect(hwnd, &clRect); + if (partialOk) { + if (itemy + itemh - 1 < dat->yScroll) { + newY = itemy; + moved = 1; + } + else if (itemy >= dat->yScroll + clRect.bottom) { + newY = itemy - clRect.bottom + itemh; + moved = 1; + } + } + else { + if (itemy < dat->yScroll) { + newY = itemy; + moved = 1; + } + else if (itemy >= dat->yScroll + clRect.bottom - itemh) { + newY = itemy - clRect.bottom + itemh; + moved = 1; + } + } + if (moved) + cli.pfnScrollTo(hwnd, dat, newY, 0); +} + +void fnRecalcScrollBar(HWND hwnd, struct ClcData *dat) +{ + SCROLLINFO si = { 0 }; + RECT clRect; + NMCLISTCONTROL nm; + + GetClientRect(hwnd, &clRect); + si.cbSize = sizeof(si); + si.fMask = SIF_ALL; + si.nMin = 0; + si.nMax = cli.pfnGetRowTotalHeight(dat)-1; + si.nPage = clRect.bottom; + si.nPos = dat->yScroll; + + if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CONTACTLIST) { + if (dat->noVScrollbar == 0) + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + } + else SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + + cli.pfnScrollTo(hwnd, dat, dat->yScroll, 1); + nm.hdr.code = CLN_LISTSIZECHANGE; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.pt.y = si.nMax; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); +} + +void fnSetGroupExpand(HWND hwnd, struct ClcData *dat, struct ClcGroup *group, int newState) +{ + int contentCount; + int groupy; + int newY, posY; + RECT clRect; + NMCLISTCONTROL nm; + + if (newState == -1) + group->expanded ^= 1; + else { + if (group->expanded == (newState != 0)) + return; + group->expanded = newState != 0; + } + cli.pfnInvalidateRect(hwnd, NULL, FALSE); + contentCount = cli.pfnGetGroupContentsCount(group, 1); + groupy = cli.pfnGetRowsPriorTo(&dat->list, group, -1); + if (dat->selection > groupy && dat->selection < groupy + contentCount) + dat->selection = groupy; + GetClientRect(hwnd, &clRect); + newY = dat->yScroll; + posY = cli.pfnGetRowBottomY(dat, groupy + contentCount); + if (posY >= newY + clRect.bottom) + newY = posY - clRect.bottom; + posY = cli.pfnGetRowTopY(dat, groupy); + if (newY > posY) + newY = posY; + cli.pfnRecalcScrollBar(hwnd, dat); + if (group->expanded) + cli.pfnScrollTo(hwnd, dat, newY, 0); + nm.hdr.code = CLN_EXPANDED; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.hItem = (HANDLE) group->groupId; + nm.action = group->expanded; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); +} + +void fnDoSelectionDefaultAction(HWND hwnd, struct ClcData *dat) +{ + struct ClcContact *contact; + + if (dat->selection == -1) + return; + dat->szQuickSearch[0] = 0; + if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1) + return; + if (contact->type == CLCIT_GROUP) + cli.pfnSetGroupExpand(hwnd, dat, contact->group, -1); + if (contact->type == CLCIT_CONTACT) + CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM) contact->hContact, 0); +} + +int fnFindRowByText(HWND hwnd, struct ClcData *dat, const TCHAR *text, int prefixOk) +{ + struct ClcGroup *group = &dat->list; + int testlen = lstrlen(text); + + group->scanIndex = 0; + for (;;) { + if (group->scanIndex == group->cl.count) { + group = group->parent; + if (group == NULL) + break; + group->scanIndex++; + continue; + } + if (group->cl.items[group->scanIndex]->type != CLCIT_DIVIDER) { + if ((prefixOk && !_tcsnicmp(text, group->cl.items[group->scanIndex]->szText, testlen)) || + (!prefixOk && !lstrcmpi(text, group->cl.items[group->scanIndex]->szText))) { + struct ClcGroup *contactGroup = group; + int contactScanIndex = group->scanIndex; + for (; group; group = group->parent) + cli.pfnSetGroupExpand(hwnd, dat, group, 1); + return cli.pfnGetRowsPriorTo(&dat->list, contactGroup, contactScanIndex); + } + if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) { + if (!(dat->exStyle & CLS_EX_QUICKSEARCHVISONLY) || group->cl.items[group->scanIndex]->group->expanded) { + group = group->cl.items[group->scanIndex]->group; + group->scanIndex = 0; + continue; + } + } + } + group->scanIndex++; + } + return -1; +} + +void fnEndRename(HWND, struct ClcData *dat, int save) +{ + HWND hwndEdit = dat->hwndRenameEdit; + if (hwndEdit == NULL) + return; + + dat->hwndRenameEdit = NULL; + if (save) { + TCHAR text[120]; text[0] = 0; + GetWindowText(hwndEdit, text, SIZEOF(text)); + + ClcContact *contact; + if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) != -1) { + if (lstrcmp(contact->szText, text) && !_tcsstr(text, _T("\\"))) { + if (contact->type == CLCIT_GROUP) { + if (contact->group->parent && contact->group->parent->parent) { + TCHAR szFullName[256]; + mir_sntprintf(szFullName, SIZEOF(szFullName), _T("%s\\%s"), + cli.pfnGetGroupName(contact->group->parent->groupId, NULL), text); + cli.pfnRenameGroup(contact->groupId, szFullName); + } + else + cli.pfnRenameGroup(contact->groupId, text); + } + else if (contact->type == CLCIT_CONTACT) { + cli.pfnInvalidateDisplayNameCacheEntry(contact->hContact); + TCHAR* otherName = cli.pfnGetContactDisplayName(contact->hContact, GCDNF_NOMYHANDLE); + if (!text[0] || !lstrcmp(otherName, text)) + DBDeleteContactSetting(contact->hContact, "CList", "MyHandle"); + else + DBWriteContactSettingTString(contact->hContact, "CList", "MyHandle", text); + mir_free(otherName); + } + } + } + } + DestroyWindow(hwndEdit); +} + +void fnDeleteFromContactList(HWND hwnd, struct ClcData *dat) +{ + struct ClcContact *contact; + if (dat->selection == -1) + return; + dat->szQuickSearch[0] = 0; + if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1) + return; + switch (contact->type) { + case CLCIT_GROUP: + CallService(MS_CLIST_GROUPDELETE, (WPARAM)contact->groupId, 0); + break; + case CLCIT_CONTACT: + CallService("CList/DeleteContactCommand", (WPARAM)contact->hContact, (LPARAM)hwnd ); + break; +} } + +static WNDPROC OldRenameEditWndProc; +static LRESULT CALLBACK RenameEditSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_KEYDOWN: + switch (wParam) { + case VK_RETURN: + cli.pfnEndRename(GetParent(hwnd), (struct ClcData *) GetWindowLongPtr(GetParent(hwnd), 0), 1); + return 0; + case VK_ESCAPE: + cli.pfnEndRename(GetParent(hwnd), (struct ClcData *) GetWindowLongPtr(GetParent(hwnd), 0), 0); + return 0; + } + break; + case WM_GETDLGCODE: + if (lParam) { + MSG *msg = (MSG *) lParam; + if (msg->message == WM_KEYDOWN && msg->wParam == VK_TAB) + return 0; + if (msg->message == WM_CHAR && msg->wParam == '\t') + return 0; + } + return DLGC_WANTMESSAGE; + case WM_KILLFOCUS: + cli.pfnEndRename(GetParent(hwnd), (struct ClcData *) GetWindowLongPtr(GetParent(hwnd), 0), 1); + return 0; + } + return CallWindowProc(OldRenameEditWndProc, hwnd, msg, wParam, lParam); +} + +void fnBeginRenameSelection(HWND hwnd, struct ClcData *dat) +{ + struct ClcContact *contact; + struct ClcGroup *group; + RECT clRect; + POINT pt; + int h; + + KillTimer(hwnd, TIMERID_RENAME); + ReleaseCapture(); + dat->iHotTrack = -1; + dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &contact, &group); + if (dat->selection == -1) + return; + if (contact->type != CLCIT_CONTACT && contact->type != CLCIT_GROUP) + return; + GetClientRect(hwnd, &clRect); + cli.pfnCalcEipPosition( dat, contact, group, &pt ); + h = cli.pfnGetRowHeight(dat, dat->selection); + dat->hwndRenameEdit = CreateWindow( _T("EDIT"), contact->szText, WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, pt.x, pt.y, clRect.right - pt.x, h, hwnd, NULL, cli.hInst, NULL); + OldRenameEditWndProc = (WNDPROC) SetWindowLongPtr(dat->hwndRenameEdit, GWLP_WNDPROC, (LONG_PTR) RenameEditSubclassProc); + SendMessage(dat->hwndRenameEdit, WM_SETFONT, (WPARAM) (contact->type == CLCIT_GROUP ? dat->fontInfo[FONTID_GROUPS].hFont : dat->fontInfo[FONTID_CONTACTS].hFont), 0); + SendMessage(dat->hwndRenameEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN | EC_USEFONTINFO, 0); + SendMessage(dat->hwndRenameEdit, EM_SETSEL, 0, (LPARAM) (-1)); + ShowWindow(dat->hwndRenameEdit, SW_SHOW); + SetFocus(dat->hwndRenameEdit); +} + +void fnCalcEipPosition( struct ClcData *dat, struct ClcContact *, struct ClcGroup *group, POINT *result) +{ + int indent; + for (indent = 0; group->parent; indent++, group = group->parent); + result->x = indent * dat->groupIndent + dat->iconXSpace - 2; + result->y = cli.pfnGetRowTopY(dat, dat->selection) - dat->yScroll; +} + +int fnGetDropTargetInformation(HWND hwnd, struct ClcData *dat, POINT pt) +{ + RECT clRect; + int hit; + struct ClcContact *contact, *movecontact; + struct ClcGroup *group, *movegroup; + DWORD hitFlags; + + GetClientRect(hwnd, &clRect); + dat->selection = dat->iDragItem; + dat->iInsertionMark = -1; + if (!PtInRect(&clRect, pt)) + return DROPTARGET_OUTSIDE; + + hit = cli.pfnHitTest(hwnd, dat, pt.x, pt.y, &contact, &group, &hitFlags); + cli.pfnGetRowByIndex(dat, dat->iDragItem, &movecontact, &movegroup); + if (hit == dat->iDragItem) + return DROPTARGET_ONSELF; + if (hit == -1 || hitFlags & CLCHT_ONITEMEXTRA) + return DROPTARGET_ONNOTHING; + + if (movecontact->type == CLCIT_GROUP) { + struct ClcContact *bottomcontact = NULL, *topcontact = NULL; + struct ClcGroup *topgroup = NULL; + int topItem = -1, bottomItem = -1; + int ok = 0; + if (pt.y + dat->yScroll < cli.pfnGetRowTopY(dat, hit) + dat->insertionMarkHitHeight) { + //could be insertion mark (above) + topItem = hit - 1; + bottomItem = hit; + bottomcontact = contact; + topItem = cli.pfnGetRowByIndex(dat, topItem, &topcontact, &topgroup); + ok = 1; + } + if (pt.y + dat->yScroll >= cli.pfnGetRowBottomY(dat, hit+1) - dat->insertionMarkHitHeight) { + //could be insertion mark (below) + topItem = hit; + bottomItem = hit + 1; + topcontact = contact; + topgroup = group; + bottomItem = cli.pfnGetRowByIndex(dat, bottomItem, &bottomcontact, NULL); + ok = 1; + } + if (ok) { + ok = 0; + if (bottomItem == -1 || bottomcontact->type != CLCIT_GROUP) { //need to special-case moving to end + if (topItem != dat->iDragItem) { + for (; topgroup; topgroup = topgroup->parent) { + if (topgroup == movecontact->group) + break; + if (topgroup == movecontact->group->parent) { + ok = 1; + break; + } + } + if (ok) + bottomItem = topItem + 1; + } + } + else if (bottomItem != dat->iDragItem && bottomcontact->type == CLCIT_GROUP && bottomcontact->group->parent == movecontact->group->parent) { + if (bottomcontact != movecontact + 1) + ok = 1; + } + if (ok) { + dat->iInsertionMark = bottomItem; + dat->selection = -1; + return DROPTARGET_INSERTION; + } + } + } + if (contact->type == CLCIT_GROUP) { + if (dat->iInsertionMark == -1) { + if (movecontact->type == CLCIT_GROUP) { //check not moving onto its own subgroup + for (; group; group = group->parent) + if (group == movecontact->group) + return DROPTARGET_ONSELF; + } + dat->selection = hit; + return DROPTARGET_ONGROUP; + } + } + return DROPTARGET_ONCONTACT; +} + +int fnClcStatusToPf2(int status) +{ + switch(status) { + case ID_STATUS_ONLINE: return PF2_ONLINE; + case ID_STATUS_AWAY: return PF2_SHORTAWAY; + case ID_STATUS_DND: return PF2_HEAVYDND; + case ID_STATUS_NA: return PF2_LONGAWAY; + case ID_STATUS_OCCUPIED: return PF2_LIGHTDND; + case ID_STATUS_FREECHAT: return PF2_FREECHAT; + case ID_STATUS_INVISIBLE: return PF2_INVISIBLE; + case ID_STATUS_ONTHEPHONE: return PF2_ONTHEPHONE; + case ID_STATUS_OUTTOLUNCH: return PF2_OUTTOLUNCH; + case ID_STATUS_OFFLINE: return MODEF_OFFLINE; + } + return 0; +} + +int fnIsHiddenMode(struct ClcData *dat, int status) +{ + return dat->offlineModes & cli.pfnClcStatusToPf2(status); +} + +void fnHideInfoTip(HWND, struct ClcData *dat) +{ + CLCINFOTIP it = { 0 }; + + if (dat->hInfoTipItem == NULL) + return; + it.isGroup = IsHContactGroup(dat->hInfoTipItem); + it.hItem = (HANDLE) ((UINT_PTR) dat->hInfoTipItem & ~HCONTACT_ISGROUP); + it.cbSize = sizeof(it); + dat->hInfoTipItem = NULL; + NotifyEventHooks(hHideInfoTipEvent, 0, (LPARAM) & it); +} + +void fnNotifyNewContact(HWND hwnd, HANDLE hContact) +{ + NMCLISTCONTROL nm; + nm.hdr.code = CLN_NEWCONTACT; + nm.hdr.hwndFrom = hwnd; + nm.hdr.idFrom = GetDlgCtrlID(hwnd); + nm.flags = 0; + nm.hItem = hContact; + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm); +} + +DWORD fnGetDefaultExStyle(void) +{ + BOOL param; + DWORD ret = CLCDEFAULT_EXSTYLE; + if (SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, ¶m, FALSE) && !param) + ret |= CLS_EX_NOSMOOTHSCROLLING; + if (SystemParametersInfo(SPI_GETHOTTRACKING, 0, ¶m, FALSE) && !param) + ret &= ~CLS_EX_TRACKSELECT; + return ret; +} + +#define DBFONTF_BOLD 1 +#define DBFONTF_ITALIC 2 +#define DBFONTF_UNDERLINE 4 + +void fnGetDefaultFontSetting(int i, LOGFONT* lf, COLORREF* colour) +{ + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), lf, FALSE); + *colour = GetSysColor(COLOR_WINDOWTEXT); + lf->lfHeight = 8; + switch (i) { + case FONTID_GROUPS: + lf->lfWeight = FW_BOLD; + break; + case FONTID_GROUPCOUNTS: + *colour = GetSysColor(COLOR_3DSHADOW); + break; + case FONTID_OFFINVIS: + case FONTID_INVIS: + lf->lfItalic = !lf->lfItalic; + break; + case FONTID_DIVIDERS: + break; + case FONTID_NOTONLIST: + *colour = GetSysColor(COLOR_3DSHADOW); + break; +} } + +void fnGetFontSetting(int i, LOGFONT* lf, COLORREF* colour) +{ + DBVARIANT dbv; + char idstr[20]; + BYTE style; + + cli.pfnGetDefaultFontSetting(i, lf, colour); + mir_snprintf(idstr, SIZEOF(idstr), "Font%dName", i); + if ( !DBGetContactSettingTString(NULL, "CLC", idstr, &dbv )) { + lstrcpy(lf->lfFaceName, dbv.ptszVal); + mir_free(dbv.pszVal); + } + mir_snprintf(idstr, SIZEOF(idstr), "Font%dCol", i); + *colour = DBGetContactSettingDword(NULL, "CLC", idstr, *colour); + mir_snprintf(idstr, SIZEOF(idstr), "Font%dSize", i); + lf->lfHeight = (char) DBGetContactSettingByte(NULL, "CLC", idstr, lf->lfHeight); + mir_snprintf(idstr, SIZEOF(idstr), "Font%dSty", i); + style = (BYTE) DBGetContactSettingByte(NULL, "CLC", idstr, (lf->lfWeight == FW_NORMAL ? 0 : DBFONTF_BOLD) | (lf->lfItalic ? DBFONTF_ITALIC : 0) | (lf->lfUnderline ? DBFONTF_UNDERLINE : 0)); + lf->lfWidth = lf->lfEscapement = lf->lfOrientation = 0; + lf->lfWeight = style & DBFONTF_BOLD ? FW_BOLD : FW_NORMAL; + lf->lfItalic = (style & DBFONTF_ITALIC) != 0; + lf->lfUnderline = (style & DBFONTF_UNDERLINE) != 0; + lf->lfStrikeOut = 0; + mir_snprintf(idstr, SIZEOF(idstr), "Font%dSet", i); + lf->lfCharSet = DBGetContactSettingByte(NULL, "CLC", idstr, lf->lfCharSet); + lf->lfOutPrecision = OUT_DEFAULT_PRECIS; + lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf->lfQuality = DEFAULT_QUALITY; + lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; +} + +void fnLoadClcOptions(HWND hwnd, struct ClcData *dat) +{ + dat->rowHeight = DBGetContactSettingByte(NULL, "CLC", "RowHeight", CLCDEFAULT_ROWHEIGHT); + { + int i; + LOGFONT lf; + SIZE fontSize; + + HDC hdc = GetDC(hwnd); + for (i = 0; i <= FONTID_MAX; i++) + { + if (!dat->fontInfo[i].changed) + DeleteObject(dat->fontInfo[i].hFont); + + cli.pfnGetFontSetting(i, &lf, &dat->fontInfo[i].colour); + lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72); + + dat->fontInfo[i].hFont = CreateFontIndirect(&lf); + dat->fontInfo[i].changed = 0; + + HFONT holdfont = (HFONT)SelectObject(hdc,dat->fontInfo[i].hFont); + GetTextExtentPoint32(hdc, _T("x"), 1, &fontSize); + SelectObject(hdc, holdfont); + + dat->fontInfo[i].fontHeight = fontSize.cy; + } + ReleaseDC(hwnd, hdc); + } + dat->leftMargin = DBGetContactSettingByte(NULL, "CLC", "LeftMargin", CLCDEFAULT_LEFTMARGIN); + dat->exStyle = DBGetContactSettingDword(NULL, "CLC", "ExStyle", cli.pfnGetDefaultExStyle()); + dat->scrollTime = DBGetContactSettingWord(NULL, "CLC", "ScrollTime", CLCDEFAULT_SCROLLTIME); + dat->groupIndent = DBGetContactSettingByte(NULL, "CLC", "GroupIndent", CLCDEFAULT_GROUPINDENT); + dat->gammaCorrection = DBGetContactSettingByte(NULL, "CLC", "GammaCorrect", CLCDEFAULT_GAMMACORRECT); + dat->showIdle = DBGetContactSettingByte(NULL, "CLC", "ShowIdle", CLCDEFAULT_SHOWIDLE); + dat->noVScrollbar = DBGetContactSettingByte(NULL, "CLC", "NoVScrollBar", 0); + SendMessage(hwnd, INTM_SCROLLBARCHANGED, 0, 0); + if (!dat->bkChanged) { + DBVARIANT dbv; + dat->bkColour = DBGetContactSettingDword(NULL, "CLC", "BkColour", CLCDEFAULT_BKCOLOUR); + if (dat->hBmpBackground) { + DeleteObject(dat->hBmpBackground); + dat->hBmpBackground = NULL; + } + if (DBGetContactSettingByte(NULL, "CLC", "UseBitmap", CLCDEFAULT_USEBITMAP)) { + if (!DBGetContactSettingString(NULL, "CLC", "BkBitmap", &dbv)) { + dat->hBmpBackground = (HBITMAP) CallService(MS_UTILS_LOADBITMAP, 0, (LPARAM) dbv.pszVal); + mir_free(dbv.pszVal); + } + } + dat->backgroundBmpUse = DBGetContactSettingWord(NULL, "CLC", "BkBmpUse", CLCDEFAULT_BKBMPUSE); + } + dat->greyoutFlags = DBGetContactSettingDword(NULL, "CLC", "GreyoutFlags", CLCDEFAULT_GREYOUTFLAGS); + dat->offlineModes = DBGetContactSettingDword(NULL, "CLC", "OfflineModes", CLCDEFAULT_OFFLINEMODES); + dat->selBkColour = DBGetContactSettingDword(NULL, "CLC", "SelBkColour", CLCDEFAULT_SELBKCOLOUR); + dat->selTextColour = DBGetContactSettingDword(NULL, "CLC", "SelTextColour", CLCDEFAULT_SELTEXTCOLOUR); + dat->hotTextColour = DBGetContactSettingDword(NULL, "CLC", "HotTextColour", CLCDEFAULT_HOTTEXTCOLOUR); + dat->quickSearchColour = DBGetContactSettingDword(NULL, "CLC", "QuickSearchColour", CLCDEFAULT_QUICKSEARCHCOLOUR); + dat->useWindowsColours = DBGetContactSettingByte(NULL, "CLC", "UseWinColours", CLCDEFAULT_USEWINDOWSCOLOURS); + { + NMHDR hdr; + hdr.code = CLN_OPTIONSCHANGED; + hdr.hwndFrom = hwnd; + hdr.idFrom = GetDlgCtrlID(hwnd); + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & hdr); + } + SendMessage(hwnd, WM_SIZE, 0, 0); +} + +#define GSIF_HASMEMBERS 0x80000000 +#define GSIF_ALLCHECKED 0x40000000 +#define GSIF_INDEXMASK 0x3FFFFFFF +void fnRecalculateGroupCheckboxes(HWND, struct ClcData *dat) +{ + struct ClcGroup *group; + int check; + + group = &dat->list; + group->scanIndex = GSIF_ALLCHECKED; + for (;;) { + if ((group->scanIndex & GSIF_INDEXMASK) == group->cl.count) { + check = (group->scanIndex & (GSIF_HASMEMBERS | GSIF_ALLCHECKED)) == (GSIF_HASMEMBERS | GSIF_ALLCHECKED); + if (group->parent == NULL) + break; + group->parent->scanIndex |= group->scanIndex & GSIF_HASMEMBERS; + group = group->parent; + if (check) + group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->flags |= CONTACTF_CHECKED; + else { + group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->flags &= ~CONTACTF_CHECKED; + group->scanIndex &= ~GSIF_ALLCHECKED; + } + } + else if (group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->type == CLCIT_GROUP) { + group = group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->group; + group->scanIndex = GSIF_ALLCHECKED; + continue; + } + else if (group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->type == CLCIT_CONTACT) { + group->scanIndex |= GSIF_HASMEMBERS; + if (!(group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->flags & CONTACTF_CHECKED)) + group->scanIndex &= ~GSIF_ALLCHECKED; + } + group->scanIndex++; + } +} + +void fnSetGroupChildCheckboxes(struct ClcGroup *group, int checked) +{ + int i; + + for (i = 0; i < group->cl.count; i++) { + if (group->cl.items[i]->type == CLCIT_GROUP) { + cli.pfnSetGroupChildCheckboxes(group->cl.items[i]->group, checked); + if (checked) + group->cl.items[i]->flags |= CONTACTF_CHECKED; + else + group->cl.items[i]->flags &= ~CONTACTF_CHECKED; + } + else if (group->cl.items[i]->type == CLCIT_CONTACT) { + if (checked) + group->cl.items[i]->flags |= CONTACTF_CHECKED; + else + group->cl.items[i]->flags &= ~CONTACTF_CHECKED; + } + } +} + +void fnInvalidateItem(HWND hwnd, struct ClcData *dat, int iItem) +{ + RECT rc; + if ( iItem == -1 ) + return; + + GetClientRect(hwnd, &rc); + rc.top = cli.pfnGetRowTopY(dat, iItem) - dat->yScroll; + rc.bottom = rc.top + cli.pfnGetRowHeight(dat, iItem); + cli.pfnInvalidateRect(hwnd, &rc, FALSE); +} + +/////////////////////////////////////////////////////////////////////////////// +// row coord functions + +int fnGetRowTopY(struct ClcData *dat, int item) +{ return item * dat->rowHeight; +} + +int fnGetRowBottomY(struct ClcData *dat, int item) +{ return (item+1) * dat->rowHeight; +} + +int fnGetRowTotalHeight(struct ClcData *dat) +{ return dat->rowHeight * cli.pfnGetGroupContentsCount(&dat->list, 1); +} + +int fnGetRowHeight(struct ClcData *dat, int) +{ return dat->rowHeight; +} + +int fnRowHitTest(struct ClcData *dat, int y) +{ if (!dat->rowHeight) + return y; + return y / dat->rowHeight; +} diff --git a/src/modules/clist/clistcore.cpp b/src/modules/clist/clistcore.cpp new file mode 100644 index 0000000000..2fe4a288e9 --- /dev/null +++ b/src/modules/clist/clistcore.cpp @@ -0,0 +1,224 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" +#include "genmenu.h" + +CLIST_INTERFACE cli = { 0 }; + +static TCHAR szTip[MAX_TIP_SIZE+1]; + +int LoadContactListModule2( void ); +int LoadCLCModule( void ); +void BuildProtoMenus( void ); + +static int interfaceInited = 0; + +static void fnPaintClc( HWND, ClcData*, HDC, RECT* ) +{ +} + +static struct ClcContact* fnCreateClcContact( void ) +{ + return ( struct ClcContact* )mir_calloc( sizeof( struct ClcContact )); +} + +static BOOL fnInvalidateRect( HWND hwnd, CONST RECT* lpRect,BOOL bErase ) +{ + return InvalidateRect( hwnd, lpRect, bErase ); +} + +static void fnOnCreateClc( void ) +{ +} + +static void fnReloadProtoMenus( void ) +{ + RebuildMenuOrder(); + if (DBGetContactSettingByte(NULL, "CList", "MoveProtoMenus", FALSE)) + BuildProtoMenus(); + cli.pfnCluiProtocolStatusChanged(0,0); +} + +static INT_PTR srvRetrieveInterface( WPARAM, LPARAM lParam ) +{ + int rc; + + if ( interfaceInited == 0 ) { + cli.version = 6; + cli.bDisplayLocked = TRUE; + + cli.pfnClcOptionsChanged = fnClcOptionsChanged; + cli.pfnClcBroadcast = fnClcBroadcast; + cli.pfnContactListControlWndProc = fnContactListControlWndProc; + cli.pfnBuildGroupPopupMenu = fnBuildGroupPopupMenu; + + cli.pfnRegisterFileDropping = fnRegisterFileDropping; + cli.pfnUnregisterFileDropping = fnUnregisterFileDropping; + + cli.pfnGetRowsPriorTo = fnGetRowsPriorTo; + cli.pfnFindItem = fnFindItem; + cli.pfnGetRowByIndex = fnGetRowByIndex; + cli.pfnContactToHItem = fnContactToHItem; + cli.pfnContactToItemHandle = fnContactToItemHandle; + + cli.pfnAddGroup = fnAddGroup; + cli.pfnAddItemToGroup = fnAddItemToGroup; + cli.pfnCreateClcContact = fnCreateClcContact; + cli.pfnRemoveItemFromGroup = fnRemoveItemFromGroup; + cli.pfnFreeContact = fnFreeContact; + cli.pfnFreeGroup = fnFreeGroup; + cli.pfnAddInfoItemToGroup = fnAddInfoItemToGroup; + cli.pfnAddContactToGroup = fnAddContactToGroup; + cli.pfnAddContactToTree = fnAddContactToTree; + cli.pfnDeleteItemFromTree = fnDeleteItemFromTree; + cli.pfnRebuildEntireList = fnRebuildEntireList; + cli.pfnGetGroupContentsCount = fnGetGroupContentsCount; + cli.pfnSortCLC = fnSortCLC; + cli.pfnSaveStateAndRebuildList = fnSaveStateAndRebuildList; + + cli.pfnProcessExternalMessages = fnProcessExternalMessages; + + cli.pfnPaintClc = fnPaintClc; + + cli.pfnGetGroupCountsText = fnGetGroupCountsText; + cli.pfnHitTest = fnHitTest; + cli.pfnScrollTo = fnScrollTo; + cli.pfnEnsureVisible = fnEnsureVisible; + cli.pfnRecalcScrollBar = fnRecalcScrollBar; + cli.pfnSetGroupExpand = fnSetGroupExpand; + cli.pfnDoSelectionDefaultAction = fnDoSelectionDefaultAction; + cli.pfnFindRowByText = fnFindRowByText; + cli.pfnEndRename = fnEndRename; + cli.pfnDeleteFromContactList = fnDeleteFromContactList; + cli.pfnBeginRenameSelection = fnBeginRenameSelection; + cli.pfnCalcEipPosition = fnCalcEipPosition; + cli.pfnGetDropTargetInformation = fnGetDropTargetInformation; + cli.pfnClcStatusToPf2 = fnClcStatusToPf2; + cli.pfnIsHiddenMode = fnIsHiddenMode; + cli.pfnHideInfoTip = fnHideInfoTip; + cli.pfnNotifyNewContact = fnNotifyNewContact; + cli.pfnGetDefaultExStyle = fnGetDefaultExStyle; + cli.pfnGetDefaultFontSetting = fnGetDefaultFontSetting; + cli.pfnGetFontSetting = fnGetFontSetting; + cli.pfnLoadClcOptions = fnLoadClcOptions; + cli.pfnRecalculateGroupCheckboxes = fnRecalculateGroupCheckboxes; + cli.pfnSetGroupChildCheckboxes = fnSetGroupChildCheckboxes; + cli.pfnInvalidateItem = fnInvalidateItem; + cli.pfnGetRowBottomY = fnGetRowBottomY; + cli.pfnGetRowHeight = fnGetRowHeight; + cli.pfnGetRowTopY = fnGetRowTopY; + cli.pfnGetRowTotalHeight = fnGetRowTotalHeight; + cli.pfnRowHitTest = fnRowHitTest; + + cli.pfnAddEvent = fnAddEvent; + cli.pfnCreateEvent = fnCreateEvent; + cli.pfnEventsProcessContactDoubleClick = fnEventsProcessContactDoubleClick; + cli.pfnEventsProcessTrayDoubleClick = fnEventsProcessTrayDoubleClick; + cli.pfnFreeEvent = fnFreeEvent; + cli.pfnGetEvent = fnGetEvent; + cli.pfnGetImlIconIndex = fnGetImlIconIndex; + cli.pfnRemoveEvent = fnRemoveEvent; + + cli.pfnGetContactDisplayName = fnGetContactDisplayName; + cli.pfnInvalidateDisplayNameCacheEntry = fnInvalidateDisplayNameCacheEntry; + cli.pfnCreateCacheItem = fnCreateCacheItem; + cli.pfnCheckCacheItem = fnCheckCacheItem; + cli.pfnFreeCacheItem = fnFreeCacheItem; + cli.pfnGetCacheEntry = fnGetCacheEntry; + + cli.szTip = szTip; + cli.pfnInitTray = fnInitTray; + cli.pfnUninitTray = fnUninitTray; + cli.pfnLockTray = fnLockTray; + cli.pfnUnlockTray = fnUnlockTray; + + cli.pfnTrayCycleTimerProc = fnTrayCycleTimerProc; + cli.pfnTrayIconAdd = fnTrayIconAdd; + cli.pfnTrayIconDestroy = fnTrayIconDestroy; + cli.pfnTrayIconIconsChanged = fnTrayIconIconsChanged; + cli.pfnTrayIconInit = fnTrayIconInit; + cli.pfnTrayIconMakeTooltip = fnTrayIconMakeTooltip; + cli.pfnTrayIconPauseAutoHide = fnTrayIconPauseAutoHide; + cli.pfnTrayIconProcessMessage = fnTrayIconProcessMessage; + cli.pfnTrayIconRemove = fnTrayIconRemove; + cli.pfnTrayIconSetBaseInfo = fnTrayIconSetBaseInfo; + cli.pfnTrayIconSetToBase = fnTrayIconSetToBase; + cli.pfnTrayIconTaskbarCreated = fnTrayIconTaskbarCreated; + cli.pfnTrayIconUpdate = fnTrayIconUpdate; + cli.pfnTrayIconUpdateBase = fnTrayIconUpdateBase; + cli.pfnTrayIconUpdateWithImageList = fnTrayIconUpdateWithImageList; + cli.pfnCListTrayNotify = fnCListTrayNotify; + + cli.pfnContactListWndProc = fnContactListWndProc; + cli.pfnLoadCluiGlobalOpts = fnLoadCluiGlobalOpts; + cli.pfnCluiProtocolStatusChanged = fnCluiProtocolStatusChanged; + cli.pfnDrawMenuItem = fnDrawMenuItem; + cli.pfnInvalidateRect = fnInvalidateRect; + cli.pfnOnCreateClc = fnOnCreateClc; + + cli.pfnChangeContactIcon = fnChangeContactIcon; + cli.pfnLoadContactTree = fnLoadContactTree; + cli.pfnCompareContacts = fnCompareContacts; + cli.pfnSortContacts = fnSortContacts; + cli.pfnSetHideOffline = fnSetHideOffline; + + cli.pfnDocking_ProcessWindowMessage = fnDocking_ProcessWindowMessage; + + cli.pfnGetIconFromStatusMode = fnGetIconFromStatusMode; + cli.pfnGetWindowVisibleState = fnGetWindowVisibleState; + cli.pfnIconFromStatusMode = fnIconFromStatusMode; + cli.pfnShowHide = fnShowHide; + cli.pfnGetStatusModeDescription = fnGetStatusModeDescription; + + cli.pfnGetGroupName = fnGetGroupName; + cli.pfnRenameGroup = fnRenameGroup; + + cli.pfnHotKeysRegister = fnHotKeysRegister; + cli.pfnHotKeysUnregister = fnHotKeysUnregister; + cli.pfnHotKeysProcess = fnHotKeysProcess; + cli.pfnHotkeysProcessMessage = fnHotkeysProcessMessage; + + cli.pfnGetProtocolVisibility = fnGetProtocolVisibility; + cli.pfnGetProtoIndexByPos = fnGetProtoIndexByPos; + cli.pfnReloadProtoMenus = fnReloadProtoMenus; + cli.pfnGetAccountIndexByPos = fnGetAccountIndexByPos; + cli.pfnGetProtocolMenu = fnGetProtocolMenu; + + cli.hInst = ( HMODULE )lParam; + + rc = LoadContactListModule2(); + if (rc == 0) + rc = LoadCLCModule(); + interfaceInited = 1; + } + + return ( LPARAM )&cli; +} + +int LoadContactListModule() +{ + CreateServiceFunction( MS_CLIST_RETRIEVE_INTERFACE, srvRetrieveInterface ); + return 0; +} diff --git a/src/modules/clist/clistevents.cpp b/src/modules/clist/clistevents.cpp new file mode 100644 index 0000000000..20f5995c4e --- /dev/null +++ b/src/modules/clist/clistevents.cpp @@ -0,0 +1,441 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +struct CListEvent +{ + int imlIconIndex; + int flashesDone; + CLISTEVENT cle; +}; + +struct CListImlIcon +{ + int index; + HICON hIcon; +}; +static struct CListImlIcon *imlIcon; +static int imlIconCount; + +extern HIMAGELIST hCListImages; + +static UINT_PTR flashTimerId; +static int iconsOn; +static int disableTrayFlash; +static int disableIconFlash; + +int fnGetImlIconIndex(HICON hIcon) +{ + int i; + + for (i = 0; i < imlIconCount; i++) { + if (imlIcon[i].hIcon == hIcon) + return imlIcon[i].index; + } + imlIcon = (struct CListImlIcon *) mir_realloc(imlIcon, sizeof(struct CListImlIcon) * (imlIconCount + 1)); + imlIconCount++; + imlIcon[i].hIcon = hIcon; + imlIcon[i].index = ImageList_AddIcon(hCListImages, hIcon); + return imlIcon[i].index; +} + +static char * GetEventProtocol(int idx) +{ + if (cli.events.count && idx>=0 && idxcle.hContact == NULL) + { + if (cli.events.items[idx]->cle.flags&CLEF_PROTOCOLGLOBAL) + szProto = cli.events.items[idx]->cle.lpszProtocol; + else + szProto = NULL; + } + else + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) cli.events.items[idx]->cle.hContact, 0); + return szProto; + } + return NULL; +} + +static void ShowOneEventInTray(int idx) +{ + cli.pfnTrayIconUpdateWithImageList((iconsOn || disableTrayFlash) ? cli.events.items[idx]->imlIconIndex : 0, cli.events.items[idx]->cle.ptszTooltip, GetEventProtocol(idx)); +} + +static void ShowEventsInTray() +{ + int i; + char ** pTrayProtos; + char nTrayProtoCnt; + int nTrayCnt=cli.trayIconCount; + if (!cli.events.count || !nTrayCnt) return; + if (cli.events.count ==1 || nTrayCnt == 1) + { + ShowOneEventInTray(0); //for only one icon in tray show topmost event + return; + } + + // in case if we have several icons in tray and several events with different protocols + // lets use several icon to show events from protocols in different icons + cli.pfnLockTray(); + pTrayProtos = (char**)_alloca(sizeof(char*)*cli.trayIconCount); + nTrayProtoCnt=0; + for (i=0; i=nTrayProtoCnt ) j=0; //event was not found so assume first icon + if ( pTrayProtos[j] ) //if not already set + ShowOneEventInTray(i); //show it + pTrayProtos[j]=NULL; //and clear slot + } + } + cli.pfnUnlockTray(); +} + +static VOID CALLBACK IconFlashTimer(HWND, UINT, UINT_PTR idEvent, DWORD) +{ + int i, j; + ShowEventsInTray(); + for (i = 0; i < cli.events.count; i++) { + for (j = 0; j < i; j++) + if (cli.events.items[j]->cle.hContact == cli.events.items[i]->cle.hContact) + break; + if (j >= i) + cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact, iconsOn || disableIconFlash ? cli.events.items[i]->imlIconIndex : 0, 0); + //decrease eflashes in any case - no need to collect all events + if (cli.events.items[i]->cle.flags & CLEF_ONLYAFEW) { + if (0 >= --cli.events.items[i]->flashesDone) + cli.pfnRemoveEvent( cli.events.items[i]->cle.hContact, cli.events.items[i]->cle.hDbEvent); + } } + + if (cli.events.count == 0) { + KillTimer(NULL, idEvent); + cli.pfnTrayIconSetToBase( NULL ); + } + + iconsOn = !iconsOn; +} + +struct CListEvent* fnAddEvent( CLISTEVENT *cle ) +{ + int i; + struct CListEvent* p; + + if (cle==NULL || cle->cbSize != sizeof(CLISTEVENT)) + return NULL; + + if (cle->flags & CLEF_URGENT) { + for (i = 0; i < cli.events.count; i++) + if (!(cli.events.items[i]->cle.flags & CLEF_URGENT)) + break; + } + else i = cli.events.count; + + if (( p = ( struct CListEvent* )cli.pfnCreateEvent()) == NULL ) + return NULL; + + List_Insert(( SortedList* )&cli.events, p, i ); + p->cle = *cle; + p->imlIconIndex = fnGetImlIconIndex(cli.events.items[i]->cle.hIcon); + p->flashesDone = 12; + p->cle.pszService = mir_strdup(cli.events.items[i]->cle.pszService); + #if defined( _UNICODE ) + if (p->cle.flags & CLEF_UNICODE) + p->cle.ptszTooltip = mir_tstrdup((TCHAR*)p->cle.ptszTooltip); + else + p->cle.ptszTooltip = mir_a2u((char*)p->cle.pszTooltip); //if no flag defined it handled as unicode + #else + p->cle.ptszTooltip = mir_tstrdup(p->cle.ptszTooltip); + #endif + if (cli.events.count == 1) { + char *szProto; + if (cle->hContact == NULL) + { + if (cle->flags&CLEF_PROTOCOLGLOBAL) + szProto = cle->lpszProtocol; + else + szProto=NULL; + } + else + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)cle->hContact, 0); + iconsOn = 1; + flashTimerId = SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "IconFlashTime", 550), IconFlashTimer); + cli.pfnTrayIconUpdateWithImageList( p->imlIconIndex, p->cle.ptszTooltip, szProto); + } + cli.pfnChangeContactIcon(cle->hContact, p->imlIconIndex, 1); + cli.pfnSortContacts(); + return p; +} + +// Removes an event from the contact list's queue +// Returns 0 if the event was successfully removed, or nonzero if the event was not found +int fnRemoveEvent( HANDLE hContact, HANDLE dbEvent ) +{ + int i; + char *szProto; + int nSameProto=0; + + // Find the event that should be removed + for (i = 0; i < cli.events.count; i++) + if ((cli.events.items[i]->cle.hContact == hContact) && (cli.events.items[i]->cle.hDbEvent == dbEvent)) + break; + + // Event was not found + if (i == cli.events.count) + return 1; + + // Update contact's icon + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact, + CallService(MS_CLIST_GETCONTACTICON, (WPARAM)cli.events.items[i]->cle.hContact, 1), + 0); + + // Free any memory allocated to the event + cli.pfnFreeEvent( cli.events.items[i] ); + List_Remove(( SortedList* )&cli.events, i ); + { + //count same protocoled events + char * szEventProto; + for (i = 0; i < cli.events.count; i++) + { + if (cli.events.items[i]->cle.hContact) + szEventProto=(char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)(cli.events.items[i]->cle.hContact), 0); + else if (cli.events.items[i]->cle.flags&CLEF_PROTOCOLGLOBAL) + szEventProto=(char *) cli.events.items[i]->cle.lpszProtocol; + else + szEventProto = NULL; + if (szEventProto && szProto && !lstrcmpA(szEventProto,szProto)) + nSameProto++; + + } + + } + if (cli.events.count == 0 || nSameProto == 0) { + if (cli.events.count == 0) + KillTimer(NULL, flashTimerId); + cli.pfnTrayIconSetToBase( hContact == NULL ? NULL : szProto); + } + else { + if (cli.events.items[0]->cle.hContact == NULL) + szProto = NULL; + else + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) cli.events.items[0]->cle.hContact, 0); + cli.pfnTrayIconUpdateWithImageList(iconsOn ? cli.events.items[0]->imlIconIndex : 0, cli.events.items[0]->cle.ptszTooltip, szProto); + } + + return 0; +} + +CLISTEVENT* fnGetEvent( HANDLE hContact, int idx ) +{ + if ( hContact == INVALID_HANDLE_VALUE) { + if (idx >= cli.events.count) + return NULL; + return &cli.events.items[idx]->cle; + } + + for (int i = 0; i < cli.events.count; i++) + if (cli.events.items[i]->cle.hContact == hContact) + if (idx-- == 0) + return &cli.events.items[i]->cle; + return NULL; +} + +int fnEventsProcessContactDoubleClick(HANDLE hContact) +{ + for (int i = 0; i < cli.events.count; i++) { + if (cli.events.items[i]->cle.hContact == hContact) { + HANDLE hDbEvent = cli.events.items[i]->cle.hDbEvent; + CallService(cli.events.items[i]->cle.pszService, (WPARAM) (HWND) NULL, (LPARAM) & cli.events.items[i]->cle); + cli.pfnRemoveEvent(hContact, hDbEvent); + return 0; + } } + + return 1; +} + +int fnEventsProcessTrayDoubleClick(int index) +{ + BOOL click_in_first_icon=FALSE; + if (cli.events.count) { + HANDLE hContact, hDbEvent; + int eventIndex=0; + cli.pfnLockTray(); + if (cli.trayIconCount>1 && index>0) { + int i; + char * szProto=NULL; + for (i=0; icle.hContact) + eventProto=(char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)cli.events.items[i]->cle.hContact, 0); + if (!eventProto) + eventProto=cli.events.items[i]->cle.lpszProtocol; + + if (!eventProto || !_strcmpi(eventProto, szProto)) { + eventIndex=i; + break; + } } + + if (i==cli.events.count) { //EventNotFound + //lets process backward try to find first event without desired proto in tray + int j; + if (click_in_first_icon) + for(i=0; icle.hContact) + eventProto=(char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)cli.events.items[i]->cle.hContact, 0); + if (!eventProto) + eventProto=cli.events.items[i]->cle.lpszProtocol; + if (eventProto) { + for (j=0; jcle.hContact; + hDbEvent = cli.events.items[eventIndex]->cle.hDbEvent; + //if (!ServiceExists(cli.events.items[eventIndex]->cle.pszService)) + // ; may be better to show send msg? + CallService(cli.events.items[eventIndex]->cle.pszService, (WPARAM) NULL, (LPARAM) & cli.events.items[eventIndex]->cle); + cli.pfnRemoveEvent(hContact, hDbEvent); + return 0; + } + return 1; +} + +static int RemoveEventsForContact(WPARAM wParam, LPARAM) +{ + int j, hit; + + /* + the for(;;) loop is used here since the cli.events.count can not be relied upon to take us + thru the cli.events.items[] array without suffering from shortsightedness about how many unseen + events remain, e.g. three events, we remove the first, we're left with 2, the event + loop exits at 2 and we never see the real new 2. + */ + + for (; cli.events.count > 0;) { + for (hit = 0, j = 0; j < cli.events.count; j++) { + if (cli.events.items[j]->cle.hContact == (HANDLE) wParam) { + cli.pfnRemoveEvent((HANDLE)wParam, cli.events.items[j]->cle.hDbEvent); + hit = 1; + } + } + if (j == cli.events.count && hit == 0) + return 0; /* got to the end of the array and didnt remove anything */ + } + + return 0; +} + +static int CListEventSettingsChanged(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE) wParam; + DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam; + if (hContact == NULL && cws && cws->szModule && cws->szSetting && strcmp(cws->szModule, "CList") == 0) { + if (strcmp(cws->szSetting, "DisableTrayFlash") == 0) + disableTrayFlash = (int) cws->value.bVal; + else if (strcmp(cws->szSetting, "NoIconBlink") == 0) + disableIconFlash = (int) cws->value.bVal; + } + return 0; +} + +/***************************************************************************************/ + +INT_PTR AddEventSyncStub(WPARAM wParam, LPARAM lParam) { return CallServiceSync(MS_CLIST_ADDEVENT"_SYNC",wParam, lParam); } +INT_PTR AddEventStub(WPARAM, LPARAM lParam) { return cli.pfnAddEvent((CLISTEVENT*)lParam ) == NULL; } +INT_PTR RemoveEventStub(WPARAM wParam, LPARAM lParam) { return cli.pfnRemoveEvent((HANDLE)wParam,(HANDLE)lParam ); } +INT_PTR GetEventStub(WPARAM wParam, LPARAM lParam) { return (INT_PTR)cli.pfnGetEvent((HANDLE)wParam,(int)lParam); } + +int InitCListEvents(void) +{ + memset( &cli.events, 0, sizeof(cli.events)); + cli.events.increment = 10; + + disableTrayFlash = DBGetContactSettingByte(NULL, "CList", "DisableTrayFlash", 0); + disableIconFlash = DBGetContactSettingByte(NULL, "CList", "NoIconBlink", 0); + CreateServiceFunction(MS_CLIST_ADDEVENT, AddEventSyncStub); //need to be called through sync to keep flash timer workable + CreateServiceFunction(MS_CLIST_ADDEVENT"_SYNC", AddEventStub); + CreateServiceFunction(MS_CLIST_REMOVEEVENT, RemoveEventStub); + CreateServiceFunction(MS_CLIST_GETEVENT, GetEventStub); + HookEvent(ME_DB_CONTACT_DELETED, RemoveEventsForContact); + HookEvent(ME_DB_CONTACT_SETTINGCHANGED, CListEventSettingsChanged); + return 0; +} + +struct CListEvent* fnCreateEvent( void ) +{ + return (struct CListEvent*)mir_calloc( sizeof(struct CListEvent)); +} + +void fnFreeEvent( struct CListEvent* p ) +{ + if ( p->cle.pszService ) + mir_free( p->cle.pszService ); + if ( p->cle.pszTooltip ) + mir_free( p->cle.pszTooltip ); + mir_free( p ); +} + +void UninitCListEvents(void) +{ + int i; + + if (cli.events.count) KillTimer(NULL, flashTimerId); + + for (i = 0; i < cli.events.count; i++) + cli.pfnFreeEvent(( struct CListEvent* )cli.events.items[i] ); + List_Destroy(( SortedList* )&cli.events ); + + if ( imlIcon != NULL ) + mir_free( imlIcon ); +} diff --git a/src/modules/clist/clistmenus.cpp b/src/modules/clist/clistmenus.cpp new file mode 100644 index 0000000000..6c2a4f7a3f --- /dev/null +++ b/src/modules/clist/clistmenus.cpp @@ -0,0 +1,1443 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#pragma hdrstop + +#include "m_hotkeys.h" + +#include "clc.h" +#include "genmenu.h" + +#define MS_CLIST_HKSTATUS "Clist/HK/SetStatus" + +#define FIRSTCUSTOMMENUITEMID 30000 +#define MENU_CUSTOMITEMMAIN 0x80000000 +//#define MENU_CUSTOMITEMCONTEXT 0x40000000 +//#define MENU_CUSTOMITEMFRAME 0x20000000 + +typedef struct { + WORD id; + int iconId; + CLISTMENUITEM mi; +} + CListIntMenuItem,*lpCListIntMenuItem; + +//new menu sys +HANDLE hMainMenuObject = 0; +HANDLE hContactMenuObject = 0; +HANDLE hStatusMenuObject = 0; +int UnloadMoveToGroup(void); + +int statustopos(int status); +void Proto_SetStatus(const char* szProto, unsigned status); + +bool prochotkey; + +HANDLE hPreBuildMainMenuEvent, hStatusModeChangeEvent, hPreBuildContactMenuEvent; + +static HANDLE hAckHook; + +static HMENU hMainMenu,hStatusMenu = 0; +static const int statusModeList[ MAX_STATUS_COUNT ] = +{ + ID_STATUS_OFFLINE, ID_STATUS_ONLINE, ID_STATUS_AWAY, ID_STATUS_NA, ID_STATUS_OCCUPIED, + ID_STATUS_DND, ID_STATUS_FREECHAT, ID_STATUS_INVISIBLE, ID_STATUS_ONTHEPHONE, ID_STATUS_OUTTOLUNCH +}; + +static const int skinIconStatusList[ MAX_STATUS_COUNT ] = +{ + SKINICON_STATUS_OFFLINE, SKINICON_STATUS_ONLINE, SKINICON_STATUS_AWAY, SKINICON_STATUS_NA, SKINICON_STATUS_OCCUPIED, + SKINICON_STATUS_DND, SKINICON_STATUS_FREE4CHAT, SKINICON_STATUS_INVISIBLE, SKINICON_STATUS_ONTHEPHONE, SKINICON_STATUS_OUTTOLUNCH +}; + +static const int statusModePf2List[ MAX_STATUS_COUNT ] = +{ + 0xFFFFFFFF, PF2_ONLINE, PF2_SHORTAWAY, PF2_LONGAWAY, PF2_LIGHTDND, + PF2_HEAVYDND, PF2_FREECHAT, PF2_INVISIBLE, PF2_ONTHEPHONE, PF2_OUTTOLUNCH +}; + +static INT_PTR statusHotkeys[ MAX_STATUS_COUNT ]; + +PMO_IntMenuItem* hStatusMainMenuHandles; +int hStatusMainMenuHandlesCnt; + +typedef struct +{ + int protoindex; + int protostatus[ MAX_STATUS_COUNT ]; + PMO_IntMenuItem menuhandle[ MAX_STATUS_COUNT ]; +} + tStatusMenuHandles,*lpStatusMenuHandles; + +lpStatusMenuHandles hStatusMenuHandles; +int hStatusMenuHandlesCnt; + +//mainmenu exec param(ownerdata) +typedef struct +{ + char *szServiceName; + TCHAR *szMenuName; + int Param1; +} + MainMenuExecParam,*lpMainMenuExecParam; + +//contactmenu exec param(ownerdata) +//also used in checkservice +typedef struct +{ + char *szServiceName; + char *pszContactOwner;//for check proc + int param; +} + ContactMenuExecParam,*lpContactMenuExecParam; + +typedef struct +{ + char *szProto; + int isOnList; + int isOnline; +} + BuildContactParam; + +typedef struct +{ + char *proto; //This is unique protoname + int protoindex; + int status; + + BOOL custom; + char *svc; + HANDLE hMenuItem; +} + StatusMenuExecParam,*lpStatusMenuExecParam; + +typedef struct _MenuItemHandles +{ + HMENU OwnerMenu; + int position; +} + MenuItemData; + +///////////////////////////////////////////////////////////////////////////////////////// +// service functions + +void FreeMenuProtos( void ) +{ + int i; + + if ( cli.menuProtos ) { + for ( i=0; i < cli.menuProtoCount; i++ ) + if ( cli.menuProtos[i].szProto ) + mir_free(cli.menuProtos[i].szProto); + + mir_free( cli.menuProtos ); + cli.menuProtos = NULL; + } + cli.menuProtoCount = 0; +} + +////////////////////////////////////////////////////////////////////////// + +int GetAverageMode(int* pNetProtoCount = NULL) +{ + int netProtoCount = 0; + int averageMode = 0; + + for ( int i=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if ( cli.pfnGetProtocolVisibility( pa->szModuleName ) == 0 ) + continue; + + netProtoCount++; + + if ( averageMode == 0 ) + averageMode = CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0 ); + else if ( averageMode > 0 && averageMode != CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0 )) { + averageMode = -1; + if (pNetProtoCount == NULL) break; + } + } + + if (pNetProtoCount) *pNetProtoCount = netProtoCount; + return averageMode; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// MAIN MENU + +/* +wparam=handle to the menu item returned by MS_CLIST_ADDCONTACTMENUITEM +return 0 on success. +*/ + +static INT_PTR RemoveMainMenuItem(WPARAM wParam, LPARAM) +{ + CallService(MO_REMOVEMENUITEM,wParam,0); + return 0; +} + +static INT_PTR BuildMainMenu(WPARAM, LPARAM) +{ + ListParam param = { 0 }; + param.MenuObjectHandle = hMainMenuObject; + + NotifyEventHooks(hPreBuildMainMenuEvent,(WPARAM)0,(LPARAM)0); + + CallService(MO_BUILDMENU,(WPARAM)hMainMenu,(LPARAM)¶m); + DrawMenuBar((HWND)CallService("CLUI/GetHwnd",(WPARAM)0,(LPARAM)0)); + return (INT_PTR)hMainMenu; +} + +static INT_PTR AddMainMenuItem(WPARAM, LPARAM lParam) +{ + CLISTMENUITEM* mi = ( CLISTMENUITEM* )lParam; + if ( mi->cbSize != sizeof( CLISTMENUITEM )) + return NULL; + + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof(tmi); + tmi.flags = mi->flags; + tmi.hIcon = mi->hIcon; + tmi.hotKey = mi->hotKey; + tmi.ptszName = mi->ptszName; + tmi.position = mi->position; + + //pszPopupName for new system mean root level + //pszPopupName for old system mean that exists popup + tmi.root = ( HGENMENU )mi->pszPopupName; + { + lpMainMenuExecParam mmep; + mmep = ( lpMainMenuExecParam )mir_alloc( sizeof( MainMenuExecParam )); + if ( mmep == NULL ) + return 0; + + //we need just one parametr. + mmep->szServiceName = mir_strdup(mi->pszService); + mmep->Param1 = mi->popupPosition; + mmep->szMenuName = tmi.ptszName; + tmi.ownerdata=mmep; + } + + PMO_IntMenuItem pimi = MO_AddNewMenuItem( hMainMenuObject, &tmi ); + + char* name; + bool needFree = false; + + if (mi->pszService) + name = mi->pszService; + else if (mi->flags & CMIF_UNICODE) { + name = mir_t2a( mi->ptszName ); + needFree = true; + } + else + name = mi->pszName; + + MO_SetOptionsMenuItem( pimi, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )name ); + if (needFree) mir_free(name); + + return ( INT_PTR )pimi; +} + +int MainMenuCheckService(WPARAM, LPARAM) +{ + return 0; +} + +//called with: +//wparam - ownerdata +//lparam - lparam from winproc +INT_PTR MainMenuExecService(WPARAM wParam, LPARAM lParam) +{ + lpMainMenuExecParam mmep = ( lpMainMenuExecParam )wParam; + if ( mmep != NULL ) { + // bug in help.c,it used wparam as parent window handle without reason. + if ( !lstrcmpA(mmep->szServiceName,"Help/AboutCommand")) + mmep->Param1 = 0; + + CallService(mmep->szServiceName,mmep->Param1,lParam); + } + return 1; +} + +INT_PTR FreeOwnerDataMainMenu(WPARAM, LPARAM lParam) +{ + lpMainMenuExecParam mmep = ( lpMainMenuExecParam )lParam; + if ( mmep != NULL ) { + FreeAndNil(( void** )&mmep->szServiceName); + FreeAndNil(( void** )&mmep); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// CONTACT MENU + +static INT_PTR RemoveContactMenuItem(WPARAM wParam, LPARAM) +{ + CallService(MO_REMOVEMENUITEM,wParam,0); + return 0; +} + +static INT_PTR AddContactMenuItem(WPARAM, LPARAM lParam) +{ + CLISTMENUITEM *mi=(CLISTMENUITEM*)lParam; + if ( mi->cbSize != sizeof( CLISTMENUITEM )) + return 0; + + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof(tmi); + tmi.flags = mi->flags; + tmi.hIcon = mi->hIcon; + tmi.hotKey = mi->hotKey; + tmi.position = mi->position; + tmi.ptszName = mi->ptszName; + tmi.root = ( HGENMENU )mi->pszPopupName; + + if ( !( mi->flags & CMIF_ROOTHANDLE )) { + //old system + tmi.flags |= CMIF_ROOTHANDLE; + tmi.root = NULL; + } + + //owner data + lpContactMenuExecParam cmep = ( lpContactMenuExecParam )mir_calloc(sizeof(ContactMenuExecParam)); + cmep->szServiceName = mir_strdup( mi->pszService ); + if ( mi->pszContactOwner != NULL ) + cmep->pszContactOwner = mir_strdup( mi->pszContactOwner ); + cmep->param = mi->popupPosition; + tmi.ownerdata = cmep; + + //may be need to change how UniqueName is formed? + PMO_IntMenuItem menuHandle = MO_AddNewMenuItem( hContactMenuObject, &tmi ); + char buf[ 256 ]; + if (mi->pszService) + mir_snprintf( buf, SIZEOF(buf), "%s/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", (mi->pszService) ? mi->pszService : "" ); + else if (mi->ptszName) + { + if (tmi.flags&CMIF_UNICODE) + { + char * temp = mir_t2a(mi->ptszName); + mir_snprintf( buf, SIZEOF(buf), "%s/NoService/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", temp ); + mir_free(temp); + } + else + mir_snprintf( buf, SIZEOF(buf), "%s/NoService/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", mi->ptszName ); + } + else buf[0]='\0'; + if (buf[0]) MO_SetOptionsMenuItem( menuHandle, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf ); + return ( INT_PTR )menuHandle; +} + +static INT_PTR BuildContactMenu(WPARAM wParam, LPARAM) +{ + HANDLE hContact = ( HANDLE )wParam; + NotifyEventHooks(hPreBuildContactMenuEvent,(WPARAM)hContact,0); + + char *szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + + BuildContactParam bcp; + bcp.szProto = szProto; + bcp.isOnList = ( DBGetContactSettingByte(hContact,"CList","NotOnList",0) == 0 ); + bcp.isOnline = ( szProto != NULL && ID_STATUS_OFFLINE != DBGetContactSettingWord(hContact,szProto,"Status",ID_STATUS_OFFLINE)); + + ListParam param = { 0 }; + param.MenuObjectHandle = hContactMenuObject; + param.wParam = (WPARAM)&bcp; + + HMENU hMenu = CreatePopupMenu(); + CallService(MO_BUILDMENU,(WPARAM)hMenu,(LPARAM)¶m); + + return (INT_PTR)hMenu; +} + +//called with: +//wparam - ownerdata +//lparam - lparam from winproc +INT_PTR ContactMenuExecService(WPARAM wParam,LPARAM lParam) +{ + if (wParam!=0) { + lpContactMenuExecParam cmep=(lpContactMenuExecParam)wParam; + //call with wParam=(WPARAM)(HANDLE)hContact,lparam=popupposition + CallService(cmep->szServiceName,lParam,cmep->param); + } + return 0; +} + +//true - ok,false ignore +INT_PTR ContactMenuCheckService(WPARAM wParam,LPARAM) +{ + PCheckProcParam pcpp = ( PCheckProcParam )wParam; + BuildContactParam *bcp=NULL; + lpContactMenuExecParam cmep=NULL; + TMO_MenuItem mi; + + if ( pcpp == NULL ) + return FALSE; + + bcp = ( BuildContactParam* )pcpp->wParam; + if ( bcp == NULL ) + return FALSE; + + cmep = ( lpContactMenuExecParam )pcpp->MenuItemOwnerData; + if ( cmep == NULL ) //this is root...build it + return TRUE; + + if ( cmep->pszContactOwner != NULL ) { + if ( bcp->szProto == NULL ) return FALSE; + if ( strcmp( cmep->pszContactOwner, bcp->szProto )) return FALSE; + } + if ( MO_GetMenuItem(( WPARAM )pcpp->MenuItemHandle, ( LPARAM )&mi ) == 0 ) { + if ( mi.flags & CMIF_HIDDEN ) return FALSE; + if ( mi.flags & CMIF_NOTONLIST && bcp->isOnList ) return FALSE; + if ( mi.flags & CMIF_NOTOFFLIST && !bcp->isOnList ) return FALSE; + if ( mi.flags & CMIF_NOTONLINE && bcp->isOnline ) return FALSE; + if ( mi.flags & CMIF_NOTOFFLINE && !bcp->isOnline ) return FALSE; + } + return TRUE; +} + +INT_PTR FreeOwnerDataContactMenu (WPARAM, LPARAM lParam) +{ + lpContactMenuExecParam cmep = ( lpContactMenuExecParam )lParam; + if ( cmep != NULL ) { + FreeAndNil(( void** )&cmep->szServiceName); + FreeAndNil(( void** )&cmep->pszContactOwner); + FreeAndNil(( void** )&cmep); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// STATUS MENU + +BOOL FindMenuHandleByGlobalID(HMENU hMenu, PMO_IntMenuItem id, MenuItemData* itdat) +{ + int i; + PMO_IntMenuItem pimi; + MENUITEMINFO mii={0}; + BOOL inSub=FALSE; + if (!itdat) + return FALSE; + + mii.cbSize = MENUITEMINFO_V4_SIZE; + mii.fMask = MIIM_SUBMENU | MIIM_DATA; + for ( i = GetMenuItemCount( hMenu )-1; i >= 0; i-- ) { + GetMenuItemInfo(hMenu,i,TRUE,&mii); + if ( mii.fType == MFT_SEPARATOR ) + continue; + if ( mii.hSubMenu ) + inSub = FindMenuHandleByGlobalID(mii.hSubMenu, id, itdat); + if ( inSub ) + return inSub; + + pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData ); + if ( pimi != NULL ) { + if ( pimi == id ) { + itdat->OwnerMenu = hMenu; + itdat->position = i; + return TRUE; + } } } + + return FALSE; +} + +INT_PTR StatusMenuCheckService(WPARAM wParam, LPARAM) +{ + PCheckProcParam pcpp = ( PCheckProcParam )wParam; + if ( !pcpp ) + return TRUE; + + PMO_IntMenuItem timi = MO_GetIntMenuItem( pcpp->MenuItemHandle ); + if ( !timi ) + return TRUE; + + StatusMenuExecParam *smep = ( StatusMenuExecParam* )pcpp->MenuItemOwnerData; + if (smep && !smep->status && smep->custom ) + { + if (wildcmp(smep->svc, "*XStatus*")) + { + int XStatus = CallProtoService(smep->proto, "/GetXStatus", 0, 0); + char buf[255]; + mir_snprintf( buf, sizeof(buf), "*XStatus%d", XStatus ); + + bool check = wildcmp(smep->svc, buf); + bool reset = wildcmp(smep->svc, "*XStatus0"); + + if (check) + timi->mi.flags |= CMIF_CHECKED; + else + timi->mi.flags &= ~CMIF_CHECKED; + + if ( reset || check ) + { + PMO_IntMenuItem timiParent = MO_GetIntMenuItem( timi->mi.root ); + if (timiParent) + { + CLISTMENUITEM mi2 = {0}; + mi2.cbSize = sizeof(mi2); + mi2.flags = CMIM_NAME | CMIF_TCHAR; + mi2.ptszName = timi->mi.hIcon ? timi->mi.ptszName : TranslateT("Custom status"); + + timiParent = MO_GetIntMenuItem( timi->mi.root ); + + MenuItemData it = {0}; + + if (FindMenuHandleByGlobalID(hStatusMenu, timiParent, &it)) + { + MENUITEMINFO mi ={0}; + TCHAR d[100]; + GetMenuString(it.OwnerMenu, it.position, d, SIZEOF(d), MF_BYPOSITION); + + if (!IsWinVer98Plus()) + { + mi.cbSize = MENUITEMINFO_V4_SIZE; + mi.fMask = MIIM_TYPE | MIIM_STATE; + mi.fType = MFT_STRING; + } + else + { + mi.cbSize = sizeof( mi ); + mi.fMask = MIIM_STRING | MIIM_STATE; + if ( timi->iconId != -1 ) + { + mi.fMask |= MIIM_BITMAP; + if (IsWinVerVistaPlus() && isThemeActive()) { + if (timi->hBmp == NULL) + timi->hBmp = ConvertIconToBitmap(NULL, timi->parent->m_hMenuIcons, timi->iconId); + mi.hbmpItem = timi->hBmp; + } + else + mi.hbmpItem = HBMMENU_CALLBACK; + } + } + + mi.fState |= (check && !reset ? MFS_CHECKED : MFS_UNCHECKED ); + mi.dwTypeData = mi2.ptszName; + SetMenuItemInfo(it.OwnerMenu, it.position, TRUE, &mi); + } + + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)timi->mi.root, (LPARAM)&mi2); + timiParent->iconId = timi->iconId; + if (timiParent->hBmp) DeleteObject(timiParent->hBmp); + timiParent->hBmp = NULL; + } } } + } + else if ( smep && smep->status && !smep->custom ) { + int curProtoStatus = ( smep->proto ) ? CallProtoService(smep->proto,PS_GETSTATUS,0,0) : GetAverageMode(); + if ( smep->status == curProtoStatus ) + timi->mi.flags |= CMIF_CHECKED; + else + timi->mi.flags &= ~CMIF_CHECKED; + } + else if (( !smep || smep->proto ) && timi->mi.pszName ) { + int curProtoStatus=0; + BOOL IconNeedDestroy=FALSE; + char* prot; + if (smep) + prot = smep->proto; + else + { + #ifdef UNICODE + char *prn=mir_u2a(timi->mi.ptszName); + prot = NEWSTR_ALLOCA( prn ); + if (prn) mir_free(prn); + #else + prot = timi->mi.ptszName; + #endif + } + if ( Proto_GetAccount( prot ) == NULL ) + return TRUE; + + if (( curProtoStatus = CallProtoService(prot,PS_GETSTATUS,0,0)) == CALLSERVICE_NOTFOUND ) + curProtoStatus = 0; + + if ( curProtoStatus >= ID_STATUS_OFFLINE && curProtoStatus < ID_STATUS_IDLE ) + timi->mi.hIcon = LoadSkinProtoIcon(prot,curProtoStatus); + else { + timi->mi.hIcon=(HICON)CallProtoService(prot,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0); + if ( timi->mi.hIcon == (HICON)CALLSERVICE_NOTFOUND ) + timi->mi.hIcon = NULL; + else + IconNeedDestroy = TRUE; + } + + if (timi->mi.hIcon) { + timi->mi.flags |= CMIM_ICON; + MO_ModifyMenuItem( timi, &timi->mi ); + if ( IconNeedDestroy ) { + DestroyIcon( timi->mi.hIcon ); + timi->mi.hIcon = NULL; + } + else IconLib_ReleaseIcon(timi->mi.hIcon,0); + } } + + return TRUE; +} + +INT_PTR StatusMenuExecService(WPARAM wParam, LPARAM) +{ + lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )wParam; + if ( smep != NULL ) { + if ( smep->custom ) { + if (smep->svc && *smep->svc) + CallService(smep->svc, 0, (LPARAM)smep->hMenuItem); + } + else { + if ( smep->status == 0 && smep->protoindex !=0 && smep->proto != NULL ) { + PMO_IntMenuItem pimi; + char *prot = smep->proto; + char szHumanName[64]={0}; + PROTOACCOUNT * acc = Proto_GetAccount( smep->proto ); + int i=(DBGetContactSettingByte(NULL,prot,"LockMainStatus",0)?0:1); + DBWriteContactSettingByte(NULL,prot,"LockMainStatus",(BYTE)i); + + CallProtoService( smep->proto, PS_GETNAME, (WPARAM)SIZEOF(szHumanName), (LPARAM)szHumanName ); + pimi = MO_GetIntMenuItem(( HGENMENU )smep->protoindex ); + PMO_IntMenuItem root = (PMO_IntMenuItem)pimi->mi.root; + mir_free( pimi->mi.pszName ); + mir_free( root->mi.pszName ); + if ( i ) { + TCHAR buf[256]; + pimi->mi.flags|=CMIF_CHECKED; + if ( cli.bDisplayLocked ) { + mir_sntprintf(buf,SIZEOF(buf),TranslateT("%s (locked)"),acc->tszAccountName); + pimi->mi.ptszName = mir_tstrdup( buf ); + root->mi.ptszName = mir_tstrdup( buf ); + } + else { + pimi->mi.ptszName = mir_tstrdup( acc->tszAccountName ); + root->mi.ptszName = mir_tstrdup( acc->tszAccountName ); + } + } + else { + pimi->mi.ptszName = mir_tstrdup( acc->tszAccountName ); + root->mi.ptszName = mir_tstrdup( acc->tszAccountName ); + pimi->mi.flags &= ~CMIF_CHECKED; + } + if ( cli.hwndStatus ) + InvalidateRect( cli.hwndStatus, NULL, TRUE ); + } + else if ( smep->proto != NULL ) { + Proto_SetStatus(smep->proto, smep->status); + NotifyEventHooks(hStatusModeChangeEvent, smep->status, (LPARAM)smep->proto); + } + else { + int MenusProtoCount = 0; + + for( int i=0; i < accounts.getCount(); i++ ) + if ( cli.pfnGetProtocolVisibility( accounts[i]->szModuleName )) + MenusProtoCount++; + + cli.currentDesiredStatusMode = smep->status; + + for ( int j=0; j < accounts.getCount(); j++ ) { + PROTOACCOUNT* pa = accounts[j]; + if ( !Proto_IsAccountEnabled( pa )) + continue; + if ( MenusProtoCount > 1 && Proto_IsAccountLocked( pa )) + continue; + + Proto_SetStatus(pa->szModuleName, cli.currentDesiredStatusMode); + } + NotifyEventHooks( hStatusModeChangeEvent, cli.currentDesiredStatusMode, 0 ); + DBWriteContactSettingWord( NULL, "CList", "Status", ( WORD )cli.currentDesiredStatusMode ); + return 1; + } } } + + return 0; +} + +INT_PTR FreeOwnerDataStatusMenu(WPARAM, LPARAM lParam) +{ + lpStatusMenuExecParam smep = (lpStatusMenuExecParam)lParam; + if ( smep != NULL ) { + FreeAndNil(( void** )&smep->proto); + FreeAndNil(( void** )&smep->svc); + FreeAndNil(( void** )&smep); + } + + return (0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Other menu functions + +//wparam MenuItemHandle +static INT_PTR ModifyCustomMenuItem(WPARAM wParam,LPARAM lParam) +{ + CLISTMENUITEM *mi=(CLISTMENUITEM*)lParam; + TMO_MenuItem tmi; + + if ( lParam == 0 ) + return -1; + if ( mi->cbSize != sizeof( CLISTMENUITEM )) + return 1; + + tmi.cbSize = sizeof(tmi); + tmi.flags = mi->flags; + tmi.hIcon = mi->hIcon; + tmi.hotKey = mi->hotKey; + tmi.ptszName = mi->ptszName; + return MO_ModifyMenuItem(( PMO_IntMenuItem )wParam, &tmi ); +} + +INT_PTR MenuProcessCommand(WPARAM wParam,LPARAM lParam) +{ + WORD cmd = LOWORD(wParam); + + if ( HIWORD(wParam) & MPCF_MAINMENU ) { + int hst = LOWORD( wParam ); + if ( hst >= ID_STATUS_OFFLINE && hst <= ID_STATUS_OUTTOLUNCH ) { + int pos = statustopos( hst ); + if ( pos != -1 && hStatusMainMenuHandles != NULL ) + return MO_ProcessCommand( hStatusMainMenuHandles[ pos ], lParam ); + } } + + if ( !( cmd >= CLISTMENUIDMIN && cmd <= CLISTMENUIDMAX )) + return 0; // DO NOT process ids outside from clist menu id range v0.7.0.27+ + + //process old menu sys + if ( HIWORD(wParam) & MPCF_CONTACTMENU ) + return MO_ProcessCommandBySubMenuIdent( (int)hContactMenuObject, LOWORD(wParam), lParam ); + + //unknown old menu + return MO_ProcessCommandByMenuIdent( LOWORD(wParam), lParam ); +} + +BOOL FindMenuHanleByGlobalID(HMENU hMenu, PMO_IntMenuItem id, MenuItemData* itdat) +{ + int i; + PMO_IntMenuItem pimi; + MENUITEMINFO mii = {0}; + BOOL inSub=FALSE; + + if ( !itdat ) + return FALSE; + + mii.cbSize = MENUITEMINFO_V4_SIZE; + mii.fMask = MIIM_SUBMENU | MIIM_DATA; + for ( i = GetMenuItemCount( hMenu )-1; i >= 0; i-- ) { + GetMenuItemInfo( hMenu, i, TRUE, &mii ); + if ( mii.fType == MFT_SEPARATOR ) + continue; + + if ( mii.hSubMenu ) + inSub = FindMenuHanleByGlobalID( mii.hSubMenu, id, itdat ); + if (inSub) + return inSub; + + pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData); + if ( pimi != NULL ) { + if ( pimi == id ) { + itdat->OwnerMenu = hMenu; + itdat->position = i; + return TRUE; + } } } + + return FALSE; +} + +static INT_PTR MenuProcessHotkey(WPARAM vKey, LPARAM) +{ + prochotkey = true; + + bool res = + MO_ProcessHotKeys( hStatusMenuObject, vKey ) || + MO_ProcessHotKeys( hMainMenuObject, vKey ); + + prochotkey = false; + + return res; +} + +static int MenuIconsChanged(WPARAM, LPARAM) +{ + //just rebuild menu + RebuildMenuOrder(); + cli.pfnCluiProtocolStatusChanged(0,0); + return 0; +} + +static INT_PTR MeasureMenuItem(WPARAM, LPARAM lParam) +{ + return MO_MeasureMenuItem(( LPMEASUREITEMSTRUCT )lParam ); +} + +static INT_PTR DrawMenuItem(WPARAM, LPARAM lParam) +{ + return MO_DrawMenuItem(( LPDRAWITEMSTRUCT )lParam ); +} + +int RecursiveDeleteMenu(HMENU hMenu) +{ + int cnt = GetMenuItemCount(hMenu); + for ( int i=0; i < cnt; i++ ) { + HMENU submenu = GetSubMenu(hMenu, 0); + if (submenu) DestroyMenu(submenu); + DeleteMenu(hMenu, 0, MF_BYPOSITION); + } + return 0; +} + +static INT_PTR MenuGetMain(WPARAM, LPARAM) +{ + RecursiveDeleteMenu(hMainMenu); + BuildMainMenu(0,0); + return (INT_PTR)hMainMenu; +} + +static INT_PTR BuildStatusMenu(WPARAM, LPARAM) +{ + ListParam param = { 0 }; + param.MenuObjectHandle = hStatusMenuObject; + + RecursiveDeleteMenu(hStatusMenu); + CallService(MO_BUILDMENU,(WPARAM)hStatusMenu,(LPARAM)¶m); + return (INT_PTR)hStatusMenu; +} + +static INT_PTR SetStatusMode(WPARAM wParam, LPARAM) +{ + prochotkey = true; + MenuProcessCommand(MAKEWPARAM(LOWORD(wParam), MPCF_MAINMENU), 0); + prochotkey = false; + return 0; +} + +int fnGetProtocolVisibility(const char* accName) +{ + if ( accName ) { + PROTOACCOUNT* pa = Proto_GetAccount( accName ); + return pa && pa->bIsVisible && Proto_IsAccountEnabled( pa ) && + pa->ppro && (pa->ppro->GetCaps( PFLAGNUM_2, 0 ) & ~pa->ppro->GetCaps( PFLAGNUM_5, 0 )); + } + + return FALSE; +} + +int fnGetProtoIndexByPos(PROTOCOLDESCRIPTOR ** proto, int protoCnt, int Pos) +{ + int p; + char buf[10]; + DBVARIANT dbv; + + _itoa( Pos, buf, 10 ); + if ( !DBGetContactSetting( NULL, "Protocols", buf, &dbv )) { + for ( p=0; p < protoCnt; p++ ) { + if ( lstrcmpA( proto[p]->szName, dbv.pszVal ) == 0 ) { + DBFreeVariant( &dbv ); + return p; + } } + + DBFreeVariant( &dbv ); + } + + return -1; +} + +int fnGetAccountIndexByPos(int Pos) +{ + int i; + for ( i=0; i < accounts.getCount(); i++ ) + if ( accounts[i]->iOrder == Pos ) + return i; + + return -1; +} + +void RebuildMenuOrder( void ) +{ + int i,j,s; + DWORD flags; + + BYTE bHideStatusMenu = DBGetContactSettingByte( NULL, "CLUI", "DontHideStatusMenu", 0 ); // cool perversion, though + + //clear statusmenu + RecursiveDeleteMenu(hStatusMenu); + + //status menu + if ( hStatusMenuObject != 0 ) { + CallService(MO_REMOVEMENUOBJECT,(WPARAM)hStatusMenuObject,0); + mir_free( hStatusMainMenuHandles ); + mir_free( hStatusMenuHandles ); + } + + TMenuParam tmp = { 0 }; + tmp.cbSize = sizeof(tmp); + tmp.ExecService = "StatusMenuExecService"; + tmp.CheckService = "StatusMenuCheckService"; + tmp.name = "StatusMenu"; + + hStatusMenuObject=(HANDLE)CallService(MO_CREATENEWMENUOBJECT,(WPARAM)0,(LPARAM)&tmp); + MO_SetOptionsMenuObject( hStatusMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataStatusMenu" ); + + hStatusMainMenuHandles = ( PMO_IntMenuItem* )mir_calloc( SIZEOF(statusModeList) * sizeof( PMO_IntMenuItem* )); + hStatusMainMenuHandlesCnt = SIZEOF(statusModeList); + + hStatusMenuHandles = ( tStatusMenuHandles* )mir_calloc(sizeof(tStatusMenuHandles)*accounts.getCount()); + hStatusMenuHandlesCnt = accounts.getCount(); + + FreeMenuProtos(); + + for ( s=0; s < accounts.getCount(); s++ ) { + i = cli.pfnGetAccountIndexByPos( s ); + if ( i == -1 ) + continue; + + PROTOACCOUNT* pa = accounts[i]; + int pos = 0; + if ( !bHideStatusMenu && !cli.pfnGetProtocolVisibility( pa->szModuleName )) + continue; + + flags = pa->ppro->GetCaps( PFLAGNUM_2, 0 ) & ~pa->ppro->GetCaps( PFLAGNUM_5, 0 ); + int j; + HICON ic; + TCHAR tbuf[256]; + + //adding root + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof(tmi); + tmi.flags = CMIF_TCHAR | CMIF_ROOTHANDLE | CMIF_KEEPUNTRANSLATED; + tmi.position = pos++; + tmi.hIcon = ic = (HICON)CallProtoService( pa->szModuleName, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0 ); + + if ( Proto_IsAccountLocked( pa ) && cli.bDisplayLocked ) { + mir_sntprintf( tbuf, SIZEOF(tbuf), TranslateT("%s (locked)"), pa->tszAccountName ); + tmi.ptszName = tbuf; + } + else tmi.ptszName = pa->tszAccountName; + + { + //owner data + lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_calloc( sizeof( StatusMenuExecParam )); + smep->proto = mir_strdup(pa->szModuleName); + tmi.ownerdata = smep; + } + PMO_IntMenuItem rootmenu = MO_AddNewMenuItem( hStatusMenuObject, &tmi ); + + memset(&tmi,0,sizeof(tmi)); + tmi.cbSize = sizeof(tmi); + tmi.flags = CMIF_TCHAR | CMIF_ROOTHANDLE | CMIF_KEEPUNTRANSLATED; + tmi.root = rootmenu; + tmi.position = pos++; + tmi.hIcon = ic; + { + //owner data + lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_alloc( sizeof( StatusMenuExecParam )); + memset( smep, 0, sizeof( *smep )); + smep->proto = mir_strdup(pa->szModuleName); + tmi.ownerdata = smep; + } + + if ( Proto_IsAccountLocked( pa )) + tmi.flags |= CMIF_CHECKED; + + if (( tmi.flags & CMIF_CHECKED ) && cli.bDisplayLocked ) { + mir_sntprintf( tbuf, SIZEOF(tbuf), TranslateT("%s (locked)"), pa->tszAccountName ); + tmi.ptszName = tbuf; + } + else tmi.ptszName = pa->tszAccountName; + + PMO_IntMenuItem menuHandle = MO_AddNewMenuItem( hStatusMenuObject, &tmi ); + ((lpStatusMenuExecParam)tmi.ownerdata)->protoindex = ( int )menuHandle; + MO_ModifyMenuItem( menuHandle, &tmi ); + + cli.menuProtos=(MenuProto*)mir_realloc(cli.menuProtos, sizeof(MenuProto)*(cli.menuProtoCount+1)); + memset(&(cli.menuProtos[cli.menuProtoCount]),0,sizeof(MenuProto)); + cli.menuProtos[cli.menuProtoCount].pMenu = rootmenu; + cli.menuProtos[cli.menuProtoCount].szProto = mir_strdup(pa->szModuleName); + + cli.menuProtoCount++; + { + char buf[256]; + mir_snprintf( buf, SIZEOF(buf), "RootProtocolIcon_%s", pa->szModuleName ); + MO_SetOptionsMenuItem( menuHandle, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf ); + } + DestroyIcon(ic); + pos += 500000; + + for ( j=0; j < SIZEOF(statusModeList); j++ ) { + if ( !( flags & statusModePf2List[j] )) + continue; + + //adding + memset( &tmi, 0, sizeof( tmi )); + tmi.cbSize = sizeof(tmi); + tmi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR; + if ( statusModeList[j] == ID_STATUS_OFFLINE ) + tmi.flags |= CMIF_CHECKED; + tmi.root = rootmenu; + tmi.position = pos++; + tmi.ptszName = cli.pfnGetStatusModeDescription( statusModeList[j], GSMDF_UNTRANSLATED ); + tmi.hIcon = LoadSkinProtoIcon( pa->szModuleName, statusModeList[j] ); + { + //owner data + lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_calloc( sizeof( StatusMenuExecParam )); + smep->custom = FALSE; + smep->status = statusModeList[j]; + smep->protoindex = i; + smep->proto = mir_strdup(pa->szModuleName); + tmi.ownerdata = smep; + } + + hStatusMenuHandles[i].protoindex = i; + hStatusMenuHandles[i].protostatus[j] = statusModeList[j]; + hStatusMenuHandles[i].menuhandle[j] = MO_AddNewMenuItem( hStatusMenuObject, &tmi ); + { + char buf[ 256 ]; + mir_snprintf(buf, SIZEOF(buf), "ProtocolIcon_%s_%s",pa->szModuleName,tmi.pszName); + MO_SetOptionsMenuItem( hStatusMenuHandles[i].menuhandle[j], OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf ); + } + IconLib_ReleaseIcon(tmi.hIcon,0); + } } + + NotifyEventHooks(cli.hPreBuildStatusMenuEvent, 0, 0); + int pos = 200000; + + //add to root menu + for ( j=0; j < SIZEOF(statusModeList); j++ ) { + for ( i=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if ( !bHideStatusMenu && !cli.pfnGetProtocolVisibility( pa->szModuleName )) + continue; + + flags = pa->ppro->GetCaps(PFLAGNUM_2, 0) & ~pa->ppro->GetCaps(PFLAGNUM_5, 0); + if ( !( flags & statusModePf2List[j] )) + continue; + + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof( tmi ); + tmi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR; + if ( statusModeList[j] == ID_STATUS_OFFLINE ) + tmi.flags |= CMIF_CHECKED; + + tmi.hIcon = LoadSkinIcon( skinIconStatusList[j] ); + tmi.position = pos++; + tmi.hotKey = MAKELPARAM(MOD_CONTROL,'0'+j); + { + //owner data + lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_alloc( sizeof( StatusMenuExecParam )); + smep->custom = FALSE; + smep->status = statusModeList[j]; + smep->proto = NULL; + smep->svc = NULL; + tmi.ownerdata = smep; + } + { + TCHAR buf[ 256 ], hotkeyName[ 100 ]; + WORD hotKey = GetHotkeyValue( statusHotkeys[j] ); + HotkeyToName( hotkeyName, SIZEOF(hotkeyName), HIBYTE(hotKey), LOBYTE(hotKey)); + mir_sntprintf( buf, SIZEOF( buf ), TranslateT("%s\t%s"), + cli.pfnGetStatusModeDescription( statusModeList[j], 0 ), hotkeyName ); + tmi.ptszName = buf; + tmi.hotKey = MAKELONG(HIBYTE(hotKey), LOBYTE(hotKey)); + hStatusMainMenuHandles[j] = MO_AddNewMenuItem( hStatusMenuObject, &tmi ); + } + { + char buf[ 256 ]; + mir_snprintf( buf, sizeof( buf ), "Root2ProtocolIcon_%s_%s", pa->szModuleName, tmi.pszName ); + MO_SetOptionsMenuItem( hStatusMainMenuHandles[j], OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf ); + } + IconLib_ReleaseIcon( tmi.hIcon, 0 ); + break; + } } + + BuildStatusMenu(0,0); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int sttRebuildHotkeys( WPARAM, LPARAM ) +{ + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof( tmi ); + tmi.flags = CMIM_HOTKEY | CMIM_NAME | CMIF_TCHAR; + + for ( int j=0; j < SIZEOF(statusModeList); j++ ) { + TCHAR buf[ 256 ], hotkeyName[ 100 ]; + WORD hotKey = GetHotkeyValue( statusHotkeys[j] ); + HotkeyToName( hotkeyName, SIZEOF(hotkeyName), HIBYTE(hotKey), LOBYTE(hotKey)); + mir_sntprintf( buf, SIZEOF( buf ), TranslateT("%s\t%s"), + cli.pfnGetStatusModeDescription( statusModeList[j], 0 ), hotkeyName ); + tmi.ptszName = buf; + tmi.hotKey = MAKELONG(HIBYTE(hotKey), LOBYTE(hotKey)); + MO_ModifyMenuItem( hStatusMainMenuHandles[j], &tmi ); + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int statustopos(int status) +{ + int j; + for ( j = 0; j < SIZEOF(statusModeList); j++ ) + if ( status == statusModeList[j] ) + return j; + + return -1; +} + +static int MenuProtoAck(WPARAM, LPARAM lParam) +{ + int i; + ACKDATA* ack=(ACKDATA*)lParam; + int overallStatus; + TMO_MenuItem tmi; + + if ( ack->type != ACKTYPE_STATUS ) return 0; + if ( ack->result != ACKRESULT_SUCCESS ) return 0; + if ( hStatusMainMenuHandles == NULL ) return 0; + + if ( cli.pfnGetProtocolVisibility( ack->szModule ) == 0 ) return 0; + + overallStatus = GetAverageMode(); + + memset(&tmi,0,sizeof(tmi)); + tmi.cbSize=sizeof(tmi); + if (overallStatus >= ID_STATUS_OFFLINE) { + int pos = statustopos(cli.currentStatusMenuItem); + if (pos==-1) pos=0; + { // reset all current possible checked statuses + int pos2; + for (pos2=0; pos2=0 && pos2 < hStatusMainMenuHandlesCnt) + { + tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE; + MO_ModifyMenuItem( hStatusMainMenuHandles[pos2], &tmi ); + } } } + + cli.currentStatusMenuItem=overallStatus; + pos = statustopos(cli.currentStatusMenuItem); + if (pos>=0 && pos < hStatusMainMenuHandlesCnt) { + tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE | CMIF_CHECKED; + MO_ModifyMenuItem( hStatusMainMenuHandles[pos], &tmi ); + } +// cli.currentDesiredStatusMode = cli.currentStatusMenuItem; + } + else { + int pos = statustopos( cli.currentStatusMenuItem ); + if ( pos == -1 ) pos=0; + if ( pos >= 0 && pos < hStatusMainMenuHandlesCnt ) { + tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE; + MO_ModifyMenuItem( hStatusMainMenuHandles[pos], &tmi ); + } + //SetMenuDefaultItem(hStatusMenu,-1,FALSE); + cli.currentStatusMenuItem=0; + } + + for ( i=0; i < accounts.getCount(); i++ ) { + if ( !lstrcmpA( accounts[i]->szModuleName, ack->szModule )) { + //hProcess is previous mode, lParam is new mode + if ((( int )ack->hProcess >= ID_STATUS_OFFLINE || ( int )ack->hProcess == 0 ) && ( int )ack->hProcess < ID_STATUS_OFFLINE + SIZEOF(statusModeList)) { + int pos = statustopos(( int )ack->hProcess); + if ( pos == -1 ) + pos = 0; + for ( pos = 0; pos < SIZEOF(statusModeList); pos++ ) { + tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE; + MO_ModifyMenuItem( hStatusMenuHandles[i].menuhandle[pos], &tmi ); + } } + + if ( ack->lParam >= ID_STATUS_OFFLINE && ack->lParam < ID_STATUS_OFFLINE + SIZEOF(statusModeList)) { + int pos = statustopos(( int )ack->lParam ); + if ( pos >= 0 && pos < SIZEOF(statusModeList)) { + tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE | CMIF_CHECKED; + MO_ModifyMenuItem( hStatusMenuHandles[i].menuhandle[pos], &tmi ); + } } + break; + } } + + //BuildStatusMenu(0,0); + return 0; +} + +static MenuProto* FindProtocolMenu( const char* proto ) +{ + for (int i=0; i < cli.menuProtoCount; i++) + if ( cli.menuProtos[i].pMenu && !lstrcmpiA( cli.menuProtos[i].szProto, proto )) + return &cli.menuProtos[i]; + + if ( cli.menuProtoCount == 1 ) + if ( !lstrcmpiA( cli.menuProtos[0].szProto, proto )) + return &cli.menuProtos[0]; + + return NULL; +} + +HGENMENU fnGetProtocolMenu( const char* proto ) +{ + MenuProto* mp = FindProtocolMenu( proto ); + if ( mp ) + return mp->pMenu; + + return NULL; +} + +static INT_PTR AddStatusMenuItem(WPARAM wParam,LPARAM lParam) +{ + CLISTMENUITEM *mi = ( CLISTMENUITEM* )lParam; + if ( mi->cbSize != sizeof( CLISTMENUITEM )) + return 0; + + PMO_IntMenuItem pRoot = NULL; + lpStatusMenuExecParam smep = NULL; + + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof(tmi); + tmi.hIcon = mi->hIcon; + tmi.hotKey = mi->hotKey; + tmi.position = mi->position; + tmi.pszName = mi->pszName; + tmi.flags = mi->flags; + tmi.root = mi->hParentMenu; + + // for new style menus the pszPopupName contains the root menu handle + if ( mi->flags & CMIF_ROOTHANDLE ) + pRoot = MO_GetIntMenuItem( mi->hParentMenu ); + + // for old style menus the pszPopupName really means the popup name + else { + MenuProto* mp = FindProtocolMenu( mi->pszContactOwner ); + if ( mp && mi->pszPopupName ) { + if ( mp->pMenu ) { + #if defined _UNICODE + TCHAR* ptszName = ( mi->flags & CMIF_UNICODE ) ? mir_tstrdup(mi->ptszPopupName) : mir_a2t(mi->pszPopupName); + pRoot = MO_RecursiveWalkMenu( mp->pMenu->submenu.first, FindRoot, ptszName ); + mir_free( ptszName ); + #else + pRoot = MO_RecursiveWalkMenu( mp->pMenu->submenu.first, FindRoot, mi->pszPopupName ); + #endif + } + if ( pRoot == NULL ) { + TMO_MenuItem tmi = { 0 }; + tmi.cbSize = sizeof(tmi); + tmi.flags = (mi->flags & CMIF_UNICODE) | CMIF_ROOTHANDLE; + tmi.position = 1001; + tmi.root = mp->pMenu; + tmi.hIcon = NULL; + tmi.pszName = mi->pszPopupName; + pRoot = MO_AddNewMenuItem( hStatusMenuObject, &tmi ); + } + + tmi.flags |= CMIF_ROOTHANDLE; + tmi.root = pRoot; + } } + + if (wParam) { + int * res=(int*)wParam; + *res = ( int )pRoot; + } + + //owner data + if ( mi->pszService ) { + smep = ( lpStatusMenuExecParam )mir_calloc(sizeof(StatusMenuExecParam)); + smep->custom = TRUE; + smep->svc=mir_strdup(mi->pszService); + { + char *buf=mir_strdup(mi->pszService); + int i=0; + while(buf[i]!='\0' && buf[i]!='/') i++; + buf[i]='\0'; + smep->proto=mir_strdup(buf); + mir_free(buf); + } + tmi.ownerdata = smep; + } + PMO_IntMenuItem menuHandle = MO_AddNewMenuItem( hStatusMenuObject, &tmi ); + if ( smep ) + smep->hMenuItem = menuHandle; + + char buf[MAX_PATH+64]; + #if defined( _UNICODE ) + { + char* p = ( pRoot ) ? mir_t2a( pRoot->mi.ptszName ) : NULL; + mir_snprintf( buf, SIZEOF(buf), "%s/%s", ( p ) ? p : "", mi->pszService ? mi->pszService : "" ); + mir_free( p ); + } + #else + mir_snprintf( buf, SIZEOF(buf), "%s/%s", pRoot ? pRoot->mi.ptszName : _T(""), mi->pszService ? mi->pszService : "" ); + #endif + MO_SetOptionsMenuItem( menuHandle, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf ); + + return ( INT_PTR )menuHandle; +} + +static INT_PTR HotkeySetStatus(WPARAM wParam,LPARAM lParam) +{ + return SetStatusMode( lParam, 0 ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// PROTOCOL MENU + +static INT_PTR AddProtoMenuItem(WPARAM wParam,LPARAM lParam) +{ + if ( DBGetContactSettingByte( NULL, "CList", "MoveProtoMenus", FALSE )) + return AddStatusMenuItem( wParam, lParam ); + + return AddMainMenuItem( wParam, lParam ); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void InitCustomMenus(void) +{ + CreateServiceFunction("MainMenuExecService",MainMenuExecService); + + CreateServiceFunction("ContactMenuExecService",ContactMenuExecService); + CreateServiceFunction("ContactMenuCheckService",ContactMenuCheckService); + + CreateServiceFunction("StatusMenuExecService",StatusMenuExecService); + CreateServiceFunction("StatusMenuCheckService",StatusMenuCheckService); + + //free services + CreateServiceFunction("CLISTMENUS/FreeOwnerDataMainMenu",FreeOwnerDataMainMenu); + CreateServiceFunction("CLISTMENUS/FreeOwnerDataContactMenu",FreeOwnerDataContactMenu); + CreateServiceFunction("CLISTMENUS/FreeOwnerDataStatusMenu",FreeOwnerDataStatusMenu); + + CreateServiceFunction(MS_CLIST_SETSTATUSMODE, SetStatusMode); + + CreateServiceFunction(MS_CLIST_ADDMAINMENUITEM,AddMainMenuItem); + CreateServiceFunction(MS_CLIST_ADDSTATUSMENUITEM,AddStatusMenuItem); + CreateServiceFunction(MS_CLIST_MENUGETMAIN,MenuGetMain); + CreateServiceFunction(MS_CLIST_REMOVEMAINMENUITEM,RemoveMainMenuItem); + CreateServiceFunction(MS_CLIST_MENUBUILDMAIN,BuildMainMenu); + + CreateServiceFunction(MS_CLIST_ADDCONTACTMENUITEM,AddContactMenuItem); + CreateServiceFunction(MS_CLIST_MENUBUILDCONTACT,BuildContactMenu); + CreateServiceFunction(MS_CLIST_REMOVECONTACTMENUITEM,RemoveContactMenuItem); + + CreateServiceFunction(MS_CLIST_MODIFYMENUITEM,ModifyCustomMenuItem); + CreateServiceFunction(MS_CLIST_MENUMEASUREITEM,MeasureMenuItem); + CreateServiceFunction(MS_CLIST_MENUDRAWITEM,DrawMenuItem); + + CreateServiceFunction(MS_CLIST_MENUGETSTATUS,BuildStatusMenu); + CreateServiceFunction(MS_CLIST_MENUPROCESSCOMMAND,MenuProcessCommand); + CreateServiceFunction(MS_CLIST_MENUPROCESSHOTKEY,MenuProcessHotkey); + + CreateServiceFunction(MS_CLIST_ADDPROTOMENUITEM,AddProtoMenuItem); + + hPreBuildContactMenuEvent=CreateHookableEvent(ME_CLIST_PREBUILDCONTACTMENU); + hPreBuildMainMenuEvent=CreateHookableEvent(ME_CLIST_PREBUILDMAINMENU); + cli.hPreBuildStatusMenuEvent=CreateHookableEvent(ME_CLIST_PREBUILDSTATUSMENU); + hStatusModeChangeEvent = CreateHookableEvent( ME_CLIST_STATUSMODECHANGE ); + + hAckHook=(HANDLE)HookEvent(ME_PROTO_ACK,MenuProtoAck); + + hMainMenu = CreatePopupMenu(); + hStatusMenu = CreatePopupMenu(); + + hStatusMainMenuHandles=NULL; + hStatusMainMenuHandlesCnt=0; + + hStatusMenuHandles=NULL; + hStatusMenuHandlesCnt=0; + + //new menu sys + InitGenMenu(); + + //main menu + { + TMenuParam tmp = { 0 }; + tmp.cbSize=sizeof(tmp); + tmp.CheckService=NULL; + tmp.ExecService="MainMenuExecService"; + tmp.name="MainMenu"; + hMainMenuObject=(HANDLE)CallService(MO_CREATENEWMENUOBJECT,(WPARAM)0,(LPARAM)&tmp); + } + + MO_SetOptionsMenuObject( hMainMenuObject, OPT_USERDEFINEDITEMS, TRUE ); + MO_SetOptionsMenuObject( hMainMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataMainMenu" ); + + //contact menu + { + TMenuParam tmp = { 0 }; + tmp.cbSize=sizeof(tmp); + tmp.CheckService="ContactMenuCheckService"; + tmp.ExecService="ContactMenuExecService"; + tmp.name="ContactMenu"; + hContactMenuObject=(HANDLE)CallService(MO_CREATENEWMENUOBJECT,(WPARAM)0,(LPARAM)&tmp); + } + + MO_SetOptionsMenuObject( hContactMenuObject, OPT_USERDEFINEDITEMS, TRUE ); + MO_SetOptionsMenuObject( hContactMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataContactMenu" ); + + // initialize hotkeys + CreateServiceFunction(MS_CLIST_HKSTATUS, HotkeySetStatus); + + HOTKEYDESC hkd = { 0 }; + hkd.cbSize = sizeof( hkd ); + hkd.ptszSection = _T("Status"); + hkd.dwFlags = HKD_TCHAR; + for ( int i = 0; i < SIZEOF(statusHotkeys); i++ ) { + char szName[30]; + mir_snprintf( szName, SIZEOF(szName), "StatusHotKey_%d", i ); + hkd.pszName = szName; + hkd.lParam = statusModeList[i]; + hkd.ptszDescription = fnGetStatusModeDescription( hkd.lParam, 0 ); + hkd.DefHotKey = HOTKEYCODE( HOTKEYF_CONTROL, '0'+i ) | HKF_MIRANDA_LOCAL; + hkd.pszService = MS_CLIST_HKSTATUS; + statusHotkeys[i] = CallService( MS_HOTKEY_REGISTER, 0, LPARAM( &hkd )); + } + + HookEvent( ME_HOTKEYS_CHANGED, sttRebuildHotkeys ); + + // add exit command to menu + { + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof( mi ); + mi.position = 0x7fffffff; + mi.flags = CMIF_ICONFROMICOLIB; + mi.pszService = "CloseAction"; + mi.pszName = LPGEN("E&xit"); + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_EXIT ); + AddMainMenuItem( 0, ( LPARAM )&mi ); + } + + cli.currentStatusMenuItem=ID_STATUS_OFFLINE; + cli.currentDesiredStatusMode=ID_STATUS_OFFLINE; + + if ( IsWinVer98Plus() ) + HookEvent(ME_SKIN_ICONSCHANGED, MenuIconsChanged ); +} + +void UninitCustomMenus(void) +{ + mir_free(hStatusMainMenuHandles); + hStatusMainMenuHandles = NULL; + + mir_free( hStatusMenuHandles ); + hStatusMenuHandles = NULL; + + if ( hMainMenuObject ) CallService( MO_REMOVEMENUOBJECT, (WPARAM)hMainMenuObject, 0 ); + if ( hStatusMenuObject ) CallService( MO_REMOVEMENUOBJECT, (WPARAM)hMainMenuObject, 0 ); + + UnloadMoveToGroup(); + FreeMenuProtos(); + + DestroyMenu(hMainMenu); + DestroyMenu(hStatusMenu); + UnhookEvent(hAckHook); +} diff --git a/src/modules/clist/clistmod.cpp b/src/modules/clist/clistmod.cpp new file mode 100644 index 0000000000..7de958342b --- /dev/null +++ b/src/modules/clist/clistmod.cpp @@ -0,0 +1,569 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +INT_PTR AddMainMenuItem(WPARAM wParam, LPARAM lParam); +INT_PTR AddContactMenuItem(WPARAM wParam, LPARAM lParam); +INT_PTR ContactChangeGroup(WPARAM wParam, LPARAM lParam); +int InitCListEvents(void); +void UninitCListEvents(void); +int ContactSettingChanged(WPARAM wParam, LPARAM lParam); +int ContactAdded(WPARAM wParam, LPARAM lParam); +int ContactDeleted(WPARAM wParam, LPARAM lParam); +INT_PTR GetContactDisplayName(WPARAM wParam, LPARAM lParam); +INT_PTR InvalidateDisplayName(WPARAM wParam, LPARAM lParam); +int InitGroupServices(void); +INT_PTR Docking_IsDocked(WPARAM wParam, LPARAM lParam); +void InitDisplayNameCache(void); +void FreeDisplayNameCache(void); +int LoadCLUIModule(void); +int InitClistHotKeys(void); + +HANDLE hContactDoubleClicked, hContactIconChangedEvent; +HIMAGELIST hCListImages; +BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T); + +extern BYTE nameOrder[]; + +struct ProtoIconIndex +{ + char *szProto; + int iIconBase; +}; + +OBJLIST protoIconIndex(5); + +static HANDLE hProtoAckHook; +static HANDLE hContactSettingChanged; + +TCHAR* fnGetStatusModeDescription( int mode, int flags ) +{ + static TCHAR szMode[64]; + TCHAR* descr; + int noPrefixReqd = 0; + switch (mode) { + case ID_STATUS_OFFLINE: + descr = _T("Offline"); + noPrefixReqd = 1; + break; + case ID_STATUS_CONNECTING: + descr = _T("Connecting"); + noPrefixReqd = 1; + break; + case ID_STATUS_ONLINE: + descr = _T("Online"); + noPrefixReqd = 1; + break; + case ID_STATUS_AWAY: + descr = _T("Away"); + break; + case ID_STATUS_DND: + descr = _T("DND"); + break; + case ID_STATUS_NA: + descr = _T("NA"); + break; + case ID_STATUS_OCCUPIED: + descr = _T("Occupied"); + break; + case ID_STATUS_FREECHAT: + descr = _T("Free for chat"); + break; + case ID_STATUS_INVISIBLE: + descr = _T("Invisible"); + break; + case ID_STATUS_OUTTOLUNCH: + descr = _T("Out to lunch"); + break; + case ID_STATUS_ONTHEPHONE: + descr = _T("On the phone"); + break; + case ID_STATUS_IDLE: + descr = _T("Idle"); + break; + default: + if (mode > ID_STATUS_CONNECTING && mode < ID_STATUS_CONNECTING + MAX_CONNECT_RETRIES) { + const TCHAR* connFmt = _T("Connecting (attempt %d)"); + mir_sntprintf(szMode, SIZEOF(szMode), (flags&GSMDF_UNTRANSLATED)?connFmt:TranslateTS(connFmt), mode - ID_STATUS_CONNECTING + 1); + return szMode; + } + return NULL; + } + if (noPrefixReqd || !(flags & GSMDF_PREFIXONLINE)) + return ( flags & GSMDF_UNTRANSLATED ) ? descr : TranslateTS( descr ); + + lstrcpy( szMode, TranslateT( "Online" )); + lstrcat( szMode, _T(": ")); + lstrcat( szMode, ( flags & GSMDF_UNTRANSLATED ) ? descr : TranslateTS( descr )); + return szMode; +} + +static INT_PTR GetStatusModeDescription(WPARAM wParam, LPARAM lParam) +{ + TCHAR* buf1 = cli.pfnGetStatusModeDescription( wParam, lParam ); + + #ifdef UNICODE + if ( !( lParam & GSMDF_TCHAR )) + { + static char szMode[64]; + char *buf2 = mir_u2a(buf1); + mir_snprintf(szMode, SIZEOF(szMode), "%s", buf2); + mir_free(buf2); + return (INT_PTR)szMode; + } + #endif + + return (INT_PTR)buf1; +} + +static int ProtocolAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA *) lParam; + + if (ack->type != ACKTYPE_STATUS) + return 0; + CallService(MS_CLUI_PROTOCOLSTATUSCHANGED, ack->lParam, (LPARAM) ack->szModule); + + if ((int) ack->hProcess < ID_STATUS_ONLINE && ack->lParam >= ID_STATUS_ONLINE) { + DWORD caps; + caps = (DWORD) CallProtoService(ack->szModule, PS_GETCAPS, PFLAGNUM_1, 0); + if (caps & PF1_SERVERCLIST) { + HANDLE hContact; + char *szProto; + + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while (hContact) { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, ack->szModule)) + if (DBGetContactSettingByte(hContact, "CList", "Delete", 0)) + CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0); + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0); + } } } + + cli.pfnTrayIconUpdateBase(ack->szModule); + return 0; +} + +HICON fnGetIconFromStatusMode( HANDLE hContact, const char *szProto, int status ) +{ + return ImageList_GetIcon( hCListImages, cli.pfnIconFromStatusMode( szProto, status, hContact ), ILD_NORMAL); +} + +int fnIconFromStatusMode(const char *szProto, int status, HANDLE ) +{ + int index, i; + + for ( index = 0; index < SIZEOF(statusModeList); index++ ) + if ( status == statusModeList[index] ) + break; + + if ( index == SIZEOF(statusModeList)) + index = 0; + if (szProto == NULL) + return index + 1; + for ( i = 0; i < protoIconIndex.getCount(); i++ ) { + if (strcmp(szProto, protoIconIndex[i].szProto) == 0) + return protoIconIndex[i].iIconBase + index; + } + return 1; +} + +static INT_PTR GetContactIcon(WPARAM wParam, LPARAM) +{ + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + HANDLE hContact = (HANDLE)wParam; + + return cli.pfnIconFromStatusMode(szProto, + szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE), hContact); +} + +static void AddProtoIconIndex( PROTOACCOUNT* pa ) +{ + ProtoIconIndex *pii = new ProtoIconIndex; + pii->szProto = pa->szModuleName; + for (int i = 0; i < SIZEOF(statusModeList); i++) { + int iImg = ImageList_AddIcon_ProtoIconLibLoaded(hCListImages, pa->szModuleName, statusModeList[i] ); + if (i == 0) + pii->iIconBase = iImg; + } + protoIconIndex.insert(pii); +} + +static void RemoveProtoIconIndex( PROTOACCOUNT* pa ) +{ + for (int i = 0; i < protoIconIndex.getCount(); i++) + if (strcmp(protoIconIndex[i].szProto, pa->szModuleName) == 0) { + protoIconIndex.remove(i); + break; + } +} + +static int ContactListModulesLoaded(WPARAM, LPARAM) +{ + if ( !ServiceExists( MS_DB_CONTACT_GETSETTING_STR )) { + MessageBox( NULL, TranslateT( "This plugin requires db3x plugin version 0.5.1.0 or later" ), _T("CList"), MB_OK ); + return 1; + } + + RebuildMenuOrder(); + for (int i = 0; i < accounts.getCount(); i++) + AddProtoIconIndex( accounts[i] ); + + cli.pfnLoadContactTree(); + + LoadCLUIModule(); + + InitClistHotKeys(); + + return 0; +} + +static int ContactListAccountsChanged( WPARAM eventCode, LPARAM lParam ) +{ + switch (eventCode) + { + case PRAC_ADDED: + AddProtoIconIndex(( PROTOACCOUNT* )lParam ); + break; + + case PRAC_REMOVED: + RemoveProtoIconIndex(( PROTOACCOUNT* )lParam ); + break; + } + cli.pfnReloadProtoMenus(); + cli.pfnTrayIconIconsChanged(); + cli.pfnClcBroadcast( INTM_RELOADOPTIONS, 0, 0 ); + cli.pfnClcBroadcast( INTM_INVALIDATE, 0, 0 ); + return 0; +} + +static INT_PTR ContactDoubleClicked(WPARAM wParam, LPARAM) +{ + // Try to process event myself + if ( cli.pfnEventsProcessContactDoubleClick(( HANDLE )wParam ) == 0 ) + return 0; + + // Allow third-party plugins to process a dblclick + if ( NotifyEventHooks( hContactDoubleClicked, wParam, 0 )) + return 0; + + // Otherwise try to execute the default action + TryProcessDoubleClick(( HANDLE )wParam ); + return 0; +} + +static INT_PTR GetIconsImageList(WPARAM, LPARAM) +{ + return (INT_PTR)hCListImages; +} + +static INT_PTR ContactFilesDropped(WPARAM wParam, LPARAM lParam) +{ + CallService(MS_FILE_SENDSPECIFICFILES, wParam, lParam); + return 0; +} + +static int CListIconsChanged(WPARAM, LPARAM) +{ + int i, j; + + for (i = 0; i < SIZEOF(statusModeList); i++) + ImageList_ReplaceIcon_IconLibLoaded(hCListImages, i + 1, LoadSkinIcon( skinIconStatusList[i] )); + ImageList_ReplaceIcon_IconLibLoaded(hCListImages, IMAGE_GROUPOPEN, LoadSkinIcon( SKINICON_OTHER_GROUPOPEN )); + ImageList_ReplaceIcon_IconLibLoaded(hCListImages, IMAGE_GROUPSHUT, LoadSkinIcon( SKINICON_OTHER_GROUPSHUT )); + for (i = 0; i < protoIconIndex.getCount(); i++) + for (j = 0; j < SIZEOF(statusModeList); j++) + ImageList_ReplaceIcon_IconLibLoaded(hCListImages, protoIconIndex[i].iIconBase + j, LoadSkinProtoIcon(protoIconIndex[i].szProto, statusModeList[j] )); + cli.pfnTrayIconIconsChanged(); + cli.pfnInvalidateRect( cli.hwndContactList, NULL, TRUE); + return 0; +} + +/* +Begin of Hrk's code for bug +*/ +#define GWVS_HIDDEN 1 +#define GWVS_VISIBLE 2 +#define GWVS_COVERED 3 +#define GWVS_PARTIALLY_COVERED 4 + +int fnGetWindowVisibleState(HWND hWnd, int iStepX, int iStepY) +{ + RECT rc, rcWin, rcWorkArea; + POINT pt; + register int i, j, width, height, iCountedDots = 0, iNotCoveredDots = 0; + BOOL bPartiallyCovered = FALSE; + HWND hAux = 0; + + if (hWnd == NULL) { + SetLastError(0x00000006); //Wrong handle + return -1; + } + //Some defaults now. The routine is designed for thin and tall windows. + if (iStepX <= 0) + iStepX = 4; + if (iStepY <= 0) + iStepY = 16; + + if (IsIconic(hWnd) || !IsWindowVisible(hWnd)) + return GWVS_HIDDEN; + else + { + if (CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) + return GWVS_VISIBLE; + + GetWindowRect(hWnd, &rcWin); + + SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, FALSE); + if (MyMonitorFromWindow) + { + HMONITOR hMon = MyMonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (MyGetMonitorInfo(hMon, &mi)) + rcWorkArea = mi.rcWork; + } + + IntersectRect(&rc, &rcWin, &rcWorkArea); + + width = rc.right - rc.left; + height = rc.bottom - rc.top; + + for (i = rc.top; i < rc.bottom; i += (height / iStepY)) { + pt.y = i; + for (j = rc.left; j < rc.right; j += (width / iStepX)) { + pt.x = j; + hAux = WindowFromPoint(pt); + while (GetParent(hAux) != NULL) + hAux = GetParent(hAux); + if (hAux != hWnd && hAux != NULL) //There's another window! + bPartiallyCovered = TRUE; + else + iNotCoveredDots++; //Let's count the not covered dots. + iCountedDots++; //Let's keep track of how many dots we checked. + } + } + if (iNotCoveredDots == iCountedDots) //Every dot was not covered: the window is visible. + return GWVS_VISIBLE; + else if (iNotCoveredDots == 0) //They're all covered! + return GWVS_COVERED; + else //There are dots which are visible, but they are not as many as the ones we counted: it's partially covered. + return GWVS_PARTIALLY_COVERED; + } +} + +int fnShowHide(WPARAM, LPARAM) +{ + BOOL bShow = FALSE; + + int iVisibleState = cli.pfnGetWindowVisibleState(cli.hwndContactList, 0, 0); + + //bShow is FALSE when we enter the switch. + switch (iVisibleState) { + case GWVS_PARTIALLY_COVERED: + //If we don't want to bring it to top, we can use a simple break. This goes against readability ;-) but the comment explains it. + if (!DBGetContactSettingByte(NULL, "CList", "BringToFront", SETTING_BRINGTOFRONT_DEFAULT)) + break; + case GWVS_COVERED: //Fall through (and we're already falling) + case GWVS_HIDDEN: + bShow = TRUE; + break; + case GWVS_VISIBLE: //This is not needed, but goes for readability. + bShow = FALSE; + break; + case -1: //We can't get here, both cli.hwndContactList and iStepX and iStepY are right. + return 0; + } + if (bShow == TRUE) { + RECT rcWindow; + + ShowWindow(cli.hwndContactList, SW_RESTORE); + if (!DBGetContactSettingByte(NULL, "CList", "OnTop", SETTING_ONTOP_DEFAULT)) + SetWindowPos(cli.hwndContactList, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + else + SetWindowPos(cli.hwndContactList, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + + SetForegroundWindow(cli.hwndContactList); + DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_NORMAL); + + //this forces the window onto the visible screen + GetWindowRect(cli.hwndContactList, &rcWindow); + if (Utils_AssertInsideScreen(&rcWindow) == 1) + { + MoveWindow(cli.hwndContactList, rcWindow.left, rcWindow.top, + rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, TRUE); + } + } + else { //It needs to be hidden + if (DBGetContactSettingByte(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) || + DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT)) + { + ShowWindow(cli.hwndContactList, SW_HIDE); + DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_HIDDEN); + } + else + { + ShowWindow(cli.hwndContactList, SW_MINIMIZE); + DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_MINIMIZED); + } + + if (MySetProcessWorkingSetSize != NULL && DBGetContactSettingByte(NULL, "CList", "DisableWorkingSet", 1)) + MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1); + } + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// old evil code. hopefully it will be deleted soon, cause nobody uses it now + +#define SAFESTRING(a) a?a:"" + +int GetStatusModeOrdering(int statusMode); +extern int sortByStatus, sortByProto; + +static INT_PTR CompareContacts( WPARAM wParam, LPARAM lParam ) +{ + HANDLE a = (HANDLE) wParam, b = (HANDLE) lParam; + TCHAR namea[128], *nameb; + int statusa, statusb; + char *szProto1, *szProto2; + int rc; + + szProto1 = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) a, 0); + szProto2 = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) b, 0); + statusa = DBGetContactSettingWord((HANDLE) a, SAFESTRING(szProto1), "Status", ID_STATUS_OFFLINE); + statusb = DBGetContactSettingWord((HANDLE) b, SAFESTRING(szProto2), "Status", ID_STATUS_OFFLINE); + + if (sortByProto) { + /* deal with statuses, online contacts have to go above offline */ + if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) { + return 2 * (statusa == ID_STATUS_OFFLINE) - 1; + } + /* both are online, now check protocols */ + rc = strcmp(SAFESTRING(szProto1), SAFESTRING(szProto2)); /* strcmp() doesn't like NULL so feed in "" as needed */ + if (rc != 0 && (szProto1 != NULL && szProto2 != NULL)) + return rc; + /* protocols are the same, order by display name */ + } + + if (sortByStatus) { + int ordera, orderb; + ordera = GetStatusModeOrdering(statusa); + orderb = GetStatusModeOrdering(statusb); + if (ordera != orderb) + return ordera - orderb; + } + else { + //one is offline: offline goes below online + if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) { + return 2 * (statusa == ID_STATUS_OFFLINE) - 1; + } + } + + nameb = cli.pfnGetContactDisplayName( a, 0); + _tcsncpy(namea, nameb, SIZEOF(namea)); + namea[ SIZEOF(namea)-1 ] = 0; + nameb = cli.pfnGetContactDisplayName( b, 0); + + //otherwise just compare names + return _tcsicmp(namea, nameb); +} + +/***************************************************************************************/ + +static INT_PTR TrayIconProcessMessageStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnTrayIconProcessMessage( wParam, lParam ); } +static INT_PTR TrayIconPauseAutoHideStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnTrayIconPauseAutoHide( wParam, lParam ); } +static INT_PTR ShowHideStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnShowHide( wParam, lParam ); } +static INT_PTR SetHideOfflineStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnSetHideOffline( wParam, lParam ); } +static INT_PTR Docking_ProcessWindowMessageStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnDocking_ProcessWindowMessage( wParam, lParam ); } +static INT_PTR HotkeysProcessMessageStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnHotkeysProcessMessage( wParam, lParam ); } + +int LoadContactListModule2(void) +{ + HookEvent(ME_SYSTEM_MODULESLOADED, ContactListModulesLoaded); + HookEvent(ME_PROTO_ACCLISTCHANGED, ContactListAccountsChanged); + hContactSettingChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChanged); + HookEvent(ME_DB_CONTACT_ADDED, ContactAdded); + HookEvent(ME_DB_CONTACT_DELETED, ContactDeleted); + hProtoAckHook = (HANDLE) HookEvent(ME_PROTO_ACK, ProtocolAck); + hContactDoubleClicked = CreateHookableEvent(ME_CLIST_DOUBLECLICKED); + hContactIconChangedEvent = CreateHookableEvent(ME_CLIST_CONTACTICONCHANGED); + CreateServiceFunction(MS_CLIST_CONTACTDOUBLECLICKED, ContactDoubleClicked); + CreateServiceFunction(MS_CLIST_CONTACTFILESDROPPED, ContactFilesDropped); + CreateServiceFunction(MS_CLIST_GETSTATUSMODEDESCRIPTION, GetStatusModeDescription); + CreateServiceFunction(MS_CLIST_GETCONTACTDISPLAYNAME, GetContactDisplayName); + CreateServiceFunction(MS_CLIST_INVALIDATEDISPLAYNAME, InvalidateDisplayName); + CreateServiceFunction(MS_CLIST_TRAYICONPROCESSMESSAGE, TrayIconProcessMessageStub ); + CreateServiceFunction(MS_CLIST_PAUSEAUTOHIDE, TrayIconPauseAutoHideStub); + CreateServiceFunction(MS_CLIST_CONTACTSCOMPARE, CompareContacts); + CreateServiceFunction(MS_CLIST_CONTACTCHANGEGROUP, ContactChangeGroup); + CreateServiceFunction(MS_CLIST_SHOWHIDE, ShowHideStub); + CreateServiceFunction(MS_CLIST_SETHIDEOFFLINE, SetHideOfflineStub); + CreateServiceFunction(MS_CLIST_DOCKINGPROCESSMESSAGE, Docking_ProcessWindowMessageStub); + CreateServiceFunction(MS_CLIST_DOCKINGISDOCKED, Docking_IsDocked); + CreateServiceFunction(MS_CLIST_HOTKEYSPROCESSMESSAGE, HotkeysProcessMessageStub); + CreateServiceFunction(MS_CLIST_GETCONTACTICON, GetContactIcon); + MySetProcessWorkingSetSize = (BOOL(WINAPI *) (HANDLE, SIZE_T, SIZE_T)) GetProcAddress(GetModuleHandleA("kernel32"), "SetProcessWorkingSetSize"); + InitDisplayNameCache(); + InitCListEvents(); + InitGroupServices(); + cli.pfnInitTray(); + + hCListImages = ImageList_Create(16, 16, ILC_MASK | (IsWinVerXPPlus()? ILC_COLOR32 : ILC_COLOR16), 13, 0); + HookEvent(ME_SKIN_ICONSCHANGED, CListIconsChanged); + CreateServiceFunction(MS_CLIST_GETICONSIMAGELIST, GetIconsImageList); + + ImageList_AddIcon_NotShared(hCListImages, MAKEINTRESOURCE(IDI_BLANK)); + + { + int i; + //now all core skin icons are loaded via icon lib. so lets release them + for (i = 0; i < SIZEOF(statusModeList); i++) + ImageList_AddIcon_IconLibLoaded(hCListImages, skinIconStatusList[i] ); + } + + //see IMAGE_GROUP... in clist.h if you add more images above here + ImageList_AddIcon_IconLibLoaded(hCListImages, SKINICON_OTHER_GROUPOPEN ); + ImageList_AddIcon_IconLibLoaded(hCListImages, SKINICON_OTHER_GROUPSHUT ); + return 0; +} + +void UnloadContactListModule() +{ + if ( hCListImages ) { + //remove transitory contacts + HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while (hContact != NULL) { + HANDLE hNext = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0); + if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0); + hContact = hNext; + } + ImageList_Destroy(hCListImages); + UnhookEvent(hProtoAckHook); + UninitCListEvents(); + protoIconIndex.destroy(); + DestroyHookableEvent(hContactDoubleClicked); + UnhookEvent(hContactSettingChanged); +} } diff --git a/src/modules/clist/clistsettings.cpp b/src/modules/clist/clistsettings.cpp new file mode 100644 index 0000000000..df92386ded --- /dev/null +++ b/src/modules/clist/clistsettings.cpp @@ -0,0 +1,331 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +SortedList* clistCache = NULL; + +static int compareContacts( ClcCacheEntryBase* p1, ClcCacheEntryBase* p2 ) +{ + return ( char* )p1->hContact - ( char* )p2->hContact; +} + +void InitDisplayNameCache(void) +{ + clistCache = List_Create( 0, 50 ); + clistCache->sortFunc = ( FSortFunc )compareContacts; +} + +void FreeDisplayNameCache(void) +{ + if ( clistCache != NULL ) { + int i; + for ( i = 0; i < clistCache->realCount; i++) { + cli.pfnFreeCacheItem(( ClcCacheEntryBase* )clistCache->items[i] ); + mir_free( clistCache->items[i] ); + } + + List_Destroy( clistCache ); + mir_free(clistCache); + clistCache = NULL; +} } + +// default handlers for the cache item creation and destruction + +ClcCacheEntryBase* fnCreateCacheItem( HANDLE hContact ) +{ + ClcCacheEntryBase* p = ( ClcCacheEntryBase* )mir_calloc( sizeof( ClcCacheEntryBase )); + if ( p == NULL ) + return NULL; + + p->hContact = hContact; + return p; +} + +void fnCheckCacheItem( ClcCacheEntryBase* p ) +{ + DBVARIANT dbv; + if ( p->group == NULL ) { + if ( !DBGetContactSettingTString( p->hContact, "CList", "Group", &dbv )) { + p->group = mir_tstrdup( dbv.ptszVal ); + mir_free( dbv.ptszVal ); + } + else p->group = mir_tstrdup( _T("") ); + } + + if ( p->isHidden == -1 ) + p->isHidden = DBGetContactSettingByte( p->hContact, "CList", "Hidden", 0 ); +} + +void fnFreeCacheItem( ClcCacheEntryBase* p ) +{ + if ( p->name ) { mir_free( p->name ); p->name = NULL; } + #if defined( _UNICODE ) + if ( p->szName ) { mir_free( p->szName); p->szName = NULL; } + #endif + if ( p->group ) { mir_free( p->group ); p->group = NULL; } + p->isHidden = -1; +} + +ClcCacheEntryBase* fnGetCacheEntry(HANDLE hContact) +{ + ClcCacheEntryBase* p; + int idx; + if ( !List_GetIndex( clistCache, &hContact, &idx )) { + if (( p = cli.pfnCreateCacheItem( hContact )) != NULL ) { + List_Insert( clistCache, p, idx ); + cli.pfnInvalidateDisplayNameCacheEntry( p ); + } + } + else p = ( ClcCacheEntryBase* )clistCache->items[idx]; + + cli.pfnCheckCacheItem( p ); + return p; +} + +void fnInvalidateDisplayNameCacheEntry(HANDLE hContact) +{ + if (hContact == INVALID_HANDLE_VALUE) { + FreeDisplayNameCache(); + InitDisplayNameCache(); + SendMessage(cli.hwndContactTree, CLM_AUTOREBUILD, 0, 0); + } + else { + int idx; + if ( List_GetIndex( clistCache, &hContact, &idx )) + cli.pfnFreeCacheItem(( ClcCacheEntryBase* )clistCache->items[idx] ); +} } + +TCHAR* fnGetContactDisplayName( HANDLE hContact, int mode ) +{ + CONTACTINFO ci; + TCHAR *buffer; + ClcCacheEntryBase* cacheEntry = NULL; + + if ( mode & GCDNF_NOCACHE ) + mode &= ~GCDNF_NOCACHE; + else if ( mode != GCDNF_NOMYHANDLE) { + cacheEntry = cli.pfnGetCacheEntry( hContact ); + if ( cacheEntry->name ) + return cacheEntry->name; + } + ZeroMemory(&ci, sizeof(ci)); + ci.cbSize = sizeof(ci); + ci.hContact = hContact; + if (ci.hContact == NULL) + ci.szProto = "ICQ"; + ci.dwFlag = ((mode == GCDNF_NOMYHANDLE) ? CNF_DISPLAYNC : CNF_DISPLAY) | CNF_TCHAR; + if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) { + if (ci.type == CNFT_ASCIIZ) { + if (cacheEntry == NULL) + return ci.pszVal; + + cacheEntry->name = ci.pszVal; + #if defined( _UNICODE ) + cacheEntry->szName = mir_u2a( ci.pszVal ); + #endif + return ci.pszVal; + } + + if (ci.type == CNFT_DWORD) { + if (cacheEntry == NULL) { + buffer = (TCHAR*) mir_alloc(15 * sizeof( TCHAR )); + _ltot(ci.dVal, buffer, 10 ); + return buffer; + } + else { + buffer = (TCHAR*) mir_alloc(15 * sizeof( TCHAR )); + _ltot(ci.dVal, buffer, 10 ); + cacheEntry->name = buffer; + #if defined( _UNICODE ) + cacheEntry->szName = mir_u2a( buffer ); + #endif + return buffer; + } } } + + CallContactService(hContact, PSS_GETINFO, SGIF_MINIMAL, 0); + buffer = TranslateT("(Unknown Contact)"); + return ( cacheEntry == NULL ) ? mir_tstrdup( buffer ) : buffer; +} + +INT_PTR GetContactDisplayName(WPARAM wParam, LPARAM lParam) +{ + CONTACTINFO ci; + ClcCacheEntryBase* cacheEntry = NULL; + char *buffer; + HANDLE hContact = (HANDLE)wParam; + + if ( lParam & GCDNF_UNICODE ) + return ( INT_PTR )cli.pfnGetContactDisplayName(hContact, lParam & ~GCDNF_UNICODE ); + + if ((int) lParam != GCDNF_NOMYHANDLE) { + cacheEntry = cli.pfnGetCacheEntry(hContact); + #if defined( _UNICODE ) + if ( cacheEntry->szName ) + return (INT_PTR)cacheEntry->szName; + #else + if ( cacheEntry->name ) + return (INT_PTR)cacheEntry->name; + #endif + } + ZeroMemory(&ci, sizeof(ci)); + ci.cbSize = sizeof(ci); + ci.hContact = hContact; + if (ci.hContact == NULL) + ci.szProto = "ICQ"; + ci.dwFlag = ((lParam == GCDNF_NOMYHANDLE) ? CNF_DISPLAYNC : CNF_DISPLAY) | CNF_TCHAR; + if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) { + if (ci.type == CNFT_ASCIIZ) { + if (cacheEntry == NULL) { + #if defined( _UNICODE ) + buffer = mir_u2a( ci.pszVal ); + mir_free(ci.pszVal); + #else + buffer = ci.pszVal; + #endif + return (INT_PTR) buffer; + } + else { + cacheEntry->name = ci.pszVal; + #if defined( _UNICODE ) + cacheEntry->szName = mir_u2a( ci.pszVal ); + return (INT_PTR)cacheEntry->szName; + #else + return (INT_PTR)cacheEntry->name; + #endif + } + } + if (ci.type == CNFT_DWORD) { + if (cacheEntry == NULL) { + buffer = ( char* )mir_alloc(15); + _ltoa(ci.dVal, buffer, 10 ); + return (INT_PTR) buffer; + } + else { + buffer = ( char* )mir_alloc(15); + _ltoa(ci.dVal, buffer, 10 ); + #if defined( _UNICODE ) + cacheEntry->szName = buffer; + cacheEntry->name = mir_a2u( buffer ); + #else + cacheEntry->name = buffer; + #endif + return (INT_PTR) buffer; + } } } + + CallContactService(hContact, PSS_GETINFO, SGIF_MINIMAL, 0); + buffer = Translate("(Unknown Contact)"); + return (INT_PTR) buffer; +} + +INT_PTR InvalidateDisplayName(WPARAM wParam, LPARAM) +{ + cli.pfnInvalidateDisplayNameCacheEntry((HANDLE)wParam); + return 0; +} + +int ContactAdded(WPARAM wParam, LPARAM) +{ + cli.pfnChangeContactIcon((HANDLE)wParam, cli.pfnIconFromStatusMode((char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0), ID_STATUS_OFFLINE, NULL), 1); + cli.pfnSortContacts(); + return 0; +} + +int ContactDeleted(WPARAM wParam, LPARAM) +{ + CallService(MS_CLUI_CONTACTDELETED, wParam, 0); + return 0; +} + +int ContactSettingChanged(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam; + DBVARIANT dbv; + HANDLE hContact = (HANDLE)wParam; + + // Early exit + if ( hContact == NULL) + return 0; + + dbv.pszVal = NULL; + if (!DBGetContactSetting(hContact, "Protocol", "p", &dbv)) { + if (!strcmp(cws->szModule, dbv.pszVal)) { + cli.pfnInvalidateDisplayNameCacheEntry(hContact); + if (!strcmp(cws->szSetting, "UIN") || !strcmp(cws->szSetting, "Nick") || !strcmp(cws->szSetting, "FirstName") + || !strcmp(cws->szSetting, "LastName") || !strcmp(cws->szSetting, "e-mail")) { + CallService(MS_CLUI_CONTACTRENAMED, wParam, 0); + } + else if (!strcmp(cws->szSetting, "Status")) { + if (!DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) { + if (DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT)) { + // User's state is changing, and we are hideOffline-ing + if (cws->value.wVal == ID_STATUS_OFFLINE) { + cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(cws->szModule, cws->value.wVal, hContact), 0); + CallService(MS_CLUI_CONTACTDELETED, wParam, 0); + mir_free(dbv.pszVal); + return 0; + } + cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(cws->szModule, cws->value.wVal, hContact), 1); + } + cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(cws->szModule, cws->value.wVal, hContact), 0); + } + } + else { + mir_free(dbv.pszVal); + return 0; + } + cli.pfnSortContacts(); + } } + + if (!strcmp(cws->szModule, "CList")) { + if (!strcmp(cws->szSetting, "Hidden")) { + if (cws->value.type == DBVT_DELETED || cws->value.bVal == 0) { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(szProto, szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE), hContact), 1); + } + else + CallService(MS_CLUI_CONTACTDELETED, wParam, 0); + } + if (!strcmp(cws->szSetting, "MyHandle")) + cli.pfnInvalidateDisplayNameCacheEntry(hContact); + } + + if (!strcmp(cws->szModule, "Protocol")) { + if (!strcmp(cws->szSetting, "p")) { + char *szProto; + if (cws->value.type == DBVT_DELETED) + szProto = NULL; + else + szProto = cws->value.pszVal; + cli.pfnChangeContactIcon(hContact, + cli.pfnIconFromStatusMode(szProto, + szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord(hContact, szProto, "Status", + ID_STATUS_OFFLINE), hContact), 0); + } } + + // Clean up + if (dbv.pszVal) + mir_free(dbv.pszVal); + + return 0; +} diff --git a/src/modules/clist/clisttray.cpp b/src/modules/clist/clisttray.cpp new file mode 100644 index 0000000000..2292020c48 --- /dev/null +++ b/src/modules/clist/clisttray.cpp @@ -0,0 +1,980 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +#define TOOLTIP_TOLERANCE 5 + +extern HIMAGELIST hCListImages; +extern BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T); + +int GetAverageMode(int* pNetProtoCount = NULL); + +static UINT WM_TASKBARCREATED; +static UINT WM_TASKBARBUTTONCREATED; +static BOOL mToolTipTrayTips = FALSE; +static UINT_PTR cycleTimerId = 0; +static int cycleStep = 0; +static UINT_PTR RefreshTimerId=0; /////by FYR +static CRITICAL_SECTION trayLockCS; + +// don't move to win2k.h, need new and old versions to work on 9x/2000/XP +#define NIF_STATE 0x00000008 +#define NIF_INFO 0x00000010 + +#define lock cli.pfnLockTray() +#define ulock cli.pfnUnlockTray() + +#define initcheck if(!fTrayInited) return + +static BOOL fTrayInited=FALSE; + +static TCHAR* sttGetXStatus( const char* szProto ) +{ + TCHAR* result = NULL; + + if ( CallProtoService( szProto, PS_GETSTATUS, 0, 0 ) > ID_STATUS_OFFLINE ) { + char str[MAXMODULELABELLENGTH]; + mir_snprintf( str, sizeof(str), "%s/GetXStatus", szProto ); + if ( ServiceExists( str )) { + char* dbTitle = "XStatusName"; + char* dbTitle2 = NULL; + int xstatus = CallProtoService( szProto, "/GetXStatus", ( WPARAM )&dbTitle, ( LPARAM )&dbTitle2 ); + if ( dbTitle && xstatus ) { + DBVARIANT dbv={0}; + if ( !DBGetContactSettingTString(NULL, szProto, dbTitle, &dbv )) { + if ( dbv.ptszVal[0] != 0 ) + result = mir_tstrdup(dbv.ptszVal); + DBFreeVariant(&dbv); + } } } } + + return result; +} + +static HICON lastTaskBarIcon; +static void SetTaskBarIcon(const HICON hIcon, const TCHAR *szNewTip) +{ + if (pTaskbarInterface) + { + wchar_t *szTip = mir_t2u(szNewTip); + pTaskbarInterface->SetOverlayIcon(cli.hwndContactList, hIcon, szTip); + mir_free(szTip); + lastTaskBarIcon = hIcon; + } +} + +TCHAR* fnTrayIconMakeTooltip( const TCHAR *szPrefix, const char *szProto ) +{ + TCHAR *szStatus, *szSeparator; + TCHAR *ProtoXStatus=NULL; + int t; + PROTOACCOUNT* pa; + initcheck NULL; + lock; + if ( !mToolTipTrayTips ) + szSeparator = (IsWinVerMEPlus()) ? szSeparator = _T("\n") : _T(" | "); + else + szSeparator = _T("\n"); + + if (szProto == NULL) { + if (accounts.getCount() == 0) { + ulock; + return NULL; + } + if (accounts.getCount() == 1) { + ulock; + return cli.pfnTrayIconMakeTooltip(szPrefix, accounts[0]->szModuleName); + } + + if (szPrefix && szPrefix[0]) { + lstrcpyn(cli.szTip, szPrefix, MAX_TIP_SIZE); + if (!DBGetContactSettingByte(NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT)) + { ulock; return cli.szTip; } + } + else cli.szTip[0] = '\0'; + cli.szTip[ MAX_TIP_SIZE-1 ] = '\0'; + + for ( t = 0; t < accounts.getCount(); t++ ) { + int i = cli.pfnGetAccountIndexByPos( t ); + if ( i == -1 ) + continue; + + pa = accounts[i]; + if ( !cli.pfnGetProtocolVisibility( pa->szModuleName )) + continue; + + szStatus = cli.pfnGetStatusModeDescription( CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0), 0); + if ( !szStatus ) + continue; + + ProtoXStatus = sttGetXStatus( pa->szModuleName ); + + if ( mToolTipTrayTips ) { + TCHAR tipline[256]; + mir_sntprintf(tipline, SIZEOF(tipline), _T("%-12.12s\t%s"), pa->tszAccountName, szStatus); + if ( cli.szTip[0] ) + _tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip)); + _tcsncat(cli.szTip, tipline, MAX_TIP_SIZE - _tcslen(cli.szTip)); + if (ProtoXStatus) { + mir_sntprintf(tipline, SIZEOF(tipline), _T("%-24.24s\n"), ProtoXStatus); + if ( cli.szTip[0] ) + _tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip)); + _tcsncat(cli.szTip, tipline, MAX_TIP_SIZE - _tcslen(cli.szTip)); + } + } + else { + if (cli.szTip[0]) + _tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip)); + + _tcsncat(cli.szTip, pa->tszAccountName, MAX_TIP_SIZE - _tcslen(cli.szTip)); + _tcsncat(cli.szTip, _T(" "), MAX_TIP_SIZE - _tcslen(cli.szTip)); + _tcsncat(cli.szTip, szStatus, MAX_TIP_SIZE - _tcslen(cli.szTip)); + } + mir_free( ProtoXStatus ); + } + } + else { + if (( pa = Proto_GetAccount( szProto )) != NULL ) { + ProtoXStatus = sttGetXStatus( szProto ); + szStatus = cli.pfnGetStatusModeDescription(CallProtoService(szProto, PS_GETSTATUS, 0, 0), 0); + if ( szPrefix && szPrefix[0] ) { + if ( DBGetContactSettingByte( NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT )) { + if ( mToolTipTrayTips ) { + if ( ProtoXStatus ) + mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s%-12.12s\t%s%s%-24.24s"), szPrefix, szSeparator, pa->tszAccountName, szStatus,szSeparator,ProtoXStatus); + else + mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s%-12.12s\t%s"), szPrefix, szSeparator, pa->tszAccountName, szStatus); + } + else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s%s %s"), szPrefix, szSeparator, pa->tszAccountName, szStatus); + } + else lstrcpyn(cli.szTip, szPrefix, MAX_TIP_SIZE); + } + else { + if ( mToolTipTrayTips ) { + if ( ProtoXStatus ) + mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("%-12.12s\t%s\n%-24.24s"), pa->tszAccountName, szStatus,ProtoXStatus); + else + mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("%-12.12s\t%s"), pa->tszAccountName, szStatus); + } + else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s %s"), pa->tszAccountName, szStatus); + } + mir_free(ProtoXStatus); + } } + + ulock; + return cli.szTip; +} + +int fnTrayIconAdd(HWND hwnd, const char *szProto, const char *szIconProto, int status) +{ + NOTIFYICONDATA nid = { 0 }; + int i; + initcheck 0; + lock; + for (i = 0; i < cli.trayIconCount; i++) + if (cli.trayIcon[i].id == 0) + break; + + cli.trayIcon[i].id = TRAYICON_ID_BASE + i; + cli.trayIcon[i].szProto = (char *) szProto; + cli.trayIcon[i].hBaseIcon = cli.pfnGetIconFromStatusMode( NULL, szIconProto ? szIconProto : cli.trayIcon[i].szProto, status ); + + nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE; + nid.hWnd = hwnd; + nid.uID = cli.trayIcon[i].id; + nid.uFlags = mToolTipTrayTips ? NIF_ICON | NIF_MESSAGE : NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = TIM_CALLBACK; + nid.hIcon = cli.trayIcon[i].hBaseIcon; + + if (cli.shellVersion >= 5) + nid.uFlags |= NIF_INFO; + + cli.pfnTrayIconMakeTooltip( NULL, cli.trayIcon[i].szProto ); + if ( !mToolTipTrayTips ) + lstrcpyn( nid.szTip, cli.szTip, SIZEOF( nid.szTip )); + cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip ); + + Shell_NotifyIcon(NIM_ADD, &nid); + cli.trayIcon[i].isBase = 1; + + if (cli.trayIconCount == 1) + SetTaskBarIcon(cli.trayIcon[0].hBaseIcon, cli.szTip); + + ulock; return i; +} + +void fnTrayIconRemove(HWND hwnd, const char *szProto) +{ + int i; + initcheck; + lock; + for ( i = 0; i < cli.trayIconCount; i++ ) { + struct trayIconInfo_t* pii = &cli.trayIcon[i]; + if ( pii->id != 0 && !lstrcmpA( szProto, pii->szProto )) { + NOTIFYICONDATA nid = { 0 }; + nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE; + nid.hWnd = hwnd; + nid.uID = pii->id; + Shell_NotifyIcon(NIM_DELETE, &nid); + + DestroyIcon(pii->hBaseIcon); + mir_free(pii->ptszToolTip); pii->ptszToolTip = NULL; + pii->id = 0; + break; + } } + + if (cli.trayIconCount == 1) + SetTaskBarIcon(NULL, NULL); + + ulock; +} + +int fnTrayIconInit(HWND hwnd) +{ + int netProtoCount = 0; + initcheck 0; + lock; + + int averageMode = GetAverageMode(&netProtoCount); + mToolTipTrayTips = ServiceExists("mToolTip/ShowTip") != 0; + + if ( cli.cycleTimerId ) { + KillTimer(NULL, cli.cycleTimerId); + cli.cycleTimerId = 0; + } + + cli.trayIconCount = 1; + + if (netProtoCount) + { + cli.trayIcon = (trayIconInfo_t *) mir_calloc(sizeof(trayIconInfo_t) * accounts.getCount()); + + int trayIconSetting = DBGetContactSettingByte(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT); + + if (trayIconSetting == SETTING_TRAYICON_SINGLE) + { + DBVARIANT dbv = { DBVT_DELETED }; + char *szProto; + if (!DBGetContactSettingString(NULL, "CList", "PrimaryStatus", &dbv) + && (averageMode < 0 || DBGetContactSettingByte(NULL, "CList", "AlwaysPrimary", 0) )) + szProto = dbv.pszVal; + else + szProto = NULL; + + cli.pfnTrayIconAdd(hwnd, NULL, szProto, szProto ? CallProtoService(szProto, PS_GETSTATUS, 0, 0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0)); + DBFreeVariant(&dbv); + } + else if (trayIconSetting == SETTING_TRAYICON_MULTI && + (averageMode < 0 || DBGetContactSettingByte(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT ))) + { + cli.trayIconCount = netProtoCount; + for (int i = 0; i < accounts.getCount(); ++i) + { + int j = cli.pfnGetAccountIndexByPos(i); + if (j >= 0) + { + PROTOACCOUNT* pa = accounts[j]; + if (cli.pfnGetProtocolVisibility(pa->szModuleName)) + cli.pfnTrayIconAdd(hwnd, pa->szModuleName, NULL, CallProtoService(pa->szModuleName, PS_GETSTATUS, 0, 0)); + } + } + } + else + { + cli.pfnTrayIconAdd(hwnd, NULL, NULL, averageMode); + + if (trayIconSetting == SETTING_TRAYICON_CYCLE && averageMode < 0) + cli.cycleTimerId = SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc); + } + } + else + { + cli.trayIcon = (trayIconInfo_t *) mir_calloc(sizeof(trayIconInfo_t)); + cli.pfnTrayIconAdd(hwnd, NULL, NULL, CallService(MS_CLIST_GETSTATUSMODE, 0, 0)); + } + + ulock; + return 0; +} + +int fnTrayIconDestroy(HWND hwnd) +{ + NOTIFYICONDATA nid = { 0 }; + int i; + initcheck 0; + lock; + + if (cli.trayIconCount == 1) + SetTaskBarIcon(NULL, NULL); + + nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE; + nid.hWnd = hwnd; + for ( i = 0; i < cli.trayIconCount; i++ ) { + if ( cli.trayIcon[i].id == 0 ) + continue; + nid.uID = cli.trayIcon[i].id; + Shell_NotifyIcon( NIM_DELETE, &nid ); + DestroyIcon( cli.trayIcon[i].hBaseIcon ); + mir_free( cli.trayIcon[i].ptszToolTip ); + } + mir_free(cli.trayIcon); + cli.trayIcon = NULL; + cli.trayIconCount = 0; + + ulock; + return 0; +} + +//called when Explorer crashes and the taskbar is remade +void fnTrayIconTaskbarCreated(HWND hwnd) +{ + initcheck; + cli.pfnTrayIconDestroy(hwnd); + cli.pfnTrayIconInit(hwnd); +} + +static VOID CALLBACK RefreshTimerProc(HWND, UINT, UINT_PTR, DWORD) +{ + int i; + if ( RefreshTimerId ) { + KillTimer(NULL,RefreshTimerId); + RefreshTimerId=0; + } + for (i=0; i < accounts.getCount(); i++) { + cli.pfnTrayIconUpdateBase( accounts[i]->szModuleName ); + } +} + +int fnTrayIconUpdate(HICON hNewIcon, const TCHAR *szNewTip, const char *szPreferredProto, int isBase) +{ + NOTIFYICONDATA nid = { 0 }; + int i; + + initcheck -1; + lock; + nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE; + nid.hWnd = cli.hwndContactList; + nid.uFlags = mToolTipTrayTips ? NIF_ICON : NIF_ICON | NIF_TIP; + nid.hIcon = hNewIcon; + if (!hNewIcon) + { ulock; return -1; } + + for (i = 0; i < cli.trayIconCount; i++) { + if (cli.trayIcon[i].id == 0) + continue; + if (lstrcmpA(cli.trayIcon[i].szProto, szPreferredProto)) + continue; + + nid.uID = cli.trayIcon[i].id; + cli.pfnTrayIconMakeTooltip(szNewTip, cli.trayIcon[i].szProto); + mir_free( cli.trayIcon[i].ptszToolTip ); + cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip ); + if (!mToolTipTrayTips) + lstrcpyn(nid.szTip, cli.szTip, SIZEOF(nid.szTip)); + Shell_NotifyIcon(NIM_MODIFY, &nid); + + if (cli.trayIconCount == 1) + SetTaskBarIcon(hNewIcon, cli.szTip); + else + SetTaskBarIcon(NULL, NULL); + + cli.trayIcon[i].isBase = isBase; + { ulock; return i; } + } + + //if there wasn't a suitable icon, change all the icons + { + for (i = 0; i < cli.trayIconCount; i++) { + if (cli.trayIcon[i].id == 0) + continue; + nid.uID = cli.trayIcon[i].id; + + cli.pfnTrayIconMakeTooltip(szNewTip, cli.trayIcon[i].szProto); + mir_free( cli.trayIcon[i].ptszToolTip ); + cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip ); + if(!mToolTipTrayTips) + lstrcpyn(nid.szTip, cli.szTip, SIZEOF(nid.szTip)); + Shell_NotifyIcon(NIM_MODIFY, &nid); + + if (cli.trayIconCount == 1) + SetTaskBarIcon(hNewIcon, cli.szTip); + else + SetTaskBarIcon(NULL, NULL); + + cli.trayIcon[i].isBase = isBase; + if (DBGetContactSettingByte(NULL,"CList","TrayIcon",SETTING_TRAYICON_DEFAULT) == SETTING_TRAYICON_MULTI) + { + DWORD time1=DBGetContactSettingWord(NULL,"CList","CycleTime",SETTING_CYCLETIME_DEFAULT)*200; + DWORD time2=DBGetContactSettingWord(NULL,"CList","IconFlashTime",550)+1000; + DWORD time=max(max(2000,time1),time2); + if(RefreshTimerId) {KillTimer(NULL,RefreshTimerId); RefreshTimerId=0;} + RefreshTimerId=SetTimer(NULL,0,time,RefreshTimerProc); // if unknown base was changed - than show preffered proto icon for 2 sec and reset it to original one after timeout + } + { ulock; return i; } + } + } + { ulock; return -1; } +} + +int fnTrayIconSetBaseInfo(HICON hIcon, const char *szPreferredProto) +{ + int i; + initcheck -1; + lock; + if (szPreferredProto) + { + for (i = 0; i < cli.trayIconCount; i++) { + if (cli.trayIcon[i].id == 0) + continue; + if (lstrcmpA(cli.trayIcon[i].szProto, szPreferredProto)) + continue; + + DestroyIcon(cli.trayIcon[i].hBaseIcon); + cli.trayIcon[i].hBaseIcon = hIcon; + ulock; return i; + } + if ((cli.pfnGetProtocolVisibility(szPreferredProto)) + && (GetAverageMode()==-1) + && (DBGetContactSettingByte(NULL,"CList","TrayIcon",SETTING_TRAYICON_DEFAULT)==SETTING_TRAYICON_MULTI) + && !(DBGetContactSettingByte(NULL,"CList","AlwaysMulti",SETTING_ALWAYSMULTI_DEFAULT))) + goto LBL_Error; + } + + //if there wasn't a specific icon, there will only be one suitable + for (i = 0; i < cli.trayIconCount; i++) { + if (cli.trayIcon[i].id == 0) + continue; + + DestroyIcon(cli.trayIcon[i].hBaseIcon); + cli.trayIcon[i].hBaseIcon = hIcon; + ulock; return i; + } + +LBL_Error: + DestroyIcon(hIcon); + ulock; return -1; +} + +void fnTrayIconUpdateWithImageList(int iImage, const TCHAR *szNewTip, char *szPreferredProto) +{ + HICON hIcon = ImageList_GetIcon(hCListImages, iImage, ILD_NORMAL); + cli.pfnTrayIconUpdate(hIcon, szNewTip, szPreferredProto, 0); + DestroyIcon(hIcon); +} + +VOID CALLBACK fnTrayCycleTimerProc(HWND, UINT, UINT_PTR, DWORD) +{ + initcheck; + lock; + + int i; + for (i = accounts.getCount() + 1; --i;) { + cycleStep = (cycleStep + 1) % accounts.getCount(); + if ( cli.pfnGetProtocolVisibility( accounts[cycleStep]->szModuleName )) + break; + } + + if (i) + { + DestroyIcon(cli.trayIcon[0].hBaseIcon); + cli.trayIcon[0].hBaseIcon = cli.pfnGetIconFromStatusMode(NULL, accounts[cycleStep]->szModuleName, + CallProtoService( accounts[cycleStep]->szModuleName, PS_GETSTATUS, 0, 0 )); + if (cli.trayIcon[0].isBase) + cli.pfnTrayIconUpdate(cli.trayIcon[0].hBaseIcon, NULL, NULL, 1); + } + + ulock; +} + +void fnTrayIconUpdateBase(const char *szChangedProto) +{ + if ( !cli.pfnGetProtocolVisibility( szChangedProto )) return; + + int i, netProtoCount, changed = -1; + HWND hwnd = cli.hwndContactList; + initcheck; + lock; + int averageMode = GetAverageMode(&netProtoCount); + + if (cli.cycleTimerId) { + KillTimer(NULL, cli.cycleTimerId); + cli.cycleTimerId = 0; + } + + for (i = 0; i < accounts.getCount(); i++) { + if (!lstrcmpA(szChangedProto, accounts[i]->szModuleName )) + cycleStep = i - 1; + } + + if (netProtoCount > 0) + { + int trayIconSetting = DBGetContactSettingByte(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT); + + if (averageMode > 0) { + if (trayIconSetting == SETTING_TRAYICON_MULTI) { + if (DBGetContactSettingByte(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT)) + //changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode((char*)szChangedProto, NULL, averageMode), (char*)szChangedProto); + changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto ); + else if (cli.trayIcon && cli.trayIcon[0].szProto != NULL) { + cli.pfnTrayIconDestroy(hwnd); + cli.pfnTrayIconInit(hwnd); + } + else + changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL ); + } + else + changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL); + } + else { + switch (trayIconSetting) { + case SETTING_TRAYICON_SINGLE: + { + DBVARIANT dbv = { DBVT_DELETED }; + char *szProto; + if (DBGetContactSettingString(NULL, "CList", "PrimaryStatus", &dbv)) + szProto = NULL; + else + szProto = dbv.pszVal; + changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szProto, szProto ? CallProtoService(szProto, PS_GETSTATUS, 0,0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0)), szProto ); + DBFreeVariant(&dbv); + break; + } + case SETTING_TRAYICON_CYCLE: + cli.cycleTimerId = + SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc); + changed = + cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon + (hCListImages, cli.pfnIconFromStatusMode(szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0), NULL), + ILD_NORMAL), NULL); + break; + case SETTING_TRAYICON_MULTI: + if (!cli.trayIcon) { + cli.pfnTrayIconRemove(NULL, NULL); + } + else if ((cli.trayIconCount > 1 || netProtoCount == 1) || DBGetContactSettingByte( NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT )) + changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto ); + else { + cli.pfnTrayIconDestroy(hwnd); + cli.pfnTrayIconInit(hwnd); + } + break; + } + } + } + else + changed = cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon(hCListImages, cli.pfnIconFromStatusMode(NULL, averageMode, NULL), ILD_NORMAL), NULL); + + if (changed != -1 && cli.trayIcon[changed].isBase) + cli.pfnTrayIconUpdate(cli.trayIcon[changed].hBaseIcon, NULL, cli.trayIcon[changed].szProto, 1); + ulock; +} + +void fnTrayIconSetToBase(char *szPreferredProto) +{ + int i; + initcheck; + lock; + + for (i = 0; i < cli.trayIconCount; i++) { + if ( cli.trayIcon[i].id == 0 ) + continue; + if ( lstrcmpA( cli.trayIcon[i].szProto, szPreferredProto )) + continue; + cli.pfnTrayIconUpdate( cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1); + ulock; return; + } + + //if there wasn't a specific icon, there will only be one suitable + for ( i = 0; i < cli.trayIconCount; i++) { + if ( cli.trayIcon[i].id == 0 ) + continue; + cli.pfnTrayIconUpdate( cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1); + ulock; return; + } + ulock; return; +} + +void fnTrayIconIconsChanged(void) +{ + initcheck; + lock; + cli.pfnTrayIconDestroy(cli.hwndContactList); + cli.pfnTrayIconInit(cli.hwndContactList); + ulock; +} + +static UINT_PTR autoHideTimerId; +static VOID CALLBACK TrayIconAutoHideTimer(HWND hwnd, UINT, UINT_PTR idEvent, DWORD) +{ + HWND hwndClui; + initcheck; + lock; + KillTimer(hwnd, idEvent); + hwndClui = cli.hwndContactList; + if (GetActiveWindow() != hwndClui) { + ShowWindow(hwndClui, SW_HIDE); + if (MySetProcessWorkingSetSize != NULL) + MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1); + } + ulock; return; +} + +int fnTrayIconPauseAutoHide(WPARAM, LPARAM) +{ + initcheck 0; + lock; + if (DBGetContactSettingByte(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) { + if ( GetActiveWindow() != cli.hwndContactList ) { + KillTimer(NULL, autoHideTimerId); + autoHideTimerId = SetTimer(NULL, 0, 1000 * DBGetContactSettingWord(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer); + } + } + ulock; return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// processes tray icon's messages + +static BYTE s_LastHoverIconID = 0; +static BOOL g_trayTooltipActive = FALSE; +static POINT tray_hover_pos = {0}; + +static void CALLBACK TrayHideToolTipTimerProc(HWND hwnd, UINT, UINT_PTR, DWORD) +{ + if ( g_trayTooltipActive ) { + POINT pt; + GetCursorPos(&pt); + if ( abs(pt.x - tray_hover_pos.x) > TOOLTIP_TOLERANCE || abs(pt.y - tray_hover_pos.y) > TOOLTIP_TOLERANCE ) { + CallService("mToolTip/HideTip", 0, 0); + g_trayTooltipActive = FALSE; + KillTimer( hwnd, TIMERID_TRAYHOVER_2 ); + } + } + else KillTimer( hwnd, TIMERID_TRAYHOVER_2 ); +} + +static void CALLBACK TrayToolTipTimerProc(HWND hwnd, UINT, UINT_PTR id, DWORD) +{ + if ( !g_trayTooltipActive && !cli.bTrayMenuOnScreen ) { + CLCINFOTIP ti = {0}; + POINT pt; + GetCursorPos( &pt ); + if ( abs(pt.x - tray_hover_pos.x) <= TOOLTIP_TOLERANCE && abs(pt.y - tray_hover_pos.y) <= TOOLTIP_TOLERANCE ) { + TCHAR* szTipCur = cli.szTip; + { + int n = s_LastHoverIconID-100; + if ( n >= 0 && n < cli.trayIconCount ) + szTipCur = cli.trayIcon[n].ptszToolTip; + } + ti.rcItem.left = pt.x - 10; + ti.rcItem.right = pt.x + 10; + ti.rcItem.top = pt.y - 10; + ti.rcItem.bottom = pt.y + 10; + ti.cbSize = sizeof( ti ); + ti.isTreeFocused = GetFocus() == cli.hwndContactList ? 1 : 0; + #if defined( _UNICODE ) + if (CallService( "mToolTip/ShowTipW", (WPARAM)szTipCur, (LPARAM)&ti ) == CALLSERVICE_NOTFOUND) + { + char* p = mir_u2a( szTipCur ); + CallService( "mToolTip/ShowTip", (WPARAM)p, (LPARAM)&ti ); + mir_free( p ); + } + #else + CallService( "mToolTip/ShowTip", (WPARAM)szTipCur, (LPARAM)&ti ); + #endif + GetCursorPos( &tray_hover_pos ); + SetTimer( cli.hwndContactList, TIMERID_TRAYHOVER_2, 600, TrayHideToolTipTimerProc ); + g_trayTooltipActive = TRUE; + } } + + KillTimer(hwnd, id); +} + +INT_PTR fnTrayIconProcessMessage(WPARAM wParam, LPARAM lParam) +{ + MSG *msg = (MSG *) wParam; + switch (msg->message) { + case WM_CREATE: { + WM_TASKBARCREATED = RegisterWindowMessage( _T("TaskbarCreated")); + WM_TASKBARBUTTONCREATED = RegisterWindowMessage( _T("TaskbarButtonCreated")); + PostMessage(msg->hwnd, TIM_CREATE, 0, 0); + break; + } + case TIM_CREATE: + cli.pfnTrayIconInit(msg->hwnd); + break; + + case WM_ACTIVATE: + if (DBGetContactSettingByte(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) { + if (LOWORD(msg->wParam) == WA_INACTIVE) + autoHideTimerId = SetTimer(NULL, 0, 1000 * DBGetContactSettingWord(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer); + else + KillTimer(NULL, autoHideTimerId); + } + break; + + case WM_DESTROY: + cli.pfnTrayIconDestroy(msg->hwnd); + cli.pfnUninitTray(); + break; + + case TIM_CALLBACK: + if ( msg->lParam == WM_RBUTTONDOWN || msg->lParam == WM_LBUTTONDOWN || msg->lParam == WM_RBUTTONDOWN && g_trayTooltipActive ) { + CallService("mToolTip/HideTip", 0, 0); + g_trayTooltipActive = FALSE; + } + + if ( msg->lParam == WM_MBUTTONUP ) + cli.pfnShowHide(0, 0); + else if (msg->lParam == (DBGetContactSettingByte(NULL, "CList", "Tray1Click", SETTING_TRAY1CLICK_DEFAULT) ? WM_LBUTTONUP : WM_LBUTTONDBLCLK)) { + if ((GetAsyncKeyState(VK_CONTROL) & 0x8000)) + { + POINT pt; + HMENU hMenu = (HMENU)CallService(MS_CLIST_MENUGETSTATUS, 0, 0); + + for (int i = 0; i < cli.trayIconCount; ++i) + { + if ((unsigned)cli.trayIcon[i].id == msg->wParam) + { + if (!cli.trayIcon[i].szProto) break; + + int ind = 0; + for (int j = 0; j < accounts.getCount(); ++j) + { + int k = cli.pfnGetAccountIndexByPos(j); + if (k >= 0) + { + if (!strcmp(cli.trayIcon[i].szProto, accounts[k]->szModuleName)) + { + HMENU hm = GetSubMenu(hMenu, ind); + if (hm) hMenu = hm; + break; + } + + if (cli.pfnGetProtocolVisibility(accounts[k]->szModuleName)) + ++ind; + } + } + break; + } + } + + SetForegroundWindow(msg->hwnd); + SetFocus(msg->hwnd); + GetCursorPos(&pt); + TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, msg->hwnd, NULL); + } + else if (cli.pfnEventsProcessTrayDoubleClick(msg->wParam)) + cli.pfnShowHide(0, 0); + } + else if (msg->lParam == WM_RBUTTONUP) { + MENUITEMINFO mi; + POINT pt; + HMENU hMainMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT)); + HMENU hMenu = GetSubMenu(hMainMenu, 0); + CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) hMenu, 0); + + ZeroMemory(&mi, sizeof(mi)); + mi.cbSize = MENUITEMINFO_V4_SIZE; + mi.fMask = MIIM_SUBMENU | MIIM_TYPE; + mi.fType = MFT_STRING; + mi.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0); + mi.dwTypeData = TranslateT("&Main Menu"); + InsertMenuItem(hMenu, 1, TRUE, &mi); + mi.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0); + mi.dwTypeData = TranslateT("&Status"); + InsertMenuItem(hMenu, 2, TRUE, &mi); + SetMenuDefaultItem(hMenu, ID_TRAY_HIDE, FALSE); + + SetForegroundWindow(msg->hwnd); + SetFocus(msg->hwnd); + GetCursorPos(&pt); + TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, msg->hwnd, NULL); + + RemoveMenu(hMenu, 1, MF_BYPOSITION); + RemoveMenu(hMenu, 1, MF_BYPOSITION); + DestroyMenu(hMainMenu); + } + else if ( msg->lParam == WM_MOUSEMOVE ) { + s_LastHoverIconID = msg->wParam; + if ( g_trayTooltipActive ) { + POINT pt; + GetCursorPos( &pt ); + if ( abs(pt.x - tray_hover_pos.x) > TOOLTIP_TOLERANCE || abs(pt.y - tray_hover_pos.y) > TOOLTIP_TOLERANCE ) { + CallService("mToolTip/HideTip", 0, 0); + g_trayTooltipActive = FALSE; + ReleaseCapture(); + } + } + else { + GetCursorPos(&tray_hover_pos); + SetTimer(cli.hwndContactList, TIMERID_TRAYHOVER, 600, TrayToolTipTimerProc); + } + break; + } + + *((LRESULT *) lParam) = 0; + return TRUE; + + default: + if (msg->message == WM_TASKBARCREATED) { + cli.pfnTrayIconTaskbarCreated(msg->hwnd); + *((LRESULT *) lParam) = 0; + return TRUE; + } + else if (msg->message == WM_TASKBARBUTTONCREATED) { + SetTaskBarIcon(lastTaskBarIcon, NULL); + *((LRESULT *) lParam) = 0; + return TRUE; + } + } + + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// processes tray icon's notifications + +int fnCListTrayNotify( MIRANDASYSTRAYNOTIFY* msn ) +{ + UINT iconId = 0; + + if ( msn == NULL ) + return 1; + + if ( msn->cbSize != sizeof(MIRANDASYSTRAYNOTIFY) || msn->szInfo == NULL || msn->szInfoTitle == NULL ) + return 1; + + if ( cli.trayIcon == NULL ) + return 2; + + if ( msn->szProto ) { + int j; + for ( j = 0; j < cli.trayIconCount; j++ ) { + if ( cli.trayIcon[j].szProto != NULL ) { + if ( !strcmp( msn->szProto, cli.trayIcon[j].szProto )) { + iconId = cli.trayIcon[j].id; + break; + } + } + else if ( cli.trayIcon[j].isBase ) { + iconId = cli.trayIcon[j].id; + break; + } } + } + else iconId = cli.trayIcon[0].id; + +#if defined(_UNICODE) + if ( msn->dwInfoFlags & NIIF_INTERN_UNICODE ) { + NOTIFYICONDATAW nid = {0}; + nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATAW_V1_SIZE; + nid.hWnd = cli.hwndContactList; + nid.uID = iconId; + nid.uFlags = NIF_INFO; + lstrcpynW( nid.szInfo, msn->tszInfo, SIZEOF( nid.szInfo )); + lstrcpynW( nid.szInfoTitle, msn->tszInfoTitle, SIZEOF( nid.szInfoTitle )); + nid.szInfo[ SIZEOF(nid.szInfo)-1 ] = 0; + nid.szInfoTitle[ SIZEOF(nid.szInfoTitle)-1 ] = 0; + nid.uTimeout = msn->uTimeout; + nid.dwInfoFlags = (msn->dwInfoFlags & ~NIIF_INTERN_UNICODE); + return Shell_NotifyIconW( NIM_MODIFY, &nid ) == 0; + } + else +#endif + { + NOTIFYICONDATAA nid = { 0 }; + nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATAA_V1_SIZE; + nid.hWnd = cli.hwndContactList; + nid.uID = iconId; + nid.uFlags = NIF_INFO; + lstrcpynA( nid.szInfo, msn->szInfo, sizeof( nid.szInfo )); + lstrcpynA( nid.szInfoTitle, msn->szInfoTitle, sizeof( nid.szInfoTitle )); + nid.uTimeout = msn->uTimeout; + nid.dwInfoFlags = msn->dwInfoFlags; + return Shell_NotifyIconA( NIM_MODIFY, &nid ) == 0; +} } + +///////////////////////////////////////////////////////////////////////////////////////// + +typedef struct _DllVersionInfo +{ + DWORD cbSize; + DWORD dwMajorVersion; // Major version + DWORD dwMinorVersion; // Minor version + DWORD dwBuildNumber; // Build number + DWORD dwPlatformID; // DLLVER_PLATFORM_* +} + DLLVERSIONINFO; + +typedef HRESULT(CALLBACK * DLLGETVERSIONPROC) (DLLVERSIONINFO *); + +static DLLVERSIONINFO dviShell; + +static INT_PTR pfnCListTrayNotifyStub(WPARAM, LPARAM lParam ) +{ return cli.pfnCListTrayNotify(( MIRANDASYSTRAYNOTIFY* )lParam ); +} + +void fnInitTray( void ) +{ + HMODULE hLib = GetModuleHandleA("shell32"); + if ( hLib ) { + DLLGETVERSIONPROC proc; + dviShell.cbSize = sizeof(dviShell); + proc = ( DLLGETVERSIONPROC )GetProcAddress( hLib, "DllGetVersion" ); + if (proc) { + proc( &dviShell ); + cli.shellVersion = dviShell.dwMajorVersion; + } + FreeLibrary(hLib); + } + InitializeCriticalSection(&trayLockCS); + if ( cli.shellVersion >= 5 ) + CreateServiceFunction(MS_CLIST_SYSTRAY_NOTIFY, pfnCListTrayNotifyStub ); + fTrayInited=TRUE; +} + +void fnUninitTray( void ) +{ + fTrayInited=FALSE; + DeleteCriticalSection( &trayLockCS ); +} +void fnLockTray( void ) +{ +// return; //stub to be removed + initcheck; + EnterCriticalSection( &trayLockCS ); +} + +void fnUnlockTray( void ) +{ +// return; //stub to be removed + initcheck; +#ifdef _DEBUG + if (trayLockCS.RecursionCount==0) DebugBreak(); //try to unlock already +#endif + LeaveCriticalSection( &trayLockCS ); +} + +#undef lock +#undef ulock +#undef initcheck diff --git a/src/modules/clist/clui.cpp b/src/modules/clist/clui.cpp new file mode 100644 index 0000000000..13ebf74a21 --- /dev/null +++ b/src/modules/clist/clui.cpp @@ -0,0 +1,1136 @@ +/* + + Miranda IM: the free IM client for Microsoft* Windows* + + Copyright 2000-2010 Miranda ICQ/IM project, + all portions of this codebase are copyrighted to the people + listed in contributors.txt. + + 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 "commonheaders.h" +#include "../database/profilemanager.h" +#include "clc.h" + +#define TM_AUTOALPHA 1 +#define MENU_MIRANDAMENU 0xFFFF1234 + +extern BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T); + +static HMODULE hUserDll; +static HANDLE hContactDraggingEvent, hContactDroppedEvent, hContactDragStopEvent; +static int transparentFocus = 1; +UINT uMsgProcessProfile; + +#define M_RESTORESTATUS (WM_USER+7) + +void LoadCluiServices(); + +typedef struct { + int showsbar; + int showgrip; + int transparent; + int alpha; +} + CluiOpts; + +static CluiOpts cluiopt = {0}; + +void fnLoadCluiGlobalOpts() +{ + cluiopt.showsbar = DBGetContactSettingByte(NULL, "CLUI", "ShowSBar", 1); + cluiopt.showgrip = DBGetContactSettingByte(NULL, "CLUI", "ShowGrip", 1); + cluiopt.transparent = DBGetContactSettingByte(NULL,"CList","Transparent",SETTING_TRANSPARENT_DEFAULT); + cluiopt.alpha = DBGetContactSettingByte(NULL, "CList", "Alpha", SETTING_ALPHA_DEFAULT); +} + +static int CluiModulesLoaded(WPARAM, LPARAM) +{ + if (cli.hMenuMain) { + MENUITEMINFO mii = { 0 }; + mii.cbSize = MENUITEMINFO_V4_SIZE; + mii.fMask = MIIM_SUBMENU; + mii.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0); + SetMenuItemInfo(cli.hMenuMain, 0, TRUE, &mii); + mii.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0); + SetMenuItemInfo(cli.hMenuMain, 1, TRUE, &mii); + } + return 0; +} + +// Disconnect all protocols. +// Happens on shutdown and standby. +static void DisconnectAll() +{ + int nProto; + for (nProto = 0; nProto < accounts.getCount(); nProto++) + CallProtoService( accounts[nProto]->szModuleName, PS_SETSTATUS, ID_STATUS_OFFLINE, 0); +} + +static int CluiIconsChanged(WPARAM, LPARAM) +{ + DrawMenuBar(cli.hwndContactList); + return 0; +} + +static HANDLE hRenameMenuItem; + +static int MenuItem_PreBuild(WPARAM, LPARAM) +{ + TCHAR cls[128]; + HANDLE hItem; + HWND hwndClist = GetFocus(); + CLISTMENUITEM mi; + + ZeroMemory(&mi, sizeof(mi)); + mi.cbSize = sizeof(mi); + mi.flags = CMIM_FLAGS; + GetClassName(hwndClist, cls, SIZEOF(cls)); + hwndClist = (!lstrcmp(CLISTCONTROL_CLASS, cls)) ? hwndClist : cli.hwndContactList; + hItem = (HANDLE) SendMessage(hwndClist, CLM_GETSELECTION, 0, 0); + if (!hItem) { + mi.flags = CMIM_FLAGS | CMIF_HIDDEN; + } + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) hRenameMenuItem, (LPARAM) & mi); + return 0; +} + +static INT_PTR MenuItem_RenameContact(WPARAM, LPARAM) +{ + TCHAR cls[128]; + HANDLE hItem; + HWND hwndClist = GetFocus(); + GetClassName(hwndClist, cls, SIZEOF(cls)); + // worst case scenario, the rename is sent to the main contact list + hwndClist = (!lstrcmp(CLISTCONTROL_CLASS, cls)) ? hwndClist : cli.hwndContactList; + hItem = (HANDLE) SendMessage(hwndClist, CLM_GETSELECTION, 0, 0); + if (hItem) { + SetFocus(hwndClist); + SendMessage(hwndClist, CLM_EDITLABEL, (WPARAM) hItem, 0); + } + return 0; +} + +static INT_PTR CALLBACK AskForConfirmationDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hWnd); + { + LOGFONT lf; + HFONT hFont; + + hFont = (HFONT) SendDlgItemMessage(hWnd, IDYES, WM_GETFONT, 0, 0); + GetObject(hFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + SendDlgItemMessage(hWnd, IDC_TOPLINE, WM_SETFONT, (WPARAM) CreateFontIndirect(&lf), 0); + } + { + TCHAR szFormat[256]; + TCHAR szFinal[256]; + + GetDlgItemText(hWnd, IDC_TOPLINE, szFormat, SIZEOF(szFormat)); + mir_sntprintf(szFinal, SIZEOF(szFinal), szFormat, cli.pfnGetContactDisplayName((HANDLE)lParam, 0)); + SetDlgItemText(hWnd, IDC_TOPLINE, szFinal); + } + SetFocus(GetDlgItem(hWnd, IDNO)); + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDYES: + if (IsDlgButtonChecked(hWnd, IDC_HIDE)) { + EndDialog(hWnd, IDC_HIDE); + break; + } + //fall through + case IDCANCEL: + case IDNO: + EndDialog(hWnd, LOWORD(wParam)); + break; + } + break; + + case WM_CLOSE: + SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(IDNO, BN_CLICKED), 0); + break; + + case WM_DESTROY: + DeleteObject((HFONT) SendDlgItemMessage(hWnd, IDC_TOPLINE, WM_GETFONT, 0, 0)); + break; + } + + return FALSE; +} + +static INT_PTR MenuItem_DeleteContact(WPARAM wParam, LPARAM lParam) +{ + //see notes about deleting contacts on PF1_SERVERCLIST servers in m_protosvc.h + UINT_PTR action; + + if (DBGetContactSettingByte(NULL, "CList", "ConfirmDelete", SETTING_CONFIRMDELETE_DEFAULT) && + !(GetKeyState(VK_SHIFT)&0x8000) ) + // Ask user for confirmation, and if the contact should be archived (hidden, not deleted) + action = DialogBoxParam(hMirandaInst, MAKEINTRESOURCE(IDD_DELETECONTACT), (HWND) lParam, AskForConfirmationDlgProc, wParam); + else + action = IDYES; + + switch (action) { + + // Delete contact + case IDYES: + { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto != NULL) { + // Check if protocol uses server side lists + DWORD caps; + + caps = (DWORD) CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); + if (caps & PF1_SERVERCLIST) { + int status; + + status = CallProtoService(szProto, PS_GETSTATUS, 0, 0); + if (status == ID_STATUS_OFFLINE || (status >= ID_STATUS_CONNECTING && status < ID_STATUS_CONNECTING + MAX_CONNECT_RETRIES)) { + // Set a flag so we remember to delete the contact when the protocol goes online the next time + DBWriteContactSettingByte((HANDLE) wParam, "CList", "Delete", 1); + MessageBox( NULL, + TranslateT("This contact is on an instant messaging system which stores its contact list on a central server. The contact will be removed from the server and from your contact list when you next connect to that network."), + TranslateT("Delete Contact"), MB_OK); + return 0; + } } } + + CallService(MS_DB_CONTACT_DELETE, wParam, 0); + } + break; + + // Archive contact + case IDC_HIDE: + DBWriteContactSettingByte((HANDLE) wParam, "CList", "Hidden", 1); + break; + } + + return 0; +} + +static INT_PTR MenuItem_AddContactToList(WPARAM wParam, LPARAM) +{ + ADDCONTACTSTRUCT acs = { 0 }; + + acs.handle = (HANDLE) wParam; + acs.handleType = HANDLE_CONTACT; + acs.szProto = ""; + + CallService(MS_ADDCONTACT_SHOW, (WPARAM) NULL, (LPARAM) & acs); + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// this is the smallest available window procedure + +#ifndef CS_DROPSHADOW +#define CS_DROPSHADOW 0x00020000 +#endif + +LRESULT CALLBACK ContactListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT result; + MSG m; + m.hwnd=hwnd; + m.message=msg; + m.wParam=wParam; + m.lParam=lParam; + if ( cli.pfnDocking_ProcessWindowMessage(( WPARAM )&m, ( LPARAM )&result )) + return result; + if ( cli.pfnTrayIconProcessMessage(( WPARAM )&m, ( LPARAM )&result )) + return result; + if ( cli.pfnHotkeysProcessMessage(( WPARAM )&m, ( LPARAM )&result )) + return result; + + return cli.pfnContactListWndProc( hwnd, msg, wParam, lParam ); +} + +int LoadCLUIModule(void) +{ + DBVARIANT dbv; + TCHAR titleText[256]; + + uMsgProcessProfile = RegisterWindowMessage( _T("Miranda::ProcessProfile")); + cli.pfnLoadCluiGlobalOpts(); + + HookEvent(ME_SYSTEM_MODULESLOADED, CluiModulesLoaded); + HookEvent(ME_SKIN_ICONSCHANGED, CluiIconsChanged); + + hContactDraggingEvent = CreateHookableEvent(ME_CLUI_CONTACTDRAGGING); + hContactDroppedEvent = CreateHookableEvent(ME_CLUI_CONTACTDROPPED); + hContactDragStopEvent = CreateHookableEvent(ME_CLUI_CONTACTDRAGSTOP); + LoadCluiServices(); + + WNDCLASSEX wndclass; + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_GLOBALCLASS; + wndclass.lpfnWndProc = cli.pfnContactListControlWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = sizeof(void *); + wndclass.hInstance = cli.hInst; + wndclass.hIcon = NULL; + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = CLISTCONTROL_CLASS; + wndclass.hIconSm = NULL; + RegisterClassEx(&wndclass); + + wndclass.style = CS_HREDRAW | CS_VREDRAW | ((IsWinVerXPPlus() && + DBGetContactSettingByte(NULL, "CList", "WindowShadow", 0) == 1) ? CS_DROPSHADOW : 0); + wndclass.lpfnWndProc = ContactListWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = cli.hInst; + wndclass.hIcon = LoadSkinIcon(SKINICON_OTHER_MIRANDA, true); + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1); + wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_CLISTMENU); + wndclass.lpszClassName = _T(MIRANDACLASS); + wndclass.hIconSm = LoadSkinIcon(SKINICON_OTHER_MIRANDA); + RegisterClassEx(&wndclass); + + if (DBGetContactSettingTString(NULL, "CList", "TitleText", &dbv)) + lstrcpyn(titleText, _T(MIRANDANAME), SIZEOF( titleText )); + else { + lstrcpyn(titleText, dbv.ptszVal, SIZEOF(titleText)); + DBFreeVariant(&dbv); + } + + RECT pos; + pos.left = (int) DBGetContactSettingDword(NULL, "CList", "x", 700); + pos.top = (int) DBGetContactSettingDword(NULL, "CList", "y", 221); + pos.right = pos.left + (int) DBGetContactSettingDword(NULL, "CList", "Width", 108); + pos.bottom = pos.top + (int) DBGetContactSettingDword(NULL, "CList", "Height", 310); + + Utils_AssertInsideScreen(&pos); + + cli.hwndContactList = CreateWindowEx( + (DBGetContactSettingByte(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ? WS_EX_TOOLWINDOW : WS_EX_APPWINDOW), + _T(MIRANDACLASS), + titleText, + WS_POPUPWINDOW | WS_THICKFRAME | WS_CLIPCHILDREN | + (DBGetContactSettingByte(NULL, "CLUI", "ShowCaption", SETTING_SHOWCAPTION_DEFAULT) ? WS_CAPTION | WS_SYSMENU | + (DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT) ? 0 : WS_MINIMIZEBOX) : 0), + pos.left, pos.top, pos.right - pos.left, pos.bottom - pos.top, + NULL, NULL, cli.hInst, NULL); + + if (DBGetContactSettingByte(NULL, "CList", "OnDesktop", 0)) { + HWND hProgMan = FindWindow(_T("Progman"), NULL); + if (IsWindow(hProgMan)) + SetParent(cli.hwndContactList, hProgMan); + } + + cli.pfnOnCreateClc(); + + PostMessage(cli.hwndContactList, M_RESTORESTATUS, 0, 0); + + { + int state = DBGetContactSettingByte(NULL, "CList", "State", SETTING_STATE_NORMAL); + cli.hMenuMain = GetMenu(cli.hwndContactList); + if (!DBGetContactSettingByte(NULL, "CLUI", "ShowMainMenu", SETTING_SHOWMAINMENU_DEFAULT)) + SetMenu(cli.hwndContactList, NULL); + if (state == SETTING_STATE_NORMAL) + ShowWindow(cli.hwndContactList, SW_SHOW); + else if (state == SETTING_STATE_MINIMIZED) + ShowWindow(cli.hwndContactList, SW_SHOWMINIMIZED); + SetWindowPos(cli.hwndContactList, + DBGetContactSettingByte(NULL, "CList", "OnTop", SETTING_ONTOP_DEFAULT) ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + } + { + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + + CreateServiceFunction("CList/DeleteContactCommand", MenuItem_DeleteContact); + mi.position = 2000070000; + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_DELETE ); + mi.pszContactOwner = NULL; //on every contact + mi.pszName = LPGEN("De&lete"); + mi.pszService = "CList/DeleteContactCommand"; + CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM) & mi); + + CreateServiceFunction("CList/RenameContactCommand", MenuItem_RenameContact); + mi.position = 2000050000; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_RENAME ); + mi.pszContactOwner = NULL; //on every contact + mi.pszName = LPGEN("&Rename"); + mi.pszService = "CList/RenameContactCommand"; + hRenameMenuItem = (HANDLE) CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM) & mi); + + CreateServiceFunction("CList/AddToListContactCommand", MenuItem_AddContactToList); + mi.position = -2050000000; + mi.flags |= CMIF_NOTONLIST; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_ADDCONTACT ); + mi.pszName = LPGEN("&Add permanently to list"); + mi.pszService = "CList/AddToListContactCommand"; + CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM) & mi); + + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, MenuItem_PreBuild); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// default contact list window procedure + +void fnDrawMenuItem(DRAWITEMSTRUCT *dis, HICON hIcon, HICON eventIcon) +{ + if (!IsWinVerXPPlus()) { + FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_MENU)); + if (dis->itemState & ODS_HOTLIGHT) + DrawEdge(dis->hDC, &dis->rcItem, BDR_RAISEDINNER, BF_RECT); + else if (dis->itemState & ODS_SELECTED) + DrawEdge(dis->hDC, &dis->rcItem, BDR_SUNKENOUTER, BF_RECT); + if (eventIcon != 0) { + DrawState(dis->hDC, NULL, NULL, (LPARAM) eventIcon, 0, 2, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, 4 + g_IconWidth, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + } + else DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, (dis->rcItem.right + dis->rcItem.left - g_IconWidth) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + } + else { + HBRUSH hBr; + BOOL bfm = FALSE; + SystemParametersInfo(SPI_GETFLATMENU, 0, &bfm, 0); + if (bfm) { + /* flat menus: fill with COLOR_MENUHILIGHT and outline with COLOR_HIGHLIGHT, otherwise use COLOR_MENUBAR */ + if (dis->itemState & ODS_SELECTED || dis->itemState & ODS_HOTLIGHT) { + /* selected or hot lighted, no difference */ + hBr = GetSysColorBrush(COLOR_MENUHILIGHT); + FillRect(dis->hDC, &dis->rcItem, hBr); + DeleteObject(hBr); + /* draw the frame */ + hBr = GetSysColorBrush(COLOR_HIGHLIGHT); + FrameRect(dis->hDC, &dis->rcItem, hBr); + DeleteObject(hBr); + } else { + /* flush the DC with the menu bar colour (only supported on XP) and then draw the icon */ + hBr = GetSysColorBrush(COLOR_MENUBAR); + FillRect(dis->hDC, &dis->rcItem, hBr); + DeleteObject(hBr); + } //if + /* draw the icon */ + if (eventIcon != 0) { + DrawState(dis->hDC, NULL, NULL, (LPARAM) eventIcon, 0, 2, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, 4 + g_IconWidth, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + } + else DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, (dis->rcItem.right + dis->rcItem.left - g_IconWidth) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + } + else { + /* non-flat menus, flush the DC with a normal menu colour */ + FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_MENU)); + if (dis->itemState & ODS_HOTLIGHT) + DrawEdge(dis->hDC, &dis->rcItem, BDR_RAISEDINNER, BF_RECT); + else if (dis->itemState & ODS_SELECTED) + DrawEdge(dis->hDC, &dis->rcItem, BDR_SUNKENOUTER, BF_RECT); + + if (eventIcon != 0) { + DrawState(dis->hDC, NULL, NULL, (LPARAM) eventIcon, 0, 2, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, 4 + g_IconWidth, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + } + else DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, (dis->rcItem.right + dis->rcItem.left - g_IconWidth) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL)); + } } + + DestroyIcon(hIcon); + return; +} + +#define M_CREATECLC (WM_USER+1) +LRESULT CALLBACK fnContactListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == uMsgProcessProfile) + { + TCHAR profile[MAX_PATH]; + int rc; + // wParam = (ATOM)hProfileAtom, lParam = 0 + if (GlobalGetAtomName((ATOM) wParam, profile, SIZEOF(profile))) + { + TCHAR *pfd = Utils_ReplaceVarsT(_T("%miranda_userdata%\\%miranda_profilename%.dat")); + rc = lstrcmpi(profile, pfd) == 0; + mir_free(pfd); + ReplyMessage(rc); + if (rc) { + ShowWindow(hwnd, SW_RESTORE); + ShowWindow(hwnd, SW_SHOW); + SetForegroundWindow(hwnd); + SetFocus(hwnd); + } + } + return 0; + } + + switch (msg) { + case WM_NCCREATE: + { + MENUITEMINFO mii = { 0 }; +/* + if (IsWinVerVistaPlus() && isThemeActive()) + { + HICON hIcon = LoadSkinnedIcon(SKINICON_OTHER_MAINMENU); + HBITMAP hBmp = ConvertIconToBitmap(hIcon, NULL, 0); + IconLib_ReleaseIcon(hIcon, NULL); + + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_BITMAP | MIIM_STRING | MIIM_DATA; + mii.hbmpItem = hBmp; + } + else +*/ + { + mii.cbSize = MENUITEMINFO_V4_SIZE; + mii.fMask = MIIM_TYPE | MIIM_DATA; + mii.dwItemData = MENU_MIRANDAMENU; + mii.fType = MFT_OWNERDRAW; + } + SetMenuItemInfo(GetMenu(hwnd), 0, TRUE, &mii); + return DefWindowProc(hwnd, msg, wParam, lParam); + } + case WM_CREATE: + CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) GetMenu(hwnd), 0); + DrawMenuBar(hwnd); + + //create the status wnd + { + int flags = WS_CHILD | CCS_BOTTOM; + flags |= cluiopt.showsbar ? WS_VISIBLE : 0; + flags |= cluiopt.showgrip ? SBARS_SIZEGRIP : 0; + cli.hwndStatus = CreateWindow(STATUSCLASSNAME, NULL, flags, 0, 0, 0, 0, hwnd, NULL, cli.hInst, NULL); + } + cli.pfnCluiProtocolStatusChanged(0, 0); + + //delay creation of CLC so that it can get the status icons right the first time (needs protocol modules loaded) + PostMessage(hwnd, M_CREATECLC, 0, 0); + + if (cluiopt.transparent) { + SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); + if (setLayeredWindowAttributes) + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA); + } + transparentFocus = 1; + return FALSE; + + case M_CREATECLC: + cli.hwndContactTree = CreateWindow( CLISTCONTROL_CLASS, _T(""), + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN + | CLS_CONTACTLIST + | (DBGetContactSettingByte(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT) ? CLS_USEGROUPS : 0) + | (DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) ? CLS_HIDEOFFLINE : 0) + | (DBGetContactSettingByte(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT) ? + CLS_HIDEEMPTYGROUPS : 0), 0, 0, 0, 0, hwnd, NULL, cli.hInst, NULL); + SendMessage(hwnd, WM_SIZE, 0, 0); + break; + + case M_RESTORESTATUS: + #ifndef _DEBUG + { + int nStatus = DBGetContactSettingWord(NULL, "CList", "Status", ID_STATUS_OFFLINE); + if (nStatus != ID_STATUS_OFFLINE) CallService(MS_CLIST_SETSTATUSMODE, nStatus, 0); + } + #endif + break; + + // Power management + case WM_POWERBROADCAST: + switch ((DWORD) wParam) { + case PBT_APMSUSPEND: + // Computer is suspending, disconnect all protocols + DisconnectAll(); + break; + + case PBT_APMRESUMEAUTOMATIC: + case PBT_APMRESUMESUSPEND: + // Computer is resuming, restore all protocols + PostMessage(hwnd, M_RESTORESTATUS, 0, 0); + break; + } + break; + + case WM_SYSCOLORCHANGE: + SendMessage(cli.hwndContactTree, msg, wParam, lParam); + SendMessage(cli.hwndStatus, msg, wParam, lParam); + // XXX: only works with 4.71 with 95, IE4. + SendMessage(cli.hwndStatus, SB_SETBKCOLOR, 0, GetSysColor(COLOR_3DFACE)); + break; + + case WM_SIZE: + if (IsZoomed(hwnd)) + ShowWindow(hwnd, SW_SHOWNORMAL); + { + RECT rect, rcStatus; + GetClientRect(hwnd, &rect); + if (cluiopt.showsbar) { + SetWindowPos(cli.hwndStatus, NULL, 0, rect.bottom - 20, rect.right - rect.left, 20, SWP_NOZORDER); + GetWindowRect(cli.hwndStatus, &rcStatus); + cli.pfnCluiProtocolStatusChanged(0, 0); + } + else + rcStatus.top = rcStatus.bottom = 0; + SetWindowPos(cli.hwndContactTree, NULL, 0, 0, rect.right, rect.bottom - (rcStatus.bottom - rcStatus.top), SWP_NOZORDER); + } + if (wParam == SIZE_MINIMIZED) + { + if ((GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) || + DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT)) + { + ShowWindow(hwnd, SW_HIDE); + DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_HIDDEN); + } + else + DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_MINIMIZED); + + if (MySetProcessWorkingSetSize != NULL && DBGetContactSettingByte(NULL, "CList", "DisableWorkingSet", 1)) + MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1); + } + // drop thru + case WM_MOVE: + if (!IsIconic(hwnd)) { + RECT rc; + GetWindowRect(hwnd, &rc); + + if (!CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) { //if docked, dont remember pos (except for width) + DBWriteContactSettingDword(NULL, "CList", "Height", (DWORD) (rc.bottom - rc.top)); + DBWriteContactSettingDword(NULL, "CList", "x", (DWORD) rc.left); + DBWriteContactSettingDword(NULL, "CList", "y", (DWORD) rc.top); + } + DBWriteContactSettingDword(NULL, "CList", "Width", (DWORD) (rc.right - rc.left)); + } + return FALSE; + + case WM_SETFOCUS: + SetFocus(cli.hwndContactTree); + return 0; + + case WM_ACTIVATE: + if (wParam == WA_INACTIVE) { + if ((HWND) wParam != hwnd) + if (cluiopt.transparent) + if (transparentFocus) + SetTimer(hwnd, TM_AUTOALPHA, 250, NULL); + } + else { + if (cluiopt.transparent) { + KillTimer(hwnd, TM_AUTOALPHA); + if (setLayeredWindowAttributes) + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA); + transparentFocus = 1; + } + } + return DefWindowProc(hwnd, msg, wParam, lParam); + + case WM_SETCURSOR: + if(cluiopt.transparent) { + if (!transparentFocus && GetForegroundWindow()!=hwnd && setLayeredWindowAttributes) { + setLayeredWindowAttributes(hwnd, RGB(0,0,0), (BYTE)cluiopt.alpha, LWA_ALPHA); + transparentFocus=1; + SetTimer(hwnd, TM_AUTOALPHA,250,NULL); + } + } + return DefWindowProc(hwnd, msg, wParam, lParam); + + case WM_NCHITTEST: + { + LRESULT result; + result = DefWindowProc(hwnd, WM_NCHITTEST, wParam, lParam); + if (result == HTSIZE || result == HTTOP || result == HTTOPLEFT || result == HTTOPRIGHT || + result == HTBOTTOM || result == HTBOTTOMRIGHT || result == HTBOTTOMLEFT) + if (DBGetContactSettingByte(NULL, "CLUI", "AutoSize", 0)) + return HTCLIENT; + return result; + } + + case WM_TIMER: + if ((int) wParam == TM_AUTOALPHA) { + int inwnd; + + if (GetForegroundWindow() == hwnd) { + KillTimer(hwnd, TM_AUTOALPHA); + inwnd = 1; + } + else { + POINT pt; + HWND hwndPt; + pt.x = (short) LOWORD(GetMessagePos()); + pt.y = (short) HIWORD(GetMessagePos()); + hwndPt = WindowFromPoint(pt); + inwnd = (hwndPt == hwnd || GetParent(hwndPt) == hwnd); + } + if (inwnd != transparentFocus && setLayeredWindowAttributes) { //change + transparentFocus = inwnd; + if (transparentFocus) + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA); + else + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) DBGetContactSettingByte(NULL, "CList", "AutoAlpha", SETTING_AUTOALPHA_DEFAULT), LWA_ALPHA); + } + if (!transparentFocus) + KillTimer(hwnd, TM_AUTOALPHA); + } + return TRUE; + + case WM_SHOWWINDOW: + { + static int noRecurse = 0; + if (lParam) + break; + if (noRecurse) + break; + if (!DBGetContactSettingByte(NULL, "CLUI", "FadeInOut", 0) || !IsWinVer2000Plus()) + break; + if (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) { + DWORD thisTick, startTick; + int sourceAlpha, destAlpha; + if (wParam) { + sourceAlpha = 0; + destAlpha = (BYTE) cluiopt.alpha; + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_ALPHA); + noRecurse = 1; + ShowWindow(hwnd, SW_SHOW); + noRecurse = 0; + } + else { + sourceAlpha = (BYTE) cluiopt.alpha; + destAlpha = 0; + } + for (startTick = GetTickCount();;) { + thisTick = GetTickCount(); + if (thisTick >= startTick + 200) + break; + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), + (BYTE) (sourceAlpha + (destAlpha - sourceAlpha) * (int) (thisTick - startTick) / 200), LWA_ALPHA); + } + setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) destAlpha, LWA_ALPHA); + } + else { + if (wParam) + SetForegroundWindow(hwnd); + animateWindow(hwnd, 200, AW_BLEND | (wParam ? 0 : AW_HIDE)); + SetWindowPos(cli.hwndContactTree, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + } + break; + } + case WM_MENURBUTTONUP: /* this API is so badly documented at MSDN!! */ + { + UINT id = 0; + + id = GetMenuItemID((HMENU) lParam, LOWORD(wParam)); /* LOWORD(wParam) contains the menu pos in its parent menu */ + if (id != (-1)) + SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(id, 0), 0); + return DefWindowProc(hwnd, msg, wParam, lParam); + } + case WM_SYSCOMMAND: + switch (wParam) + { + case SC_MAXIMIZE: + return 0; + + case SC_MINIMIZE: + case SC_CLOSE: + if ((GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) || + DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT)) + { + ShowWindow(hwnd, SW_HIDE); + DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_HIDDEN); + + if (MySetProcessWorkingSetSize != NULL && DBGetContactSettingByte(NULL, "CList", "DisableWorkingSet", 1)) + MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1); + + return 0; + } + else if (wParam == SC_CLOSE) + wParam = SC_MINIMIZE; + } + return DefWindowProc(hwnd, msg, wParam, lParam); + + case WM_COMMAND: + if (CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_MAINMENU), (LPARAM) (HANDLE) NULL)) + break; + switch (LOWORD(wParam)) { + case ID_TRAY_EXIT: + case ID_ICQ_EXIT: + if (CallService(MS_SYSTEM_OKTOEXIT, 0, 0)) + DestroyWindow(hwnd); + break; + case ID_TRAY_HIDE: + CallService(MS_CLIST_SHOWHIDE, 0, 0); + break; + case POPUP_NEWGROUP: + SendMessage(cli.hwndContactTree, CLM_SETHIDEEMPTYGROUPS, 0, 0); + CallService(MS_CLIST_GROUPCREATE, 0, 0); + break; + case POPUP_HIDEOFFLINE: + CallService(MS_CLIST_SETHIDEOFFLINE, (WPARAM) (-1), 0); + break; + case POPUP_HIDEOFFLINEROOT: + SendMessage(cli.hwndContactTree, CLM_SETHIDEOFFLINEROOT, !SendMessage(cli.hwndContactTree, CLM_GETHIDEOFFLINEROOT, 0, 0), 0); + break; + case POPUP_HIDEEMPTYGROUPS: + { + int newVal = !(GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS); + DBWriteContactSettingByte(NULL, "CList", "HideEmptyGroups", (BYTE) newVal); + SendMessage(cli.hwndContactTree, CLM_SETHIDEEMPTYGROUPS, newVal, 0); + break; + } + case POPUP_DISABLEGROUPS: + { + int newVal = !(GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS); + DBWriteContactSettingByte(NULL, "CList", "UseGroups", (BYTE) newVal); + SendMessage(cli.hwndContactTree, CLM_SETUSEGROUPS, newVal, 0); + break; + } + case POPUP_HIDEMIRANDA: + { + CallService(MS_CLIST_SHOWHIDE, 0, 0); + break; + } } + return FALSE; + case WM_KEYDOWN: + CallService(MS_CLIST_MENUPROCESSHOTKEY, wParam, MPCF_MAINMENU | MPCF_CONTACTMENU); + break; + + case WM_GETMINMAXINFO: + DefWindowProc(hwnd, msg, wParam, lParam); + ((LPMINMAXINFO) lParam)->ptMinTrackSize.x = 16 + GetSystemMetrics(SM_CXHTHUMB); + ((LPMINMAXINFO) lParam)->ptMinTrackSize.y = 16; + return 0; + + case WM_SETTINGCHANGE: + if (wParam == SPI_SETWORKAREA && (GetWindowLong(hwnd, GWL_STYLE) & (WS_VISIBLE | WS_MINIMIZE)) == WS_VISIBLE && + !CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) + { + RECT rc; + GetWindowRect(hwnd, &rc); + if (Utils_AssertInsideScreen(&rc) == 1) + MoveWindow(hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); + } + return DefWindowProc(hwnd, msg, wParam, lParam); + + case WM_DISPLAYCHANGE: + DefWindowProc(hwnd, msg, wParam, lParam); + SendMessage(cli.hwndContactTree, WM_SIZE, 0, 0); //forces it to send a cln_listsizechanged + break; + + //MSG FROM CHILD CONTROL + case WM_NOTIFY: + if (((LPNMHDR) lParam)->hwndFrom == cli.hwndContactTree) { + switch (((LPNMHDR) lParam)->code) { + case CLN_EXPANDED: + { + NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam; + CallService(MS_CLIST_GROUPSETEXPANDED, (WPARAM) nmc->hItem, nmc->action); + return FALSE; + } + case CLN_DRAGGING: + { + NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam; + ClientToScreen(hwnd, &nmc->pt); + if (!(nmc->flags & CLNF_ISGROUP)) + if (NotifyEventHooks(hContactDraggingEvent, (WPARAM) nmc->hItem, MAKELPARAM(nmc->pt.x, nmc->pt.y))) { + SetCursor(LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER))); + return TRUE; + } + break; + } + case CLN_DRAGSTOP: + { + NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam; + if (!(nmc->flags & CLNF_ISGROUP)) + NotifyEventHooks(hContactDragStopEvent, (WPARAM) nmc->hItem, 0); + break; + } + case CLN_DROPPED: + { + NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam; + ClientToScreen(hwnd, &nmc->pt); + if (!(nmc->flags & CLNF_ISGROUP)) + if (NotifyEventHooks(hContactDroppedEvent, (WPARAM) nmc->hItem, MAKELPARAM(nmc->pt.x, nmc->pt.y))) { + SetCursor(LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER))); + return TRUE; + } + break; + } + case NM_KEYDOWN: + { + NMKEY *nmkey = (NMKEY *) lParam; + return CallService(MS_CLIST_MENUPROCESSHOTKEY, nmkey->nVKey, MPCF_MAINMENU | MPCF_CONTACTMENU); + } + case CLN_LISTSIZECHANGE: + { + NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam; + RECT rcWindow, rcTree, rcWorkArea; + int maxHeight, newHeight; + + if (!DBGetContactSettingByte(NULL, "CLUI", "AutoSize", 0)) + break; + if (CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) + break; + maxHeight = DBGetContactSettingByte(NULL, "CLUI", "MaxSizeHeight", 75); + GetWindowRect(hwnd, &rcWindow); + GetWindowRect(cli.hwndContactTree, &rcTree); + + SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, FALSE); + if (MyMonitorFromWindow) + { + HMONITOR hMon = MyMonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (MyGetMonitorInfo(hMon, &mi)) + rcWorkArea = mi.rcWork; + } + + newHeight = max(nmc->pt.y, 9) + 1 + (rcWindow.bottom - rcWindow.top) - (rcTree.bottom - rcTree.top); + if (newHeight > (rcWorkArea.bottom - rcWorkArea.top) * maxHeight / 100) + newHeight = (rcWorkArea.bottom - rcWorkArea.top) * maxHeight / 100; + if (DBGetContactSettingByte(NULL, "CLUI", "AutoSizeUpward", 0)) { + rcWindow.top = rcWindow.bottom - newHeight; + if (rcWindow.top < rcWorkArea.top) + rcWindow.top = rcWorkArea.top; + } + else { + rcWindow.bottom = rcWindow.top + newHeight; + if (rcWindow.bottom > rcWorkArea.bottom) + rcWindow.bottom = rcWorkArea.bottom; + } + SetWindowPos(hwnd, 0, rcWindow.left, rcWindow.top, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, + SWP_NOZORDER | SWP_NOACTIVATE); + break; + } + case NM_CLICK: + { + NMCLISTCONTROL *nm = (NMCLISTCONTROL *) lParam; + DWORD hitFlags; + + if (SendMessage(cli.hwndContactTree, CLM_HITTEST, (WPARAM) & hitFlags, MAKELPARAM(nm->pt.x, nm->pt.y))) + break; + if ((hitFlags & (CLCHT_NOWHERE | CLCHT_INLEFTMARGIN | CLCHT_BELOWITEMS)) == 0) + break; + if (DBGetContactSettingByte(NULL, "CLUI", "ClientAreaDrag", SETTING_CLIENTDRAG_DEFAULT)) { + POINT pt; + pt = nm->pt; + ClientToScreen(cli.hwndContactTree, &pt); + return SendMessage(hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, MAKELPARAM(pt.x, pt.y)); + } + break; + } } + } + else if (((LPNMHDR) lParam)->hwndFrom == cli.hwndStatus) { + if (((LPNMHDR) lParam)->code == NM_CLICK) + { + unsigned int nParts, nPanel; + NMMOUSE *nm = (NMMOUSE *) lParam; + HMENU hMenu; + RECT rc; + POINT pt; + + hMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0); + nParts = SendMessage(cli.hwndStatus, SB_GETPARTS, 0, 0); + if (nm->dwItemSpec == 0xFFFFFFFE) { + nPanel = nParts - 1; + SendMessage(cli.hwndStatus, SB_GETRECT, nPanel, (LPARAM) & rc); + if (nm->pt.x < rc.left) + return FALSE; + } + else nPanel = nm->dwItemSpec; + + if (nParts > 0) + { + unsigned int cpnl = 0; + int mcnt = GetMenuItemCount(hMenu); + for (int i=0; iitemData == MENU_MIRANDAMENU) { + ((LPMEASUREITEMSTRUCT) lParam)->itemWidth = g_IconWidth * 4 / 3; + ((LPMEASUREITEMSTRUCT) lParam)->itemHeight = 0; + return TRUE; + } + return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam); + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT) lParam; + if (dis->hwndItem == cli.hwndStatus) { + char *szProto = (char *) dis->itemData; + if (szProto == NULL) return 0; + int status, x; + SIZE textSize; + BYTE showOpts = DBGetContactSettingByte(NULL, "CLUI", "SBarShow", 1); + status = CallProtoService(szProto, PS_GETSTATUS, 0, 0); + SetBkMode(dis->hDC, TRANSPARENT); + x = dis->rcItem.left; + if (showOpts & 1) { + HICON hIcon = LoadSkinProtoIcon(szProto, status); + DrawIconEx(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - g_IconHeight) >> 1, hIcon, + g_IconWidth, g_IconHeight, 0, NULL, DI_NORMAL); + IconLib_ReleaseIcon(hIcon,0); + if ( Proto_IsAccountLocked( Proto_GetAccount( szProto ))) { + hIcon = LoadSkinnedIcon(SKINICON_OTHER_STATUS_LOCKED); + if (hIcon != NULL) { + DrawIconEx(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - g_IconHeight) >> 1, hIcon, + g_IconWidth, g_IconHeight, 0, NULL, DI_NORMAL); + IconLib_ReleaseIcon(hIcon,0); + } + + } + x += g_IconWidth + 2; + } + else + x += 2; + if (showOpts & 2) { + PROTOACCOUNT* pa; + TCHAR tszName[64]; + if (( pa = Proto_GetAccount( szProto )) != NULL ) + mir_sntprintf( tszName, SIZEOF(tszName), _T("%s "), pa->tszAccountName ); + else + tszName[0] = 0; + + GetTextExtentPoint32(dis->hDC, tszName, lstrlen(tszName), &textSize); + TextOut(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - textSize.cy) >> 1, tszName, lstrlen(tszName)); + x += textSize.cx; + } + if (showOpts & 4) { + TCHAR* szStatus = cli.pfnGetStatusModeDescription( status, 0 ); + if ( !szStatus ) + szStatus = _T(""); + GetTextExtentPoint32( dis->hDC, szStatus, lstrlen(szStatus), &textSize ); + TextOut( dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - textSize.cy ) >> 1, szStatus, lstrlen( szStatus )); + } + } + else if (dis->CtlType == ODT_MENU) { + if (dis->itemData == MENU_MIRANDAMENU) { + HICON hIcon = LoadSkinnedIcon(SKINICON_OTHER_MAINMENU); + fnDrawMenuItem(dis, CopyIcon(hIcon), NULL); + IconLib_ReleaseIcon(hIcon, NULL); + return TRUE; + } + return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam); + } } + return 0; + + case WM_CLOSE: + if (CallService(MS_SYSTEM_OKTOEXIT, 0, 0)) + DestroyWindow(hwnd); + return FALSE; + + case WM_DESTROY: + if (!IsIconic(hwnd)) { + RECT rc; + GetWindowRect(hwnd, &rc); + + if (!CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) { //if docked, dont remember pos (except for width) + DBWriteContactSettingDword(NULL, "CList", "Height", (DWORD) (rc.bottom - rc.top)); + DBWriteContactSettingDword(NULL, "CList", "x", (DWORD) rc.left); + DBWriteContactSettingDword(NULL, "CList", "y", (DWORD) rc.top); + } + DBWriteContactSettingDword(NULL, "CList", "Width", (DWORD) (rc.right - rc.left)); + } + + RemoveMenu(cli.hMenuMain, 0, MF_BYPOSITION); + RemoveMenu(cli.hMenuMain, 0, MF_BYPOSITION); + + if ( cli.hwndStatus ) { + DestroyWindow( cli.hwndStatus ); + cli.hwndStatus = NULL; + } + + // Disconnect all protocols + DisconnectAll(); + + ShowWindow(hwnd, SW_HIDE); + DestroyWindow(cli.hwndContactTree); + FreeLibrary(hUserDll); + PostQuitMessage(0); + + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + return TRUE; +} diff --git a/src/modules/clist/cluiservices.cpp b/src/modules/clist/cluiservices.cpp new file mode 100644 index 0000000000..c6b6efda71 --- /dev/null +++ b/src/modules/clist/cluiservices.cpp @@ -0,0 +1,221 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +static INT_PTR GetHwnd(WPARAM, LPARAM) +{ + return (INT_PTR)cli.hwndContactList; +} + +static INT_PTR GetHwndTree(WPARAM, LPARAM) +{ + return (INT_PTR)cli.hwndContactTree; +} + +static INT_PTR CluiProtocolStatusChanged(WPARAM wParam, LPARAM lParam) +{ + cli.pfnCluiProtocolStatusChanged( wParam, (const char*)lParam ); + return 0; +} + +INT_PTR SortList(WPARAM, LPARAM) +{ + //unnecessary: CLC does this automatically + return 0; +} + +static INT_PTR GroupAdded(WPARAM wParam, LPARAM lParam) +{ + //CLC does this automatically unless it's a new group + if (lParam) { + HANDLE hItem; + TCHAR szFocusClass[64]; + HWND hwndFocus = GetFocus(); + + GetClassName(hwndFocus, szFocusClass, SIZEOF(szFocusClass)); + if (!lstrcmp(szFocusClass, CLISTCONTROL_CLASS)) { + hItem = (HANDLE) SendMessage(hwndFocus, CLM_FINDGROUP, wParam, 0); + if (hItem) + SendMessage(hwndFocus, CLM_EDITLABEL, (WPARAM) hItem, 0); + } + } + return 0; +} + +static INT_PTR ContactSetIcon(WPARAM, LPARAM) +{ + //unnecessary: CLC does this automatically + return 0; +} + +static INT_PTR ContactDeleted(WPARAM, LPARAM) +{ + //unnecessary: CLC does this automatically + return 0; +} + +static INT_PTR ContactAdded(WPARAM, LPARAM) +{ + //unnecessary: CLC does this automatically + return 0; +} + +static INT_PTR ListBeginRebuild(WPARAM, LPARAM) +{ + //unnecessary: CLC does this automatically + return 0; +} + +static INT_PTR ListEndRebuild(WPARAM, LPARAM) +{ + int rebuild = 0; + //CLC does this automatically, but we need to force it if hideoffline or hideempty has changed + if ((DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEOFFLINE) == 0)) { + if (DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT)) + SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) | CLS_HIDEOFFLINE); + else + SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & ~CLS_HIDEOFFLINE); + rebuild = 1; + } + if ((DBGetContactSettingByte(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) == 0)) { + if (DBGetContactSettingByte(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT)) + SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) | CLS_HIDEEMPTYGROUPS); + else + SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS); + rebuild = 1; + } + if ((DBGetContactSettingByte(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS) == 0)) { + if (DBGetContactSettingByte(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT)) + SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) | CLS_USEGROUPS); + else + SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & ~CLS_USEGROUPS); + rebuild = 1; + } + if (rebuild) + SendMessage(cli.hwndContactTree, CLM_AUTOREBUILD, 0, 0); + return 0; +} + +static INT_PTR ContactRenamed(WPARAM, LPARAM) +{ + //unnecessary: CLC does this automatically + return 0; +} + +static INT_PTR GetCaps(WPARAM wParam, LPARAM) +{ + switch (wParam) { + case CLUICAPS_FLAGS1: + return CLUIF_HIDEEMPTYGROUPS | CLUIF_DISABLEGROUPS | CLUIF_HASONTOPOPTION | CLUIF_HASAUTOHIDEOPTION; + } + return 0; +} + +void LoadCluiServices(void) +{ + CreateServiceFunction(MS_CLUI_GETHWND, GetHwnd); + CreateServiceFunction(MS_CLUI_GETHWNDTREE,GetHwndTree); + CreateServiceFunction(MS_CLUI_PROTOCOLSTATUSCHANGED, CluiProtocolStatusChanged); + CreateServiceFunction(MS_CLUI_GROUPADDED, GroupAdded); + CreateServiceFunction(MS_CLUI_CONTACTSETICON, ContactSetIcon); + CreateServiceFunction(MS_CLUI_CONTACTADDED, ContactAdded); + CreateServiceFunction(MS_CLUI_CONTACTDELETED, ContactDeleted); + CreateServiceFunction(MS_CLUI_CONTACTRENAMED, ContactRenamed); + CreateServiceFunction(MS_CLUI_LISTBEGINREBUILD, ListBeginRebuild); + CreateServiceFunction(MS_CLUI_LISTENDREBUILD, ListEndRebuild); + CreateServiceFunction(MS_CLUI_SORTLIST, SortList); + CreateServiceFunction(MS_CLUI_GETCAPS, GetCaps); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// default protocol status notification handler + +void fnCluiProtocolStatusChanged(int, const char* ) +{ + int i, *partWidths; + int borders[3]; + int flags = 0; + + if ( cli.menuProtoCount == 0 ) { + SendMessage(cli.hwndStatus, SB_SETPARTS, 0, 0); + SendMessage( cli.hwndStatus, SB_SETTEXT, SBT_OWNERDRAW, 0 ); + return; + } + + SendMessage(cli.hwndStatus, SB_GETBORDERS, 0, ( LPARAM )&borders); + + partWidths = ( int* )alloca( cli.menuProtoCount * sizeof( int )); + if ( DBGetContactSettingByte( NULL, "CLUI", "EqualSections", 0 )) { + RECT rc; + GetClientRect( cli.hwndStatus, &rc ); + rc.right -= borders[0] * 2 + ( DBGetContactSettingByte( NULL, "CLUI", "ShowGrip", 1 ) ? GetSystemMetrics( SM_CXVSCROLL ) : 0 ); + for ( i = 0; i < cli.menuProtoCount; i++ ) + partWidths[ i ] = ( i+1 ) * rc.right / cli.menuProtoCount - (borders[2] >> 1); + } + else { + HDC hdc; + SIZE textSize; + BYTE showOpts = DBGetContactSettingByte(NULL, "CLUI", "SBarShow", 1); + + hdc = GetDC(NULL); + SelectObject(hdc, (HFONT) SendMessage(cli.hwndStatus, WM_GETFONT, 0, 0)); + for ( i = 0; i < cli.menuProtoCount; i++ ) { //count down since built in ones tend to go at the end + int x = 2; + if ( showOpts & 1 ) + x += g_IconWidth; + if ( showOpts & 2 ) { + TCHAR tszName[64]; + PROTOACCOUNT* pa = Proto_GetAccount( cli.menuProtos[i].szProto ); + if ( pa ) + mir_sntprintf( tszName, SIZEOF(tszName), _T("%s "), pa->tszAccountName ); + else + tszName[0] = 0; + + if ( showOpts & 4 && lstrlen(tszName) < SIZEOF(tszName)-1 ) + lstrcat( tszName, _T(" ")); + GetTextExtentPoint32(hdc, tszName, lstrlen(tszName), &textSize); + x += textSize.cx; + x += GetSystemMetrics(SM_CXBORDER) * 4; // The SB panel doesnt allocate enough room + } + if ( showOpts & 4 ) { + TCHAR* modeDescr = cli.pfnGetStatusModeDescription( CallProtoService( cli.menuProtos[i].szProto, PS_GETSTATUS, 0, 0), 0 ); + GetTextExtentPoint32(hdc, modeDescr, lstrlen(modeDescr), &textSize); + x += textSize.cx; + x += GetSystemMetrics(SM_CXBORDER) * 4; // The SB panel doesnt allocate enough room + } + partWidths[ i ] = ( i ? partWidths[ i-1] : 0 ) + x + 2; + } + ReleaseDC(NULL, hdc); + } + + partWidths[ cli.menuProtoCount-1 ] = -1; + SendMessage(cli.hwndStatus, SB_SETMINHEIGHT, g_IconHeight, 0); + SendMessage(cli.hwndStatus, SB_SETPARTS, cli.menuProtoCount, ( LPARAM )partWidths); + flags = SBT_OWNERDRAW; + if ( DBGetContactSettingByte( NULL, "CLUI", "SBarBevel", 1 ) == 0 ) + flags |= SBT_NOBORDERS; + for ( i = 0; i < cli.menuProtoCount; i++ ) { + SendMessage( cli.hwndStatus, SB_SETTEXT, i | flags, ( LPARAM )cli.menuProtos[i].szProto ); + } +} diff --git a/src/modules/clist/contact.cpp b/src/modules/clist/contact.cpp new file mode 100644 index 0000000000..f996dd11ac --- /dev/null +++ b/src/modules/clist/contact.cpp @@ -0,0 +1,193 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +extern HANDLE hContactIconChangedEvent; +extern HANDLE hGroupChangeEvent; + +int sortByStatus; +int sortByProto; + +static const struct { + int status,order; +} statusModeOrder[]={ + {ID_STATUS_OFFLINE,500}, + {ID_STATUS_ONLINE,10}, + {ID_STATUS_AWAY,200}, + {ID_STATUS_DND,110}, + {ID_STATUS_NA,450}, + {ID_STATUS_OCCUPIED,100}, + {ID_STATUS_FREECHAT,0}, + {ID_STATUS_INVISIBLE,20}, + {ID_STATUS_ONTHEPHONE,150}, + {ID_STATUS_OUTTOLUNCH,425}}; + +static int GetContactStatus(HANDLE hContact) +{ + char* szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto == NULL) + return ID_STATUS_OFFLINE; + return DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE); +} + +void fnChangeContactIcon(HANDLE hContact, int iIcon, int add) +{ + CallService(add ? MS_CLUI_CONTACTADDED : MS_CLUI_CONTACTSETICON, (WPARAM) hContact, iIcon); + NotifyEventHooks(hContactIconChangedEvent, (WPARAM) hContact, iIcon); +} + +int GetStatusModeOrdering(int statusMode) +{ + int i; + for (i = 0; i < SIZEOF(statusModeOrder); i++) + if (statusModeOrder[i].status == statusMode) + return statusModeOrder[i].order; + return 1000; +} + +void fnLoadContactTree(void) +{ + HANDLE hContact; + int i, status, hideOffline; + + CallService(MS_CLUI_LISTBEGINREBUILD, 0, 0); + for (i = 1;; i++) { + if ( cli.pfnGetGroupName(i, NULL) == NULL) + break; + CallService(MS_CLUI_GROUPADDED, i, 0); + } + + hideOffline = DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT); + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while (hContact != NULL) { + status = GetContactStatus(hContact); + if ((!hideOffline || status != ID_STATUS_OFFLINE) && !DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) + cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode((char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0), status, hContact), 1); + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0); + } + sortByStatus = DBGetContactSettingByte(NULL, "CList", "SortByStatus", SETTING_SORTBYSTATUS_DEFAULT); + sortByProto = DBGetContactSettingByte(NULL, "CList", "SortByProto", SETTING_SORTBYPROTO_DEFAULT); + CallService(MS_CLUI_SORTLIST, 0, 0); + CallService(MS_CLUI_LISTENDREBUILD, 0, 0); +} + +int fnCompareContacts(const struct ClcContact* c1, const struct ClcContact* c2) +{ + HANDLE a = c1->hContact, b = c2->hContact; + TCHAR namea[128], *nameb; + int statusa, statusb; + int rc; + + statusa = DBGetContactSettingWord((HANDLE) a, c1->proto, "Status", ID_STATUS_OFFLINE); + statusb = DBGetContactSettingWord((HANDLE) b, c2->proto, "Status", ID_STATUS_OFFLINE); + + if (sortByProto) { + /* deal with statuses, online contacts have to go above offline */ + if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) { + return 2 * (statusa == ID_STATUS_OFFLINE) - 1; + } + /* both are online, now check protocols */ + rc = lstrcmpA( c1->proto, c2->proto); + if (rc != 0 && (c1->proto != NULL && c2->proto != NULL)) + return rc; + /* protocols are the same, order by display name */ + } + + if (sortByStatus) { + int ordera, orderb; + ordera = GetStatusModeOrdering(statusa); + orderb = GetStatusModeOrdering(statusb); + if (ordera != orderb) + return ordera - orderb; + } + else { + //one is offline: offline goes below online + if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) { + return 2 * (statusa == ID_STATUS_OFFLINE) - 1; + } + } + + nameb = cli.pfnGetContactDisplayName( a, 0); + _tcsncpy(namea, nameb, SIZEOF(namea)); + namea[ SIZEOF(namea)-1 ] = 0; + nameb = cli.pfnGetContactDisplayName( b, 0); + + //otherwise just compare names + return _tcsicmp(namea, nameb); +} + +static UINT_PTR resortTimerId = 0; +static VOID CALLBACK SortContactsTimer(HWND, UINT, UINT_PTR, DWORD) +{ + KillTimer(NULL, resortTimerId); + resortTimerId = 0; + CallService(MS_CLUI_SORTLIST, 0, 0); +} + +void fnSortContacts(void) +{ + //avoid doing lots of resorts in quick succession + sortByStatus = DBGetContactSettingByte(NULL, "CList", "SortByStatus", SETTING_SORTBYSTATUS_DEFAULT); + sortByProto = DBGetContactSettingByte(NULL, "CList", "SortByProto", SETTING_SORTBYPROTO_DEFAULT); + if (resortTimerId) + KillTimer(NULL, resortTimerId); + // setting this to a higher delay causes shutdown waits. + resortTimerId = SetTimer(NULL, 0, 500, SortContactsTimer); +} + +INT_PTR ContactChangeGroup(WPARAM wParam, LPARAM lParam) +{ + CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, NULL }; + + CallService(MS_CLUI_CONTACTDELETED, wParam, 0); + if ((HANDLE) lParam == NULL) + DBDeleteContactSetting((HANDLE) wParam, "CList", "Group"); + else { + grpChg.pszNewName = cli.pfnGetGroupName(lParam, NULL); + DBWriteContactSettingTString((HANDLE) wParam, "CList", "Group", grpChg.pszNewName); + } + CallService(MS_CLUI_CONTACTADDED, wParam, + cli.pfnIconFromStatusMode((char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0), GetContactStatus((HANDLE) wParam), (HANDLE) wParam)); + + NotifyEventHooks(hGroupChangeEvent, wParam, (LPARAM)&grpChg); + return 0; +} + +int fnSetHideOffline(WPARAM wParam, LPARAM) +{ + switch(( int )wParam ) { + case 0: + DBWriteContactSettingByte(NULL, "CList", "HideOffline", 0); + break; + case 1: + DBWriteContactSettingByte(NULL, "CList", "HideOffline", 1); + break; + case -1: + DBWriteContactSettingByte(NULL, "CList", "HideOffline", + (BYTE) ! DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT)); + break; + } + cli.pfnLoadContactTree(); + return 0; +} diff --git a/src/modules/clist/genmenu.cpp b/src/modules/clist/genmenu.cpp new file mode 100644 index 0000000000..92ec6d3edc --- /dev/null +++ b/src/modules/clist/genmenu.cpp @@ -0,0 +1,1294 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "genmenu.h" + +static bool bIsGenMenuInited; +bool bIconsDisabled; +static CRITICAL_SECTION csMenuHook; + +static int NextObjectId = 0x100, NextObjectMenuItemId = CLISTMENUIDMIN; + +#if defined( _DEBUG ) +static void DumpMenuItem( TMO_IntMenuItem* pParent, int level = 0 ) +{ + char temp[ 30 ]; + memset( temp, '\t', level ); + temp[ level ] = 0; + + for ( PMO_IntMenuItem pimi = pParent; pimi != NULL; pimi = pimi->next ) { + Netlib_Logf( NULL, "%sMenu item %08p [%08p]: %S", temp, pimi, pimi->mi.root, pimi->mi.ptszName ); + + PMO_IntMenuItem submenu = pimi->submenu.first; + if ( submenu ) + DumpMenuItem( submenu, level+1 ); +} } + +#endif + +static int CompareMenus( const TIntMenuObject* p1, const TIntMenuObject* p2 ) +{ + return lstrcmpA( p1->Name, p2->Name ); +} + +LIST g_menus( 10, CompareMenus ); + +void FreeAndNil( void **p ) +{ + if ( p == NULL ) + return; + + if ( *p != NULL ) { + mir_free( *p ); + *p = NULL; +} } + +int GetMenuObjbyId( const int id ) +{ + for ( int i=0; i < g_menus.getCount(); i++ ) + if ( g_menus[i]->id == id ) + return i; + + return -1; +} + +PMO_IntMenuItem MO_RecursiveWalkMenu( PMO_IntMenuItem parent, pfnWalkFunc func, void* param ) +{ + if ( parent == NULL ) + return FALSE; + + PMO_IntMenuItem pnext; + for ( PMO_IntMenuItem pimi = parent; pimi != NULL; pimi = pnext ) { + PMO_IntMenuItem submenu = pimi->submenu.first; + pnext = pimi->next; + if ( func( pimi, param )) // it can destroy the menu item + return pimi; + + if ( submenu ) { + PMO_IntMenuItem res = MO_RecursiveWalkMenu( submenu, func, param ); + if ( res ) + return res; + } + } + + return FALSE; +} + +//wparam=0 +//lparam=LPMEASUREITEMSTRUCT +int MO_MeasureMenuItem( LPMEASUREITEMSTRUCT mis ) +{ + // prevent win9x from ugly menus displaying when there is no icon + mis->itemWidth = 0; + mis->itemHeight = 0; + + if ( !bIsGenMenuInited ) + return -1; + + if ( mis == NULL ) + return FALSE; + + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )mis->itemData ); + if ( pimi == NULL ) + return FALSE; + + if ( pimi->iconId == -1 ) + return FALSE; + + mis->itemWidth = max(0,GetSystemMetrics(SM_CXSMICON)-GetSystemMetrics(SM_CXMENUCHECK)+4); + mis->itemHeight = GetSystemMetrics(SM_CYSMICON)+2; + return TRUE; +} + +//wparam=0 +//lparam=LPDRAWITEMSTRUCT +int MO_DrawMenuItem( LPDRAWITEMSTRUCT dis ) +{ + if ( !bIsGenMenuInited ) + return -1; + + if ( dis == NULL ) + return FALSE; + + EnterCriticalSection( &csMenuHook ); + + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )dis->itemData ); + if ( pimi == NULL || pimi->iconId == -1 ) { + LeaveCriticalSection( &csMenuHook ); + return FALSE; + } + + int y = (dis->rcItem.bottom - dis->rcItem.top - GetSystemMetrics(SM_CYSMICON))/2+1; + if ( dis->itemState & ODS_SELECTED ) { + if ( dis->itemState & ODS_CHECKED ) { + RECT rc; + rc.left = 2; rc.right = GetSystemMetrics(SM_CXSMICON)+2; + rc.top = y; rc.bottom = rc.top+GetSystemMetrics(SM_CYSMICON)+2; + FillRect(dis->hDC, &rc, GetSysColorBrush( COLOR_HIGHLIGHT )); + ImageList_DrawEx( pimi->parent->m_hMenuIcons, pimi->iconId, dis->hDC, 2, y, 0, 0, CLR_NONE, CLR_DEFAULT, ILD_SELECTED ); + } + else ImageList_DrawEx( pimi->parent->m_hMenuIcons, pimi->iconId, dis->hDC, 2, y, 0, 0, CLR_NONE, CLR_DEFAULT, ILD_FOCUS ); + } + else { + if ( dis->itemState & ODS_CHECKED) { + RECT rc; + rc.left = 0; rc.right = GetSystemMetrics(SM_CXSMICON)+4; + rc.top = y-2; rc.bottom = rc.top + GetSystemMetrics(SM_CYSMICON)+4; + DrawEdge(dis->hDC,&rc,BDR_SUNKENOUTER,BF_RECT); + InflateRect(&rc,-1,-1); + COLORREF menuCol = GetSysColor(COLOR_MENU); + COLORREF hiliteCol = GetSysColor(COLOR_3DHIGHLIGHT); + HBRUSH hBrush = CreateSolidBrush(RGB((GetRValue(menuCol)+GetRValue(hiliteCol))/2,(GetGValue(menuCol)+GetGValue(hiliteCol))/2,(GetBValue(menuCol)+GetBValue(hiliteCol))/2)); + FillRect(dis->hDC,&rc,GetSysColorBrush(COLOR_MENU)); + DeleteObject(hBrush); + ImageList_DrawEx(pimi->parent->m_hMenuIcons,pimi->iconId,dis->hDC,2,y,0,0,CLR_NONE,GetSysColor(COLOR_MENU),ILD_BLEND50); + } + else ImageList_DrawEx(pimi->parent->m_hMenuIcons,pimi->iconId,dis->hDC,2,y,0,0,CLR_NONE,CLR_NONE,ILD_NORMAL); + } + LeaveCriticalSection( &csMenuHook ); + return TRUE; +} + +int MO_RemoveAllObjects() +{ + int i; + for ( i=0; i < g_menus.getCount(); i++ ) + delete g_menus[i]; + + g_menus.destroy(); + return 0; +} + +//wparam=MenuObjectHandle +INT_PTR MO_RemoveMenuObject(WPARAM wParam, LPARAM) +{ + int objidx; + + if ( !bIsGenMenuInited) return -1; + EnterCriticalSection( &csMenuHook ); + + objidx = GetMenuObjbyId(( int )wParam ); + if ( objidx == -1 ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + delete g_menus[ objidx ]; + g_menus.remove( objidx ); + LeaveCriticalSection( &csMenuHook ); + return 0; +} + +//wparam=MenuObjectHandle +//lparam=vKey +INT_PTR MO_ProcessHotKeys( HANDLE menuHandle, INT_PTR vKey ) +{ + if ( !bIsGenMenuInited) + return -1; + + EnterCriticalSection( &csMenuHook ); + + int objidx = GetMenuObjbyId( (int)menuHandle ); + if ( objidx == -1 ) { + LeaveCriticalSection( &csMenuHook ); + return FALSE; + } + + for ( PMO_IntMenuItem pimi = g_menus[objidx]->m_items.first; pimi != NULL; pimi = pimi->next ) { + if ( pimi->mi.hotKey == 0 ) continue; + if ( HIWORD(pimi->mi.hotKey) != vKey) continue; + if ( !(LOWORD(pimi->mi.hotKey) & MOD_ALT ) != !( GetKeyState( VK_MENU ) & 0x8000)) continue; + if ( !(LOWORD(pimi->mi.hotKey) & MOD_CONTROL ) != !( GetKeyState( VK_CONTROL ) & 0x8000)) continue; + if ( !(LOWORD(pimi->mi.hotKey) & MOD_SHIFT ) != !( GetKeyState( VK_SHIFT ) & 0x8000)) continue; + + MO_ProcessCommand( pimi, 0 ); + LeaveCriticalSection( &csMenuHook ); + return TRUE; + } + + LeaveCriticalSection( &csMenuHook ); + return FALSE; +} + +INT_PTR MO_GetProtoRootMenu(WPARAM wParam,LPARAM lParam) +{ + char* szProto = ( char* )wParam; + if ( szProto == NULL ) + return 0; + + if ( DBGetContactSettingByte( NULL, "CList", "MoveProtoMenus", FALSE )) + return ( INT_PTR )cli.pfnGetProtocolMenu( szProto ); + + int objidx = GetMenuObjbyId(( int )hMainMenuObject ); + if ( objidx == -1 ) + return NULL; + + EnterCriticalSection( &csMenuHook ); + + TIntMenuObject* pmo = g_menus[objidx]; + PMO_IntMenuItem p; + for ( p = pmo->m_items.first; p != NULL; p = p->next ) + if ( !lstrcmpA( p->UniqName, szProto )) + break; + + LeaveCriticalSection( &csMenuHook ); + return ( INT_PTR )p; +} + +//wparam=MenuItemHandle +//lparam=PMO_MenuItem +INT_PTR MO_GetMenuItem(WPARAM wParam,LPARAM lParam) +{ + PMO_MenuItem mi = (PMO_MenuItem)lParam; + if ( !bIsGenMenuInited || mi == NULL ) + return -1; + + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam); + EnterCriticalSection( &csMenuHook ); + if ( !pimi ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + *mi = pimi->mi; + LeaveCriticalSection( &csMenuHook ); + return 0; +} + +static int FindDefaultItem( PMO_IntMenuItem pimi, void* ) +{ + if ( pimi->mi.flags & ( CMIF_GRAYED | CMIF_HIDDEN )) + return FALSE; + + return ( pimi->mi.flags & CMIF_DEFAULT ) ? TRUE : FALSE; +} + +INT_PTR MO_GetDefaultMenuItem(WPARAM wParam, LPARAM) +{ + if ( !bIsGenMenuInited ) + return -1; + + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam); + EnterCriticalSection( &csMenuHook ); + if ( pimi ) + pimi = MO_RecursiveWalkMenu( pimi, FindDefaultItem, NULL ); + + LeaveCriticalSection( &csMenuHook ); + return ( INT_PTR )pimi; +} + +//wparam MenuItemHandle +//lparam PMO_MenuItem +int MO_ModifyMenuItem( PMO_IntMenuItem menuHandle, PMO_MenuItem pmi ) +{ + int oldflags; + + if ( !bIsGenMenuInited || pmi == NULL || pmi->cbSize != sizeof( TMO_MenuItem )) + return -1; + + EnterCriticalSection( &csMenuHook ); + + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )menuHandle ); + if ( !pimi ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + if ( pmi->flags & CMIM_NAME ) { + FreeAndNil(( void** )&pimi->mi.pszName ); +#if defined( _UNICODE ) + if ( pmi->flags & CMIF_UNICODE ) + pimi->mi.ptszName = mir_tstrdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : TranslateTS( pmi->ptszName )); + else { + if ( pmi->flags & CMIF_KEEPUNTRANSLATED ) { + int len = lstrlenA( pmi->pszName ); + pimi->mi.ptszName = ( TCHAR* )mir_alloc( sizeof( TCHAR )*( len+1 )); + MultiByteToWideChar( CP_ACP, 0, pmi->pszName, -1, pimi->mi.ptszName, len+1 ); + pimi->mi.ptszName[ len ] = 0; + } + else pimi->mi.ptszName = LangPackPcharToTchar( pmi->pszName ); + } +#else + pimi->mi.ptszName = mir_strdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : Translate( pmi->ptszName )); +#endif + } + if ( pmi->flags & CMIM_FLAGS ) { + oldflags = pimi->mi.flags & ( CMIF_ROOTHANDLE | CMIF_ICONFROMICOLIB ); + pimi->mi.flags = (pmi->flags & ~CMIM_ALL) | oldflags; + } + if ( (pmi->flags & CMIM_ICON) && !bIconsDisabled ) { + if ( pimi->mi.flags & CMIF_ICONFROMICOLIB ) { + HICON hIcon = IcoLib_GetIconByHandle( pmi->hIcolibItem, false ); + if ( hIcon != NULL ) { + pimi->hIcolibItem = pmi->hIcolibItem; + pimi->iconId = ImageList_ReplaceIcon( pimi->parent->m_hMenuIcons, pimi->iconId, hIcon ); + IconLib_ReleaseIcon( hIcon, 0 ); + } + else pimi->iconId = -1, pimi->hIcolibItem = NULL; + } + else { + pimi->mi.hIcon = pmi->hIcon; + if ( pmi->hIcon != NULL ) + pimi->iconId = ImageList_ReplaceIcon( pimi->parent->m_hMenuIcons, pimi->iconId, pmi->hIcon ); + else + pimi->iconId = -1; //fixme, should remove old icon & shuffle all iconIds + } + if (pimi->hBmp) DeleteObject(pimi->hBmp); pimi->hBmp = NULL; + } + + if ( pmi->flags & CMIM_HOTKEY ) + pimi->mi.hotKey = pmi->hotKey; + + LeaveCriticalSection( &csMenuHook ); + return 0; +} + +//wparam MenuItemHandle +//return ownerdata useful to free ownerdata before delete menu item, +//NULL on error. +INT_PTR MO_MenuItemGetOwnerData(WPARAM wParam, LPARAM) +{ + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam ); + if ( !pimi ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + INT_PTR res = ( INT_PTR )pimi->mi.ownerdata; + LeaveCriticalSection( &csMenuHook ); + return res; +} + +PMO_IntMenuItem MO_GetIntMenuItem(HGENMENU wParam) +{ + PMO_IntMenuItem result = ( PMO_IntMenuItem )wParam; + if ( result == NULL || wParam == (HGENMENU)0xffff1234 || wParam == HGENMENU_ROOT) + return NULL; + + __try + { + if ( result->signature != MENUITEM_SIGNATURE ) + result = NULL; + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + result = NULL; + } + + return result; +} + +//LOWORD(wparam) menuident + +static int FindMenuByCommand( PMO_IntMenuItem pimi, void* pCommand ) +{ + return ( pimi->iCommand == (int)pCommand ); +} + +int MO_ProcessCommandBySubMenuIdent(int menuID, int command, LPARAM lParam) +{ + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + + int objidx = GetMenuObjbyId( menuID ); + if ( objidx == -1 ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + PMO_IntMenuItem pimi = MO_RecursiveWalkMenu( g_menus[objidx]->m_items.first, FindMenuByCommand, ( void* )command ); + if ( pimi ) { + LeaveCriticalSection( &csMenuHook ); + return MO_ProcessCommand( pimi, lParam ); + } + + LeaveCriticalSection( &csMenuHook ); + return -1; +} + +INT_PTR MO_ProcessCommandByMenuIdent(WPARAM wParam,LPARAM lParam) +{ + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + + for ( int i=0; i < g_menus.getCount(); i++ ) { + PMO_IntMenuItem pimi = MO_RecursiveWalkMenu( g_menus[i]->m_items.first, FindMenuByCommand, ( void* )wParam ); + if ( pimi ) { + LeaveCriticalSection( &csMenuHook ); + return MO_ProcessCommand( pimi, lParam ); + } } + + LeaveCriticalSection( &csMenuHook ); + return FALSE; +} + +int MO_ProcessCommand( PMO_IntMenuItem aHandle, LPARAM lParam ) +{ + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + PMO_IntMenuItem pimi = MO_GetIntMenuItem( aHandle ); + if ( !pimi ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + char *srvname = pimi->parent->ExecService; + void *ownerdata = pimi->mi.ownerdata; + LeaveCriticalSection( &csMenuHook ); + CallService( srvname, ( WPARAM )ownerdata, lParam ); + return 1; +} + +int MO_SetOptionsMenuItem( PMO_IntMenuItem aHandle, int setting, INT_PTR value ) +{ + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + PMO_IntMenuItem pimi = MO_GetIntMenuItem( aHandle ); + if ( !pimi ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + int res = -1; + __try + { + res = 1; + if ( setting == OPT_MENUITEMSETUNIQNAME ) { + mir_free( pimi->UniqName ); + pimi->UniqName = mir_strdup(( char* )value ); + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) {} + + LeaveCriticalSection( &csMenuHook ); + return res; +} + +int MO_SetOptionsMenuObject( HANDLE handle, int setting, INT_PTR value ) +{ + int pimoidx; + int res = 0; + + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + __try + { + pimoidx = GetMenuObjbyId( (int)handle ); + res = pimoidx != -1; + if ( res ) { + TIntMenuObject* pmo = g_menus[pimoidx]; + + switch ( setting ) { + case OPT_MENUOBJECT_SET_ONADD_SERVICE: + FreeAndNil(( void** )&pmo->onAddService ); + pmo->onAddService = mir_strdup(( char* )value ); + break; + + case OPT_MENUOBJECT_SET_FREE_SERVICE: + FreeAndNil(( void** )&pmo->FreeService ); + pmo->FreeService = mir_strdup(( char* )value ); + break; + + case OPT_MENUOBJECT_SET_CHECK_SERVICE: + FreeAndNil(( void** )&pmo->CheckService ); + pmo->CheckService = mir_strdup(( char* )value); + break; + + case OPT_USERDEFINEDITEMS: + pmo->m_bUseUserDefinedItems = ( BOOL )value; + break; + } + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) {} + + LeaveCriticalSection( &csMenuHook ); + return res; +} + +//wparam=0; +//lparam=PMenuParam; +//result=MenuObjectHandle +INT_PTR MO_CreateNewMenuObject(WPARAM, LPARAM lParam) +{ + PMenuParam pmp = ( PMenuParam )lParam; + if ( !bIsGenMenuInited || pmp == NULL ) + return -1; + + EnterCriticalSection( &csMenuHook ); + TIntMenuObject* p = new TIntMenuObject(); + p->id = NextObjectId++; + p->Name = mir_strdup( pmp->name ); + p->CheckService = mir_strdup( pmp->CheckService ); + p->ExecService = mir_strdup( pmp->ExecService ); + p->m_hMenuIcons = ImageList_Create( GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16) | ILC_MASK, 15, 100 ); + g_menus.insert(p); + + LeaveCriticalSection( &csMenuHook ); + return p->id; +} + +//wparam=MenuItemHandle +//lparam=0 + +static int FreeMenuItem( TMO_IntMenuItem* pimi, void* ) +{ + pimi->parent->freeItem( pimi ); + return FALSE; +} + +static int FindParent( TMO_IntMenuItem* pimi, void* p ) +{ + return pimi->next == p; +} + +INT_PTR MO_RemoveMenuItem(WPARAM wParam, LPARAM) +{ + EnterCriticalSection( &csMenuHook ); + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam ); + if ( !pimi ) { + LeaveCriticalSection( &csMenuHook ); + return -1; + } + + if ( pimi->submenu.first ) { + MO_RecursiveWalkMenu( pimi->submenu.first, FreeMenuItem, NULL ); + pimi->submenu.first = NULL; + } + + PMO_IntMenuItem prev = MO_RecursiveWalkMenu( pimi->owner->first, FindParent, pimi ); + if ( prev ) + prev->next = pimi->next; + if ( pimi->owner->first == pimi ) + pimi->owner->first = pimi->next; + if ( pimi->owner->last == pimi ) + pimi->owner->last = prev; + + pimi->signature = 0; // invalidate all future calls to that object + pimi->parent->freeItem( pimi ); + + LeaveCriticalSection( &csMenuHook ); + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// we presume that this function is being called inside csMenuHook only + +static int PackMenuItems( PMO_IntMenuItem pimi, void* ) +{ + pimi->iCommand = NextObjectMenuItemId++; + return FALSE; +} + +static int GetNextObjectMenuItemId() +{ + // if menu commands are exausted, pack the menu array + if ( NextObjectMenuItemId >= CLISTMENUIDMAX ) { + NextObjectMenuItemId = CLISTMENUIDMIN; + for ( int i=0; i < g_menus.getCount(); i++ ) + MO_RecursiveWalkMenu( g_menus[i]->m_items.first, PackMenuItems, NULL ); + } + + return NextObjectMenuItemId++; +} + +//wparam=MenuObjectHandle +//lparam=PMO_MenuItem +//return MenuItemHandle +PMO_IntMenuItem MO_AddNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi ) +{ + if ( !bIsGenMenuInited || pmi == NULL || pmi->cbSize != sizeof( TMO_MenuItem )) + return NULL; + + //old mode + if ( !( pmi->flags & CMIF_ROOTHANDLE )) + return MO_AddOldNewMenuItem( menuobjecthandle, pmi ); + + EnterCriticalSection( &csMenuHook ); + int objidx = GetMenuObjbyId( (int)menuobjecthandle ); + if ( objidx == -1 ) { + LeaveCriticalSection( &csMenuHook ); + return NULL; + } + + TIntMenuObject* pmo = g_menus[objidx]; + + TMO_IntMenuItem* p = ( TMO_IntMenuItem* )mir_calloc( sizeof( TMO_IntMenuItem )); + p->parent = pmo; + p->signature = MENUITEM_SIGNATURE; + p->iCommand = GetNextObjectMenuItemId(); + p->mi = *pmi; + p->iconId = -1; + p->OverrideShow = TRUE; + p->originalPosition = pmi->position; + #if defined( _UNICODE ) + if ( pmi->flags & CMIF_UNICODE ) + p->mi.ptszName = mir_tstrdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : TranslateTS( pmi->ptszName )); + else { + if ( pmi->flags & CMIF_KEEPUNTRANSLATED ) + p->mi.ptszName = mir_a2u(pmi->pszName); + else + p->mi.ptszName = LangPackPcharToTchar( pmi->pszName ); + } + #else + p->mi.ptszName = mir_strdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : Translate( pmi->ptszName )); + #endif + + if ( pmi->hIcon != NULL && !bIconsDisabled ) { + if ( pmi->flags & CMIF_ICONFROMICOLIB ) { + HICON hIcon = IcoLib_GetIconByHandle( pmi->hIcolibItem, false ); + p->iconId = ImageList_AddIcon( pmo->m_hMenuIcons, hIcon ); + p->hIcolibItem = pmi->hIcolibItem; + IconLib_ReleaseIcon( hIcon, 0 ); + } + else { + HANDLE hIcolibItem = IcoLib_IsManaged( pmi->hIcon ); + if ( hIcolibItem ) { + p->iconId = ImageList_AddIcon( pmo->m_hMenuIcons, pmi->hIcon ); + p->hIcolibItem = hIcolibItem; + } + else p->iconId = ImageList_AddIcon( pmo->m_hMenuIcons, pmi->hIcon ); + } } + + if ( p->mi.root == HGENMENU_ROOT ) + p->mi.root = NULL; + + PMO_IntMenuItem pRoot = ( p->mi.root != NULL ) ? MO_GetIntMenuItem( p->mi.root ) : NULL; + if ( pRoot ) + p->owner = &pRoot->submenu; + else + p->owner = &pmo->m_items; + + if ( !p->owner->first ) + p->owner->first = p; + if ( p->owner->last ) + p->owner->last->next = p; + p->owner->last = p; + + LeaveCriticalSection( &csMenuHook ); + return p; +} + +//wparam=MenuObjectHandle +//lparam=PMO_MenuItem + +int FindRoot( PMO_IntMenuItem pimi, void* param ) +{ + if ( pimi->mi.pszName != NULL ) + if ( pimi->submenu.first && !_tcscmp( pimi->mi.ptszName, ( TCHAR* )param )) + return TRUE; + + return FALSE; +} + +PMO_IntMenuItem MO_AddOldNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi ) +{ + if ( !bIsGenMenuInited || pmi == NULL ) + return NULL; + + int objidx = GetMenuObjbyId( (int)menuobjecthandle ); + if ( objidx == -1 ) + return NULL; + + if ( pmi->cbSize != sizeof( TMO_MenuItem )) + return NULL; + + if ( pmi->flags & CMIF_ROOTHANDLE ) + return NULL; + + //is item with popup or not + if ( pmi->root == 0 ) { + //yes,this without popup + pmi->root = NULL; //first level + } + else { // no,search for needed root and create it if need + TCHAR* tszRoot; +#if defined( _UNICODE ) + if ( pmi->flags & CMIF_UNICODE ) + tszRoot = mir_tstrdup(TranslateTS(( TCHAR* )pmi->root )); + else + tszRoot = LangPackPcharToTchar(( char* )pmi->root ); +#else + tszRoot = mir_tstrdup(TranslateTS(( TCHAR* )pmi->root )); +#endif + + PMO_IntMenuItem oldroot = MO_RecursiveWalkMenu( g_menus[objidx]->m_items.first, FindRoot, tszRoot ); + mir_free( tszRoot ); + + if ( oldroot == NULL ) { + //not found,creating root + TMO_MenuItem tmi = { 0 }; + tmi = *pmi; + tmi.flags |= CMIF_ROOTHANDLE; + tmi.ownerdata = 0; + tmi.root = NULL; + //copy pszPopupName + tmi.ptszName = ( TCHAR* )pmi->root; + if (( oldroot = MO_AddNewMenuItem( menuobjecthandle, &tmi )) != NULL ) + MO_SetOptionsMenuItem( oldroot, OPT_MENUITEMSETUNIQNAME, (INT_PTR)pmi->root ); + } + pmi->root = oldroot; + + //popup will be created in next commands + } + pmi->flags |= CMIF_ROOTHANDLE; + //add popup(root allready exists) + return MO_AddNewMenuItem( menuobjecthandle, pmi ); +} + +static int WhereToPlace( HMENU hMenu, PMO_MenuItem mi ) +{ + MENUITEMINFO mii = { 0 }; + mii.cbSize = MENUITEMINFO_V4_SIZE; + mii.fMask = MIIM_SUBMENU | MIIM_DATA; + for ( int i=GetMenuItemCount( hMenu )-1; i >= 0; i-- ) { + GetMenuItemInfo( hMenu, i, TRUE, &mii ); + if ( mii.fType != MFT_SEPARATOR ) { + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData); + if ( pimi != NULL ) + if ( pimi->mi.position <= mi->position ) + return i+1; + } } + + return 0; +} + +static void InsertMenuItemWithSeparators(HMENU hMenu, int uItem, MENUITEMINFO *lpmii) +{ + int needSeparator = 0; + MENUITEMINFO mii; + + PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )lpmii->dwItemData ); + if ( pimi == NULL ) + return; + + int thisItemPosition = pimi->mi.position; + + ZeroMemory( &mii, sizeof( mii )); + mii.cbSize = MENUITEMINFO_V4_SIZE; + //check for separator before + if ( uItem ) { + mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE; + GetMenuItemInfo( hMenu, uItem-1, TRUE, &mii ); + pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData ); + if ( pimi != NULL ) { + if ( mii.fType == MFT_SEPARATOR ) + needSeparator = 0; + else + needSeparator = ( pimi->mi.position / SEPARATORPOSITIONINTERVAL ) != thisItemPosition / SEPARATORPOSITIONINTERVAL; + } + if ( needSeparator) { + //but might be supposed to be after the next one instead + mii.fType = 0; + if ( uItem < GetMenuItemCount( hMenu )) { + mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE; + GetMenuItemInfo( hMenu, uItem, TRUE, &mii ); + } + if ( mii.fType != MFT_SEPARATOR) { + mii.fMask = MIIM_TYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem( hMenu, uItem, TRUE, &mii ); + } + uItem++; + } } + + //check for separator after + if ( uItem < GetMenuItemCount( hMenu )) { + mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE; + mii.cch = 0; + GetMenuItemInfo( hMenu, uItem, TRUE, &mii ); + pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData ); + if ( pimi != NULL ) { + if ( mii.fType == MFT_SEPARATOR ) + needSeparator=0; + else + needSeparator = pimi->mi.position / SEPARATORPOSITIONINTERVAL != thisItemPosition / SEPARATORPOSITIONINTERVAL; + } + if ( needSeparator) { + mii.fMask = MIIM_TYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem( hMenu, uItem, TRUE, &mii ); + } } + + if ( uItem == GetMenuItemCount( hMenu )-1 ) { + TCHAR str[32]; + mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE; + mii.dwTypeData = str; + mii.cch = SIZEOF( str ); + GetMenuItemInfo( hMenu, uItem, TRUE, &mii ); + } + + // create local copy *lpmii so we can change some flags + MENUITEMINFO mii_copy = *lpmii; + lpmii = &mii_copy; + + if (( GetMenuItemCount( hMenu ) % 35 ) == 33 /* will be 34 after addition :) */ && pimi != NULL ) + if ( pimi->mi.root != NULL ) { + if ( !( lpmii->fMask & MIIM_FTYPE )) + lpmii->fType = 0; + lpmii->fMask |= MIIM_FTYPE; + lpmii->fType |= MFT_MENUBARBREAK; + } + + InsertMenuItem( hMenu, uItem, TRUE, lpmii ); +} + +//wparam started hMenu +//lparam ListParam* +//result hMenu +INT_PTR MO_BuildMenu(WPARAM wParam,LPARAM lParam) +{ + if ( !bIsGenMenuInited ) + return -1; + + EnterCriticalSection( &csMenuHook ); + + ListParam *lp = ( ListParam* )lParam; + int pimoidx = GetMenuObjbyId( (int)lp->MenuObjectHandle ); + if ( pimoidx == -1 ) { + LeaveCriticalSection( &csMenuHook ); + return 0; + } + + #if defined( _DEBUG ) + // DumpMenuItem( g_menus[pimoidx]->m_items.first ); + #endif + + INT_PTR res = (INT_PTR)BuildRecursiveMenu(( HMENU )wParam, g_menus[pimoidx]->m_items.first, ( ListParam* )lParam ); + LeaveCriticalSection( &csMenuHook ); + return res; +} + +#ifdef _DEBUG +#define PUTPOSITIONSONMENU +#endif + +void GetMenuItemName( PMO_IntMenuItem pMenuItem, char* pszDest, size_t cbDestSize ) +{ + if ( pMenuItem->UniqName ) + mir_snprintf( pszDest, cbDestSize, "{%s}", pMenuItem->UniqName ); + else if (pMenuItem->mi.flags & CMIF_UNICODE) { + char* name = mir_t2a( pMenuItem->mi.ptszName ); + mir_snprintf( pszDest, cbDestSize, "{%s}", name ); + mir_free(name); + } + else + mir_snprintf( pszDest, cbDestSize, "{%s}", pMenuItem->mi.pszName ); +} + +HMENU BuildRecursiveMenu(HMENU hMenu, PMO_IntMenuItem pRootMenu, ListParam *param) +{ + if ( param == NULL || pRootMenu == NULL ) + return NULL; + + TIntMenuObject* pmo = pRootMenu->parent; + + int rootlevel = ( param->rootlevel == -1 ) ? 0 : param->rootlevel; + + ListParam localparam = *param; + + while ( rootlevel == 0 && GetMenuItemCount( hMenu ) > 0 ) + DeleteMenu( hMenu, 0, MF_BYPOSITION ); + + for ( PMO_IntMenuItem pmi = pRootMenu; pmi != NULL; pmi = pmi->next ) { + PMO_MenuItem mi = &pmi->mi; + if ( mi->cbSize != sizeof( TMO_MenuItem )) + continue; + + if ( mi->flags & CMIF_HIDDEN ) + continue; + + if ( pmo->CheckService != NULL ) { + TCheckProcParam CheckParam; + CheckParam.lParam = param->lParam; + CheckParam.wParam = param->wParam; + CheckParam.MenuItemOwnerData = mi->ownerdata; + CheckParam.MenuItemHandle = pmi; + if ( CallService( pmo->CheckService, ( WPARAM )&CheckParam, 0 ) == FALSE ) + continue; + } + + /**************************************/ + if ( rootlevel == 0 && mi->root == NULL && pmo->m_bUseUserDefinedItems ) { + char DBString[256]; + DBVARIANT dbv = { 0 }; + int pos; + char MenuNameItems[256]; + mir_snprintf(MenuNameItems, SIZEOF(MenuNameItems), "%s_Items", pmo->Name); + + char menuItemName[256]; + GetMenuItemName( pmi, menuItemName, sizeof( menuItemName )); + + // check if it visible + mir_snprintf( DBString, SIZEOF(DBString), "%s_visible", menuItemName ); + if ( DBGetContactSettingByte( NULL, MenuNameItems, DBString, -1 ) == -1 ) + DBWriteContactSettingByte( NULL, MenuNameItems, DBString, 1 ); + + pmi->OverrideShow = TRUE; + if ( !DBGetContactSettingByte( NULL, MenuNameItems, DBString, 1 )) { + pmi->OverrideShow = FALSE; + continue; // find out what value to return if not getting added + } + + // mi.pszName + mir_snprintf( DBString, SIZEOF(DBString), "%s_name", menuItemName ); + if ( !DBGetContactSettingTString( NULL, MenuNameItems, DBString, &dbv )) { + if ( _tcslen( dbv.ptszVal ) > 0 ) { + if ( pmi->CustomName ) mir_free( pmi->CustomName ); + pmi->CustomName = mir_tstrdup( dbv.ptszVal ); + } + DBFreeVariant( &dbv ); + } + + mir_snprintf( DBString, SIZEOF(DBString), "%s_pos", menuItemName ); + if (( pos = DBGetContactSettingDword( NULL, MenuNameItems, DBString, -1 )) == -1 ) { + DBWriteContactSettingDword( NULL, MenuNameItems, DBString, mi->position ); + if ( pmi->submenu.first ) + mi->position = 0; + } + else mi->position = pos; + } + + /**************************************/ + + if ( rootlevel != (int)pmi->mi.root ) + continue; + + MENUITEMINFO mii = { 0 }; + mii.dwItemData = ( LPARAM )pmi; + + int i = WhereToPlace( hMenu, mi ); + + if ( !IsWinVer98Plus()) { + mii.cbSize = MENUITEMINFO_V4_SIZE; + mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID; + mii.fType = MFT_STRING; + } + else { + mii.cbSize = sizeof( mii ); + mii.fMask = MIIM_DATA | MIIM_ID | MIIM_STRING; + if ( pmi->iconId != -1 ) { + mii.fMask |= MIIM_BITMAP; + if (IsWinVerVistaPlus() && isThemeActive()) { + if (pmi->hBmp == NULL) + pmi->hBmp = ConvertIconToBitmap(NULL, pmi->parent->m_hMenuIcons, pmi->iconId); + mii.hbmpItem = pmi->hBmp; + } + else + mii.hbmpItem = HBMMENU_CALLBACK; + } + } + + mii.fMask |= MIIM_STATE; + mii.fState = (( pmi->mi.flags & CMIF_GRAYED ) ? MFS_GRAYED : MFS_ENABLED ); + mii.fState |= (( pmi->mi.flags & CMIF_CHECKED) ? MFS_CHECKED : MFS_UNCHECKED ); + if ( pmi->mi.flags & CMIF_DEFAULT ) mii.fState |= MFS_DEFAULT; + + mii.dwTypeData = ( pmi->CustomName ) ? pmi->CustomName : mi->ptszName; + + // it's a submenu + if ( pmi->submenu.first ) { + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = CreatePopupMenu(); + + #ifdef PUTPOSITIONSONMENU + if ( GetKeyState(VK_CONTROL) & 0x8000) { + TCHAR str[256]; + mir_sntprintf( str, SIZEOF(str), _T( "%s (%d,id %x)" ), mi->pszName, mi->position, mii.dwItemData ); + mii.dwTypeData = str; + } + #endif + + InsertMenuItemWithSeparators( hMenu, i, &mii); + localparam.rootlevel = LPARAM( pmi ); + BuildRecursiveMenu( mii.hSubMenu, pmi->submenu.first, &localparam ); + } + else { + mii.wID = pmi->iCommand; + + #ifdef PUTPOSITIONSONMENU + if ( GetKeyState(VK_CONTROL) & 0x8000) { + TCHAR str[256]; + mir_sntprintf( str, SIZEOF(str), _T("%s (%d,id %x)"), mi->pszName, mi->position, mii.dwItemData ); + mii.dwTypeData = str; + } + #endif + + if ( pmo->onAddService != NULL ) + if ( CallService( pmo->onAddService, ( WPARAM )&mii, ( LPARAM )pmi ) == FALSE ) + continue; + + InsertMenuItemWithSeparators( hMenu, i, &mii ); + } } + + return hMenu; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// iconlib in menu + +static int MO_ReloadIcon( PMO_IntMenuItem pmi, void* ) +{ + if ( pmi->hIcolibItem ) { + HICON newIcon = IcoLib_GetIconByHandle( pmi->hIcolibItem, false ); + if ( newIcon ) + ImageList_ReplaceIcon( pmi->parent->m_hMenuIcons, pmi->iconId, newIcon ); + + IconLib_ReleaseIcon(newIcon,0); + } + + return FALSE; +} + +int OnIconLibChanges(WPARAM, LPARAM) +{ + EnterCriticalSection( &csMenuHook ); + for ( int mo=0; mo < g_menus.getCount(); mo++ ) + if ( (int)hStatusMenuObject != g_menus[mo]->id ) //skip status menu + MO_RecursiveWalkMenu( g_menus[mo]->m_items.first, MO_ReloadIcon, 0 ); + + LeaveCriticalSection( &csMenuHook ); + + cli.pfnReloadProtoMenus(); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// + +static int MO_RegisterIcon( PMO_IntMenuItem pmi, void* ) +{ + char *uname, *descr; + uname = pmi->UniqName; + if ( uname == NULL ) + #ifdef UNICODE + uname = mir_u2a(pmi->CustomName); + descr = mir_u2a(pmi->mi.ptszName); + #else + uname = pmi->CustomName; + descr = pmi->mi.pszName; + #endif + + if ( !uname && !descr ) + return FALSE; + + if ( !pmi->hIcolibItem ) { + HICON hIcon = ImageList_GetIcon( pmi->parent->m_hMenuIcons, pmi->iconId, 0 ); + char* buf = NEWSTR_ALLOCA( descr ); + + char sectionName[256], iconame[256]; + mir_snprintf( sectionName, sizeof(sectionName), "Menu Icons/%s", pmi->parent->Name ); + + // remove '&' + char* start = buf; + while ( start ) { + if (( start = strchr( start, '&' )) == NULL ) + break; + + memmove(start,start+1,strlen(start+1)+1); + if (*start!='\0') start++; + else break; + } + + mir_snprintf(iconame, sizeof(iconame), "genmenu_%s_%s", pmi->parent->Name, uname && *uname ? uname : descr); + + SKINICONDESC sid={0}; + sid.cbSize = sizeof(sid); + sid.cx = 16; + sid.cy = 16; + sid.pszSection = sectionName; + sid.pszName = iconame; + sid.pszDefaultFile = NULL; + sid.pszDescription = buf; + sid.hDefaultIcon = hIcon; + pmi->hIcolibItem = ( HANDLE )CallService(MS_SKIN2_ADDICON, 0, (LPARAM)&sid); + + Safe_DestroyIcon( hIcon ); + if ( hIcon = ( HICON )CallService( MS_SKIN2_GETICON, 0, (LPARAM)iconame )) { + ImageList_ReplaceIcon( pmi->parent->m_hMenuIcons, pmi->iconId, hIcon ); + IconLib_ReleaseIcon( hIcon, 0 ); + } } + + #ifdef UNICODE + if ( !pmi->UniqName ) + mir_free( uname ); + mir_free( descr ); + #endif + + return FALSE; +} + +int RegisterAllIconsInIconLib() +{ + //register all icons + for ( int mo=0; mo < g_menus.getCount(); mo++ ) { + if ( (int)hStatusMenuObject == g_menus[mo]->id ) //skip status menu + continue; + + MO_RecursiveWalkMenu( g_menus[mo]->m_items.first, MO_RegisterIcon, 0 ); + } + + return 0; +} + +int TryProcessDoubleClick( HANDLE hContact ) +{ + int iMenuID = GetMenuObjbyId( (int)hContactMenuObject ); + if ( iMenuID != -1 ) { + NotifyEventHooks(hPreBuildContactMenuEvent,(WPARAM)hContact,0); + + PMO_IntMenuItem pimi = ( PMO_IntMenuItem )MO_GetDefaultMenuItem(( WPARAM )g_menus[ iMenuID ]->m_items.first, 0 ); + if ( pimi != NULL ) { + MO_ProcessCommand( pimi, ( LPARAM )hContact ); + return 0; + } } + + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Static services + +int posttimerid; + +static VOID CALLBACK PostRegisterIcons( HWND, UINT, UINT_PTR, DWORD ) +{ + KillTimer( 0, posttimerid ); + RegisterAllIconsInIconLib(); +} + +static int OnModulesLoaded(WPARAM, LPARAM) +{ + posttimerid = SetTimer(( HWND )NULL, 0, 5, ( TIMERPROC )PostRegisterIcons ); + HookEvent(ME_SKIN2_ICONSCHANGED,OnIconLibChanges); + return 0; +} + +static INT_PTR SRVMO_SetOptionsMenuObject( WPARAM, LPARAM lParam) +{ + lpOptParam lpop = ( lpOptParam )lParam; + if ( lpop == NULL ) + return 0; + + return MO_SetOptionsMenuObject( lpop->Handle, lpop->Setting, lpop->Value ); +} + +static INT_PTR SRVMO_SetOptionsMenuItem( WPARAM, LPARAM lParam) +{ + lpOptParam lpop = ( lpOptParam )lParam; + if ( lpop == NULL ) + return 0; + + return MO_SetOptionsMenuItem(( PMO_IntMenuItem )lpop->Handle, lpop->Setting, lpop->Value ); +} + +int InitGenMenu() +{ + InitializeCriticalSection( &csMenuHook ); + CreateServiceFunction( MO_BUILDMENU, MO_BuildMenu ); + + CreateServiceFunction( MO_PROCESSCOMMAND, ( MIRANDASERVICE )MO_ProcessCommand ); + CreateServiceFunction( MO_CREATENEWMENUOBJECT, MO_CreateNewMenuObject ); + CreateServiceFunction( MO_REMOVEMENUITEM, MO_RemoveMenuItem ); + CreateServiceFunction( MO_ADDNEWMENUITEM, ( MIRANDASERVICE )MO_AddNewMenuItem ); + CreateServiceFunction( MO_MENUITEMGETOWNERDATA, MO_MenuItemGetOwnerData ); + CreateServiceFunction( MO_MODIFYMENUITEM, ( MIRANDASERVICE )MO_ModifyMenuItem ); + CreateServiceFunction( MO_GETMENUITEM, MO_GetMenuItem ); + CreateServiceFunction( MO_GETDEFAULTMENUITEM, MO_GetDefaultMenuItem ); + CreateServiceFunction( MO_PROCESSCOMMANDBYMENUIDENT, MO_ProcessCommandByMenuIdent ); + CreateServiceFunction( MO_PROCESSHOTKEYS, ( MIRANDASERVICE )MO_ProcessHotKeys ); + CreateServiceFunction( MO_REMOVEMENUOBJECT, MO_RemoveMenuObject ); + CreateServiceFunction( MO_GETPROTOROOTMENU, MO_GetProtoRootMenu ); + + CreateServiceFunction( MO_SETOPTIONSMENUOBJECT, SRVMO_SetOptionsMenuObject ); + CreateServiceFunction( MO_SETOPTIONSMENUITEM, SRVMO_SetOptionsMenuItem ); + + bIconsDisabled = DBGetContactSettingByte(NULL, "CList", "DisableMenuIcons", 0) != 0; + + EnterCriticalSection( &csMenuHook ); + bIsGenMenuInited = true; + LeaveCriticalSection( &csMenuHook ); + + HookEvent( ME_SYSTEM_MODULESLOADED, OnModulesLoaded ); + HookEvent( ME_OPT_INITIALISE, GenMenuOptInit ); + return 0; +} + +int UnitGenMenu() +{ + if ( bIsGenMenuInited ) { + EnterCriticalSection( &csMenuHook ); + MO_RemoveAllObjects(); + bIsGenMenuInited=false; + + LeaveCriticalSection( &csMenuHook ); + DeleteCriticalSection(&csMenuHook); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +TIntMenuObject::TIntMenuObject() +{ +} + +TIntMenuObject::~TIntMenuObject() +{ + MO_RecursiveWalkMenu( m_items.first, FreeMenuItem, NULL ); + + FreeAndNil(( void** )&FreeService ); + FreeAndNil(( void** )&onAddService ); + FreeAndNil(( void** )&CheckService ); + FreeAndNil(( void** )&ExecService ); + FreeAndNil(( void** )&Name ); + + ImageList_Destroy(m_hMenuIcons); +} + +void TIntMenuObject::freeItem( TMO_IntMenuItem* p ) +{ + if ( FreeService ) + CallService( FreeService, ( WPARAM )p, ( LPARAM )p->mi.ownerdata ); + + FreeAndNil(( void** )&p->mi.pszName ); + FreeAndNil(( void** )&p->UniqName ); + FreeAndNil(( void** )&p->CustomName ); + if ( p->hBmp ) DeleteObject( p->hBmp ); + mir_free( p ); +} diff --git a/src/modules/clist/genmenu.h b/src/modules/clist/genmenu.h new file mode 100644 index 0000000000..63c665b940 --- /dev/null +++ b/src/modules/clist/genmenu.h @@ -0,0 +1,146 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ +#ifndef GENMENU_H +#define GENMENU_H +//general menu object module +#include "m_genmenu.h" + +/* genmenu structs */ + +#define MENUITEM_SIGNATURE 0xDEADBEEF + +typedef struct +{ + struct _tagIntMenuItem *first, // first element of submenu, or NULL + *last; // last element of submenu, or NULL +} + TMO_LinkedList; + +typedef struct _tagIntMenuItem +{ + DWORD signature; + int iCommand; + int iconId; // icon index in the section's image list + TMO_MenuItem mi; // user-defined data + BOOL OverrideShow; + char* UniqName; // unique name + TCHAR* CustomName; + HANDLE hIcolibItem; // handle of iconlib item + HBITMAP hBmp; + int originalPosition; + + struct _tagIntMenuItem *next; // next item in list + struct TIntMenuObject *parent; + TMO_LinkedList *owner; + TMO_LinkedList submenu; +} + TMO_IntMenuItem,*PMO_IntMenuItem; + +struct TIntMenuObject +{ + TIntMenuObject(); + ~TIntMenuObject(); + + __inline void* operator new( size_t size ) + { return mir_calloc( size ); + } + __inline void operator delete( void* p ) + { mir_free( p ); + } + + char* Name; + int id; + + //ExecService + //LPARAM lParam;//owner data + //WPARAM wParam;//allways lparam from winproc + char *ExecService; + + //CheckService called when building menu + //return false to skip item. + //LPARAM lParam;//0 + //WPARAM wParam;//CheckParam + char *CheckService;//analog to check_proc + + //LPARAM lParam;//ownerdata + //WPARAM wParam;//menuitemhandle + char *FreeService;//callback service used to free ownerdata for menuitems + + //LPARAM lParam;//MENUITEMINFO filled with all needed data + //WPARAM wParam;//menuitemhandle + char *onAddService;//called just before add MENUITEMINFO to hMenu + + TMO_LinkedList m_items; + HIMAGELIST m_hMenuIcons; + BOOL m_bUseUserDefinedItems; + + void freeItem( TMO_IntMenuItem* ); +}; + +extern LIST g_menus; + +#define SEPARATORPOSITIONINTERVAL 100000 + +//internal usage +HMENU BuildRecursiveMenu(HMENU hMenu, PMO_IntMenuItem, ListParam *param); +void GetMenuItemName( PMO_IntMenuItem pMenuItem, char* pszDest, size_t cbDestSize ); + +PMO_IntMenuItem MO_GetIntMenuItem( HGENMENU ); + +PMO_IntMenuItem MO_AddNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi ); +PMO_IntMenuItem MO_AddOldNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi ); + +int MO_DrawMenuItem( LPDRAWITEMSTRUCT dis ); +int MO_MeasureMenuItem( LPMEASUREITEMSTRUCT mis ); +int MO_ModifyMenuItem( PMO_IntMenuItem menuHandle, PMO_MenuItem pmiparam ); +int MO_ProcessCommand( PMO_IntMenuItem pimi, LPARAM lParam ); +INT_PTR MO_ProcessHotKeys( HANDLE menuHandle, INT_PTR vKey ); +int MO_SetOptionsMenuItem( PMO_IntMenuItem menuobjecthandle, int setting, INT_PTR value ); +int MO_SetOptionsMenuObject( HANDLE menuobjecthandle, int setting, INT_PTR value ); + +INT_PTR MO_ProcessCommandByMenuIdent(WPARAM wParam,LPARAM lParam); +int MO_ProcessCommandBySubMenuIdent(int menuID, int command, LPARAM lParam); + +// function returns TRUE if the walk should be immediately stopped +typedef int ( *pfnWalkFunc )( PMO_IntMenuItem, void* ); + +// returns the item, on which pfnWalkFunc returned TRUE +PMO_IntMenuItem MO_RecursiveWalkMenu( PMO_IntMenuItem, pfnWalkFunc, void* ); + +//general stuff +int InitGenMenu(); +int UnitGenMenu(); + +int FindRoot( PMO_IntMenuItem pimi, void* param ); + +TMO_IntMenuItem * GetMenuItemByGlobalID(int globalMenuID); +BOOL FindMenuHanleByGlobalID(HMENU hMenu, int globalID, struct _MenuItemHandles * dat); //GenMenu.c + +int GenMenuOptInit(WPARAM wParam, LPARAM lParam); +int GetMenuObjbyId(const int id); +int GetMenuItembyId(const int objpos,const int id); +INT_PTR MO_GetMenuItem(WPARAM wParam,LPARAM lParam); +void FreeAndNil(void **p); +static int RemoveFromList(int pos,void **lpList,int *ListElemCount,int ElemSize); +static int RemoveFromList(int pos,void **lpList,int *ListElemCount,int ElemSize); +#endif diff --git a/src/modules/clist/genmenuopt.cpp b/src/modules/clist/genmenuopt.cpp new file mode 100644 index 0000000000..7fefbf8dcb --- /dev/null +++ b/src/modules/clist/genmenuopt.cpp @@ -0,0 +1,870 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "genmenu.h" + +#define STR_SEPARATOR _T("-----------------------------------") + +extern bool bIconsDisabled; +extern int DefaultImageListColorDepth; +long handleCustomDraw(HWND hWndTreeView, LPNMTVCUSTOMDRAW pNMTVCD); +void RebuildProtoMenus( int ); + +struct OrderData +{ + int dragging; + HTREEITEM hDragItem; + int iInitMenuValue; +}; + +typedef struct tagMenuItemOptData +{ + TCHAR* name; + TCHAR* defname; + char* uniqname; + + int pos; + boolean show; + DWORD isSelected; + int id; + + PMO_IntMenuItem pimi; +} + MenuItemOptData,*lpMenuItemOptData; + +static BOOL GetCurrentMenuObjectID(HWND hwndDlg, int* result) +{ + TVITEM tvi; + HWND hTree = GetDlgItem(hwndDlg, IDC_MENUOBJECTS); + HTREEITEM hti = TreeView_GetSelection(hTree); + if ( hti == NULL ) + return FALSE; + + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + tvi.hItem = hti; + TreeView_GetItem(hTree, &tvi); + *result = (int)tvi.lParam; + return TRUE; +} + +static int SaveTree(HWND hwndDlg) +{ + TVITEM tvi; + int count; + TCHAR idstr[100]; + char menuItemName[256], DBString[256], MenuNameItems[256]; + int menupos; + int MenuObjectId, runtimepos; + TIntMenuObject* pimo; + MenuItemOptData* iod; + HWND hTree = GetDlgItem( hwndDlg, IDC_MENUITEMS ); + + if ( !GetCurrentMenuObjectID( hwndDlg, &MenuObjectId )) + return 0; + + tvi.hItem = TreeView_GetRoot( hTree ); + tvi.cchTextMax = 99; + tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE; + tvi.pszText = idstr; + count = 0; + + menupos = GetMenuObjbyId( MenuObjectId ); + if ( menupos == -1 ) + return -1; + + pimo = g_menus[menupos]; + + mir_snprintf( MenuNameItems, sizeof(MenuNameItems), "%s_Items", pimo->Name); + runtimepos = 100; + + while ( tvi.hItem != NULL ) { + TreeView_GetItem( hTree, &tvi ); + iod = ( MenuItemOptData* )tvi.lParam; + if ( iod->pimi ) { + GetMenuItemName( iod->pimi, menuItemName, sizeof( menuItemName )); + + mir_snprintf( DBString, SIZEOF(DBString), "%s_visible", menuItemName ); + DBWriteContactSettingByte( NULL, MenuNameItems, DBString, iod->show ); + + mir_snprintf( DBString, SIZEOF(DBString), "%s_pos", menuItemName ); + DBWriteContactSettingDword( NULL, MenuNameItems, DBString, runtimepos ); + + mir_snprintf( DBString, SIZEOF(DBString), "%s_name", menuItemName ); + if ( lstrcmp( iod->name, iod->defname ) != 0 ) + DBWriteContactSettingTString( NULL, MenuNameItems, DBString, iod->name ); + else + DBDeleteContactSetting( NULL, MenuNameItems, DBString ); + + runtimepos += 100; + } + + if ( iod->name && !_tcscmp( iod->name, STR_SEPARATOR) && iod->show ) + runtimepos += SEPARATORPOSITIONINTERVAL; + + tvi.hItem = TreeView_GetNextSibling( hTree, tvi.hItem ); + count++; + } + return 1; +} + +static int BuildMenuObjectsTree(HWND hwndDlg) +{ + TVINSERTSTRUCT tvis; + HWND hTree = GetDlgItem(hwndDlg,IDC_MENUOBJECTS); + int i; + + tvis.hParent = NULL; + tvis.hInsertAfter = TVI_LAST; + tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + TreeView_DeleteAllItems( hTree ); + if ( g_menus.getCount() == 0 ) + return FALSE; + + for ( i=0; i < g_menus.getCount(); i++ ) { + if ( g_menus[i]->id == (int)hStatusMenuObject || !g_menus[i]->m_bUseUserDefinedItems ) + continue; + + tvis.item.lParam = ( LPARAM )g_menus[i]->id; + tvis.item.pszText = LangPackPcharToTchar( g_menus[i]->Name ); + tvis.item.iImage = tvis.item.iSelectedImage = TRUE; + TreeView_InsertItem( hTree, &tvis ); + mir_free( tvis.item.pszText ); + } + return 1; +} + +static int sortfunc(const void *a,const void *b) +{ + lpMenuItemOptData *sd1,*sd2; + sd1=(lpMenuItemOptData *)a; + sd2=(lpMenuItemOptData *)b; + if ((*sd1)->pos > (*sd2)->pos) + return 1; + + if ((*sd1)->pos < (*sd2)->pos) + return -1; + + return 0; +} + +static int InsertSeparator(HWND hwndDlg) +{ + MenuItemOptData *PD; + TVINSERTSTRUCT tvis = {0}; + TVITEM tvi = {0}; + HTREEITEM hti = {0}; + HWND hMenuTree = GetDlgItem( hwndDlg, IDC_MENUITEMS ); + + hti = TreeView_GetSelection( hMenuTree ); + if ( hti == NULL ) + return 1; + + tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT; + tvi.hItem = hti; + if ( TreeView_GetItem( hMenuTree, &tvi) == FALSE ) + return 1; + + PD = ( MenuItemOptData* )mir_alloc( sizeof( MenuItemOptData )); + ZeroMemory( PD, sizeof( MenuItemOptData )); + PD->id = -1; + PD->name = mir_tstrdup( STR_SEPARATOR ); + PD->show = TRUE; + PD->pos = ((MenuItemOptData *)tvi.lParam)->pos-1; + + tvis.item.lParam = (LPARAM)(PD); + tvis.item.pszText = PD->name; + tvis.item.iImage = tvis.item.iSelectedImage = PD->show; + tvis.hParent = NULL; + tvis.hInsertAfter = hti; + tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + TreeView_InsertItem( hMenuTree, &tvis ); + return 1; +} + +static void FreeTreeData( HWND hwndDlg ) +{ + HTREEITEM hItem = TreeView_GetRoot(GetDlgItem(hwndDlg,IDC_MENUITEMS)); + while ( hItem != NULL ) { + TVITEM tvi; + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + tvi.hItem = hItem; + TreeView_GetItem( GetDlgItem( hwndDlg, IDC_MENUITEMS ), &tvi ); + { MenuItemOptData* O = (MenuItemOptData *)tvi.lParam; + if ( O->name ) mir_free( O->name ); + if ( O->defname ) mir_free( O->defname ); + if ( O->uniqname ) mir_free( O->uniqname ); + mir_free( O ); + } + + tvi.lParam = 0; + TreeView_SetItem(GetDlgItem(hwndDlg,IDC_MENUITEMS), &tvi); + + hItem = TreeView_GetNextSibling(GetDlgItem(hwndDlg,IDC_MENUITEMS), hItem); +} } + +static int BuildTree(HWND hwndDlg,int MenuObjectId, BOOL bReread) +{ + char menuItemName[256],MenuNameItems[256]; + char buf[256]; + + FreeTreeData( hwndDlg ); + TreeView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_MENUITEMS)); + + int menupos = GetMenuObjbyId( MenuObjectId ); + if ( menupos == -1 ) + return FALSE; + + TIntMenuObject* pimo = g_menus[menupos]; + if ( pimo->m_items.first == NULL ) + return FALSE; + + mir_snprintf( MenuNameItems, sizeof(MenuNameItems), "%s_Items", pimo->Name ); + + int count = 0; + { + for ( PMO_IntMenuItem p = pimo->m_items.first; p != NULL; p = p->next ) + if ( p->mi.root == ( HGENMENU )-1 || p->mi.root == NULL ) + count++; + } + + lpMenuItemOptData *PDar = ( lpMenuItemOptData* )mir_alloc( sizeof( lpMenuItemOptData )*count ); + + count = 0; + { + for ( PMO_IntMenuItem p = pimo->m_items.first; p != NULL; p = p->next ) { + if ( p->mi.root != ( HGENMENU )-1 && p->mi.root != NULL ) + continue; + + MenuItemOptData *PD = ( MenuItemOptData* )mir_calloc( sizeof( MenuItemOptData )); + GetMenuItemName( p, menuItemName, sizeof( menuItemName )); + { + DBVARIANT dbv; + mir_snprintf(buf, SIZEOF(buf), "%s_name", menuItemName); + + if ( !DBGetContactSettingTString( NULL, MenuNameItems, buf, &dbv )) { + PD->name = mir_tstrdup( dbv.ptszVal ); + DBFreeVariant( &dbv ); + } + else PD->name = mir_tstrdup( p->mi.ptszName ); + } + + PD->pimi = p; + PD->defname = mir_tstrdup( p->mi.ptszName ); + + mir_snprintf( buf, SIZEOF(buf), "%s_visible", menuItemName ); + PD->show = DBGetContactSettingByte( NULL, MenuNameItems, buf, 1 ); + + if ( bReread ) { + mir_snprintf( buf, SIZEOF(buf), "%s_pos", menuItemName ); + PD->pos = DBGetContactSettingDword( NULL, MenuNameItems, buf, 1 ); + } + else PD->pos = ( PD->pimi ) ? PD->pimi->originalPosition : 0; + + PD->id = p->iCommand; + + if ( p->UniqName ) + PD->uniqname = mir_strdup( p->UniqName ); + + PDar[ count ] = PD; + count++; + } } + + qsort( PDar, count, sizeof( lpMenuItemOptData ), sortfunc ); + + SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, WM_SETREDRAW, FALSE, 0); + int lastpos = 0; + bool first = TRUE; + + TVINSERTSTRUCT tvis; + tvis.hParent = NULL; + tvis.hInsertAfter = TVI_LAST; + tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + for ( int i=0; i < count; i++ ) { + if ( PDar[i]->pos - lastpos >= SEPARATORPOSITIONINTERVAL ) { + MenuItemOptData *PD = ( MenuItemOptData* )mir_calloc( sizeof( MenuItemOptData )); + PD->id = -1; + PD->name = mir_tstrdup( STR_SEPARATOR ); + PD->pos = PDar[i]->pos - 1; + PD->show = TRUE; + + tvis.item.lParam = ( LPARAM )PD; + tvis.item.pszText = PD->name; + tvis.item.iImage = tvis.item.iSelectedImage = PD->show; + SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, TVM_INSERTITEM, 0, (LPARAM)&tvis); + } + + tvis.item.lParam = ( LPARAM )PDar[i]; + tvis.item.pszText = PDar[i]->name; + tvis.item.iImage = tvis.item.iSelectedImage = PDar[i]->show; + + HTREEITEM hti = (HTREEITEM)SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, TVM_INSERTITEM, 0, (LPARAM)&tvis); + if ( first ) { + TreeView_SelectItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),hti); + first=FALSE; + } + + lastpos = PDar[i]->pos; + } + + SendDlgItemMessage( hwndDlg, IDC_MENUITEMS, WM_SETREDRAW, TRUE, 0 ); + mir_free( PDar ); + ShowWindow( GetDlgItem( hwndDlg, IDC_NOTSUPPORTWARNING ),( pimo->m_bUseUserDefinedItems ) ? SW_HIDE : SW_SHOW ); + EnableWindow( GetDlgItem( hwndDlg, IDC_MENUITEMS ), pimo->m_bUseUserDefinedItems ); + EnableWindow( GetDlgItem( hwndDlg, IDC_INSERTSEPARATOR ), pimo->m_bUseUserDefinedItems ); + return 1; +} + +static void RebuildCurrent( HWND hwndDlg ) +{ + int MenuObjectID; + if ( GetCurrentMenuObjectID( hwndDlg, &MenuObjectID )) + BuildTree( hwndDlg, MenuObjectID, TRUE ); +} + +static void ResetMenuItems( HWND hwndDlg ) +{ + int MenuObjectID; + if ( GetCurrentMenuObjectID( hwndDlg, &MenuObjectID )) + BuildTree( hwndDlg, MenuObjectID, FALSE ); +} + +static HTREEITEM MoveItemAbove(HWND hTreeWnd, HTREEITEM hItem, HTREEITEM hInsertAfter) +{ + TVITEM tvi = { 0 }; + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + tvi.hItem = hItem; + if ( !SendMessage(hTreeWnd, TVM_GETITEM, 0, ( LPARAM )&tvi )) + return NULL; + if ( hItem && hInsertAfter ) { + TVINSERTSTRUCT tvis; + TCHAR name[128]; + if ( hItem == hInsertAfter ) + return hItem; + + tvis.item.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + tvis.item.stateMask = 0xFFFFFFFF; + tvis.item.pszText = name; + tvis.item.cchTextMax = sizeof( name ); + tvis.item.hItem = hItem; + tvis.item.iImage = tvis.item.iSelectedImage = (( MenuItemOptData* )tvi.lParam)->show; + if(!SendMessage(hTreeWnd, TVM_GETITEM, 0, (LPARAM)&tvis.item)) + return NULL; + if (!TreeView_DeleteItem(hTreeWnd,hItem)) + return NULL; + tvis.hParent=NULL; + tvis.hInsertAfter=hInsertAfter; + return TreeView_InsertItem(hTreeWnd, &tvis); + } + return NULL; +} + +WNDPROC MyOldWindowProc=NULL; + +LRESULT CALLBACK LBTNDOWNProc(HWND hwnd,UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg==WM_LBUTTONDOWN && !(GetKeyState(VK_CONTROL)&0x8000)) { + + TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(lParam); + hti.pt.y=(short)HIWORD(lParam); + // ClientToScreen(hwndDlg,&hti.pt); + // ScreenToClient(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti.pt); + TreeView_HitTest(hwnd,&hti); + if (hti.flags&TVHT_ONITEMLABEL) { + /// LabelClicked Set/unset selection + TVITEM tvi; + HWND tvw=hwnd; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + tvi.hItem=hti.hItem; + TreeView_GetItem( tvw, &tvi ); + + if (!((MenuItemOptData *)tvi.lParam)->isSelected) { /* is not Selected*/ + // reset all selection except current + HTREEITEM hit; + hit=TreeView_GetRoot(tvw); + if (hit) + do { + TVITEM tvi={0}; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + tvi.hItem=hit; + TreeView_GetItem(tvw, &tvi); + + if (hti.hItem!=hit) + ((MenuItemOptData *)tvi.lParam)->isSelected=0; + else + ((MenuItemOptData *)tvi.lParam)->isSelected=1; + TreeView_SetItem(tvw, &tvi); + } + while (hit=TreeView_GetNextSibling(tvw,hit)); + } } } + + return CallWindowProc(MyOldWindowProc,hwnd,uMsg,wParam,lParam); +} + +static INT_PTR CALLBACK GenMenuOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct OrderData *dat = (struct OrderData*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_USERDATA); + LPNMHDR hdr; + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + dat=(struct OrderData*)mir_alloc(sizeof(struct OrderData)); + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_USERDATA,(LONG_PTR)dat); + dat->dragging = 0; + dat->iInitMenuValue = DBGetContactSettingByte( NULL, "CList", "MoveProtoMenus", FALSE ); + MyOldWindowProc = (WNDPROC)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_WNDPROC); + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_WNDPROC,(LONG_PTR)&LBTNDOWNProc); + { + HIMAGELIST himlCheckBoxes; + himlCheckBoxes=ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16) | ILC_MASK, 2, 2); + + ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_NOTICK); + ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_TICK); + + TreeView_SetImageList(GetDlgItem(hwndDlg,IDC_MENUOBJECTS),himlCheckBoxes,TVSIL_NORMAL); + TreeView_SetImageList(GetDlgItem(hwndDlg,IDC_MENUITEMS),himlCheckBoxes,TVSIL_NORMAL); + } + CheckDlgButton(hwndDlg, dat->iInitMenuValue ? IDC_RADIO2 : IDC_RADIO1, TRUE ); + CheckDlgButton(hwndDlg, IDC_DISABLEMENUICONS, bIconsDisabled ); + BuildMenuObjectsTree(hwndDlg); + return TRUE; + + case WM_COMMAND: + if ( HIWORD(wParam) == BN_CLICKED || HIWORD( wParam ) == BN_DBLCLK ) { + switch ( LOWORD( wParam )) { + case IDC_INSERTSEPARATOR: + InsertSeparator(hwndDlg); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + + case IDC_RESETMENU: + ResetMenuItems( hwndDlg ); + SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 ); + break; + + case IDC_DISABLEMENUICONS: + case IDC_RADIO1: + case IDC_RADIO2: + SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 ); + break; + + case IDC_GENMENU_DEFAULT: + { + TVITEM tvi; + HTREEITEM hti; + MenuItemOptData *iod; + + hti=TreeView_GetSelection(GetDlgItem(hwndDlg,IDC_MENUITEMS)); + if (hti==NULL) + break; + + tvi.mask=TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM; + tvi.hItem=hti; + TreeView_GetItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),&tvi); + iod = ( MenuItemOptData * )tvi.lParam; + + if ( iod->name && _tcsstr( iod->name, STR_SEPARATOR )) + break; + + if (iod->name) + mir_free(iod->name); + iod->name = mir_tstrdup( iod->defname ); + + SaveTree(hwndDlg); + RebuildCurrent(hwndDlg); + SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 ); + } + break; + + case IDC_GENMENU_SET: + { + TVITEM tvi; + TCHAR buf[256]; + MenuItemOptData *iod; + + HTREEITEM hti = TreeView_GetSelection( GetDlgItem( hwndDlg,IDC_MENUITEMS )); + if ( hti == NULL ) + break; + + tvi.mask = TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM; + tvi.hItem = hti; + SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, TVM_GETITEM, 0, (LPARAM)&tvi); + iod = ( MenuItemOptData * )tvi.lParam; + + if ( iod->name && _tcsstr(iod->name, STR_SEPARATOR )) + break; + + ZeroMemory(buf,sizeof( buf )); + GetDlgItemText( hwndDlg, IDC_GENMENU_CUSTOMNAME, buf, SIZEOF( buf )); + if (iod->name) + mir_free(iod->name); + + iod->name = mir_tstrdup(buf); + + SaveTree(hwndDlg); + RebuildCurrent(hwndDlg); + SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 ); + } + break; + } } + break; + + case WM_NOTIFY: + hdr = (LPNMHDR)lParam; + switch( hdr->idFrom ) { + case 0: + if (hdr->code == PSN_APPLY ) { + bIconsDisabled = IsDlgButtonChecked(hwndDlg, IDC_DISABLEMENUICONS) != 0; + DBWriteContactSettingByte(NULL, "CList", "DisableMenuIcons", bIconsDisabled); + SaveTree(hwndDlg); + int iNewMenuValue = IsDlgButtonChecked(hwndDlg, IDC_RADIO1) ? 0 : 1; + if ( iNewMenuValue != dat->iInitMenuValue ) { + RebuildProtoMenus( iNewMenuValue ); + dat->iInitMenuValue = iNewMenuValue; + } + RebuildCurrent(hwndDlg); + } + break; + + case IDC_MENUOBJECTS: + if (hdr->code == TVN_SELCHANGEDA ) + RebuildCurrent( hwndDlg ); + break; + + case IDC_MENUITEMS: + switch (hdr->code) { + case NM_CUSTOMDRAW: + { + int i= handleCustomDraw(GetDlgItem(hwndDlg,IDC_MENUITEMS),(LPNMTVCUSTOMDRAW) lParam); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, i); + return TRUE; + } + + case TVN_BEGINDRAGA: + SetCapture(hwndDlg); + dat->dragging=1; + dat->hDragItem=((LPNMTREEVIEW)lParam)->itemNew.hItem; + TreeView_SelectItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),dat->hDragItem); + break; + + case NM_CLICK: + { + TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(GetMessagePos()); + hti.pt.y=(short)HIWORD(GetMessagePos()); + ScreenToClient(hdr->hwndFrom,&hti.pt); + if (TreeView_HitTest(hdr->hwndFrom,&hti)) { + if (hti.flags&TVHT_ONITEMICON) { + TVITEM tvi; + tvi.mask=TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM; + tvi.hItem=hti.hItem; + TreeView_GetItem(hdr->hwndFrom,&tvi); + + tvi.iImage=tvi.iSelectedImage=!tvi.iImage; + ((MenuItemOptData *)tvi.lParam)->show=tvi.iImage; + TreeView_SetItem(hdr->hwndFrom,&tvi); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + + //all changes take effect in runtime + //ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTONORDERTREEWARNING),SW_SHOW); + } + /*--------MultiSelection----------*/ + if (hti.flags&TVHT_ONITEMLABEL) { + /// LabelClicked Set/unset selection + TVITEM tvi; + HWND tvw=hdr->hwndFrom; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + tvi.hItem=hti.hItem; + TreeView_GetItem(tvw,&tvi); + if (GetKeyState(VK_CONTROL)&0x8000) { + if (((MenuItemOptData *)tvi.lParam)->isSelected) + ((MenuItemOptData *)tvi.lParam)->isSelected=0; + else + ((MenuItemOptData *)tvi.lParam)->isSelected=1; //current selection order++. + TreeView_SetItem(tvw,&tvi); + } + else if (GetKeyState(VK_SHIFT)&0x8000) { + ; // shifted click + } + else { + // reset all selection except current + HTREEITEM hit; + hit=TreeView_GetRoot(tvw); + if (hit) + do { + TVITEM tvi={0}; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + tvi.hItem=hit; + TreeView_GetItem(tvw,&tvi); + + if (hti.hItem!=hit) + ((MenuItemOptData *)tvi.lParam)->isSelected=0; + else + ((MenuItemOptData *)tvi.lParam)->isSelected=1; + TreeView_SetItem(tvw,&tvi); + } + while (hit=TreeView_GetNextSibling(tvw,hit)); + } } } + break; + } + case TVN_SELCHANGING: + { + LPNMTREEVIEW pn; + pn = (LPNMTREEVIEW) lParam; + //((MenuItemOptData *)(pn->itemNew.lParam))->isSelected=1; + /*if (pn->action==NotKeyPressed) + { + remove all selection + } + */ + } + case TVN_SELCHANGEDA: + { + TVITEM tvi; + HTREEITEM hti; + MenuItemOptData *iod; + + SetDlgItemTextA(hwndDlg,IDC_GENMENU_CUSTOMNAME,""); + SetDlgItemTextA(hwndDlg,IDC_GENMENU_SERVICE,""); + + EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_CUSTOMNAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_DEFAULT),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_SET),FALSE); + + hti=TreeView_GetSelection(GetDlgItem(hwndDlg,IDC_MENUITEMS)); + if (hti==NULL) + break; + + tvi.mask=TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM; + tvi.hItem=hti; + TreeView_GetItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),&tvi); + + if ( tvi.lParam == 0 ) + break; + + iod = ( MenuItemOptData * )tvi.lParam; + + if ( iod->name && _tcsstr(iod->name, STR_SEPARATOR)) + break; + + SetDlgItemText(hwndDlg,IDC_GENMENU_CUSTOMNAME,iod->name); + + if (iod->pimi->submenu.first == NULL && iod->uniqname) + SetDlgItemTextA(hwndDlg, IDC_GENMENU_SERVICE, iod->uniqname); + + EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_DEFAULT), lstrcmp(iod->name, iod->defname) != 0); + EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_SET),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_CUSTOMNAME),TRUE); + break; + } + break; + } } + break; + + case WM_MOUSEMOVE: + if (!dat||!dat->dragging) break; + { + TVHITTESTINFO hti; + + hti.pt.x=(short)LOWORD(lParam); + hti.pt.y=(short)HIWORD(lParam); + ClientToScreen(hwndDlg,&hti.pt); + ScreenToClient(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti.pt); + TreeView_HitTest(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti); + if (hti.flags&(TVHT_ONITEM|TVHT_ONITEMRIGHT)) { + HTREEITEM it = hti.hItem; + hti.pt.y -= TreeView_GetItemHeight(GetDlgItem(hwndDlg,IDC_MENUITEMS))/2; + TreeView_HitTest(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti); + if (!(hti.flags&TVHT_ABOVE)) + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),hti.hItem,1); + else + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),it,0); + } + else { + if (hti.flags&TVHT_ABOVE) SendDlgItemMessage(hwndDlg,IDC_MENUITEMS,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0); + if (hti.flags&TVHT_BELOW) SendDlgItemMessage(hwndDlg,IDC_MENUITEMS,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0); + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),NULL,0); + } } + break; + + case WM_LBUTTONUP: + if (!dat->dragging) + break; + + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),NULL,0); + dat->dragging=0; + ReleaseCapture(); + { + TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(lParam); + hti.pt.y=(short)HIWORD(lParam); + ClientToScreen(hwndDlg,&hti.pt); + ScreenToClient(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti.pt); + hti.pt.y-=TreeView_GetItemHeight(GetDlgItem(hwndDlg,IDC_MENUITEMS))/2; + TreeView_HitTest(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti); + if (hti.flags&TVHT_ABOVE) hti.hItem=TVI_FIRST; + if (dat->hDragItem==hti.hItem) break; + dat->hDragItem=NULL; + if (hti.flags&(TVHT_ONITEM|TVHT_ONITEMRIGHT)||(hti.hItem==TVI_FIRST)) { + HWND tvw; + HTREEITEM * pSIT; + HTREEITEM FirstItem=NULL; + UINT uITCnt,uSic ; + tvw=GetDlgItem(hwndDlg,IDC_MENUITEMS); + uITCnt=TreeView_GetCount(tvw); + uSic=0; + if (uITCnt) { + pSIT=(HTREEITEM *)mir_alloc(sizeof(HTREEITEM)*uITCnt); + if (pSIT) { + HTREEITEM hit; + hit=TreeView_GetRoot(tvw); + if (hit) + do { + TVITEM tvi={0}; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + tvi.hItem=hit; + TreeView_GetItem(tvw,&tvi); + if (((MenuItemOptData *)tvi.lParam)->isSelected) { + pSIT[uSic]=tvi.hItem; + + uSic++; + } + }while (hit=TreeView_GetNextSibling(tvw,hit)); + // Proceed moving + { + UINT i; + HTREEITEM insertAfter; + insertAfter=hti.hItem; + for (i=0; inmcd.dwDrawStage ) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + + case CDDS_ITEMPREPAINT: + { + HTREEITEM hItem = (HTREEITEM) pNMTVCD->nmcd.dwItemSpec; + TCHAR buf[255]; + TVITEM tvi = {0}; + int k=0; + tvi.mask = TVIF_HANDLE |TVIF_PARAM|TVIS_SELECTED|TVIF_TEXT|TVIF_IMAGE; + tvi.stateMask=TVIS_SELECTED; + tvi.hItem = hItem; + tvi.pszText=(LPTSTR)(&buf); + tvi.cchTextMax=254; + TreeView_GetItem(hWndTreeView, &tvi); + if (((MenuItemOptData *)tvi.lParam)->isSelected) { + pNMTVCD->clrTextBk = GetSysColor(COLOR_HIGHLIGHT); + pNMTVCD->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT); + } + else { + pNMTVCD->clrTextBk = GetSysColor(COLOR_WINDOW); + pNMTVCD->clrText = GetSysColor(COLOR_WINDOWTEXT); + } + + /* At this point, you can change the background colors for the item + and any subitems and return CDRF_NEWFONT. If the list-view control + is in report mode, you can simply return CDRF_NOTIFYSUBITEMREDRAW + to customize the item's subitems individually */ + if ( tvi.iImage == -1 ) { + HBRUSH br; + SIZE sz; + RECT rc; + k=1; + + GetTextExtentPoint32(pNMTVCD->nmcd.hdc,tvi.pszText,lstrlen(tvi.pszText),&sz); + + if (sz.cx+3>pNMTVCD->nmcd.rc.right-pNMTVCD->nmcd.rc.left) rc=pNMTVCD->nmcd.rc; + else SetRect(&rc,pNMTVCD->nmcd.rc.left,pNMTVCD->nmcd.rc.top,pNMTVCD->nmcd.rc.left+sz.cx+3,pNMTVCD->nmcd.rc.bottom); + + br=CreateSolidBrush(pNMTVCD->clrTextBk); + SetTextColor(pNMTVCD->nmcd.hdc,pNMTVCD->clrText); + SetBkColor(pNMTVCD->nmcd.hdc,pNMTVCD->clrTextBk); + FillRect(pNMTVCD->nmcd.hdc,&rc,br); + DeleteObject(br); + DrawText(pNMTVCD->nmcd.hdc,tvi.pszText,lstrlen(tvi.pszText),&pNMTVCD->nmcd.rc,DT_LEFT|DT_VCENTER|DT_NOPREFIX); + } + + return CDRF_NEWFONT|(k?CDRF_SKIPDEFAULT:0); + } + } + return 0; +} + +INT_PTR CALLBACK ProtocolOrderOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +int GenMenuOptInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize=sizeof(odp); + odp.hInstance = hMirandaInst; + odp.pszGroup = LPGEN("Customize"); + + odp.position = -1000000000; + odp.pszTemplate = MAKEINTRESOURCEA( IDD_OPT_GENMENU ); + odp.pszTitle = LPGEN("Menus"); + odp.pfnDlgProc = GenMenuOpts; + odp.flags = ODPF_BOLDGROUPS; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + + odp.position = -10000000; + odp.groupPosition = 1000000; + odp.pszTemplate = MAKEINTRESOURCEA( IDD_OPT_PROTOCOLORDER ); + odp.pszTitle = LPGEN("Accounts"); + odp.pfnDlgProc = ProtocolOrderOpts; + odp.flags = ODPF_BOLDGROUPS|ODPF_EXPERTONLY; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} diff --git a/src/modules/clist/groups.cpp b/src/modules/clist/groups.cpp new file mode 100644 index 0000000000..0693c25036 --- /dev/null +++ b/src/modules/clist/groups.cpp @@ -0,0 +1,577 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" + +HANDLE hGroupChangeEvent; + +static INT_PTR RenameGroup(WPARAM wParam, LPARAM lParam); +static INT_PTR MoveGroupBefore(WPARAM wParam, LPARAM lParam); + +static int CountGroups(void) +{ + DBVARIANT dbv; + int i; + char str[33]; + + for (i = 0;; i++) { + _itoa(i, str, 10); + if (DBGetContactSetting(NULL, "CListGroups", str, &dbv)) + break; + DBFreeVariant(&dbv); + } + return i; +} + +static int GroupNameExists(const TCHAR *name, int skipGroup) +{ + char idstr[33]; + DBVARIANT dbv; + int i; + + for (i = 0;; i++) { + if (i == skipGroup) + continue; + _itoa(i, idstr, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv)) + break; + if (!_tcscmp(dbv.ptszVal + 1, name)) { + DBFreeVariant(&dbv); + return i+1; + } + DBFreeVariant(&dbv); + } + return 0; +} + +static INT_PTR CreateGroup(WPARAM wParam, LPARAM lParam) +{ + int newId = CountGroups(); + TCHAR newBaseName[127], newName[128]; + char str[33]; + int i; + DBVARIANT dbv; + + const TCHAR* grpName = lParam ? (TCHAR*)lParam : TranslateT("New Group"); + if (wParam) { + _itoa(wParam - 1, str, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv)) + return 0; + + mir_sntprintf( newBaseName, SIZEOF(newBaseName), _T("%s\\%s"), dbv.ptszVal + 1, grpName ); + mir_free(dbv.pszVal); + } + else lstrcpyn( newBaseName, grpName, SIZEOF( newBaseName )); + + _itoa(newId, str, 10); + lstrcpyn( newName + 1, newBaseName, SIZEOF(newName) - 1); + if (lParam) { + i = GroupNameExists(newBaseName, -1); + if (i) newId = i - 1; + i = !i; + } + else { + i = 1; + while (GroupNameExists(newName + 1, -1)) + mir_sntprintf( newName + 1, SIZEOF(newName) - 1, _T("%s (%d)"), newBaseName, ++i ); + } + if (i) { + const CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, newName }; + + newName[0] = 1 | GROUPF_EXPANDED; //1 is required so we never get '\0' + DBWriteContactSettingTString(NULL, "CListGroups", str, newName); + CallService(MS_CLUI_GROUPADDED, newId + 1, 1); + + NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg); + } + + return newId + 1; +} + +static INT_PTR GetGroupName2(WPARAM wParam, LPARAM lParam) +{ + char idstr[33]; + DBVARIANT dbv; + static char name[128]; + + _itoa(wParam - 1, idstr, 10); + if (DBGetContactSettingString(NULL, "CListGroups", idstr, &dbv)) + return (INT_PTR) (char *) NULL; + lstrcpynA(name, dbv.pszVal + 1, SIZEOF(name)); + if ((DWORD *) lParam != NULL) + *(DWORD *) lParam = dbv.pszVal[0]; + DBFreeVariant(&dbv); + return (INT_PTR) name; +} + +TCHAR* fnGetGroupName( int idx, DWORD* pdwFlags ) +{ + char idstr[33]; + DBVARIANT dbv; + static TCHAR name[128]; + + _itoa( idx-1, idstr, 10); + if (DBGetContactSettingTString( NULL, "CListGroups", idstr, &dbv )) + return NULL; + + lstrcpyn( name, dbv.ptszVal + 1, SIZEOF( name )); + if ( pdwFlags != NULL ) + *pdwFlags = dbv.ptszVal[0]; + DBFreeVariant( &dbv ); + return name; +} + +static INT_PTR GetGroupName(WPARAM wParam, LPARAM lParam) +{ + INT_PTR ret; + ret = GetGroupName2(wParam, lParam); + if ((int *) lParam) + *(int *) lParam = 0 != (*(int *) lParam & GROUPF_EXPANDED); + return ret; +} + +static INT_PTR DeleteGroup(WPARAM wParam, LPARAM) +{ + int i; + char str[33]; + DBVARIANT dbv; + HANDLE hContact; + TCHAR name[256], szNewParent[256], *pszLastBackslash; + + //get the name + _itoa(wParam - 1, str, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv)) + return 1; + lstrcpyn(name, dbv.ptszVal + 1, SIZEOF(name)); + DBFreeVariant(&dbv); + if (DBGetContactSettingByte(NULL, "CList", "ConfirmDelete", SETTING_CONFIRMDELETE_DEFAULT)) + { + TCHAR szQuestion[256+100]; + mir_sntprintf( szQuestion, SIZEOF(szQuestion), TranslateT("Are you sure you want to delete group '%s'? This operation can not be undone."), name ); + if (MessageBox(cli.hwndContactList, szQuestion, TranslateT("Delete Group"), MB_YESNO|MB_ICONQUESTION)==IDNO) + return 1; + } + SetCursor(LoadCursor(NULL, IDC_WAIT)); + //must remove setting from all child contacts too + //children are demoted to the next group up, not deleted. + lstrcpy(szNewParent, name); + pszLastBackslash = _tcsrchr(szNewParent, '\\'); + if (pszLastBackslash) + pszLastBackslash[0] = '\0'; + else + szNewParent[0] = '\0'; + + CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, NULL }; + + for (hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + hContact ; + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0)) + { + if (DBGetContactSettingTString(hContact, "CList", "Group", &dbv)) + continue; + + if (_tcscmp(dbv.ptszVal, name)) + { + DBFreeVariant(&dbv); + continue; + } + DBFreeVariant(&dbv); + + if (szNewParent[0]) + { + DBWriteContactSettingTString(hContact, "CList", "Group", szNewParent); + grpChg.pszNewName = szNewParent; + } + else + { + DBDeleteContactSetting(hContact, "CList", "Group"); + grpChg.pszNewName = NULL; + } + NotifyEventHooks(hGroupChangeEvent, (WPARAM)hContact, (LPARAM)&grpChg); + } + //shuffle list of groups up to fill gap + for (i = wParam - 1;; i++) { + _itoa(i + 1, str, 10); + if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv)) + break; + _itoa(i, str, 10); + DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal); + DBFreeVariant(&dbv); + } + _itoa(i, str, 10); + DBDeleteContactSetting(NULL, "CListGroups", str); + //rename subgroups + { + TCHAR szNewName[256]; + int len; + + len = lstrlen(name); + for (i = 0;; i++) { + _itoa(i, str, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv)) + break; + if (!_tcsncmp(dbv.ptszVal + 1, name, len) && dbv.pszVal[len + 1] == '\\' && _tcschr(dbv.ptszVal + len + 2, '\\') == NULL) { + if (szNewParent[0]) + mir_sntprintf(szNewName, SIZEOF(szNewName), _T("%s\\%s"), szNewParent, dbv.ptszVal + len + 2); + else + lstrcpyn(szNewName, dbv.ptszVal + len + 2, SIZEOF(szNewName)); + cli.pfnRenameGroup(i + 1, szNewName); + } + DBFreeVariant(&dbv); + } + } + SetCursor(LoadCursor(NULL, IDC_ARROW)); + cli.pfnLoadContactTree(); + + { + const CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), name, NULL }; + NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg); + } + return 0; +} + +static int RenameGroupWithMove(int groupId, const TCHAR *szName, int move) +{ + char idstr[33]; + TCHAR str[256], oldName[256]; + DBVARIANT dbv; + HANDLE hContact; + + if (GroupNameExists(szName, groupId)) { + MessageBox(NULL, TranslateT("You already have a group with that name. Please enter a unique name for the group."), TranslateT("Rename Group"), MB_OK); + return 1; + } + + //do the change + _itoa(groupId, idstr, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv)) + return 1; + str[0] = dbv.pszVal[0] & 0x7F; + lstrcpyn(oldName, dbv.ptszVal + 1, SIZEOF(oldName)); + DBFreeVariant(&dbv); + lstrcpyn(str + 1, szName, SIZEOF(str) - 1); + DBWriteContactSettingTString(NULL, "CListGroups", idstr, str); + + //must rename setting in all child contacts too + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + do { + ClcCacheEntryBase* cache = cli.pfnGetCacheEntry( hContact ); + if ( !lstrcmp(cache->group, oldName)) { + DBWriteContactSettingTString(hContact, "CList", "Group", szName); + mir_free(cache->group); + cache->group = 0; + cli.pfnCheckCacheItem(cache); + } + } + while ((hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0)) != NULL); + + //rename subgroups + { + TCHAR szNewName[256]; + int len, i; + + len = lstrlen(oldName); + for (i = 0;; i++) { + if (i == groupId) + continue; + _itoa(i, idstr, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv)) + break; + if ( !_tcsncmp(dbv.ptszVal + 1, oldName, len) && dbv.ptszVal[len + 1] == '\\' && _tcschr(dbv.ptszVal + len + 2, '\\') == NULL) { + mir_sntprintf( szNewName, SIZEOF(szNewName), _T("%s\\%s"), szName, dbv.ptszVal + len + 2 ); + RenameGroupWithMove(i, szNewName, 0); //luckily, child groups will never need reordering + } + DBFreeVariant(&dbv); + } + } + + //finally must make sure it's after any parent items + if (move) { + TCHAR *pszLastBackslash; + int i; + + lstrcpyn(str, szName, SIZEOF(str)); + pszLastBackslash = _tcsrchr(str, '\\'); + if (pszLastBackslash != NULL) { + *pszLastBackslash = '\0'; + for (i = 0;; i++) { + _itoa(i, idstr, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv)) + break; + if (!lstrcmp(dbv.ptszVal + 1, str)) { + if (i < groupId) + break; //is OK + MoveGroupBefore(groupId + 1, i + 2); + break; + } + DBFreeVariant(&dbv); + } + } + } + { + const CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), oldName, (TCHAR*)szName }; + NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg); + } + return 0; +} + +int fnRenameGroup( int groupID, TCHAR* newName ) +{ + return -1 != RenameGroupWithMove( groupID-1, newName, 1); +} + +static INT_PTR RenameGroup(WPARAM wParam, LPARAM lParam) +{ + #if defined( _UNICODE ) + WCHAR* temp = mir_a2u(( char* )lParam ); + int result = ( -1 != RenameGroupWithMove(wParam - 1, temp, 1)); + mir_free( temp ); + return result; + #else + return -1 != RenameGroupWithMove(wParam - 1, (TCHAR*) lParam, 1); + #endif +} + +static INT_PTR SetGroupExpandedState(WPARAM wParam, LPARAM lParam) +{ + char idstr[33]; + DBVARIANT dbv; + + _itoa(wParam - 1, idstr, 10); + if (DBGetContactSettingStringUtf(NULL, "CListGroups", idstr, &dbv)) + return 1; + if (lParam) + dbv.pszVal[0] |= GROUPF_EXPANDED; + else + dbv.pszVal[0] = dbv.pszVal[0] & ~GROUPF_EXPANDED; + DBWriteContactSettingStringUtf(NULL, "CListGroups", idstr, dbv.pszVal); + DBFreeVariant(&dbv); + return 0; +} + +static INT_PTR SetGroupFlags(WPARAM wParam, LPARAM lParam) +{ + char idstr[33]; + DBVARIANT dbv; + int flags, oldval, newval; + + _itoa(wParam - 1, idstr, 10); + if (DBGetContactSettingStringUtf(NULL, "CListGroups", idstr, &dbv)) + return 1; + flags = LOWORD(lParam) & HIWORD(lParam); + oldval = dbv.pszVal[0]; + newval = dbv.pszVal[0] = ((oldval & ~HIWORD(lParam)) | flags) & 0x7f; + DBWriteContactSettingStringUtf(NULL, "CListGroups", idstr, dbv.pszVal); + DBFreeVariant(&dbv); + if ((oldval & GROUPF_HIDEOFFLINE) != (newval & GROUPF_HIDEOFFLINE)) + cli.pfnLoadContactTree(); + return 0; +} + +static INT_PTR MoveGroupBefore(WPARAM wParam, LPARAM lParam) +{ + int i, shuffleFrom, shuffleTo, shuffleDir; + char str[33]; + TCHAR *szMoveName; + DBVARIANT dbv; + + if (wParam == 0 || (LPARAM) wParam == lParam) + return 0; + _itoa(wParam - 1, str, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv)) + return 0; + szMoveName = dbv.ptszVal; + //shuffle list of groups up to fill gap + if (lParam == 0) { + shuffleFrom = wParam - 1; + shuffleTo = -1; + shuffleDir = -1; + } + else { + if ((LPARAM) wParam < lParam) { + shuffleFrom = wParam - 1; + shuffleTo = lParam - 2; + shuffleDir = -1; + } + else { + shuffleFrom = wParam - 1; + shuffleTo = lParam - 1; + shuffleDir = 1; + } + } + if (shuffleDir == -1) { + for (i = shuffleFrom; i != shuffleTo; i++) { + _itoa(i + 1, str, 10); + if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv)) { + shuffleTo = i; + break; + } + _itoa(i, str, 10); + DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal); + DBFreeVariant(&dbv); + } + } + else { + for (i = shuffleFrom; i != shuffleTo; i--) { + _itoa(i - 1, str, 10); + if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv)) { + mir_free(szMoveName); + return 1; + } //never happens + _itoa(i, str, 10); + DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal); + DBFreeVariant(&dbv); + } + } + _itoa(shuffleTo, str, 10); + DBWriteContactSettingTString(NULL, "CListGroups", str, szMoveName); + mir_free(szMoveName); + return shuffleTo + 1; +} + +static INT_PTR BuildGroupMenu(WPARAM, LPARAM) +{ + char idstr[33]; + DBVARIANT dbv; + int groupId; + HMENU hRootMenu, hThisMenu; + int nextMenuId = 100; + TCHAR *pBackslash, *pNextField, szThisField[128], szThisMenuItem[128]; + int menuId, compareResult, menuItemCount; + MENUITEMINFO mii = { 0 }; + + if (DBGetContactSettingStringUtf(NULL, "CListGroups", "0", &dbv)) + return (INT_PTR) (HMENU) NULL; + DBFreeVariant(&dbv); + hRootMenu = CreateMenu(); + for (groupId = 0;; groupId++) { + _itoa(groupId, idstr, 10); + if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv)) + break; + + pNextField = dbv.ptszVal + 1; + hThisMenu = hRootMenu; + mii.cbSize = MENUITEMINFO_V4_SIZE; + do { + pBackslash = _tcschr(pNextField, '\\'); + if (pBackslash == NULL) { + lstrcpyn(szThisField, pNextField, SIZEOF(szThisField)); + pNextField = NULL; + } + else { + lstrcpyn(szThisField, pNextField, min( SIZEOF(szThisField), pBackslash - pNextField + 1)); + pNextField = pBackslash + 1; + } + compareResult = 1; + menuItemCount = GetMenuItemCount(hThisMenu); + for (menuId = 0; menuId < menuItemCount; menuId++) { + mii.fMask = MIIM_TYPE | MIIM_SUBMENU | MIIM_DATA; + mii.cch = SIZEOF(szThisMenuItem); + mii.dwTypeData = szThisMenuItem; + GetMenuItemInfo(hThisMenu, menuId, TRUE, &mii); + compareResult = lstrcmp(szThisField, szThisMenuItem); + if (compareResult == 0) { + if (pNextField == NULL) { + mii.fMask = MIIM_DATA; + mii.dwItemData = groupId + 1; + SetMenuItemInfo(hThisMenu, menuId, TRUE, &mii); + } + else { + if (mii.hSubMenu == NULL) { + mii.fMask = MIIM_SUBMENU; + mii.hSubMenu = CreateMenu(); + SetMenuItemInfo(hThisMenu, menuId, TRUE, &mii); + mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID; + //dwItemData doesn't change + mii.fType = MFT_STRING; + mii.dwTypeData = TranslateT("This group"); + mii.wID = nextMenuId++; + InsertMenuItem(mii.hSubMenu, 0, TRUE, &mii); + mii.fMask = MIIM_TYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(mii.hSubMenu, 1, TRUE, &mii); + } + hThisMenu = mii.hSubMenu; + } + break; + } + if ((int) mii.dwItemData - 1 > groupId) + break; + } + if (compareResult) { + mii.fMask = MIIM_TYPE | MIIM_ID; + mii.wID = nextMenuId++; + mii.dwTypeData = szThisField; + mii.fType = MFT_STRING; + if (pNextField) { + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = CreateMenu(); + } + else { + mii.fMask |= MIIM_DATA; + mii.dwItemData = groupId + 1; + } + InsertMenuItem(hThisMenu, menuId, TRUE, &mii); + if (pNextField) { + hThisMenu = mii.hSubMenu; + } + } + } while (pNextField); + + DBFreeVariant(&dbv); + } + return (INT_PTR) hRootMenu; +} + +int InitGroupServices(void) +{ + for (int i = 0; ; i++) + { + char str[32]; + _itoa(i, str, 10); + + DBVARIANT dbv; + if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv)) + break; + if (dbv.pszVal[0] & 0x80) + { + dbv.pszVal[0] &= 0x7f; + DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal); + } + DBFreeVariant(&dbv); + } + + CreateServiceFunction(MS_CLIST_GROUPCREATE, CreateGroup); + CreateServiceFunction(MS_CLIST_GROUPDELETE, DeleteGroup); + CreateServiceFunction(MS_CLIST_GROUPRENAME, RenameGroup); + CreateServiceFunction(MS_CLIST_GROUPGETNAME, GetGroupName); + CreateServiceFunction(MS_CLIST_GROUPGETNAME2, GetGroupName2); + CreateServiceFunction(MS_CLIST_GROUPSETEXPANDED, SetGroupExpandedState); + CreateServiceFunction(MS_CLIST_GROUPSETFLAGS, SetGroupFlags); + CreateServiceFunction(MS_CLIST_GROUPMOVEBEFORE, MoveGroupBefore); + CreateServiceFunction(MS_CLIST_GROUPBUILDMENU, BuildGroupMenu); + + hGroupChangeEvent = CreateHookableEvent( ME_CLIST_GROUPCHANGE ); + + return 0; +} diff --git a/src/modules/clist/keyboard.cpp b/src/modules/clist/keyboard.cpp new file mode 100644 index 0000000000..2c9cdce69c --- /dev/null +++ b/src/modules/clist/keyboard.cpp @@ -0,0 +1,173 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "clc.h" +#include + +static INT_PTR hkHideShow(WPARAM, LPARAM) +{ + cli.pfnShowHide(0,0); + return 0; +} +/* +INT_PTR hkSearch(WPARAM wParam,LPARAM lParam) +{ + DBVARIANT dbv={0}; + if(!DBGetContactSettingString(NULL,"CList","SearchUrl",&dbv)) { + CallService(MS_UTILS_OPENURL,DBGetContactSettingByte(NULL,"CList","HKSearchNewWnd",0),(LPARAM)dbv.pszVal); + DBFreeVariant(&dbv); + } + return 0; +} +*/ +static INT_PTR hkRead(WPARAM, LPARAM) +{ + if(cli.pfnEventsProcessTrayDoubleClick(0)==0) return TRUE; + SetForegroundWindow(cli.hwndContactList); + SetFocus(cli.hwndContactList); + return 0; +} + +static INT_PTR hkOpts(WPARAM, LPARAM) +{ + CallService("Options/OptionsCommand",0, 0); + return 0; +} +/* +static INT_PTR hkCloseMiranda(WPARAM wParam,LPARAM lParam) +{ + CallService("CloseAction", 0, 0); + return 0; +} + +INT_PTR hkRestoreStatus(WPARAM wParam,LPARAM lParam) +{ + int nStatus = DBGetContactSettingWord(NULL, "CList", "Status", ID_STATUS_OFFLINE); + CallService(MS_CLIST_SETSTATUSMODE, nStatus, 0); + return 0; +} + +static INT_PTR hkAllOffline(WPARAM, LPARAM) +{ + CallService(MS_CLIST_SETSTATUSMODE, ID_STATUS_OFFLINE, 0); + return 0; +} +*/ +int InitClistHotKeys(void) +{ + HOTKEYDESC shk = {0}; + + CreateServiceFunction("CLIST/HK/SHOWHIDE",hkHideShow); + CreateServiceFunction("CLIST/HK/Opts",hkOpts); + CreateServiceFunction("CLIST/HK/Read",hkRead); +// CreateServiceFunction("CLIST/HK/CloseMiranda",hkCloseMiranda); +// CreateServiceFunction("CLIST/HK/RestoreStatus",hkRestoreStatus); +// CreateServiceFunction("CLIST/HK/AllOffline",hkAllOffline); + + shk.cbSize=sizeof(shk); + shk.pszDescription="Show Hide Contact List"; + shk.pszName="ShowHide"; + shk.pszSection="Main"; + shk.pszService="CLIST/HK/SHOWHIDE"; + shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'A'); + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); + + shk.pszDescription="Read Message"; + shk.pszName="ReadMessage"; + shk.pszSection="Main"; + shk.pszService="CLIST/HK/Read"; + shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'I'); + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); +/* + shk.pszDescription="Search in site"; + shk.pszName="SearchInWeb"; + shk.pszSection="Main"; + shk.pszService="CLIST/HK/Search"; + shk.DefHotKey=846; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); +*/ + shk.pszDescription = "Open Options Page"; + shk.pszName = "ShowOptions"; + shk.pszSection = "Main"; + shk.pszService = "CLIST/HK/Opts"; + shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'O') | HKF_MIRANDA_LOCAL; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); + + shk.pszDescription = "Open Logging Options"; + shk.pszName = "ShowLogOptions"; + shk.pszSection = "Main"; + shk.pszService = "Netlib/Log/Win"; + shk.DefHotKey = 0; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); + + shk.pszDescription="Open Find User Dialog"; + shk.pszName="FindUsers"; + shk.pszSection="Main"; + shk.pszService="FindAdd/FindAddCommand"; + shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'F') | HKF_MIRANDA_LOCAL; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); + +/* + shk.pszDescription="Close Miranda"; + shk.pszName="CloseMiranda"; + shk.pszSection="Main"; + shk.pszService="CLIST/HK/CloseMiranda"; + shk.DefHotKey=0; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); + + shk.pszDescription="Restore last status"; + shk.pszName="RestoreLastStatus"; + shk.pszSection="Status"; + shk.pszService="CLIST/HK/RestoreStatus"; + shk.DefHotKey=0; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); + + shk.pszDescription="Set All Offline"; + shk.pszName="AllOffline"; + shk.pszSection="Status"; + shk.pszService="CLIST/HK/AllOffline"; + shk.DefHotKey=0; + CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk); +*/ + return 0; +} + + +int fnHotKeysRegister(HWND) +{ + return 0; +} + +void fnHotKeysUnregister(HWND) +{ +} + +int fnHotKeysProcess(HWND, WPARAM, LPARAM) +{ + return TRUE; +} + +int fnHotkeysProcessMessage(WPARAM, LPARAM) +{ + return FALSE; +} diff --git a/src/modules/clist/movetogroup.cpp b/src/modules/clist/movetogroup.cpp new file mode 100644 index 0000000000..9924bef2aa --- /dev/null +++ b/src/modules/clist/movetogroup.cpp @@ -0,0 +1,160 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +HANDLE hOnCntMenuBuild; +HGENMENU hMoveToGroupItem=0, hPriorityItem = 0, hFloatingItem = 0; + +LIST lphGroupsItems(5); + +//service +//wparam - hcontact +//lparam .popupposition from CLISTMENUITEM + +#define MTG_MOVE "MoveToGroup/Move" + +struct GroupItemSort +{ + TCHAR* name; + int position; + + GroupItemSort(TCHAR* pname, int pos) + : name(mir_tstrdup(pname)), position(pos) {} + + ~GroupItemSort() { mir_free(name); } + + static int compare(const GroupItemSort* d1, const GroupItemSort* d2) + { return _tcscoll(d1->name, d2->name); } +}; + +static TCHAR* PrepareGroupName( TCHAR* str ) +{ + TCHAR* p = _tcschr( str, '&' ), *d; + if ( p == NULL ) + return mir_tstrdup( str ); + + d = p = ( TCHAR* )mir_alloc( sizeof( TCHAR )*( 2*_tcslen( str )+1 )); + while ( *str ) { + if ( *str == '&' ) + *d++ = '&'; + *d++ = *str++; + } + + *d++ = 0; + return p; +} + +static void AddGroupItem(HGENMENU hRoot, TCHAR* name, int pos, WPARAM param, bool checked) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.hParentMenu = hRoot; + mi.popupPosition = param; // param to pszService - only with CMIF_CHILDPOPUP !!!!!! + mi.position = pos; + mi.ptszName = PrepareGroupName( name ); + mi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED; + if ( checked ) + mi.flags |= CMIF_CHECKED; + mi.pszService = MTG_MOVE; + HANDLE result = ( HANDLE )CallService(MS_CLIST_ADDCONTACTMENUITEM, param, (LPARAM)&mi); + mir_free( mi.ptszName ); + + lphGroupsItems.insert((HANDLE*)result); +} + +static int OnContactMenuBuild(WPARAM wParam,LPARAM) +{ + int i; + OBJLIST groups(10, GroupItemSort::compare); + + if (!hMoveToGroupItem) + { + CLISTMENUITEM mi = {0}; + + mi.cbSize = sizeof(mi); + mi.position = 100000; + mi.pszName = LPGEN("&Move to Group"); + mi.flags = CMIF_ROOTHANDLE | CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_GROUP); + + hMoveToGroupItem = (HGENMENU)CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM)&mi); + } + + for (i = 0; i < lphGroupsItems.getCount(); i++) + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)lphGroupsItems[i], 0); + lphGroupsItems.destroy(); + + TCHAR *szContactGroup = DBGetStringT((HANDLE)wParam, "CList", "Group"); + + int pos = 1000; + + AddGroupItem(hMoveToGroupItem, TranslateT(""), pos, -1, !szContactGroup); + + pos += 100000; // Separator + + for (i = 0; ; ++i) + { + char intname[20]; + _itoa(i, intname, 10); + + DBVARIANT dbv; + if (DBGetContactSettingTString(NULL, "CListGroups", intname, &dbv)) + break; + + if (dbv.ptszVal[0]) + groups.insert(new GroupItemSort(dbv.ptszVal + 1, i + 1)); + + mir_free(dbv.ptszVal); + } + + for (i = 0; i < groups.getCount(); ++i) + { + bool checked = szContactGroup && !_tcscmp(szContactGroup, groups[i].name); + AddGroupItem(hMoveToGroupItem, groups[i].name, ++pos, groups[i].position, checked); + } + + groups.destroy(); + mir_free(szContactGroup); + + return 0; +} + +static INT_PTR MTG_DOMOVE(WPARAM wParam,LPARAM lParam) +{ + CallService(MS_CLIST_CONTACTCHANGEGROUP, wParam, lParam < 0 ? 0 : lParam); + return 0; +} + +void MTG_OnmodulesLoad() +{ + hOnCntMenuBuild=HookEvent(ME_CLIST_PREBUILDCONTACTMENU,OnContactMenuBuild); + CreateServiceFunction(MTG_MOVE,MTG_DOMOVE); +} + +int UnloadMoveToGroup(void) +{ + UnhookEvent(hOnCntMenuBuild); + lphGroupsItems.destroy(); + + return 0; +} diff --git a/src/modules/clist/protocolorder.cpp b/src/modules/clist/protocolorder.cpp new file mode 100644 index 0000000000..ff77babfec --- /dev/null +++ b/src/modules/clist/protocolorder.cpp @@ -0,0 +1,351 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +// options dialog for protocol order and visibility +// written by daniel vijge +// gpl license ect... + +#include "commonheaders.h" +#include "clc.h" + +typedef struct tagProtocolData +{ + char *RealName; + int protopos; + int show, enabled; +} + ProtocolData; + +struct ProtocolOrderData +{ + int dragging; + HTREEITEM hDragItem; +}; + +typedef struct { + char* protoName; + int visible; +} + tempProtoItem; + +int isProtoSuitable( PROTO_INTERFACE* ppi ) +{ + if ( ppi == NULL ) + return TRUE; + + return ppi->GetCaps( PFLAGNUM_2, 0 ) & ~ppi->GetCaps( PFLAGNUM_5, 0 ); +} + +bool CheckProtocolOrder(void) +{ + bool changed = false; + int i, id = 0; + + for (;;) + { + // Find account with this id + for (i = 0; i < accounts.getCount(); i++) + if (accounts[i]->iOrder == id) break; + + // Account with id not found + if (i == accounts.getCount()) + { + // Check if this is skipped id, if it is decrement all other ids + bool found = false; + for (i = 0; i < accounts.getCount(); i++) + { + if (accounts[i]->iOrder < 1000000 && accounts[i]->iOrder > id) + { + --accounts[i]->iOrder; + found = true; + } + } + if (found) changed = true; + else break; + } + else + ++id; + } + + if (id < accounts.getCount()) + { + // Remove huge ids + for (i = 0; i < accounts.getCount(); i++) + { + if (accounts[i]->iOrder >= 1000000) + accounts[i]->iOrder = id++; + } + changed = true; + } + + if (id < accounts.getCount()) + { + // Remove duplicate ids + for (i = 0; i < accounts.getCount(); i++) + { + bool found = false; + for (int j = 0; j < accounts.getCount(); j++) + { + if (accounts[j]->iOrder == i) + { + if (found) accounts[j]->iOrder = id++; + else found = true; + } + } + } + changed = true; + } + + return changed; +} + + +int FillTree(HWND hwnd) +{ + ProtocolData *PD; + int i; + PROTOACCOUNT* pa; + + TVINSERTSTRUCT tvis; + tvis.hParent = NULL; + tvis.hInsertAfter = TVI_LAST; + tvis.item.mask = TVIF_PARAM|TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + + TreeView_DeleteAllItems(hwnd); + if ( accounts.getCount() == 0 ) + return FALSE; + + for ( i = 0; i < accounts.getCount(); i++ ) { + int idx = cli.pfnGetAccountIndexByPos( i ); + if ( idx == -1 ) + continue; + + pa = accounts[idx]; + + PD = ( ProtocolData* )mir_alloc( sizeof( ProtocolData )); + PD->RealName = pa->szModuleName; + PD->protopos = pa->iOrder; + PD->enabled = Proto_IsAccountEnabled( pa ) && isProtoSuitable( pa->ppro ); + PD->show = PD->enabled ? pa->bIsVisible : 100; + + tvis.item.lParam = ( LPARAM )PD; + tvis.item.pszText = pa->tszAccountName; + tvis.item.iImage = tvis.item.iSelectedImage = PD->show; + TreeView_InsertItem( hwnd, &tvis ); + } + + return 0; +} + +INT_PTR CALLBACK ProtocolOrderOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hwndProtoOrder = GetDlgItem(hwndDlg, IDC_PROTOCOLORDER); + struct ProtocolOrderData *dat = (ProtocolOrderData*)GetWindowLongPtr(hwndProtoOrder, GWLP_USERDATA); + + switch (msg) + { + case WM_DESTROY: + ImageList_Destroy(TreeView_GetImageList(hwndProtoOrder, TVSIL_NORMAL)); + mir_free( dat ); + break; + + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + dat = (ProtocolOrderData*)mir_calloc(sizeof(ProtocolOrderData)); + SetWindowLongPtr(hwndProtoOrder, GWLP_USERDATA, (LONG_PTR)dat); + dat->dragging=0; + + SetWindowLong(hwndProtoOrder, GWL_STYLE, GetWindowLong(hwndProtoOrder, GWL_STYLE) | TVS_NOHSCROLL); + { + HIMAGELIST himlCheckBoxes = ImageList_Create( GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ), ILC_COLOR32|ILC_MASK, 2, 2 ); + ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_NOTICK); + ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_TICK); + TreeView_SetImageList(hwndProtoOrder, himlCheckBoxes, TVSIL_NORMAL); + } + + FillTree(hwndProtoOrder); + return TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDC_RESETPROTOCOLDATA && HIWORD(wParam) == BN_CLICKED) + { + for ( int i = 0; i < accounts.getCount(); i++ ) + accounts[i]->iOrder = i; + + FillTree(hwndProtoOrder); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0); + } + break; + + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_APPLY ) { + int count = 0; + + TVITEM tvi; + tvi.hItem = TreeView_GetRoot(hwndProtoOrder); + tvi.cchTextMax = 32; + tvi.mask = TVIF_PARAM | TVIF_HANDLE; + + while ( tvi.hItem != NULL ) { + TreeView_GetItem(hwndProtoOrder, &tvi); + + if (tvi.lParam!=0) { + ProtocolData* ppd = ( ProtocolData* )tvi.lParam; + PROTOACCOUNT* pa = Proto_GetAccount( ppd->RealName ); + if ( pa != NULL ) { + pa->iOrder = count++; + if ( ppd->enabled ) + pa->bIsVisible = ppd->show; + } + } + + tvi.hItem = TreeView_GetNextSibling(hwndProtoOrder, tvi.hItem ); + } + + WriteDbAccounts(); + cli.pfnReloadProtoMenus(); + cli.pfnTrayIconIconsChanged(); + cli.pfnClcBroadcast( INTM_RELOADOPTIONS, 0, 0 ); + cli.pfnClcBroadcast( INTM_INVALIDATE, 0, 0 ); + } + break; + + case IDC_PROTOCOLORDER: + switch (((LPNMHDR)lParam)->code) { + case TVN_DELETEITEMA: + { + NMTREEVIEWA * pnmtv = (NMTREEVIEWA *) lParam; + if (pnmtv && pnmtv->itemOld.lParam) + mir_free((ProtocolData*)pnmtv->itemOld.lParam); + } + break; + + case TVN_BEGINDRAGA: + SetCapture(hwndDlg); + dat->dragging=1; + dat->hDragItem=((LPNMTREEVIEW)lParam)->itemNew.hItem; + TreeView_SelectItem(hwndProtoOrder, dat->hDragItem); + break; + + case NM_CLICK: + { + TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(GetMessagePos()); + hti.pt.y=(short)HIWORD(GetMessagePos()); + ScreenToClient(((LPNMHDR)lParam)->hwndFrom,&hti.pt); + if ( TreeView_HitTest(((LPNMHDR)lParam)->hwndFrom, &hti )) { + if ( hti.flags & TVHT_ONITEMICON ) { + TVITEMA tvi; + tvi.mask = TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + tvi.hItem = hti.hItem; + TreeView_GetItem(((LPNMHDR)lParam)->hwndFrom,&tvi); + + ProtocolData *pData = ( ProtocolData* )tvi.lParam; + if ( pData->enabled ) { + tvi.iImage = tvi.iSelectedImage = !tvi.iImage; + pData->show = tvi.iImage; + TreeView_SetItem(((LPNMHDR)lParam)->hwndFrom,&tvi); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0); + } } } } } + break; + } + break; + + case WM_MOUSEMOVE: + if ( dat->dragging ) { + TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(lParam); + hti.pt.y=(short)HIWORD(lParam); + ClientToScreen(hwndDlg, &hti.pt); + ScreenToClient(hwndProtoOrder, &hti.pt); + TreeView_HitTest(hwndProtoOrder, &hti); + if ( hti.flags & (TVHT_ONITEM|TVHT_ONITEMRIGHT )) + { + HTREEITEM it = hti.hItem; + hti.pt.y -= TreeView_GetItemHeight(hwndProtoOrder) / 2; + TreeView_HitTest(hwndProtoOrder, &hti); + if ( !( hti.flags & TVHT_ABOVE )) + TreeView_SetInsertMark(hwndProtoOrder, hti.hItem, 1); + else + TreeView_SetInsertMark(hwndProtoOrder, it, 0); + } + else { + if (hti.flags&TVHT_ABOVE) SendMessage(hwndProtoOrder, WM_VSCROLL, MAKEWPARAM(SB_LINEUP, 0), 0); + if (hti.flags&TVHT_BELOW) SendMessage(hwndProtoOrder, WM_VSCROLL, MAKEWPARAM(SB_LINEDOWN, 0), 0); + TreeView_SetInsertMark(hwndProtoOrder, NULL, 0); + } } + break; + + case WM_LBUTTONUP: + if ( dat->dragging ) { + TVHITTESTINFO hti; + TVITEM tvi; + + TreeView_SetInsertMark(hwndProtoOrder, NULL, 0); + dat->dragging = 0; + ReleaseCapture(); + + hti.pt.x = (short)LOWORD(lParam); + hti.pt.y = (short)HIWORD(lParam); + ClientToScreen(hwndDlg, &hti.pt); + ScreenToClient(hwndProtoOrder, &hti.pt); + hti.pt.y -= TreeView_GetItemHeight(hwndProtoOrder) / 2; + TreeView_HitTest(hwndProtoOrder, &hti); + if (dat->hDragItem == hti.hItem) break; + if (hti.flags & TVHT_ABOVE) hti.hItem = TVI_FIRST; + tvi.mask = TVIF_HANDLE|TVIF_PARAM; + tvi.hItem = dat->hDragItem; + TreeView_GetItem(hwndProtoOrder, &tvi); + if ( hti.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT) || (hti.hItem == TVI_FIRST)) + { + TVINSERTSTRUCT tvis; + TCHAR name[128]; + ProtocolData * lpOldData; + tvis.item.mask = TVIF_HANDLE|TVIF_PARAM|TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE; + tvis.item.stateMask = 0xFFFFFFFF; + tvis.item.pszText = name; + tvis.item.cchTextMax = SIZEOF(name); + tvis.item.hItem = dat->hDragItem; + tvis.item.iImage = tvis.item.iSelectedImage = ((ProtocolData *)tvi.lParam)->show; + TreeView_GetItem(hwndProtoOrder, &tvis.item); + + //the pointed lParam will be freed inside TVN_DELETEITEM + //so lets substitute it with 0 + lpOldData=(ProtocolData *)tvis.item.lParam; + tvis.item.lParam=0; + TreeView_SetItem(hwndProtoOrder, &tvis.item); + tvis.item.lParam=(LPARAM)lpOldData; + + //now current item contain lParam=0 we can delete it. the memory will be kept. + TreeView_DeleteItem(hwndProtoOrder, dat->hDragItem); + tvis.hParent = NULL; + tvis.hInsertAfter = hti.hItem; + TreeView_SelectItem(hwndProtoOrder, TreeView_InsertItem(hwndProtoOrder, &tvis)); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0); + } } + break; + } + return FALSE; +} diff --git a/src/modules/contacts/contacts.cpp b/src/modules/contacts/contacts.cpp new file mode 100644 index 0000000000..f8bc787286 --- /dev/null +++ b/src/modules/contacts/contacts.cpp @@ -0,0 +1,513 @@ +/* +Miranda IM + +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 "commonheaders.h" + +#define NAMEORDERCOUNT 8 +static TCHAR* nameOrderDescr[ NAMEORDERCOUNT ] = +{ + _T( "My custom name (not moveable)" ), + _T( "Nick" ), + _T( "FirstName" ), + _T( "E-mail" ), + _T( "LastName" ), + _T( "Username" ), + _T( "FirstName LastName" ), + _T( "'(Unknown Contact)' (not moveable)" ) +}; + +BYTE nameOrder[NAMEORDERCOUNT]; + +static int GetDatabaseString( CONTACTINFO *ci, const char* setting, DBVARIANT* dbv ) +{ + if (strcmp(ci->szProto, "CList") && CallProtoService(ci->szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_INFOSETTINGSVC) + { + DBCONTACTGETSETTING cgs = { ci->szProto, setting, dbv }; + dbv->type = (ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ; + + int res = CallProtoService(ci->szProto, PS_GETINFOSETTING, (WPARAM)ci->hContact, (LPARAM)&cgs); + if (res != CALLSERVICE_NOTFOUND) return res; + } + + if ( ci->dwFlag & CNF_UNICODE ) + return DBGetContactSettingWString(ci->hContact,ci->szProto,setting,dbv); + + return DBGetContactSettingString(ci->hContact,ci->szProto,setting,dbv); +} + +static int ProcessDatabaseValueDefault(CONTACTINFO *ci, const char* setting) +{ + DBVARIANT dbv; + if ( !GetDatabaseString( ci, setting, &dbv )) { + switch (dbv.type) { + case DBVT_ASCIIZ: + if (!dbv.pszVal[0]) break; + case DBVT_WCHAR: + if (!dbv.pwszVal[0]) break; + ci->type = CNFT_ASCIIZ; + ci->pszVal = dbv.ptszVal; + return 0; + } + DBFreeVariant( &dbv ); + } + + if ( DBGetContactSetting( ci->hContact, ci->szProto, setting, &dbv )) + return 1; + + switch (dbv.type) { + case DBVT_BYTE: + ci->type = CNFT_BYTE; + ci->bVal = dbv.bVal; + return 0; + case DBVT_WORD: + ci->type = CNFT_WORD; + ci->wVal = dbv.wVal; + return 0; + case DBVT_DWORD: + ci->type = CNFT_DWORD; + ci->dVal = dbv.dVal; + return 0; + } + + DBFreeVariant( &dbv ); + return 1; +} + +static INT_PTR GetContactInfo(WPARAM, LPARAM lParam) { + DBVARIANT dbv; + CONTACTINFO *ci = (CONTACTINFO*)lParam; + + if (ci==NULL) return 1; + if (ci->szProto==NULL) ci->szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEACCOUNT,(WPARAM)ci->hContact,0); + if (ci->szProto==NULL) return 1; + ci->type = 0; + switch(ci->dwFlag & 0x7F) { + case CNF_FIRSTNAME: return ProcessDatabaseValueDefault( ci, "FirstName" ); + case CNF_LASTNAME: return ProcessDatabaseValueDefault( ci, "LastName" ); + case CNF_NICK: return ProcessDatabaseValueDefault( ci, "Nick" ); + case CNF_EMAIL: return ProcessDatabaseValueDefault( ci, "e-mail" ); + case CNF_CITY: return ProcessDatabaseValueDefault( ci, "City" ); + case CNF_STATE: return ProcessDatabaseValueDefault( ci, "State" ); + case CNF_PHONE: return ProcessDatabaseValueDefault( ci, "Phone" ); + case CNF_HOMEPAGE: return ProcessDatabaseValueDefault( ci, "Homepage" ); + case CNF_ABOUT: return ProcessDatabaseValueDefault( ci, "About" ); + case CNF_AGE: return ProcessDatabaseValueDefault( ci, "Age" ); + case CNF_GENDER: return ProcessDatabaseValueDefault( ci, "Gender" ); + case CNF_FAX: return ProcessDatabaseValueDefault( ci, "Fax" ); + case CNF_CELLULAR: return ProcessDatabaseValueDefault( ci, "Cellular" ); + case CNF_BIRTHDAY: return ProcessDatabaseValueDefault( ci, "BirthDay" ); + case CNF_BIRTHMONTH: return ProcessDatabaseValueDefault( ci, "BirthMonth" ); + case CNF_BIRTHYEAR: return ProcessDatabaseValueDefault( ci, "BirthYear" ); + case CNF_STREET: return ProcessDatabaseValueDefault( ci, "Street" ); + case CNF_ZIP: return ProcessDatabaseValueDefault( ci, "ZIP" ); + case CNF_LANGUAGE1: return ProcessDatabaseValueDefault( ci, "Language1" ); + case CNF_LANGUAGE2: return ProcessDatabaseValueDefault( ci, "Language2" ); + case CNF_LANGUAGE3: return ProcessDatabaseValueDefault( ci, "Language3" ); + case CNF_CONAME: return ProcessDatabaseValueDefault( ci, "Company" ); + case CNF_CODEPT: return ProcessDatabaseValueDefault( ci, "CompanyDepartment" ); + case CNF_COPOSITION: return ProcessDatabaseValueDefault( ci, "CompanyPosition" ); + case CNF_COSTREET: return ProcessDatabaseValueDefault( ci, "CompanyStreet" ); + case CNF_COCITY: return ProcessDatabaseValueDefault( ci, "CompanyCity" ); + case CNF_COSTATE: return ProcessDatabaseValueDefault( ci, "CompanyState" ); + case CNF_COZIP: return ProcessDatabaseValueDefault( ci, "CompanyZIP" ); + case CNF_COHOMEPAGE: return ProcessDatabaseValueDefault( ci, "CompanyHomepage" ); + + case CNF_CUSTOMNICK: + { + char* saveProto = ci->szProto; ci->szProto = "CList"; + if ( ci->hContact != NULL && !ProcessDatabaseValueDefault( ci, "MyHandle" )) { + ci->szProto = saveProto; + return 0; + } + ci->szProto = saveProto; + break; + } + case CNF_COUNTRY: + case CNF_COCOUNTRY: + if ( !GetDatabaseString( ci, (ci->dwFlag & 0x7F) == CNF_COUNTRY ? "CountryName" : "CompanyCountryName", &dbv )) + return 0; + + if ( !DBGetContactSetting( ci->hContact, ci->szProto, (ci->dwFlag & 0x7F)==CNF_COUNTRY ? "Country" : "CompanyCountry", &dbv )) { + if ( dbv.type == DBVT_WORD ) { + int i,countryCount; + struct CountryListEntry *countries; + CallService(MS_UTILS_GETCOUNTRYLIST,(WPARAM)&countryCount,(LPARAM)&countries); + for(i=0;idwFlag & CNF_UNICODE ) { + int cbLen = MultiByteToWideChar( CP_ACP, 0, ( LPCSTR )countries[i].szName, -1, NULL, 0 ); + WCHAR* buf = ( WCHAR* )mir_alloc( sizeof( WCHAR )*(cbLen+1) ); + if ( buf != NULL ) + MultiByteToWideChar( CP_ACP, 0, ( LPCSTR )countries[i].szName, -1, buf, cbLen ); + ci->pszVal = ( TCHAR* )buf; + } + else ci->pszVal = ( TCHAR* )mir_strdup(countries[i].szName); + + ci->type = CNFT_ASCIIZ; + DBFreeVariant(&dbv); + return 0; + } + } + else return ProcessDatabaseValueDefault( ci, (ci->dwFlag & 0x7F)==CNF_COUNTRY ? "Country" : "CompanyCountry" ); + DBFreeVariant(&dbv); + } + break; + + case CNF_FIRSTLAST: + if( !GetDatabaseString( ci, "FirstName", &dbv )) { + DBVARIANT dbv2; + if(!GetDatabaseString(ci,"LastName",&dbv2)) { + ci->type = CNFT_ASCIIZ; + if ( ci->dwFlag & CNF_UNICODE ) { + size_t len = wcslen(dbv.pwszVal) + wcslen(dbv2.pwszVal) + 2; + WCHAR* buf = ( WCHAR* )mir_alloc( sizeof( WCHAR )*len ); + if ( buf != NULL ) + wcscat( wcscat( wcscpy( buf, dbv.pwszVal ), L" " ), dbv2.pwszVal ); + ci->pszVal = ( TCHAR* )buf; + } + else { + size_t len = strlen(dbv.pszVal) + strlen(dbv2.pszVal) + 2; + char* buf = ( char* )mir_alloc( len ); + if ( buf != NULL ) + strcat( strcat( strcpy( buf, dbv.pszVal ), " " ), dbv2.pszVal ); + ci->pszVal = ( TCHAR* )buf; + } + DBFreeVariant( &dbv ); + DBFreeVariant( &dbv2 ); + return 0; + } + DBFreeVariant( &dbv ); + } + break; + + case CNF_UNIQUEID: + { + char *uid = (char*)CallProtoService(ci->szProto,PS_GETCAPS,PFLAG_UNIQUEIDSETTING,0); + if ((INT_PTR)uid!=CALLSERVICE_NOTFOUND&&uid) + if (!ProcessDatabaseValueDefault(ci,uid)) + return 0; + + break; + } + case CNF_DISPLAYUID: + { + if (!ProcessDatabaseValueDefault(ci, "display_uid")) + return 0; + char *uid = (char*)CallProtoService(ci->szProto,PS_GETCAPS,PFLAG_UNIQUEIDSETTING,0); + if ((INT_PTR)uid!=CALLSERVICE_NOTFOUND&&uid) + if (!ProcessDatabaseValueDefault(ci,uid)) + return 0; + + break; + } + case CNF_DISPLAYNC: + case CNF_DISPLAY: + { + int i; + for( i=0; i < NAMEORDERCOUNT; i++ ) { + switch(nameOrder[i]) { + case 0: // custom name + { + // make sure we aren't in CNF_DISPLAYNC mode + // don't get custom name for NULL contact + char* saveProto = ci->szProto; ci->szProto = "CList"; + if (ci->hContact!=NULL && (ci->dwFlag&0x7F)==CNF_DISPLAY && !ProcessDatabaseValueDefault(ci,"MyHandle")) { + ci->szProto = saveProto; + return 0; + } + ci->szProto = saveProto; + break; + } + case 1: + if ( !ProcessDatabaseValueDefault( ci, "Nick" )) // nick + return 0; + break; + case 2: + if ( !ProcessDatabaseValueDefault( ci, "FirstName" )) // First Name + return 0; + break; + case 3: + if ( !ProcessDatabaseValueDefault( ci, "e-mail" )) // E-mail + return 0; + break; + case 4: + if ( !ProcessDatabaseValueDefault( ci, "LastName" )) // Last Name + return 0; + break; + case 5: // Unique id + { + // protocol must define a PFLAG_UNIQUEIDSETTING + char *uid = (char*)CallProtoService(ci->szProto,PS_GETCAPS,PFLAG_UNIQUEIDSETTING,0); + if ((INT_PTR)uid!=CALLSERVICE_NOTFOUND&&uid) { + if (!GetDatabaseString(ci,uid,&dbv)) { + if ( dbv.type == DBVT_BYTE || dbv.type == DBVT_WORD || dbv.type == DBVT_DWORD ) { + long value = (dbv.type == DBVT_BYTE) ? dbv.bVal:(dbv.type==DBVT_WORD ? dbv.wVal : dbv.dVal); + if ( ci->dwFlag & CNF_UNICODE ) { + WCHAR buf[ 40 ]; + _ltow( value, buf, 10 ); + ci->pszVal = ( TCHAR* )mir_wstrdup( buf ); + } + else { + char buf[ 40 ]; + _ltoa( value, buf, 10 ); + ci->pszVal = ( TCHAR* )mir_strdup(buf); + } + ci->type = CNFT_ASCIIZ; + return 0; + } + if (dbv.type == DBVT_ASCIIZ && !(ci->dwFlag & CNF_UNICODE)) { + ci->type = CNFT_ASCIIZ; + ci->pszVal = dbv.ptszVal; + return 0; + } + if (dbv.type == DBVT_WCHAR && (ci->dwFlag & CNF_UNICODE)) { + ci->type = CNFT_ASCIIZ; + ci->pszVal = dbv.ptszVal; + return 0; + } } } + break; + } + case 6: // first + last name + if(!GetDatabaseString(ci,"FirstName",&dbv)) { + DBVARIANT dbv2; + if(!GetDatabaseString(ci,"LastName",&dbv2)) { + ci->type = CNFT_ASCIIZ; + + if ( ci->dwFlag & CNF_UNICODE ) { + size_t len = wcslen(dbv.pwszVal) + wcslen(dbv2.pwszVal) + 2; + WCHAR* buf = ( WCHAR* )mir_alloc( sizeof( WCHAR )*len ); + if ( buf != NULL ) + wcscat( wcscat( wcscpy( buf, dbv.pwszVal ), L" " ), dbv2.pwszVal ); + ci->pszVal = ( TCHAR* )buf; + } + else { + size_t len = strlen(dbv.pszVal) + strlen(dbv2.pszVal) + 2; + char* buf = ( char* )mir_alloc( len ); + if ( buf != NULL ) + strcat( strcat( strcpy( buf, dbv.pszVal ), " " ), dbv2.pszVal ); + ci->pszVal = ( TCHAR* )buf; + } + + DBFreeVariant( &dbv ); + DBFreeVariant( &dbv2 ); + return 0; + } + DBFreeVariant( &dbv ); + } + break; + + case 7: + if ( ci->dwFlag & CNF_UNICODE ) + ci->pszVal = ( TCHAR* )mir_wstrdup( TranslateW( L"'(Unknown Contact)'" )); + else + ci->pszVal = ( TCHAR* )mir_strdup( Translate("'(Unknown Contact)'")); + ci->type = CNFT_ASCIIZ; + return 0; + } } } + break; + + case CNF_TIMEZONE: { + HANDLE hTz = tmi.createByContact(ci->hContact, TZF_KNOWNONLY); + if (hTz) + { + LPTIME_ZONE_INFORMATION tzi = tmi.getTzi(hTz); + int offset = tzi->Bias + tzi->StandardBias; + + char str[80]; + mir_snprintf(str, SIZEOF(str), offset ? "UTC%+d:%02d" : "UTC", offset / -60, abs(offset % 60)); + ci->pszVal = ci->dwFlag & CNF_UNICODE ? (TCHAR*)mir_a2u(str) : (TCHAR*)mir_strdup(str); + ci->type = CNFT_ASCIIZ; + return 0; + } + break; + } + case CNF_MYNOTES: { + char* saveProto = ci->szProto; ci->szProto = "UserInfo"; + if (!ProcessDatabaseValueDefault(ci,"MyNotes")) { + ci->szProto = saveProto; + return 0; + } + ci->szProto = saveProto; + break; + } } + + return 1; +} + +struct ContactOptionsData { + int dragging; + HTREEITEM hDragItem; +}; + +static INT_PTR CALLBACK ContactOpts(HWND hwndDlg, UINT msg, WPARAM, LPARAM lParam) +{ struct ContactOptionsData *dat; + + dat=(struct ContactOptionsData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) + { + case WM_INITDIALOG: + { TranslateDialogDefault(hwndDlg); + dat=(struct ContactOptionsData*)mir_alloc(sizeof(struct ContactOptionsData)); + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)dat); + dat->dragging=0; + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_NAMEORDER),GWL_STYLE,GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_NAMEORDER),GWL_STYLE)|TVS_NOHSCROLL); + { TVINSERTSTRUCT tvis; + int i; + tvis.hParent = NULL; + tvis.hInsertAfter = TVI_LAST; + tvis.item.mask = TVIF_TEXT|TVIF_PARAM; + for(i=0; i < SIZEOF(nameOrderDescr); i++ ) { + tvis.item.lParam = nameOrder[i]; + tvis.item.pszText = TranslateTS( nameOrderDescr[ nameOrder[i]] ); + TreeView_InsertItem( GetDlgItem(hwndDlg,IDC_NAMEORDER), &tvis ); + } } + return TRUE; + } + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_APPLY) + { DBCONTACTWRITESETTING cws; + TVITEM tvi; + int i; + cws.szModule = "Contact"; + cws.szSetting = "NameOrder"; + cws.value.type = DBVT_BLOB; + cws.value.cpbVal = SIZEOF(nameOrderDescr); + cws.value.pbVal = nameOrder; + tvi.hItem = TreeView_GetRoot(GetDlgItem(hwndDlg,IDC_NAMEORDER)); + i=0; + while( tvi.hItem != NULL ) { + tvi.mask = TVIF_PARAM | TVIF_HANDLE; + TreeView_GetItem( GetDlgItem(hwndDlg,IDC_NAMEORDER), &tvi ); + nameOrder[i++] = (BYTE)tvi.lParam; + tvi.hItem = TreeView_GetNextSibling(GetDlgItem(hwndDlg,IDC_NAMEORDER),tvi.hItem); + } + CallService(MS_DB_CONTACT_WRITESETTING,(WPARAM)(HANDLE)NULL,(LPARAM)&cws); + CallService(MS_CLIST_INVALIDATEDISPLAYNAME,(WPARAM)INVALID_HANDLE_VALUE,0); + } + break; + case IDC_NAMEORDER: + if (((LPNMHDR)lParam)->code == TVN_BEGINDRAGA) { + LPNMTREEVIEWA notify = (LPNMTREEVIEWA)lParam; + if ( notify->itemNew.lParam==0 || notify->itemNew.lParam == SIZEOF(nameOrderDescr)-1 ) + break; + SetCapture(hwndDlg); + dat->dragging=1; + dat->hDragItem=((LPNMTREEVIEW)lParam)->itemNew.hItem; + TreeView_SelectItem(GetDlgItem(hwndDlg,IDC_NAMEORDER),dat->hDragItem); + } + break; + } + break; + case WM_MOUSEMOVE: + if(!dat->dragging) break; + { TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(lParam); + hti.pt.y=(short)HIWORD(lParam); + ClientToScreen(hwndDlg,&hti.pt); + ScreenToClient(GetDlgItem(hwndDlg,IDC_NAMEORDER),&hti.pt); + TreeView_HitTest(GetDlgItem(hwndDlg,IDC_NAMEORDER),&hti); + if(hti.flags&(TVHT_ONITEM|TVHT_ONITEMRIGHT)) { + hti.pt.y-=TreeView_GetItemHeight(GetDlgItem(hwndDlg,IDC_NAMEORDER))/2; + TreeView_HitTest(GetDlgItem(hwndDlg,IDC_NAMEORDER),&hti); + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_NAMEORDER),hti.hItem,1); + } + else { + if(hti.flags&TVHT_ABOVE) SendDlgItemMessage(hwndDlg,IDC_NAMEORDER,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0); + if(hti.flags&TVHT_BELOW) SendDlgItemMessage(hwndDlg,IDC_NAMEORDER,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0); + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_NAMEORDER),NULL,0); + } + } + break; + case WM_LBUTTONUP: + if(!dat->dragging) break; + TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_NAMEORDER),NULL,0); + dat->dragging=0; + ReleaseCapture(); + { TVHITTESTINFO hti; + TVITEM tvi; + hti.pt.x=(short)LOWORD(lParam); + hti.pt.y=(short)HIWORD(lParam); + ClientToScreen(hwndDlg,&hti.pt); + ScreenToClient(GetDlgItem(hwndDlg,IDC_NAMEORDER),&hti.pt); + hti.pt.y-=TreeView_GetItemHeight(GetDlgItem(hwndDlg,IDC_NAMEORDER))/2; + TreeView_HitTest(GetDlgItem(hwndDlg,IDC_NAMEORDER),&hti); + if(dat->hDragItem==hti.hItem) break; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + tvi.hItem=hti.hItem; + TreeView_GetItem(GetDlgItem(hwndDlg,IDC_NAMEORDER),&tvi); + if(tvi.lParam == SIZEOF(nameOrderDescr)-1) break; + if(hti.flags&(TVHT_ONITEM|TVHT_ONITEMRIGHT)) { + TVINSERTSTRUCT tvis; + TCHAR name[128]; + tvis.item.mask=TVIF_HANDLE|TVIF_PARAM|TVIF_TEXT|TVIF_PARAM; + tvis.item.stateMask=0xFFFFFFFF; + tvis.item.pszText=name; + tvis.item.cchTextMax=SIZEOF(name); + tvis.item.hItem=dat->hDragItem; + TreeView_GetItem(GetDlgItem(hwndDlg,IDC_NAMEORDER),&tvis.item); + TreeView_DeleteItem(GetDlgItem(hwndDlg,IDC_NAMEORDER),dat->hDragItem); + tvis.hParent=NULL; + tvis.hInsertAfter=hti.hItem; + TreeView_SelectItem(GetDlgItem(hwndDlg,IDC_NAMEORDER),TreeView_InsertItem(GetDlgItem(hwndDlg,IDC_NAMEORDER),&tvis)); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + } + break; + case WM_DESTROY: + mir_free(dat); + break; + } + return FALSE; +} + +static int ContactOptInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.position = -1000000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_CONTACT); + odp.pszGroup = LPGEN("Customize"); + odp.pszTitle = LPGEN("Contacts"); + odp.pfnDlgProc = ContactOpts; + odp.flags = ODPF_BOLDGROUPS; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} + +int LoadContactsModule(void) { + { + // Load the name order + BYTE i; + DBVARIANT dbv; + + for(i=0; i 0 && _tcsicmp(&name[len], _T(".dat")) == 0; +} + +// returns 1 if the profile manager should be shown +static bool showProfileManager(void) +{ + TCHAR Mgr[32]; + // is control pressed? + if (GetAsyncKeyState(VK_CONTROL)&0x8000) + return 1; + + // wanna show it? + GetPrivateProfileString(_T("Database"), _T("ShowProfileMgr"), _T("never"), Mgr, SIZEOF(Mgr), mirandabootini); + return ( _tcsicmp(Mgr, _T("yes")) == 0 ); +} + +bool shouldAutoCreate(TCHAR *szProfile) +{ + if (szProfile[0] == 0) + return false; + + TCHAR ac[32]; + GetPrivateProfileString(_T("Database"), _T("AutoCreate"), _T(""), ac, SIZEOF(ac), mirandabootini); + return _tcsicmp(ac, _T("yes")) == 0; +} + +static void getDefaultProfile(TCHAR * szProfile, size_t cch, TCHAR * profiledir) +{ + TCHAR defaultProfile[MAX_PATH]; + GetPrivateProfileString(_T("Database"), _T("DefaultProfile"), _T(""), defaultProfile, SIZEOF(defaultProfile), mirandabootini); + + if (defaultProfile[0] == 0) + return; + + TCHAR* res = Utils_ReplaceVarsT(defaultProfile); + if (res) { + mir_sntprintf(szProfile, cch, _T("%s\\%s\\%s%s"), profiledir, res, res, isValidProfileName(res) ? _T("") : _T(".dat")); + mir_free(res); + } + else szProfile[0] = 0; +} + +// returns 1 if something that looks like a profile is there +static int getProfileCmdLineArgs(TCHAR * szProfile, size_t cch) +{ + TCHAR *szCmdLine = GetCommandLine(); + TCHAR *szEndOfParam; + TCHAR szThisParam[1024]; + int firstParam=1; + + while(szCmdLine[0]) + { + if(szCmdLine[0]=='"') + { + szEndOfParam = _tcschr(szCmdLine+1, '"'); + if(szEndOfParam == NULL) break; + lstrcpyn(szThisParam, szCmdLine+1, min(SIZEOF(szThisParam), szEndOfParam - szCmdLine)); + szCmdLine = szEndOfParam + 1; + } + else + { + szEndOfParam = szCmdLine + _tcscspn(szCmdLine, _T(" \t")); + lstrcpyn(szThisParam, szCmdLine, min(SIZEOF(szThisParam), szEndOfParam - szCmdLine+1)); + szCmdLine = szEndOfParam; + } + while(*szCmdLine && *szCmdLine<=' ') szCmdLine++; + if (firstParam) { firstParam=0; continue; } //first param is executable name + if (szThisParam[0] == '/' || szThisParam[0] == '-') continue; //no switches supported + + TCHAR* res = Utils_ReplaceVarsT(szThisParam); + if (res == NULL) return 0; + _tcsncpy(szProfile, res, cch); szProfile[cch-1] = 0; + mir_free(res); + return 1; + } + return 0; +} + +void getProfileCmdLine(TCHAR * szProfile, size_t cch, TCHAR * profiledir) +{ + TCHAR buf[MAX_PATH]; + if (getProfileCmdLineArgs(buf, SIZEOF(buf))) + { + TCHAR *p, profileName[MAX_PATH], newProfileDir[MAX_PATH]; + + p = _tcsrchr(buf, '\\'); if (p) ++p; else p = buf; + + if (!isValidProfileName(buf) && *p) + _tcscat(buf, _T(".dat")); + + _tcscpy(profileName, p); + p = _tcsrchr(profileName, '.'); if (p) *p = 0; + + mir_sntprintf(newProfileDir, cch, _T("%s\\%s\\"), profiledir, profileName); + pathToAbsoluteT(buf, szProfile, newProfileDir); + + if (_tcschr(buf, '\\')) + { + _tcscpy(profiledir, szProfile); + if (profileName[0]) + { + p = _tcsrchr(profiledir, '\\'); *p = 0; + p = _tcsrchr(profiledir, '\\'); + if (p && _tcsicmp(p + 1, profileName) == 0) + *p = 0; + } + else + szProfile[0] = 0; + + } + } +} + +// move profile from profile subdir +static void moveProfileDirProfiles(TCHAR * profiledir, BOOL isRootDir = TRUE) +{ + TCHAR pfd[MAX_PATH]; + if (isRootDir) { + TCHAR *path = Utils_ReplaceVarsT(_T("%miranda_path%\\*.dat")); + mir_sntprintf(pfd, SIZEOF(pfd), _T("%s"), path); + mir_free(path); + } + else + mir_sntprintf(pfd, SIZEOF(pfd), _T("%s\\*.dat"), profiledir); + + WIN32_FIND_DATA ffd; + HANDLE hFind = FindFirstFile(pfd, &ffd); + if (hFind != INVALID_HANDLE_VALUE) + { + TCHAR *c =_tcsrchr(pfd, '\\'); if (c) *c = 0; + do + { + TCHAR path[MAX_PATH], path2[MAX_PATH]; + TCHAR* profile = mir_tstrdup(ffd.cFileName); + TCHAR *c =_tcsrchr(profile, '.'); if (c) *c = 0; + mir_sntprintf(path, SIZEOF(path), _T("%s\\%s"), pfd, ffd.cFileName); + mir_sntprintf(path2, SIZEOF(path2), _T("%s\\%s"), profiledir, profile); + CreateDirectoryTreeT(path2); + mir_sntprintf(path2, SIZEOF(path2), _T("%s\\%s\\%s"), profiledir, profile, ffd.cFileName); + if (_taccess(path2, 0) == 0) + { + const TCHAR tszMoveMsg[] = + _T("Miranda is trying upgrade your profile structure.\n") + _T("It cannot move profile %s to the new location %s\n") + _T("Because profile with this name already exist. Please resolve the issue manually."); + TCHAR buf[512]; + + mir_sntprintf(buf, SIZEOF(buf), TranslateTS(tszMoveMsg), path, path2); + MessageBox(NULL, buf, _T("Miranda IM"), MB_ICONERROR | MB_OK); + } + else if (MoveFile(path, path2) == 0) + { + const TCHAR tszMoveMsg[] = + _T("Miranda is trying upgrade your profile structure.\n") + _T("It cannot move profile %s to the new location %s automatically\n") + _T("Most likely due to insufficient privileges. Please move profile manually."); + TCHAR buf[512]; + + mir_sntprintf(buf, SIZEOF(buf), TranslateTS(tszMoveMsg), path, path2); + MessageBox(NULL, buf, _T("Miranda IM"), MB_ICONERROR | MB_OK); + break; + } + mir_free(profile); + } + while(FindNextFile(hFind, &ffd)); + } + FindClose(hFind); +} + +// returns 1 if a single profile (full path) is found within the profile dir +static int getProfile1(TCHAR * szProfile, size_t cch, TCHAR * profiledir, BOOL * noProfiles) +{ + unsigned int found = 0; + + if (IsInsideRootDir(profiledir, false)) + moveProfileDirProfiles(profiledir); + moveProfileDirProfiles(profiledir, FALSE); + + bool nodprof = szProfile[0] == 0; + bool reqfd = !nodprof && (_taccess(szProfile, 0) == 0 || shouldAutoCreate(szProfile)); + bool shpm = showProfileManager(); + + if (reqfd) + found++; + + if (shpm || !reqfd) { + TCHAR searchspec[MAX_PATH]; + mir_sntprintf(searchspec, SIZEOF(searchspec), _T("%s\\*.*"), profiledir); + + WIN32_FIND_DATA ffd; + HANDLE hFind = FindFirstFile(searchspec, &ffd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + // make sure the first hit is actually a *.dat file + if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && _tcscmp(ffd.cFileName, _T(".")) && _tcscmp(ffd.cFileName, _T(".."))) { + TCHAR newProfile[MAX_PATH]; + mir_sntprintf(newProfile, MAX_PATH, _T("%s\\%s\\%s.dat"), profiledir, ffd.cFileName, ffd.cFileName); + if (_taccess(newProfile, 0) == 0) + if (++found == 1 && nodprof) + _tcscpy(szProfile, newProfile); + } + } + while (FindNextFile(hFind, &ffd)); + + FindClose(hFind); + } + reqfd = !shpm && found == 1 && nodprof; + } + + if ( noProfiles ) + *noProfiles = found == 0; + + if ( nodprof && !reqfd ) + szProfile[0] = 0; + + return reqfd; +} + +// returns 1 if a default profile should be selected instead of showing the manager. +static int getProfileAutoRun(TCHAR * szProfile) +{ + TCHAR Mgr[32]; + GetPrivateProfileString(_T("Database"), _T("ShowProfileMgr"), _T(""), Mgr, SIZEOF(Mgr), mirandabootini); + if (_tcsicmp(Mgr, _T("never"))) + return 0; + + return fileExist(szProfile) || shouldAutoCreate(szProfile); +} + +// returns 1 if a profile was selected +static int getProfile(TCHAR * szProfile, size_t cch) +{ + getProfilePath(g_profileDir, SIZEOF(g_profileDir)); + if (IsInsideRootDir(g_profileDir, true)) + { + if (WritePrivateProfileString(_T("Database"), _T("ProfileDir"), _T(""), mirandabootini)) + getProfilePath(g_profileDir, SIZEOF(g_profileDir)); + } + + getDefaultProfile(szProfile, cch, g_profileDir); + getProfileCmdLine(szProfile, cch, g_profileDir); + if (IsInsideRootDir(g_profileDir, true)) + { + MessageBox(NULL, + _T("Profile cannot be placed into Miranda root folder.\n") + _T("Please move Miranda profile to some other location."), + _T("Miranda IM"), MB_ICONERROR | MB_OK); + return 0; + } + if (getProfileAutoRun(szProfile)) + return 1; + + PROFILEMANAGERDATA pd = {0}; + if (getProfile1(szProfile, cch, g_profileDir, &pd.noProfiles)) + return 1; + + pd.szProfile = szProfile; + pd.szProfileDir = g_profileDir; + return getProfileManager(&pd); +} + +// carefully converts a file name from TCHAR* to char* +char* makeFileName( const TCHAR* tszOriginalName ) +{ + char* szResult = NULL; + char* szFileName = mir_t2a( tszOriginalName ); + TCHAR* tszFileName = mir_a2t( szFileName ); + if ( _tcscmp( tszOriginalName, tszFileName )) { + TCHAR tszProfile[MAX_PATH]; + if ( GetShortPathName( tszOriginalName, tszProfile, MAX_PATH) != 0) + szResult = mir_t2a( tszProfile ); + } + + if ( !szResult ) + szResult = szFileName; + else + mir_free(szFileName); + mir_free(tszFileName); + + return szResult; +} + +// called by the UI, return 1 on success, use link to create profile, set error if any +int makeDatabase(TCHAR * profile, DATABASELINK * link, HWND hwndDlg) +{ + TCHAR buf[256]; + int err=0; + // check if the file already exists + TCHAR * file = _tcsrchr(profile, '\\'); + if (file) file++; + if (_taccess(profile, 0) == 0) { + // file already exists! + mir_sntprintf(buf, SIZEOF(buf), TranslateTS( _T("The profile '%s' already exists. Do you want to move it to the ") + _T("Recycle Bin? \n\nWARNING: The profile will be deleted if Recycle Bin is disabled.\n") + _T("WARNING: A profile may contain confidential information and should be properly deleted.")), file ); + if ( MessageBox(hwndDlg, buf, TranslateT("The profile already exists"), MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2) != IDYES ) + return 0; + + // move the file + SHFILEOPSTRUCT sf = {0}; + sf.wFunc = FO_DELETE; + sf.pFrom = buf; + sf.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO; + mir_sntprintf(buf, SIZEOF(buf), _T("%s\0"), profile); + if ( SHFileOperation(&sf) != 0 ) { + mir_sntprintf(buf, SIZEOF(buf),TranslateT("Couldn't move '%s' to the Recycle Bin, Please select another profile name."),file); + MessageBox(0,buf,TranslateT("Problem moving profile"),MB_ICONINFORMATION|MB_OK); + return 0; + } + // now the file should be gone! + } + // ask the database to create the profile + CreatePathToFileT(profile); + char *prf = makeFileName(profile); + if (link->makeDatabase(prf, &err)) { + mir_sntprintf(buf, SIZEOF(buf),TranslateT("Unable to create the profile '%s', the error was %x"),file, err); + MessageBox(hwndDlg,buf,TranslateT("Problem creating profile"),MB_ICONERROR|MB_OK); + mir_free(prf); + return 0; + } + dbCreated = true; + // the profile has been created! woot + mir_free(prf); + return 1; +} + +// enumerate all plugins that had valid DatabasePluginInfo() +static int FindDbPluginForProfile(const char*, DATABASELINK * dblink, LPARAM lParam) +{ + TCHAR* tszProfile = ( TCHAR* )lParam; + + int res = DBPE_CONT; + if ( dblink && dblink->cbSize == sizeof(DATABASELINK)) { + char* szProfile = makeFileName(tszProfile); + // liked the profile? + int err = 0; + if (dblink->grokHeader(szProfile, &err) == 0) { + // added APIs? + if ( !dblink->Load(szProfile, &pluginCoreLink)) { + fillProfileName( tszProfile ); + res = DBPE_DONE; + } + else res = DBPE_HALT; + } + else { + res = DBPE_HALT; + switch ( err ) { + case EGROKPRF_CANTREAD: + case EGROKPRF_UNKHEADER: + // just not supported. + res = DBPE_CONT; + + case EGROKPRF_VERNEWER: + case EGROKPRF_DAMAGED: + break; + } + } //if + mir_free(szProfile); + } + return res; +} + +// enumerate all plugins that had valid DatabasePluginInfo() +static int FindDbPluginAutoCreate(const char*, DATABASELINK * dblink, LPARAM lParam) +{ + TCHAR* tszProfile = ( TCHAR* )lParam; + + int res = DBPE_CONT; + if (dblink && dblink->cbSize == sizeof(DATABASELINK)) { + CreatePathToFileT( tszProfile ); + + int err; + char *szProfile = makeFileName( tszProfile ); + if (dblink->makeDatabase(szProfile, &err) == 0) { + dbCreated = true; + if ( !dblink->Load(szProfile, &pluginCoreLink)) { + fillProfileName( tszProfile ); + res = DBPE_DONE; + } + else res = DBPE_HALT; + } + mir_free(szProfile); + } + return res; +} + +typedef struct { + TCHAR * profile; + UINT msg; + ATOM aPath; + int found; +} ENUMMIRANDAWINDOW; + +static BOOL CALLBACK EnumMirandaWindows(HWND hwnd, LPARAM lParam) +{ + TCHAR classname[256]; + ENUMMIRANDAWINDOW * x = (ENUMMIRANDAWINDOW *)lParam; + DWORD_PTR res=0; + if ( GetClassName(hwnd,classname,SIZEOF(classname)) && lstrcmp( _T("Miranda"),classname)==0 ) { + if ( SendMessageTimeout(hwnd, x->msg, (WPARAM)x->aPath, 0, SMTO_ABORTIFHUNG, 100, &res) && res ) { + x->found++; + return FALSE; + } + } + return TRUE; +} + +static int FindMirandaForProfile(TCHAR * szProfile) +{ + ENUMMIRANDAWINDOW x={0}; + x.profile=szProfile; + x.msg=RegisterWindowMessage( _T( "Miranda::ProcessProfile" )); + x.aPath=GlobalAddAtom(szProfile); + EnumWindows(EnumMirandaWindows, (LPARAM)&x); + GlobalDeleteAtom(x.aPath); + return x.found; +} + +int LoadDatabaseModule(void) +{ + TCHAR szProfile[MAX_PATH]; + pathToAbsoluteT(_T("."), szProfile, NULL); + _tchdir(szProfile); + szProfile[0] = 0; + + // load the older basic services of the db + InitUtils(); + + // find out which profile to load + if ( !getProfile( szProfile, SIZEOF( szProfile ))) + return 1; + + PLUGIN_DB_ENUM dbe; + dbe.cbSize = sizeof(PLUGIN_DB_ENUM); + dbe.lParam = (LPARAM)szProfile; + + if ( _taccess(szProfile, 0) && shouldAutoCreate( szProfile )) + dbe.pfnEnumCallback=( int(*) (const char*,void*,LPARAM) )FindDbPluginAutoCreate; + else + dbe.pfnEnumCallback=( int(*) (const char*,void*,LPARAM) )FindDbPluginForProfile; + + // find a driver to support the given profile + int rc = CallService(MS_PLUGINS_ENUMDBPLUGINS, 0, (LPARAM)&dbe); + switch ( rc ) { + case -1: { + // no plugins at all + TCHAR buf[256]; + TCHAR* p = _tcsrchr(szProfile,'\\'); + mir_sntprintf(buf,SIZEOF(buf),TranslateT("Miranda is unable to open '%s' because you do not have any profile plugins installed.\nYou need to install dbx_3x.dll or equivalent."), p ? ++p : szProfile ); + MessageBox(0,buf,TranslateT("No profile support installed!"),MB_OK | MB_ICONERROR); + break; + } + case 1: + // if there were drivers but they all failed cos the file is locked, try and find the miranda which locked it + if (fileExist(szProfile)) { + // file isn't locked, just no driver could open it. + TCHAR buf[256]; + TCHAR* p = _tcsrchr(szProfile,'\\'); + mir_sntprintf(buf,SIZEOF(buf),TranslateT("Miranda was unable to open '%s', it's in an unknown format.\nThis profile might also be damaged, please run DB-tool which should be installed."), p ? ++p : szProfile); + MessageBox(0,buf,TranslateT("Miranda can't understand that profile"),MB_OK | MB_ICONERROR); + } + else if (!FindMirandaForProfile(szProfile)) { + TCHAR buf[256]; + TCHAR* p = _tcsrchr(szProfile,'\\'); + mir_sntprintf(buf,SIZEOF(buf),TranslateT("Miranda was unable to open '%s'\nIt's inaccessible or used by other application or Miranda instance"), p ? ++p : szProfile); + MessageBox(0,buf,TranslateT("Miranda can't open that profile"),MB_OK | MB_ICONERROR); + } + break; + } + return (rc != 0); +} diff --git a/src/modules/database/dbini.cpp b/src/modules/database/dbini.cpp new file mode 100644 index 0000000000..01e6645593 --- /dev/null +++ b/src/modules/database/dbini.cpp @@ -0,0 +1,490 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "../srfile/file.h" + +static bool bModuleInitialized = false; +static HANDLE hIniChangeNotification; + +extern TCHAR mirandabootini[MAX_PATH]; +extern bool dbCreated; + +static INT_PTR CALLBACK InstallIniDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch(message) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetDlgItemText(hwndDlg, IDC_ININAME, (TCHAR*)lParam); + { + TCHAR szSecurity[11]; + const TCHAR *pszSecurityInfo; + + GetPrivateProfileString(_T("AutoExec"), _T("Warn"), _T("notsafe"), szSecurity, SIZEOF(szSecurity), mirandabootini); + if(!lstrcmpi(szSecurity, _T("all"))) + pszSecurityInfo = LPGENT("Security systems to prevent malicious changes are in place and you will be warned before every change that is made."); + else if (!lstrcmpi(szSecurity, _T("onlyunsafe"))) + pszSecurityInfo = LPGENT("Security systems to prevent malicious changes are in place and you will be warned before changes that are known to be unsafe."); + else if (!lstrcmpi(szSecurity, _T("none"))) + pszSecurityInfo = LPGENT("Security systems to prevent malicious changes have been disabled. You will receive no further warnings."); + else pszSecurityInfo = NULL; + if (pszSecurityInfo) SetDlgItemText(hwndDlg, IDC_SECURITYINFO, TranslateTS(pszSecurityInfo)); + } + return TRUE; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_VIEWINI: + { TCHAR szPath[MAX_PATH]; + GetDlgItemText(hwndDlg, IDC_ININAME, szPath, SIZEOF(szPath)); + ShellExecute(hwndDlg, _T("open"), szPath, NULL, NULL, SW_SHOW); + break; + } + case IDOK: + case IDCANCEL: + case IDC_NOTOALL: + EndDialog(hwndDlg,LOWORD(wParam)); + break; + } + break; + } + return FALSE; +} + +static bool IsInSpaceSeparatedList(const char *szWord,const char *szList) +{ + const char *szItem,*szEnd; + int wordLen = lstrlenA(szWord); + + for(szItem = szList;;) { + szEnd = strchr(szItem,' '); + if (szEnd == NULL) + return !lstrcmpA( szItem, szWord ); + if ( szEnd - szItem == wordLen ) { + if ( !strncmp( szItem, szWord, wordLen )) + return true; + } + szItem = szEnd+1; +} } + +struct warnSettingChangeInfo_t { + TCHAR *szIniPath; + char *szSection; + char *szSafeSections; + char *szUnsafeSections; + char *szName; + char *szValue; + int warnNoMore,cancel; +}; + +static INT_PTR CALLBACK WarnIniChangeDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + static struct warnSettingChangeInfo_t *warnInfo; + + switch(message) { + case WM_INITDIALOG: + { char szSettingName[256]; + const TCHAR *pszSecurityInfo; + warnInfo = (warnSettingChangeInfo_t*)lParam; + TranslateDialogDefault(hwndDlg); + SetDlgItemText(hwndDlg, IDC_ININAME, warnInfo->szIniPath); + lstrcpyA(szSettingName, warnInfo->szSection); + lstrcatA(szSettingName," / "); + lstrcatA(szSettingName,warnInfo->szName); + SetDlgItemTextA(hwndDlg,IDC_SETTINGNAME,szSettingName); + SetDlgItemTextA(hwndDlg,IDC_NEWVALUE,warnInfo->szValue); + if(IsInSpaceSeparatedList(warnInfo->szSection,warnInfo->szSafeSections)) + pszSecurityInfo=LPGENT("This change is known to be safe."); + else if(IsInSpaceSeparatedList(warnInfo->szSection,warnInfo->szUnsafeSections)) + pszSecurityInfo=LPGENT("This change is known to be potentially hazardous."); + else + pszSecurityInfo=LPGENT("This change is not known to be safe."); + SetDlgItemText(hwndDlg,IDC_SECURITYINFO,TranslateTS(pszSecurityInfo)); + return TRUE; + } + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + warnInfo->cancel=1; + case IDYES: + case IDNO: + warnInfo->warnNoMore=IsDlgButtonChecked(hwndDlg,IDC_WARNNOMORE); + EndDialog(hwndDlg,LOWORD(wParam)); + break; + } + break; + } + return FALSE; +} + +static INT_PTR CALLBACK IniImportDoneDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch(message) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetDlgItemText(hwndDlg,IDC_ININAME,(TCHAR*)lParam); + SetDlgItemText(hwndDlg,IDC_NEWNAME,(TCHAR*)lParam); + return TRUE; + case WM_COMMAND: + { TCHAR szIniPath[MAX_PATH]; + GetDlgItemText(hwndDlg,IDC_ININAME,szIniPath,SIZEOF(szIniPath)); + switch(LOWORD(wParam)) { + case IDC_DELETE: + DeleteFile(szIniPath); + case IDC_LEAVE: + EndDialog(hwndDlg,LOWORD(wParam)); + break; + case IDC_RECYCLE: + { SHFILEOPSTRUCT shfo={0}; + shfo.wFunc=FO_DELETE; + shfo.pFrom=szIniPath; + szIniPath[lstrlen(szIniPath)+1]='\0'; + shfo.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO; + SHFileOperation(&shfo); + } + EndDialog(hwndDlg,LOWORD(wParam)); + break; + case IDC_MOVE: + { TCHAR szNewPath[MAX_PATH]; + GetDlgItemText(hwndDlg,IDC_NEWNAME,szNewPath,SIZEOF(szNewPath)); + MoveFile(szIniPath,szNewPath); + } + EndDialog(hwndDlg,LOWORD(wParam)); + break; + } + break; + } + } + return FALSE; +} + +// settings: +struct SettingsList +{ + char *name; + SettingsList *next; +} *setting_items = NULL; + +int SettingsEnumProc(const char *szSetting, LPARAM lParam) +{ + SettingsList *newItem = (SettingsList *)mir_alloc(sizeof(SettingsList)); + newItem->name = mir_strdup(szSetting); + newItem->next = setting_items; + setting_items = newItem; + return 0; +} + +void ConvertBackslashes(char *, UINT); +static void ProcessIniFile(TCHAR* szIniPath, char *szSafeSections, char *szUnsafeSections, int secur, bool secFN) +{ + FILE *fp = _tfopen(szIniPath, _T("rt")); + if ( fp == NULL ) + return; + + bool warnThisSection = false; + char szSection[128]; szSection[0] = 0; + + while(!feof(fp)) { + char szLine[2048]; + if (fgets(szLine,sizeof(szLine),fp) == NULL) + break; + + int lineLength = lstrlenA(szLine); + while (lineLength && (BYTE)(szLine[lineLength-1])<=' ') + szLine[--lineLength]='\0'; + + if (szLine[0]==';' || szLine[0]<=' ') + continue; + + if (szLine[0]=='[') { + char *szEnd = strchr(szLine+1,']'); + if (szEnd == NULL) + continue; + + if (szLine[1] == '!') + szSection[0] = '\0'; + else { + lstrcpynA(szSection,szLine+1,min(sizeof(szSection),(int)(szEnd-szLine))); + switch (secur) { + case 0: + warnThisSection = false; + break; + + case 1: + warnThisSection = !IsInSpaceSeparatedList(szSection, szSafeSections); + break; + + case 2: + warnThisSection = IsInSpaceSeparatedList(szSection, szUnsafeSections); + break; + + default: + warnThisSection = true; + break; + } + if (secFN) warnThisSection=0; + } + if (szLine[1] == '?') { + DBCONTACTENUMSETTINGS dbces; + dbces.pfnEnumProc=SettingsEnumProc; + lstrcpynA(szSection,szLine+2,min(sizeof(szSection),(int)(szEnd-szLine-1))); + dbces.szModule=szSection; + dbces.ofsSettings=0; + CallService(MS_DB_CONTACT_ENUMSETTINGS,0,(LPARAM)&dbces); + while (setting_items) { + SettingsList *next = setting_items->next; + + DBCONTACTGETSETTING dbcgs; + dbcgs.szModule = szSection; + dbcgs.szSetting = setting_items->name; + CallService(MS_DB_CONTACT_DELETESETTING, 0, (LPARAM)&dbcgs); + + mir_free(setting_items->name); + mir_free(setting_items); + setting_items = next; + } + } + continue; + } + + if(szSection[0]=='\0') + continue; + + char *szValue=strchr(szLine,'='); + if ( szValue == NULL ) + continue; + + char szName[128]; + lstrcpynA(szName,szLine,min(sizeof(szName),(int)(szValue-szLine+1))); + szValue++; + { + warnSettingChangeInfo_t warnInfo; + warnInfo.szIniPath=szIniPath; + warnInfo.szName=szName; + warnInfo.szSafeSections=szSafeSections; + warnInfo.szSection=szSection; + warnInfo.szUnsafeSections=szUnsafeSections; + warnInfo.szValue=szValue; + warnInfo.warnNoMore=0; + warnInfo.cancel=0; + if(warnThisSection && IDNO==DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(IDD_WARNINICHANGE),NULL,WarnIniChangeDlgProc,(LPARAM)&warnInfo)) + continue; + if(warnInfo.cancel) + break; + if(warnInfo.warnNoMore) + warnThisSection=0; + } + + switch(szValue[0]) { + case 'b': + case 'B': + DBWriteContactSettingByte(NULL,szSection,szName,(BYTE)strtol(szValue+1,NULL,0)); + break; + case 'w': + case 'W': + DBWriteContactSettingWord(NULL,szSection,szName,(WORD)strtol(szValue+1,NULL,0)); + break; + case 'd': + case 'D': + DBWriteContactSettingDword(NULL,szSection,szName,(DWORD)strtoul(szValue+1,NULL,0)); + break; + case 'l': + case 'L': + DBDeleteContactSetting(NULL,szSection,szName); + break; + case 'e': + case 'E': + ConvertBackslashes(szValue+1, LangPackGetDefaultCodePage()); + case 's': + case 'S': + DBWriteContactSettingString(NULL,szSection,szName,szValue+1); + break; + case 'g': + case 'G': + { char *pstr; + for(pstr=szValue+1;*pstr;pstr++){ + if(*pstr=='\\'){ + switch(pstr[1]){ + case 'n': *pstr='\n'; break; + case 't': *pstr='\t'; break; + case 'r': *pstr='\r'; break; + default: *pstr=pstr[1]; break; + } + MoveMemory(pstr+1,pstr+2,lstrlenA(pstr+2)+1); + } } } + case 'u': + case 'U': + DBWriteContactSettingStringUtf(NULL,szSection,szName,szValue+1); + break; + case 'n': + case 'h': + case 'N': + case 'H': + { PBYTE buf; + int len; + char *pszValue,*pszEnd; + DBCONTACTWRITESETTING cws; + + buf=(PBYTE)mir_alloc(lstrlenA(szValue+1)); + for(len=0,pszValue=szValue+1;;len++) { + buf[len]=(BYTE)strtol(pszValue,&pszEnd,0x10); + if(pszValue==pszEnd) break; + pszValue=pszEnd; + } + cws.szModule=szSection; + cws.szSetting=szName; + cws.value.type=DBVT_BLOB; + cws.value.pbVal=buf; + cws.value.cpbVal=len; + CallService(MS_DB_CONTACT_WRITESETTING,(WPARAM)(HANDLE)NULL,(LPARAM)&cws); + mir_free(buf); + } + break; + default: + MessageBox(NULL,TranslateT("Invalid setting type. The first character of every value must be b, w, d, l, s, e, u, g, h or n."),TranslateT("Install Database Settings"),MB_OK); + break; + } + } + fclose(fp); +} + +static void DoAutoExec(void) +{ + TCHAR szUse[7], szIniPath[MAX_PATH], szFindPath[MAX_PATH]; + TCHAR *str2; + TCHAR buf[2048], szSecurity[11], szOverrideSecurityFilename[MAX_PATH], szOnCreateFilename[MAX_PATH]; + char *szSafeSections, *szUnsafeSections; + int secur; + + GetPrivateProfileString(_T("AutoExec"),_T("Use"),_T("prompt"),szUse,SIZEOF(szUse),mirandabootini); + if(!lstrcmpi(szUse,_T("no"))) return; + GetPrivateProfileString(_T("AutoExec"),_T("Safe"),_T("CLC Icons CLUI CList SkinSounds"),buf,SIZEOF(buf),mirandabootini); + szSafeSections = mir_t2a(buf); + GetPrivateProfileString(_T("AutoExec"),_T("Unsafe"),_T("ICQ MSN"),buf,SIZEOF(buf),mirandabootini); + szUnsafeSections = mir_t2a(buf); + GetPrivateProfileString(_T("AutoExec"),_T("Warn"),_T("notsafe"),szSecurity,SIZEOF(szSecurity),mirandabootini); + if (!lstrcmpi(szSecurity,_T("none"))) secur = 0; + else if (!lstrcmpi(szSecurity,_T("notsafe"))) secur = 1; + else if (!lstrcmpi(szSecurity,_T("onlyunsafe"))) secur = 2; + + GetPrivateProfileString(_T("AutoExec"),_T("OverrideSecurityFilename"),_T(""),szOverrideSecurityFilename,SIZEOF(szOverrideSecurityFilename),mirandabootini); + GetPrivateProfileString(_T("AutoExec"),_T("OnCreateFilename"),_T(""),szOnCreateFilename,SIZEOF(szOnCreateFilename),mirandabootini); + GetPrivateProfileString(_T("AutoExec"),_T("Glob"),_T("autoexec_*.ini"),szFindPath,SIZEOF(szFindPath),mirandabootini); + + if (dbCreated && szOnCreateFilename[0]) { + str2 = Utils_ReplaceVarsT(szOnCreateFilename); + pathToAbsoluteT(str2, szIniPath, NULL); + mir_free(str2); + + ProcessIniFile(szIniPath, szSafeSections, szUnsafeSections, 0, 1); + } + + str2 = Utils_ReplaceVarsT(szFindPath); + pathToAbsoluteT(str2, szFindPath, NULL); + mir_free(str2); + + WIN32_FIND_DATA fd; + HANDLE hFind = FindFirstFile(szFindPath, &fd); + if (hFind == INVALID_HANDLE_VALUE) { + mir_free(szSafeSections); + mir_free(szUnsafeSections); + return; + } + + str2 = _tcsrchr(szFindPath, '\\'); + if (str2 == NULL) szFindPath[0] = 0; + else str2[1] = 0; + + do { + bool secFN = lstrcmpi(fd.cFileName,szOverrideSecurityFilename) == 0; + + mir_sntprintf(szIniPath, SIZEOF(szIniPath), _T("%s%s"), szFindPath, fd.cFileName); + if(!lstrcmpi(szUse,_T("prompt")) && !secFN) { + int result=DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(IDD_INSTALLINI),NULL,InstallIniDlgProc,(LPARAM)szIniPath); + if(result==IDC_NOTOALL) break; + if(result==IDCANCEL) continue; + } + + ProcessIniFile(szIniPath, szSafeSections, szUnsafeSections, secur, secFN); + + if(secFN) + DeleteFile(szIniPath); + else { + TCHAR szOnCompletion[8]; + GetPrivateProfileString(_T("AutoExec"),_T("OnCompletion"),_T("recycle"),szOnCompletion,SIZEOF(szOnCompletion),mirandabootini); + if(!lstrcmpi(szOnCompletion,_T("delete"))) + DeleteFile(szIniPath); + else if(!lstrcmpi(szOnCompletion,_T("recycle"))) { + SHFILEOPSTRUCT shfo={0}; + shfo.wFunc=FO_DELETE; + shfo.pFrom=szIniPath; + szIniPath[lstrlen(szIniPath)+1]=0; + shfo.fFlags=FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO; + SHFileOperation(&shfo); + } + else if(!lstrcmpi(szOnCompletion,_T("rename"))) { + TCHAR szRenamePrefix[MAX_PATH]; + TCHAR szNewPath[MAX_PATH]; + GetPrivateProfileString(_T("AutoExec"),_T("RenamePrefix"),_T("done_"),szRenamePrefix,SIZEOF(szRenamePrefix),mirandabootini); + lstrcpy(szNewPath,szFindPath); + lstrcat(szNewPath,szRenamePrefix); + lstrcat(szNewPath,fd.cFileName); + MoveFile(szIniPath,szNewPath); + } + else if(!lstrcmpi(szOnCompletion,_T("ask"))) + DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(IDD_INIIMPORTDONE),NULL,IniImportDoneDlgProc,(LPARAM)szIniPath); + } + } while (FindNextFile(hFind, &fd)); + FindClose(hFind); + mir_free(szSafeSections); + mir_free(szUnsafeSections); +} + +static INT_PTR CheckIniImportNow(WPARAM, LPARAM) +{ + DoAutoExec(); + FindNextChangeNotification(hIniChangeNotification); + return 0; +} + +int InitIni(void) +{ + TCHAR szMirandaDir[MAX_PATH]; + + bModuleInitialized = true; + + DoAutoExec(); + pathToAbsoluteT(_T("."), szMirandaDir, NULL); + hIniChangeNotification=FindFirstChangeNotification(szMirandaDir, 0, FILE_NOTIFY_CHANGE_FILE_NAME); + if (hIniChangeNotification != INVALID_HANDLE_VALUE) { + CreateServiceFunction("DB/Ini/CheckImportNow", CheckIniImportNow); + CallService(MS_SYSTEM_WAITONHANDLE, (WPARAM)hIniChangeNotification, (LPARAM)"DB/Ini/CheckImportNow"); + } + return 0; +} + +void UninitIni(void) +{ + if ( !bModuleInitialized ) return; + CallService(MS_SYSTEM_REMOVEWAIT,(WPARAM)hIniChangeNotification,0); + FindCloseChangeNotification(hIniChangeNotification); +} diff --git a/src/modules/database/dblists.cpp b/src/modules/database/dblists.cpp new file mode 100644 index 0000000000..02332ab283 --- /dev/null +++ b/src/modules/database/dblists.cpp @@ -0,0 +1,282 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +/* a simple sorted list implementation */ + +SortedList* List_Create( int p_limit, int p_increment ) +{ + SortedList* result = ( SortedList* )mir_calloc( sizeof( SortedList )); + if ( result == NULL ) + return(NULL); + + result->increment = p_increment; + result->limit = p_limit; + return(result); +} + +void List_Destroy( SortedList* p_list ) +{ + if ( p_list == NULL ) + return; + + if ( p_list->items != NULL ) { + mir_free( p_list->items ); + p_list->items = NULL; + } + + p_list->realCount = p_list->limit = 0; +} + +void* List_Find( SortedList* p_list, void* p_value ) +{ + int index; + + if ( !List_GetIndex( p_list, p_value, &index )) + return(NULL); + + return(p_list->items[ index ]); +} + +#ifdef _DEBUG +#pragma optimize( "gt", on ) +#endif + +int List_GetIndex( SortedList* p_list, void* p_value, int* p_index ) +{ + if (p_value == NULL) + { + *p_index = -1; + return 0; + } + + switch ((INT_PTR)p_list->sortFunc) + { + case 0: + break; + + case HandleKeySort: +#ifdef _WIN64 + { + const unsigned __int64 val = *(unsigned __int64 *)p_value; + int low = 0; + int high = p_list->realCount - 1; + + while (low <= high) + { + int i = (low + high) / 2; + unsigned __int64 vali = *(unsigned __int64 *)p_list->items[i]; + if (vali == val) + { + *p_index = i; + return 1; + } + + if (vali < val) + low = i + 1; + else + high = i - 1; + } + + *p_index = low; + } + break; +#endif + + case NumericKeySort: + { + const unsigned val = *(unsigned *)p_value; + int low = 0; + int high = p_list->realCount - 1; + + while (low <= high) + { + int i = (low + high) / 2; + unsigned vali = *(unsigned *)p_list->items[i]; + if (vali == val) + { + *p_index = i; + return 1; + } + + if (vali < val) + low = i + 1; + else + high = i - 1; + } + + *p_index = low; + } + break; + + case PtrKeySort: + { + int low = 0; + int high = p_list->realCount - 1; + + while (low <= high) + { + int i = (low + high) / 2; + const void* vali = p_list->items[i]; + if (vali == p_value) + { + *p_index = i; + return 1; + } + + if (vali < p_value) + low = i + 1; + else + high = i - 1; + } + + *p_index = low; + } + break; + + default: + { + int low = 0; + int high = p_list->realCount - 1; + + while (low <= high) + { + int i = (low + high) / 2; + int result = p_list->sortFunc(p_list->items[i], p_value); + if (result == 0) + { + *p_index = i; + return 1; + } + + if (result < 0) + low = i + 1; + else + high = i - 1; + } + + *p_index = low; + } + break; + } + + return 0; +} + +int List_IndexOf( SortedList* p_list, void* p_value ) +{ + if ( p_value == NULL ) + return -1; + + int i; + for ( i=0; i < p_list->realCount; i++ ) + if ( p_list->items[i] == p_value ) + return i; + + return -1; +} + +#ifdef _DEBUG +#pragma optimize( "", on ) +#endif + +int List_Insert( SortedList* p_list, void* p_value, int p_index) +{ + if ( p_value == NULL || p_index > p_list->realCount ) + return 0; + + if ( p_list->realCount == p_list->limit ) + { + p_list->items = ( void** )mir_realloc( p_list->items, sizeof( void* )*(p_list->realCount + p_list->increment)); + p_list->limit += p_list->increment; + } + + if ( p_index < p_list->realCount ) + memmove( p_list->items+p_index+1, p_list->items+p_index, sizeof( void* )*( p_list->realCount-p_index )); + + p_list->realCount++; + + p_list->items[ p_index ] = p_value; + return 1; +} + +int List_InsertPtr( SortedList* list, void* p ) +{ + if ( p == NULL ) + return -1; + + int idx = list->realCount; + List_GetIndex( list, p, &idx ); + return List_Insert( list, p, idx ); +} + +int List_Remove( SortedList* p_list, int index ) +{ + if ( index < 0 || index > p_list->realCount ) + return(0); + + p_list->realCount--; + if ( p_list->realCount > index ) + { + memmove( p_list->items+index, p_list->items+index+1, sizeof( void* )*( p_list->realCount-index )); + p_list->items[ p_list->realCount ] = NULL; + } + + return 1; +} + +int List_RemovePtr( SortedList* list, void* p ) +{ + int idx = -1; + if ( List_GetIndex( list, p, &idx )) + List_Remove( list, idx ); + + return idx; +} + +void List_Copy( SortedList* s, SortedList* d, size_t itemSize ) +{ + int i; + + d->increment = s->increment; + d->sortFunc = s->sortFunc; + + for ( i = 0; i < s->realCount; i++ ) { + void* item = mir_alloc( itemSize ); + memcpy( item, s->items[i], itemSize ); + List_Insert( d, item, i ); +} } + +void List_ObjCopy( SortedList* s, SortedList* d, size_t itemSize ) +{ + int i; + + d->increment = s->increment; + d->sortFunc = s->sortFunc; + + for ( i = 0; i < s->realCount; i++ ) { + void* item = new char[ itemSize ]; + memcpy( item, s->items[i], itemSize ); + List_Insert( d, item, i ); +} } diff --git a/src/modules/database/dblists.h b/src/modules/database/dblists.h new file mode 100644 index 0000000000..30886973a4 --- /dev/null +++ b/src/modules/database/dblists.h @@ -0,0 +1,39 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +/* a simple sorted list implementation */ + +SortedList* List_Create( int, int ); +void List_Destroy( SortedList* ); + +void* List_Find( SortedList*, void* ); +int List_GetIndex( SortedList*, void*, int* ); +int List_Insert( SortedList*, void*, int ); +int List_Remove( SortedList*, int ); +int List_IndexOf( SortedList*, void* ); + +int List_InsertPtr( SortedList* list, void* p ); +int List_RemovePtr( SortedList* list, void* p ); + +void List_Copy( SortedList*, SortedList*, size_t ); +void List_ObjCopy( SortedList*, SortedList*, size_t ); \ No newline at end of file diff --git a/src/modules/database/dbutils.cpp b/src/modules/database/dbutils.cpp new file mode 100644 index 0000000000..1e73ddd18e --- /dev/null +++ b/src/modules/database/dbutils.cpp @@ -0,0 +1,365 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "profilemanager.h" + +static int CompareEventTypes( const DBEVENTTYPEDESCR* p1, const DBEVENTTYPEDESCR* p2 ) +{ + int result = strcmp( p1->module, p2->module ); + if ( result ) + return result; + + return p1->eventType - p2->eventType; +} + +static LIST eventTypes( 10, CompareEventTypes ); + +static BOOL bModuleInitialized = FALSE; + +static INT_PTR DbEventTypeRegister(WPARAM, LPARAM lParam) +{ + DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )lParam; + if ( eventTypes.getIndex( et ) == -1 ) { + DBEVENTTYPEDESCR* p = ( DBEVENTTYPEDESCR* )mir_alloc( sizeof( DBEVENTTYPEDESCR )); + p->cbSize = DBEVENTTYPEDESCR_SIZE; + p->module = mir_strdup( et->module ); + p->eventType = et->eventType; + p->descr = mir_strdup( et->descr ); + p->textService = NULL; + p->iconService = NULL; + p->eventIcon = NULL; + p->flags = 0; + if ( et->cbSize == DBEVENTTYPEDESCR_SIZE ) { + if ( et->textService ) + p->textService = mir_strdup( et->textService ); + if ( et->iconService ) + p->iconService = mir_strdup( et->iconService ); + p->eventIcon = et->eventIcon; + p->flags = et->flags; + } + if ( !p->textService ) { + char szServiceName[100]; + mir_snprintf( szServiceName, sizeof(szServiceName), "%s/GetEventText%d", p->module, p->eventType ); + p->textService = mir_strdup( szServiceName ); + } + if ( !p->iconService ) { + char szServiceName[100]; + mir_snprintf( szServiceName, sizeof(szServiceName), "%s/GetEventIcon%d", p->module, p->eventType ); + p->iconService = mir_strdup( szServiceName ); + } + eventTypes.insert( p ); + } + + return 0; +} + +static INT_PTR DbEventTypeGet(WPARAM wParam, LPARAM lParam) +{ + DBEVENTTYPEDESCR tmp; + int idx; + + tmp.module = ( char* )wParam; + tmp.eventType = lParam; + if ( !List_GetIndex(( SortedList* )&eventTypes, &tmp, &idx )) + return 0; + + return ( INT_PTR )eventTypes[idx]; +} + +static INT_PTR DbEventGetText(WPARAM wParam, LPARAM lParam) +{ + DBEVENTGETTEXT* egt = (DBEVENTGETTEXT*)lParam; + BOOL bIsDenyUnicode = (egt->datatype & DBVTF_DENYUNICODE); + + DBEVENTINFO* dbei = egt->dbei; + DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )DbEventTypeGet( ( WPARAM )dbei->szModule, ( LPARAM )dbei->eventType ); + + if ( et && ServiceExists( et->textService )) + return CallService( et->textService, wParam, lParam ); + + if ( !dbei->pBlob ) return 0; + + if ( dbei->eventType == EVENTTYPE_FILE ) { + char* filename = ((char *)dbei->pBlob) + sizeof(DWORD); + char* descr = filename + lstrlenA( filename ) + 1; + char* str = (*descr == 0) ? filename : descr; + switch ( egt->datatype ) { + case DBVT_WCHAR: + return ( INT_PTR )(( dbei->flags & DBEF_UTF ) ? + Utf8DecodeT( str ) : mir_a2t( str )); + case DBVT_ASCIIZ: + return ( INT_PTR )(( dbei->flags & DBEF_UTF ) ? Utf8Decode( mir_strdup( str ), NULL ) : mir_strdup( str )); + } + return 0; + } + + // temporary fix for bug with event types conflict between jabber chat states notifications + // and srmm's status changes, must be commented out in future releases + if ( dbei->eventType == 25368 && dbei->cbBlob == 1 && dbei->pBlob[0] == 1 ) + return 0; + + egt->datatype &= ~DBVTF_DENYUNICODE; + if ( egt->datatype == DBVT_WCHAR ) + { + WCHAR* msg = NULL; + if ( dbei->flags & DBEF_UTF ) { + char* str = (char*)alloca(dbei->cbBlob + 1); + if (str == NULL) return NULL; + memcpy(str, dbei->pBlob, dbei->cbBlob); + str[dbei->cbBlob] = 0; + Utf8DecodeCP( str, egt->codepage, &msg ); + } + else { + size_t msglen = strlen(( char* )dbei->pBlob) + 1, msglenW = 0; + if ( msglen != dbei->cbBlob ) { + size_t i, count = (( dbei->cbBlob - msglen ) / sizeof( WCHAR )); + WCHAR* p = ( WCHAR* )&dbei->pBlob[ msglen ]; + for ( i=0; i < count; i++ ) { + if ( p[i] == 0 ) { + msglenW = i; + break; + } } } + + if ( msglenW > 0 && msglenW < msglen && !bIsDenyUnicode ) + msg = mir_wstrdup(( WCHAR* )&dbei->pBlob[ msglen ] ); + else { + msg = ( WCHAR* )mir_alloc( sizeof(WCHAR) * msglen ); + MultiByteToWideChar( egt->codepage, 0, (char *) dbei->pBlob, -1, msg, (int)msglen ); + } } + return ( INT_PTR )msg; + } + else if ( egt->datatype == DBVT_ASCIIZ ) { + char* msg = mir_strdup(( char* )dbei->pBlob ); + if (dbei->flags & DBEF_UTF) + Utf8DecodeCP( msg, egt->codepage, NULL ); + + return ( INT_PTR )msg; + } + return 0; +} + +static INT_PTR DbEventGetIcon( WPARAM wParam, LPARAM lParam ) +{ + DBEVENTINFO* dbei = ( DBEVENTINFO* )lParam; + HICON icon = NULL; + DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )DbEventTypeGet( ( WPARAM )dbei->szModule, ( LPARAM )dbei->eventType ); + + if ( et && ServiceExists( et->iconService )) { + icon = ( HICON )CallService( et->iconService, wParam, lParam ); + if ( icon ) + return ( INT_PTR )icon; + } + if ( et && et->eventIcon ) + icon = ( HICON )CallService( MS_SKIN2_GETICONBYHANDLE, 0, ( LPARAM )et->eventIcon ); + if ( !icon ) { + char szName[100]; + mir_snprintf( szName, sizeof( szName ), "eventicon_%s%d", dbei->szModule, dbei->eventType ); + icon = ( HICON )CallService( MS_SKIN2_GETICON, 0, ( LPARAM )szName ); + } + + if ( !icon ) + { + switch( dbei->eventType ) { + case EVENTTYPE_URL: + icon = LoadSkinIcon( SKINICON_EVENT_URL ); + break; + + case EVENTTYPE_FILE: + icon = LoadSkinIcon( SKINICON_EVENT_FILE ); + break; + + default: // EVENTTYPE_MESSAGE and unknown types + icon = LoadSkinIcon( SKINICON_EVENT_MESSAGE ); + break; + } + } + + if ( wParam & LR_SHARED ) + return ( INT_PTR )icon; + else + return ( INT_PTR )CopyIcon( icon ); +} + +static INT_PTR DbEventGetStringT( WPARAM wParam, LPARAM lParam ) +{ + DBEVENTINFO* dbei = ( DBEVENTINFO* )wParam; + char* string = ( char* )lParam; + + #if defined( _UNICODE ) + if ( dbei->flags & DBEF_UTF ) + return ( INT_PTR )Utf8DecodeUcs2( string ); + + return ( INT_PTR )mir_a2t( string ); + #else + char* res = mir_strdup( string ); + if ( dbei->flags & DBEF_UTF ) + Utf8Decode( res, NULL ); + return ( INT_PTR )res; + #endif +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int sttEnumVars( const char* szVarName, LPARAM lParam ) +{ + LIST* vars = ( LIST* )lParam; + vars->insert( mir_strdup( szVarName )); + return 0; +} + +static INT_PTR DbDeleteModule( WPARAM, LPARAM lParam ) +{ + LIST vars( 20 ); + + DBCONTACTENUMSETTINGS dbces = { 0 }; + dbces.pfnEnumProc = sttEnumVars; + dbces.lParam = ( LPARAM )&vars; + dbces.szModule = ( char* )lParam; + CallService( MS_DB_CONTACT_ENUMSETTINGS, NULL, (LPARAM)&dbces ); + + for ( int i=vars.getCount()-1; i >= 0; i-- ) { + DBDeleteContactSetting( NULL, ( char* )lParam, vars[i] ); + mir_free( vars[i] ); + } + vars.destroy(); + return 0; +} + +static INT_PTR GetProfilePath(WPARAM wParam, LPARAM lParam) +{ + if (!wParam || !lParam) + return 1; + + char* dst = (char*)lParam; + + #if defined( _UNICODE ) + char* tmp = mir_t2a( g_profileDir ); + strncpy( dst, tmp, wParam ); + mir_free( tmp ); + #else + strncpy( dst, g_profileDir, wParam ); + #endif + + if (wParam <= _tcslen(g_profileName)) + { + dst[wParam - 1] = 0; + return 1; + } + return 0; +} + +static INT_PTR GetProfileName(WPARAM wParam, LPARAM lParam) +{ + if (!wParam || !lParam) + return 1; + + char* dst = (char*)lParam; + + #if defined( _UNICODE ) + char* tmp = makeFileName( g_profileName ); + strncpy( dst, tmp, wParam ); + mir_free( tmp ); + #else + strncpy( dst, g_profileName, wParam ); + #endif + + if (wParam <= _tcslen(g_profileName)) + { + dst[wParam - 1] = 0; + return 1; + } + return 0; +} + +#if defined( _UNICODE ) + +static INT_PTR GetProfilePathW(WPARAM wParam, LPARAM lParam) +{ + if (!wParam || !lParam) + return 1; + + wchar_t* dst = (wchar_t*)lParam; + wcsncpy(dst, g_profileDir, wParam); + if (wParam <= wcslen(g_profileDir)) + { + dst[wParam - 1] = 0; + return 1; + } + return 0; +} + +static INT_PTR GetProfileNameW(WPARAM wParam, LPARAM lParam) +{ + wchar_t* dst = (wchar_t*)lParam; + wcsncpy(dst, g_profileName, wParam ); + if (wParam <= wcslen(g_profileName)) + { + dst[wParam - 1] = 0; + return 1; + } + return 0; +} + +#endif + +///////////////////////////////////////////////////////////////////////////////////////// + +int InitUtils() +{ + bModuleInitialized = TRUE; + + CreateServiceFunction(MS_DB_EVENT_REGISTERTYPE, DbEventTypeRegister); + CreateServiceFunction(MS_DB_EVENT_GETTYPE, DbEventTypeGet); + CreateServiceFunction(MS_DB_EVENT_GETTEXT, DbEventGetText); + CreateServiceFunction(MS_DB_EVENT_GETICON, DbEventGetIcon); + CreateServiceFunction(MS_DB_EVENT_GETSTRINGT, DbEventGetStringT); + + CreateServiceFunction(MS_DB_MODULE_DELETE, DbDeleteModule); + + CreateServiceFunction(MS_DB_GETPROFILEPATH,GetProfilePath); + CreateServiceFunction(MS_DB_GETPROFILENAME,GetProfileName); + #if defined( _UNICODE ) + CreateServiceFunction(MS_DB_GETPROFILEPATHW,GetProfilePathW); + CreateServiceFunction(MS_DB_GETPROFILENAMEW,GetProfileNameW); + #endif + return 0; +} + +void UnloadEventsModule() +{ + int i; + + if ( !bModuleInitialized ) return; + + for ( i=0; i < eventTypes.getCount(); i++ ) { + DBEVENTTYPEDESCR* p = eventTypes[i]; + mir_free( p->module ); + mir_free( p->descr ); + mir_free( p->textService ); + mir_free( p->iconService ); + mir_free( p ); + } + + eventTypes.destroy(); +} diff --git a/src/modules/database/profilemanager.cpp b/src/modules/database/profilemanager.cpp new file mode 100644 index 0000000000..06cd356d7c --- /dev/null +++ b/src/modules/database/profilemanager.cpp @@ -0,0 +1,850 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "profilemanager.h" +#include + +#define WM_INPUTCHANGED (WM_USER + 0x3000) +#define WM_FOCUSTEXTBOX (WM_USER + 0x3001) + +typedef BOOL (__cdecl *ENUMPROFILECALLBACK) (TCHAR * fullpath, TCHAR * profile, LPARAM lParam); + +struct DetailsPageInit { + int pageCount; + OPTIONSDIALOGPAGE *odp; +}; + +struct DetailsPageData { + DLGTEMPLATE *pTemplate; + HINSTANCE hInst; + DLGPROC dlgProc; + HWND hwnd; + int changed; +}; + +struct DlgProfData { + PROPSHEETHEADER * psh; + HWND hwndOK; // handle to OK button + PROFILEMANAGERDATA * pd; + HANDLE hFileNotify; +}; + +struct DetailsData { + HINSTANCE hInstIcmp; + HFONT hBoldFont; + int pageCount; + int currentPage; + struct DetailsPageData *opd; + RECT rcDisplay; + struct DlgProfData * prof; +}; + +struct ProfileEnumData { + HWND hwnd; + TCHAR* szProfile; +}; + +extern TCHAR mirandabootini[MAX_PATH]; + +char **GetSeviceModePluginsList(void); +void SetServiceModePlugin( int idx ); + +static void ThemeDialogBackground(HWND hwnd) +{ + if (enableThemeDialogTexture) + enableThemeDialogTexture(hwnd, ETDT_ENABLETAB); +} + +static int findProfiles(TCHAR * szProfileDir, ENUMPROFILECALLBACK callback, LPARAM lParam) +{ + // find in Miranda IM profile subfolders + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd; + TCHAR searchspec[MAX_PATH]; + mir_sntprintf(searchspec, SIZEOF(searchspec), _T("%s\\*.*"), szProfileDir); + hFind = FindFirstFile(searchspec, &ffd); + if ( hFind == INVALID_HANDLE_VALUE ) + return 0; + + do { + // find all subfolders except "." and ".." + if ( (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && _tcscmp(ffd.cFileName, _T(".")) && _tcscmp(ffd.cFileName, _T("..")) ) { + TCHAR buf[MAX_PATH], profile[MAX_PATH]; + mir_sntprintf(buf, SIZEOF(buf), _T("%s\\%s\\%s.dat"), szProfileDir, ffd.cFileName, ffd.cFileName); + if (_taccess(buf, 0) == 0) { + mir_sntprintf(profile, SIZEOF(profile), _T("%s.dat"), ffd.cFileName); + if ( !callback(buf, profile, lParam )) + break; + } + } + } + while ( FindNextFile(hFind, &ffd) ); + FindClose(hFind); + + return 1; +} + +static LRESULT CALLBACK ProfileNameValidate(HWND edit, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if ( msg == WM_CHAR ) { + if ( _tcschr( _T(".?/\\#' "), (TCHAR)wParam) != 0 ) + return 0; + PostMessage(GetParent(edit),WM_INPUTCHANGED,0,0); + } + return CallWindowProc((WNDPROC)GetWindowLongPtr(edit,GWLP_USERDATA),edit,msg,wParam,lParam); +} + +static int FindDbProviders(const char*, DATABASELINK * dblink, LPARAM lParam) +{ + HWND hwndDlg = (HWND)lParam; + HWND hwndCombo = GetDlgItem(hwndDlg, IDC_PROFILEDRIVERS); + char szName[64]; + + if ( dblink->getFriendlyName(szName,SIZEOF(szName),1) == 0 ) { + // add to combo box + TCHAR* p = LangPackPcharToTchar( szName ); + LRESULT index = SendMessage( hwndCombo, CB_ADDSTRING, 0, (LPARAM)p ); + mir_free( p ); + SendMessage(hwndCombo, CB_SETITEMDATA, index, (LPARAM)dblink); + } + return DBPE_CONT; +} + +static INT_PTR CALLBACK DlgProfileNew(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct DlgProfData * dat = (struct DlgProfData *)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault( hwndDlg ); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + dat = (struct DlgProfData *)lParam; + { + // fill in the db plugins present + PLUGIN_DB_ENUM dbe; + dbe.cbSize = sizeof(dbe); + dbe.pfnEnumCallback = (int(*)(const char*,void*,LPARAM))FindDbProviders; + dbe.lParam = (LPARAM)hwndDlg; + if ( CallService( MS_PLUGINS_ENUMDBPLUGINS, 0, ( LPARAM )&dbe ) == -1 ) { + // no plugins?! + EnableWindow( GetDlgItem(hwndDlg, IDC_PROFILEDRIVERS ), FALSE ); + EnableWindow( GetDlgItem(hwndDlg, IDC_PROFILENAME ), FALSE ); + ShowWindow( GetDlgItem(hwndDlg, IDC_NODBDRIVERS ), TRUE ); + } + // default item + SendDlgItemMessage(hwndDlg, IDC_PROFILEDRIVERS, CB_SETCURSEL, 0, 0); + } + // subclass the profile name box + { + HWND hwndProfile = GetDlgItem(hwndDlg, IDC_PROFILENAME); + WNDPROC proc = (WNDPROC)GetWindowLongPtr(hwndProfile, GWLP_WNDPROC); + SetWindowLongPtr(hwndProfile,GWLP_USERDATA,(LONG_PTR)proc); + SetWindowLongPtr(hwndProfile,GWLP_WNDPROC,(LONG_PTR)ProfileNameValidate); + } + + // decide if there is a default profile name given in the INI and if it should be used + if (dat->pd->noProfiles || (shouldAutoCreate(dat->pd->szProfile) && _taccess(dat->pd->szProfile, 0))) + { + TCHAR* profile = _tcsrchr(dat->pd->szProfile, '\\'); + if (profile) ++profile; + else profile = dat->pd->szProfile; + + TCHAR *p = _tcsrchr(profile, '.'); + TCHAR c = 0; + if (p) { c = *p; *p = 0; } + + SetDlgItemText(hwndDlg, IDC_PROFILENAME, profile); + if (c) *p = c; + } + + // focus on the textbox + PostMessage( hwndDlg, WM_FOCUSTEXTBOX, 0, 0 ); + return TRUE; + + case WM_FOCUSTEXTBOX: + SetFocus( GetDlgItem( hwndDlg, IDC_PROFILENAME )); + break; + + case WM_INPUTCHANGED: // when input in the edit box changes + SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 ); + EnableWindow( dat->hwndOK, GetWindowTextLength( GetDlgItem( hwndDlg, IDC_PROFILENAME )) > 0 ); + break; + + case WM_SHOWWINDOW: + if ( wParam ) { + SetWindowText( dat->hwndOK, TranslateT("&Create")); + SendMessage( hwndDlg, WM_INPUTCHANGED, 0, 0 ); + } + break; + + case WM_NOTIFY: + { + NMHDR* hdr = ( NMHDR* )lParam; + if ( hdr && hdr->code == PSN_APPLY && dat && IsWindowVisible( hwndDlg )) { + TCHAR szName[MAX_PATH]; + LRESULT curSel = SendDlgItemMessage(hwndDlg,IDC_PROFILEDRIVERS,CB_GETCURSEL,0,0); + if ( curSel == CB_ERR ) break; // should never happen + GetDlgItemText(hwndDlg, IDC_PROFILENAME, szName, SIZEOF( szName )); + if ( szName[0] == 0 ) + break; + + // profile placed in "profile_name" subfolder + mir_sntprintf( dat->pd->szProfile, MAX_PATH, _T("%s\\%s\\%s.dat"), dat->pd->szProfileDir, szName, szName ); + dat->pd->newProfile = 1; + dat->pd->dblink = (DATABASELINK *)SendDlgItemMessage( hwndDlg, IDC_PROFILEDRIVERS, CB_GETITEMDATA, ( WPARAM )curSel, 0 ); + + if ( makeDatabase( dat->pd->szProfile, dat->pd->dblink, hwndDlg ) == 0 ) { + SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE ); + } } } + break; + } + + return FALSE; +} + +static int DetectDbProvider(const char*, DATABASELINK * dblink, LPARAM lParam) +{ + int error; + +#ifdef _UNICODE + char* fullpath = makeFileName(( TCHAR* )lParam ); +#else + char* fullpath = (char*)lParam; +#endif + + int ret = dblink->grokHeader(fullpath, &error); +#ifdef _UNICODE + mir_free( fullpath ); +#endif + if ( ret == 0) { +#ifdef _UNICODE + char tmp[ MAX_PATH ]; + dblink->getFriendlyName(tmp, SIZEOF(tmp), 1); + MultiByteToWideChar(CP_ACP, 0, tmp, -1, (TCHAR*)lParam, MAX_PATH); +#else + dblink->getFriendlyName((TCHAR*)lParam, MAX_PATH, 1); +#endif + return DBPE_HALT; + } + + return DBPE_CONT; +} + +BOOL EnumProfilesForList(TCHAR * fullpath, TCHAR * profile, LPARAM lParam) +{ + ProfileEnumData *ped = (ProfileEnumData*)lParam; + HWND hwndList = GetDlgItem(ped->hwnd, IDC_PROFILELIST); + + TCHAR sizeBuf[64]; + int iItem=0; + struct _stat statbuf; + bool bFileExists = false, bFileLocked = true; + + TCHAR* p = _tcsrchr(profile, '.'); + _tcscpy(sizeBuf, _T("0 KB")); + if ( p != NULL ) *p=0; + + LVITEM item = { 0 }; + item.mask = LVIF_TEXT | LVIF_IMAGE; + item.pszText = profile; + item.iItem = 0; + + if ( _tstat(fullpath, &statbuf) == 0) { + if ( statbuf.st_size > 1000000 ) { + mir_sntprintf(sizeBuf,SIZEOF(sizeBuf), _T("%.3lf"), (double)statbuf.st_size / 1048576.0 ); + _tcscpy(sizeBuf+5, _T(" MB")); + } + else { + mir_sntprintf(sizeBuf,SIZEOF(sizeBuf), _T("%.3lf"), (double)statbuf.st_size / 1024.0 ); + _tcscpy(sizeBuf+5, _T(" KB")); + } + bFileExists = TRUE; + + bFileLocked = !fileExist(fullpath); + } + + item.iImage = bFileLocked; + + iItem = SendMessage( hwndList, LVM_INSERTITEM, 0, (LPARAM)&item ); + if ( lstrcmpi(ped->szProfile, fullpath) == 0 ) + ListView_SetItemState(hwndList, iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + + item.iItem = iItem; + item.iSubItem = 2; + item.pszText = sizeBuf; + SendMessage( hwndList, LVM_SETITEMTEXT, iItem, (LPARAM)&item ); + + if ( bFileExists ) { + PLUGIN_DB_ENUM dbe; + TCHAR szPath[MAX_PATH]; + + LVITEM item2; + item2.mask = LVIF_TEXT; + item2.iItem = iItem; + + dbe.cbSize = sizeof(dbe); + dbe.pfnEnumCallback = (int(*)(const char*,void*,LPARAM))DetectDbProvider; + dbe.lParam = (LPARAM)szPath; + _tcscpy(szPath, fullpath); + if ( CallService( MS_PLUGINS_ENUMDBPLUGINS, 0, ( LPARAM )&dbe ) == 1 ) { + if (bFileLocked) { + // file locked + item2.pszText = TranslateT( "" ); + item2.iSubItem = 1; + SendMessage( hwndList, LVM_SETITEMTEXT, iItem, ( LPARAM )&item2 ); + } + else { + item.pszText = szPath; + item.iSubItem = 1; + SendMessage( hwndList, LVM_SETITEMTEXT, iItem, (LPARAM)&item ); + } } + + item2.iSubItem = 3; + item2.pszText = rtrim( _tctime( &statbuf.st_ctime )); + SendMessage( hwndList, LVM_SETITEMTEXT, iItem, (LPARAM)&item2 ); + + item2.iSubItem = 4; + item2.pszText = rtrim( _tctime( &statbuf.st_mtime )); + SendMessage( hwndList, LVM_SETITEMTEXT, iItem, (LPARAM)&item2 ); + } + return TRUE; +} + +void DeleteProfile(HWND hwndList, int iItem, DlgProfData* dat) +{ + if (iItem < 0) + return; + + TCHAR profile[MAX_PATH], profilef[MAX_PATH*2]; + + LVITEM item = {0}; + item.mask = LVIF_TEXT; + item.iItem = iItem; + item.pszText = profile; + item.cchTextMax = SIZEOF(profile); + if (!ListView_GetItem(hwndList, &item)) + return; + + mir_sntprintf(profilef, SIZEOF(profilef), TranslateT("Are you sure you want to remove profile \"%s\"?"), profile); + + if (IDYES != MessageBox(NULL, profilef, _T("Miranda IM"), MB_YESNO | MB_TASKMODAL | MB_ICONWARNING)) + return; + + mir_sntprintf(profilef, SIZEOF(profilef), _T("%s\\%s%c"), dat->pd->szProfileDir, profile, 0); + + SHFILEOPSTRUCT sf = {0}; + sf.wFunc = FO_DELETE; + sf.pFrom = profilef; + sf.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO; + SHFileOperation(&sf); + ListView_DeleteItem(hwndList, item.iItem); +} + +static INT_PTR CALLBACK DlgProfileSelect(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + DlgProfData* dat = (struct DlgProfData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + HWND hwndList = GetDlgItem(hwndDlg, IDC_PROFILELIST); + + switch (msg) { + case WM_INITDIALOG: + { + HIMAGELIST hImgList; + LVCOLUMN col; + + TranslateDialogDefault( hwndDlg ); + + dat = (DlgProfData*) lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + // set columns + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = TranslateT("Profile"); + col.cx=122; + ListView_InsertColumn( hwndList, 0, &col ); + + col.pszText = TranslateT("Driver"); + col.cx=100; + ListView_InsertColumn( hwndList, 1, &col ); + + col.pszText = TranslateT("Size"); + col.cx=60; + ListView_InsertColumn( hwndList, 2, &col ); + + col.pszText = TranslateT("Created"); + col.cx=145; + ListView_InsertColumn( hwndList, 3, &col ); + + col.pszText = TranslateT("Modified"); + col.cx=145; + ListView_InsertColumn( hwndList, 4, &col ); + + // icons + hImgList = ImageList_Create(16, 16, ILC_MASK | (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16), 2, 1); + ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_USERDETAILS)); + ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_DELETE)); + + // LV will destroy the image list + SetWindowLongPtr(hwndList, GWL_STYLE, GetWindowLongPtr(hwndList, GWL_STYLE) | LVS_SORTASCENDING); + ListView_SetImageList(hwndList, hImgList, LVSIL_SMALL); + ListView_SetExtendedListViewStyle(hwndList, + ListView_GetExtendedListViewStyle(hwndList) | LVS_EX_DOUBLEBUFFER | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT); + + // find all the profiles + ProfileEnumData ped = { hwndDlg, dat->pd->szProfile }; + findProfiles(dat->pd->szProfileDir, EnumProfilesForList, (LPARAM)&ped); + PostMessage(hwndDlg, WM_FOCUSTEXTBOX, 0, 0); + + dat->hFileNotify = FindFirstChangeNotification(dat->pd->szProfileDir, TRUE, + FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE); + if (dat->hFileNotify != INVALID_HANDLE_VALUE) + SetTimer(hwndDlg, 0, 1200, NULL); + return TRUE; + } + + case WM_DESTROY: + KillTimer(hwndDlg, 0); + FindCloseChangeNotification(dat->hFileNotify); + break; + + case WM_TIMER: + if (WaitForSingleObject(dat->hFileNotify, 0) == WAIT_OBJECT_0) + { + ListView_DeleteAllItems(hwndList); + ProfileEnumData ped = { hwndDlg, dat->pd->szProfile }; + findProfiles(dat->pd->szProfileDir, EnumProfilesForList, (LPARAM)&ped); + FindNextChangeNotification(dat->hFileNotify); + } + break; + + case WM_FOCUSTEXTBOX: + SetFocus(hwndList); + if (dat->pd->szProfile[0] == 0 || ListView_GetSelectedCount(hwndList) == 0) + ListView_SetItemState(hwndList, 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + break; + + case WM_SHOWWINDOW: + if ( wParam ) + { + SetWindowText(dat->hwndOK, TranslateT("&Run")); + EnableWindow(dat->hwndOK, ListView_GetSelectedCount(hwndList)==1); + } + break; + + case WM_CONTEXTMENU: + { + LVHITTESTINFO lvht = {0}; + lvht.pt.x = GET_X_LPARAM(lParam); + lvht.pt.y = GET_Y_LPARAM(lParam); + ScreenToClient(hwndList, &lvht.pt); + if (ListView_HitTest(hwndList, &lvht) < 0) break; + + lvht.pt.x = GET_X_LPARAM(lParam); + lvht.pt.y = GET_Y_LPARAM(lParam); + + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, 1, TranslateT("Run")); + AppendMenu(hMenu, MF_SEPARATOR, 2, NULL); + AppendMenu(hMenu, MF_STRING, 3, TranslateT("Delete")); + int index = TrackPopupMenu(hMenu, TPM_RETURNCMD, lvht.pt.x, lvht.pt.y, 0, hwndDlg, NULL); + switch (index) { + case 1: + SendMessage(GetParent(hwndDlg), WM_COMMAND, IDOK, 0); + break; + + case 3: + DeleteProfile(hwndList, lvht.iItem, dat); + break; + } + DestroyMenu(hMenu); + break; + } + + + case WM_NOTIFY: + { + LPNMHDR hdr = (LPNMHDR) lParam; + if (hdr && hdr->code == PSN_INFOCHANGED) + break; + + if (hdr && hdr->idFrom == IDC_PROFILELIST) + { + switch (hdr->code) + { + case LVN_ITEMCHANGED: + EnableWindow(dat->hwndOK, ListView_GetSelectedCount(hwndList) == 1); + + case NM_DBLCLK: + { + LVITEM item = {0}; + TCHAR profile[MAX_PATH]; + + if (dat == NULL) break; + + item.mask = LVIF_TEXT; + item.iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED | LVNI_ALL); + item.pszText = profile; + item.cchTextMax = SIZEOF(profile); + + if (ListView_GetItem(hwndList, &item)) { + // profile is placed in "profile_name" subfolder + TCHAR tmpPath[MAX_PATH]; + mir_sntprintf(tmpPath, SIZEOF(tmpPath), _T("%s\\%s.dat"), dat->pd->szProfileDir, profile); + HANDLE hFile = CreateFile(tmpPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile == INVALID_HANDLE_VALUE) + mir_sntprintf(dat->pd->szProfile, MAX_PATH, _T("%s\\%s\\%s.dat"), dat->pd->szProfileDir, profile, profile); + else + _tcscpy(dat->pd->szProfile, tmpPath); + CloseHandle(hFile); + if (hdr->code == NM_DBLCLK) EndDialog(GetParent(hwndDlg), 1); + } + return TRUE; + } + + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN hdrk = (LPNMLVKEYDOWN) lParam; + if (hdrk->wVKey == VK_DELETE) + DeleteProfile(hwndList, ListView_GetNextItem(hwndList, -1, LVNI_SELECTED | LVNI_ALL), dat); + break; + } + } + } + break; + } } + + return FALSE; +} + +static INT_PTR CALLBACK DlgProfileManager(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct DetailsData* dat = ( struct DetailsData* )GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + { + struct DlgProfData * prof = (struct DlgProfData *)lParam; + PROPSHEETHEADER *psh = prof->psh; + TranslateDialogDefault(hwndDlg); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadImage(hMirandaInst, MAKEINTRESOURCE(IDI_USERDETAILS),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0)); + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadImage(hMirandaInst, MAKEINTRESOURCE(IDI_USERDETAILS),IMAGE_ICON,GetSystemMetrics(SM_CXICON),GetSystemMetrics(SM_CYICON),0)); + dat = (struct DetailsData*)mir_alloc(sizeof(struct DetailsData)); + dat->prof = prof; + prof->hwndOK = GetDlgItem( hwndDlg, IDOK ); + EnableWindow( prof->hwndOK, FALSE ); + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, (LONG_PTR)dat ); + + { + TCHAR buf[512]; + mir_sntprintf(buf, SIZEOF(buf), _T("%s: %s\n%s"), TranslateT("Miranda Profiles from"), prof->pd->szProfileDir, + TranslateT("Select or create your Miranda IM user profile")); + SetDlgItemText(hwndDlg, IDC_NAME, buf); + } + + { OPTIONSDIALOGPAGE *odp; + int i; + TCITEM tci; + + dat->currentPage = 0; + dat->pageCount = psh->nPages; + dat->opd = ( struct DetailsPageData* )mir_calloc( sizeof( struct DetailsPageData )*dat->pageCount ); + odp = ( OPTIONSDIALOGPAGE* )psh->ppsp; + + tci.mask = TCIF_TEXT; + for( i=0; i < dat->pageCount; i++ ) { + dat->opd[i].pTemplate = (DLGTEMPLATE *)LockResource(LoadResource(odp[i].hInstance,FindResourceA(odp[i].hInstance,odp[i].pszTemplate,MAKEINTRESOURCEA(5)))); + dat->opd[i].dlgProc = odp[i].pfnDlgProc; + dat->opd[i].hInst = odp[i].hInstance; + dat->opd[i].hwnd = NULL; + dat->opd[i].changed = 0; + tci.pszText = ( TCHAR* )odp[i].ptszTitle; + if (dat->prof->pd->noProfiles || shouldAutoCreate(dat->prof->pd->szProfile)) + dat->currentPage = 1; + TabCtrl_InsertItem( GetDlgItem(hwndDlg,IDC_TABS), i, &tci ); + } } + + GetWindowRect(GetDlgItem(hwndDlg,IDC_TABS),&dat->rcDisplay); + TabCtrl_AdjustRect(GetDlgItem(hwndDlg,IDC_TABS),FALSE,&dat->rcDisplay); + { + POINT pt = {0,0}; + ClientToScreen( hwndDlg, &pt ); + OffsetRect( &dat->rcDisplay, -pt.x, -pt.y ); + } + + TabCtrl_SetCurSel( GetDlgItem( hwndDlg, IDC_TABS ), dat->currentPage ); + dat->opd[dat->currentPage].hwnd = CreateDialogIndirectParam(dat->opd[dat->currentPage].hInst,dat->opd[dat->currentPage].pTemplate,hwndDlg,dat->opd[dat->currentPage].dlgProc,(LPARAM)dat->prof); + ThemeDialogBackground( dat->opd[dat->currentPage].hwnd ); + SetWindowPos( dat->opd[dat->currentPage].hwnd, HWND_TOP, dat->rcDisplay.left, dat->rcDisplay.top, 0, 0, SWP_NOSIZE ); + { PSHNOTIFY pshn; + pshn.hdr.code = PSN_INFOCHANGED; + pshn.hdr.hwndFrom = dat->opd[dat->currentPage].hwnd; + pshn.hdr.idFrom = 0; + pshn.lParam = ( LPARAM )0; + SendMessage( dat->opd[dat->currentPage].hwnd, WM_NOTIFY, 0, ( LPARAM )&pshn ); + } + // service mode combobox + { + char **list = GetSeviceModePluginsList(); + if ( !list ) { + ShowWindow( GetDlgItem(hwndDlg, IDC_SM_LABEL ), FALSE ); + ShowWindow( GetDlgItem(hwndDlg, IDC_SM_COMBO ), FALSE ); + } else { + int i = 0; + LRESULT index; + HWND hwndCombo = GetDlgItem(hwndDlg, IDC_SM_COMBO ); + index = SendMessage( hwndCombo, CB_ADDSTRING, 0, (LPARAM)_T("") ); + SendMessage( hwndCombo, CB_SETITEMDATA, index, (LPARAM)-1 ); + SendMessage( hwndCombo, CB_SETCURSEL, 0, 0); + while ( list[i] ) { + TCHAR *str = LangPackPcharToTchar( list[i] ); + index = SendMessage( hwndCombo, CB_ADDSTRING, 0, (LPARAM)str ); + mir_free(str); + SendMessage( hwndCombo, CB_SETITEMDATA, index, (LPARAM)i ); + i++; + } + mir_free(list); + } + } + ShowWindow( dat->opd[dat->currentPage].hwnd, SW_SHOW ); + return TRUE; + } + case WM_CTLCOLORSTATIC: + switch ( GetDlgCtrlID(( HWND )lParam )) { + case IDC_WHITERECT: + SetBkColor(( HDC )wParam, GetSysColor( COLOR_WINDOW )); + return ( INT_PTR )GetSysColorBrush( COLOR_WINDOW ); + } + break; + + case PSM_CHANGED: + dat->opd[dat->currentPage].changed=1; + return TRUE; + + case PSM_FORCECHANGED: + { PSHNOTIFY pshn; + int i; + + pshn.hdr.code = PSN_INFOCHANGED; + pshn.hdr.idFrom = 0; + pshn.lParam = (LPARAM)0; + for ( i=0; i < dat->pageCount; i++ ) { + pshn.hdr.hwndFrom = dat->opd[i].hwnd; + if ( dat->opd[i].hwnd != NULL ) + SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } + break; + } + case WM_NOTIFY: + switch(wParam) { + case IDC_TABS: + switch(((LPNMHDR)lParam)->code) { + case TCN_SELCHANGING: + { PSHNOTIFY pshn; + if ( dat->currentPage == -1 || dat->opd[dat->currentPage].hwnd == NULL ) + break; + pshn.hdr.code = PSN_KILLACTIVE; + pshn.hdr.hwndFrom = dat->opd[dat->currentPage].hwnd; + pshn.hdr.idFrom = 0; + pshn.lParam = 0; + if ( SendMessage( dat->opd[dat->currentPage].hwnd, WM_NOTIFY, 0, ( LPARAM )&pshn )) { + SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, TRUE ); + return TRUE; + } + break; + } + case TCN_SELCHANGE: + if ( dat->currentPage != -1 && dat->opd[dat->currentPage].hwnd != NULL ) + ShowWindow( dat->opd[ dat->currentPage ].hwnd, SW_HIDE ); + + dat->currentPage = TabCtrl_GetCurSel(GetDlgItem(hwndDlg,IDC_TABS)); + if ( dat->currentPage != -1 ) { + if ( dat->opd[dat->currentPage].hwnd == NULL ) { + PSHNOTIFY pshn; + dat->opd[dat->currentPage].hwnd=CreateDialogIndirectParam(dat->opd[dat->currentPage].hInst,dat->opd[dat->currentPage].pTemplate,hwndDlg,dat->opd[dat->currentPage].dlgProc,(LPARAM)dat->prof); + ThemeDialogBackground(dat->opd[dat->currentPage].hwnd); + SetWindowPos(dat->opd[dat->currentPage].hwnd,HWND_TOP,dat->rcDisplay.left,dat->rcDisplay.top,0,0,SWP_NOSIZE); + pshn.hdr.code=PSN_INFOCHANGED; + pshn.hdr.hwndFrom=dat->opd[dat->currentPage].hwnd; + pshn.hdr.idFrom=0; + pshn.lParam=(LPARAM)0; + SendMessage(dat->opd[dat->currentPage].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } + ShowWindow(dat->opd[dat->currentPage].hwnd,SW_SHOW); + } + break; + } + break; + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + { int i; + PSHNOTIFY pshn; + pshn.hdr.idFrom=0; + pshn.lParam=0; + pshn.hdr.code=PSN_RESET; + for(i=0;ipageCount;i++) { + if (dat->opd[i].hwnd==NULL || !dat->opd[i].changed) continue; + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } + EndDialog(hwndDlg,0); + } + break; + + case IDC_REMOVE: + if (!dat->prof->pd->noProfiles) { + HWND hwndList = GetDlgItem(dat->opd[0].hwnd, IDC_PROFILELIST); + DeleteProfile(hwndList, ListView_GetNextItem(hwndList, -1, LVNI_SELECTED | LVNI_ALL), dat->prof); + } + break; + + case IDOK: + { + int i; + PSHNOTIFY pshn; + pshn.hdr.idFrom=0; + pshn.lParam=(LPARAM)0; + if ( dat->currentPage != -1 ) { + pshn.hdr.code = PSN_KILLACTIVE; + pshn.hdr.hwndFrom = dat->opd[dat->currentPage].hwnd; + if ( SendMessage(dat->opd[dat->currentPage].hwnd, WM_NOTIFY, 0, ( LPARAM )&pshn )) + break; + } + + pshn.hdr.code=PSN_APPLY; + for ( i=0; i < dat->pageCount; i++ ) { + if ( dat->opd[i].hwnd == NULL || !dat->opd[i].changed ) + continue; + + pshn.hdr.hwndFrom = dat->opd[i].hwnd; + SendMessage( dat->opd[i].hwnd, WM_NOTIFY, 0, ( LPARAM )&pshn ); + if ( GetWindowLongPtr( dat->opd[i].hwnd, DWLP_MSGRESULT ) == PSNRET_INVALID_NOCHANGEPAGE) { + TabCtrl_SetCurSel( GetDlgItem( hwndDlg, IDC_TABS ), i ); + if ( dat->currentPage != -1 ) + ShowWindow( dat->opd[ dat->currentPage ].hwnd, SW_HIDE ); + dat->currentPage = i; + ShowWindow( dat->opd[dat->currentPage].hwnd, SW_SHOW ); + return 0; + } } + EndDialog(hwndDlg,1); + break; + } } + break; + + case WM_DESTROY: + { + LRESULT curSel = SendDlgItemMessage(hwndDlg,IDC_SM_COMBO,CB_GETCURSEL,0,0); + if ( curSel != CB_ERR ) { + int idx = SendDlgItemMessage( hwndDlg, IDC_SM_COMBO, CB_GETITEMDATA, ( WPARAM )curSel, 0 ); + SetServiceModePlugin(idx); + } + } + DestroyIcon(( HICON )SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, 0)); + DestroyIcon(( HICON )SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 0)); + DeleteObject( dat->hBoldFont ); + { int i; + for ( i=0; i < dat->pageCount; i++ ) + if ( dat->opd[i].hwnd != NULL ) + DestroyWindow( dat->opd[i].hwnd ); + } + mir_free( dat->opd ); + mir_free( dat ); + break; + } + return FALSE; +} + +static int AddProfileManagerPage(struct DetailsPageInit * opi, OPTIONSDIALOGPAGE * odp) +{ + if ( odp->cbSize != sizeof( OPTIONSDIALOGPAGE )) + return 1; + + opi->odp = ( OPTIONSDIALOGPAGE* )mir_realloc( opi->odp, sizeof( OPTIONSDIALOGPAGE )*( opi->pageCount+1 )); + { + OPTIONSDIALOGPAGE* p = opi->odp + opi->pageCount++; + p->cbSize = sizeof(OPTIONSDIALOGPAGE); + p->hInstance = odp->hInstance; + p->pfnDlgProc = odp->pfnDlgProc; + p->position = odp->position; + p->ptszTitle = LangPackPcharToTchar(odp->pszTitle); + p->pszGroup = NULL; + p->groupPosition = odp->groupPosition; + p->hGroupIcon = odp->hGroupIcon; + p->hIcon = odp->hIcon; + if (( DWORD_PTR )odp->pszTemplate & 0xFFFF0000 ) + p->pszTemplate = mir_strdup( odp->pszTemplate ); + else + p->pszTemplate = odp->pszTemplate; + } + return 0; +} + +int getProfileManager(PROFILEMANAGERDATA * pd) +{ + DetailsPageInit opi; + opi.pageCount=0; + opi.odp=NULL; + + { + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.pszTitle = LPGEN("My Profiles"); + odp.pfnDlgProc = DlgProfileSelect; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_PROFILE_SELECTION); + odp.hInstance = hMirandaInst; + AddProfileManagerPage(&opi, &odp); + + odp.pszTitle = LPGEN("New Profile"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_PROFILE_NEW); + odp.pfnDlgProc = DlgProfileNew; + AddProfileManagerPage(&opi, &odp); + } + + PROPSHEETHEADER psh = { 0 }; + psh.dwSize = sizeof(psh); + psh.dwFlags = PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW; + psh.hwndParent = NULL; + psh.nPages = opi.pageCount; + psh.pStartPage = 0; + psh.ppsp = (PROPSHEETPAGE*)opi.odp; + + DlgProfData prof; + prof.pd = pd; + prof.psh = &psh; + int rc = DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(IDD_PROFILEMANAGER),NULL,DlgProfileManager,(LPARAM)&prof); + + if ( rc != -1 ) + for ( int i=0; i < opi.pageCount; i++ ) { + mir_free(( char* )opi.odp[i].pszTitle ); + mir_free( opi.odp[i].pszGroup ); + if (( DWORD_PTR )opi.odp[i].pszTemplate & 0xFFFF0000 ) + mir_free(( char* )opi.odp[i].pszTemplate ); + } + + if ( opi.odp != NULL ) + mir_free(opi.odp); + + return rc; +} diff --git a/src/modules/database/profilemanager.h b/src/modules/database/profilemanager.h new file mode 100644 index 0000000000..fc6bd56020 --- /dev/null +++ b/src/modules/database/profilemanager.h @@ -0,0 +1,43 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +typedef struct { + TCHAR * szProfile; // in/out + TCHAR * szProfileDir; // in/out + BOOL noProfiles; // in + BOOL newProfile; // out + DATABASELINK * dblink; // out +} PROFILEMANAGERDATA; + +int InitUtils(void); + +char* makeFileName( const TCHAR* tszOriginalName ); +int makeDatabase(TCHAR * profile, DATABASELINK * link, HWND hwndDlg); +int getProfileManager(PROFILEMANAGERDATA * pd); +int getProfilePath(TCHAR * buf, size_t cch); +int isValidProfileName(const TCHAR * name); +bool fileExist(TCHAR* fname); +bool shouldAutoCreate(TCHAR *szProfile); + +extern TCHAR g_profileDir[MAX_PATH]; +extern TCHAR g_profileName[MAX_PATH]; diff --git a/src/modules/findadd/findadd.cpp b/src/modules/findadd/findadd.cpp new file mode 100644 index 0000000000..b0bef4fef4 --- /dev/null +++ b/src/modules/findadd/findadd.cpp @@ -0,0 +1,1033 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "findadd.h" + +#define TIMERID_THROBBER 111 + +#define HM_SEARCHACK (WM_USER+10) +#define M_SETGROUPVISIBILITIES (WM_USER+11) + +static HWND hwndFindAdd=NULL; +static HANDLE hHookModulesLoaded = 0; +static HANDLE hMainMenuItem = NULL; +static int OnSystemModulesLoaded(WPARAM wParam,LPARAM lParam); + +static int FindAddDlgResizer(HWND,LPARAM lParam,UTILRESIZECONTROL *urc) +{ + static int y,nextY,oldTop; + struct FindAddDlgData *dat; + + dat=(struct FindAddDlgData*)lParam; + switch(urc->wId) { + case IDC_RESULTS: + return RD_ANCHORX_WIDTH|RD_ANCHORY_HEIGHT; + case IDOK: + dat->minDlgHeight=nextY+urc->rcItem.bottom-urc->rcItem.top; + return RD_ANCHORX_LEFT|RD_ANCHORY_BOTTOM; + case IDC_ADD: + case IDC_MOREOPTIONS: + return RD_ANCHORX_RIGHT|RD_ANCHORY_BOTTOM; + case IDC_STATUSBAR: + return RD_ANCHORX_WIDTH|RD_ANCHORY_BOTTOM; + case IDC_PROTOIDGROUP: //the resize is always processed in template order + nextY=y=urc->rcItem.top; + if(dat->showProtoId) nextY=y+urc->rcItem.bottom-urc->rcItem.top+7; + break; + case IDC_EMAILGROUP: + oldTop=urc->rcItem.top; + y=nextY; + if(dat->showEmail) nextY=y+urc->rcItem.bottom-urc->rcItem.top+7; + OffsetRect(&urc->rcItem,0,y-oldTop); + return RD_ANCHORX_LEFT|RD_ANCHORY_CUSTOM; + case IDC_NAMEGROUP: + oldTop=urc->rcItem.top; + y=nextY; + if(dat->showName) nextY=y+urc->rcItem.bottom-urc->rcItem.top+7; + OffsetRect(&urc->rcItem,0,y-oldTop); + return RD_ANCHORX_LEFT|RD_ANCHORY_CUSTOM; + case IDC_ADVANCEDGROUP: + oldTop=urc->rcItem.top; + y=nextY; + if(dat->showAdvanced) nextY=y+urc->rcItem.bottom-urc->rcItem.top+7; + OffsetRect(&urc->rcItem,0,y-oldTop); + return RD_ANCHORX_LEFT|RD_ANCHORY_CUSTOM; + case IDC_TINYEXTENDEDGROUP: + oldTop=urc->rcItem.top; + y=nextY; + if(dat->showTiny) + { + int height= urc->dlgNewSize.cy-y-(urc->dlgOriginalSize.cy-urc->rcItem.bottom); + nextY=y+200; //min height for custom dialog + urc->rcItem.top=urc->rcItem.bottom-height; + } + return RD_ANCHORX_LEFT|RD_ANCHORY_BOTTOM; + case IDC_BYEMAIL: + case IDC_EMAIL: + case IDC_BYNAME: + case IDC_STNAMENICK: + case IDC_STNAMEFIRST: + case IDC_STNAMELAST: + case IDC_NAMENICK: + case IDC_NAMEFIRST: + case IDC_NAMELAST: + case IDC_BYADVANCED: + case IDC_BYCUSTOM: + case IDC_ADVANCED: + OffsetRect(&urc->rcItem,0,y-oldTop); + return RD_ANCHORX_LEFT|RD_ANCHORY_CUSTOM; + case IDC_HEADERBAR: + return RD_ANCHORX_LEFT|RD_ANCHORY_TOP|RD_ANCHORX_WIDTH; + } + return RD_ANCHORX_LEFT|RD_ANCHORY_TOP; +} + +static void RenderThrobber(HDC hdc,RECT *rcItem,int *throbbing,int *pivot) +{ + HBRUSH hBr; + HDC hMemDC; + HBITMAP hBitmap; + HPEN hPen; + RECT rc; + int x,width,height,height2; + + InflateRect(rcItem,-1,0); + width=rcItem->right-rcItem->left; + height=rcItem->bottom-rcItem->top; + height2=height/2; + + if (*throbbing) { + /* create memdc */ + hMemDC=CreateCompatibleDC(0); + hBitmap=( HBITMAP )SelectObject(hMemDC, CreateCompatibleBitmap(hdc,width,height)); + /* flush it */ + rc.left=rc.top=0; + rc.right=width; + rc.bottom=height; + hBr=GetSysColorBrush(COLOR_BTNFACE); + FillRect(hMemDC,&rc,hBr); + DeleteObject(hBr); + /* set up the pen */ + hPen=(HPEN)SelectObject(hMemDC,CreatePen(PS_SOLID,4,GetSysColor(COLOR_BTNSHADOW))); + /* draw everything before the pivot */ + x=*pivot; + while (x>(-height)) { + MoveToEx(hMemDC,x+height2,0,NULL); + LineTo(hMemDC,x-height2,height); + x-=12; + } + + /* draw everything after the pivot */ + x = *pivot; + while (x < width+height) { + MoveToEx(hMemDC,x+height2,0,NULL); + LineTo(hMemDC,x-height2,height); + x+=12; + } + + /* move the pivot */ + *pivot+=2; + /* reset the pivot point if it gets past the rect */ + if (*pivot>width) *pivot=0; + /* put back the old pen and delete the new one */ + DeleteObject(SelectObject(hMemDC,hPen)); + /* cap the top and bottom */ + hPen=(HPEN)SelectObject(hMemDC,CreatePen(PS_SOLID,1,GetSysColor(COLOR_BTNFACE))); + MoveToEx(hMemDC,0,0,NULL); + LineTo(hMemDC,width,0); + MoveToEx(hMemDC,0,height-1,NULL); + LineTo(hMemDC,width,height-1); + /* select in the old pen and delete the new pen */ + DeleteObject(SelectObject(hMemDC,hPen)); + /* paint to screen */ + BitBlt(hdc,rcItem->left,rcItem->top,width,height,hMemDC,0,0,SRCCOPY); + /* select back in the old bitmap and delete the created one, as well as freeing the mem dc. */ + hBitmap=( HBITMAP )SelectObject(hMemDC,hBitmap); + DeleteObject(hBitmap); + DeleteDC(hMemDC); + } + else { + /* just flush the DC */ + hBr=GetSysColorBrush(COLOR_BTNFACE); + FillRect(hdc,rcItem,hBr); + DeleteObject(hBr); +} } + +static void StartThrobber(HWND hwndDlg,struct FindAddDlgData *dat) +{ + dat->throbbing=1; + SetTimer(hwndDlg,TIMERID_THROBBER,25,NULL); +} + +static void StopThrobber(HWND hwndDlg,struct FindAddDlgData *dat) +{ + KillTimer(hwndDlg,TIMERID_THROBBER); + dat->throbbing=0; + dat->pivot=0; + InvalidateRect(GetDlgItem(hwndDlg,IDC_STATUSBAR),NULL,FALSE); +} + +static void ShowAdvancedSearchDlg(HWND hwndDlg,struct FindAddDlgData *dat) +{ + char *szProto=(char*)SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCURSEL,0,0),0); + + if(szProto==NULL) return; + if(dat->hwndAdvSearch==NULL) { + RECT rc; + dat->hwndAdvSearch=(HWND)CallProtoService(szProto,PS_CREATEADVSEARCHUI,0,(LPARAM)hwndDlg); + GetWindowRect(GetDlgItem(hwndDlg,IDC_RESULTS),&rc); + SetWindowPos(dat->hwndAdvSearch,0,rc.left,rc.top,0,0,SWP_NOZORDER|SWP_NOSIZE); + } + if(animateWindow) { + animateWindow(dat->hwndAdvSearch,150,AW_ACTIVATE|AW_SLIDE|AW_HOR_POSITIVE); + RedrawWindow(dat->hwndAdvSearch,NULL,NULL,RDW_INVALIDATE|RDW_UPDATENOW|RDW_ALLCHILDREN); + } else ShowWindow(dat->hwndAdvSearch,SW_SHOW); + CheckDlgButton(hwndDlg,IDC_ADVANCED,BST_CHECKED); +} + +static void ReposTinySearchDlg(HWND hwndDlg,struct FindAddDlgData *dat) +{ + if ( dat->hwndTinySearch != NULL ) { + RECT rc; + RECT clientRect; + POINT pt={0,0}; + GetWindowRect(GetDlgItem(hwndDlg,IDC_TINYEXTENDEDGROUP),&rc); + GetWindowRect(dat->hwndTinySearch,&clientRect); + pt.x=rc.left; + pt.y=rc.top; + ScreenToClient(hwndDlg,&pt); + SetWindowPos(dat->hwndTinySearch,0,pt.x+5,pt.y+15,rc.right-rc.left-10,rc.bottom-rc.top-30,SWP_NOZORDER); + //SetWindowPos(GetDlgItem(hwndDlg,IDC_TINYEXTENDEDGROUP),0,0,0,rc.right-rc.left,clientRect.bottom-clientRect.top+20,SWP_NOMOVE|SWP_NOZORDER); +} } + +static void ShowTinySearchDlg(HWND hwndDlg,struct FindAddDlgData *dat) +{ + char *szProto=(char*)SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCURSEL,0,0),0); + if(szProto==NULL) return; + if(dat->hwndTinySearch==NULL) { + dat->hwndTinySearch=(HWND)CallProtoService(szProto,PS_CREATEADVSEARCHUI,0,(LPARAM)/*GetDlgItem(*/hwndDlg/*,IDC_TINYEXTENDEDGROUP)*/); + if (dat->hwndTinySearch) + ReposTinySearchDlg(hwndDlg, dat); + else + dat->showTiny = false; + } + ShowWindow(dat->hwndTinySearch,SW_SHOW); +} + +static void HideAdvancedSearchDlg(HWND hwndDlg,struct FindAddDlgData *dat) +{ + if(dat->hwndAdvSearch==NULL) return; + if(animateWindow && IsWinVerXPPlus()) //blending is quite slow on win2k + animateWindow(dat->hwndAdvSearch,150,AW_HIDE|AW_BLEND); + else ShowWindow(dat->hwndAdvSearch,SW_HIDE); + CheckDlgButton(hwndDlg,IDC_ADVANCED,BST_UNCHECKED); +} + +void EnableResultButtons(HWND hwndDlg,int enable) +{ + EnableWindow(GetDlgItem(hwndDlg,IDC_ADD), enable || IsDlgButtonChecked(hwndDlg, IDC_BYPROTOID)); + EnableWindow(GetDlgItem(hwndDlg,IDC_MOREOPTIONS),enable); +} + +static void CheckSearchTypeRadioButton(HWND hwndDlg,int idControl) +{ + int i; + static const int controls[]={IDC_BYPROTOID,IDC_BYEMAIL,IDC_BYNAME,IDC_BYADVANCED,IDC_BYCUSTOM}; + for( i=0; i < SIZEOF(controls); i++ ) + CheckDlgButton(hwndDlg,controls[i],idControl==controls[i]?BST_CHECKED:BST_UNCHECKED); +} + +#define sttErrMsg TranslateT("You haven't filled in the search field. Please enter a search term and try again.") +#define sttErrTitle TranslateT("Search") + +static void SetListItemText( HWND hwndList, int idx, int col, TCHAR* szText ) +{ + if ( szText && szText[0] ) + { + ListView_SetItemText( hwndList, idx, col, szText ); + } + else + { + ListView_SetItemText( hwndList, idx, col, TranslateT("")); + } +} + +static INT_PTR CALLBACK DlgProcFindAdd(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct FindAddDlgData* dat = ( struct FindAddDlgData* )GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + HWND hwndList = GetDlgItem(hwndDlg, IDC_RESULTS); + + switch (msg) { + case WM_INITDIALOG: + { + int i,netProtoCount; + COMBOBOXEXITEM cbei; + HICON hIcon; + + TranslateDialogDefault(hwndDlg); + Window_SetIcon_IcoLib(hwndDlg, SKINICON_OTHER_FINDUSER); + dat=(struct FindAddDlgData*)mir_calloc(sizeof(struct FindAddDlgData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->notSearchedYet=1; + dat->iLastColumnSortIndex=1; + dat->bSortAscending=1; + dat->hBmpSortUp=(HBITMAP)LoadImage(hMirandaInst,MAKEINTRESOURCE(IDB_SORTCOLUP),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS); + dat->hBmpSortDown=(HBITMAP)LoadImage(hMirandaInst,MAKEINTRESOURCE(IDB_SORTCOLDOWN),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS); + SendDlgItemMessage(hwndDlg,IDC_MOREOPTIONS,BUTTONSETARROW,1,0); + ListView_SetExtendedListViewStyle(hwndList,LVS_EX_FULLROWSELECT|LVS_EX_HEADERDRAGDROP); + + { LVCOLUMN lvc; + RECT rc; + LVITEM lvi; + + GetClientRect(hwndList,&rc); + lvc.mask = LVCF_TEXT | LVCF_WIDTH; + lvc.pszText = TranslateT("Results"); + lvc.cx = rc.right-1; + ListView_InsertColumn(hwndList, 0, &lvc); + lvi.mask=LVIF_TEXT; + lvi.iItem=0; + lvi.iSubItem=0; + lvi.pszText=TranslateT("There are no results to display."); + ListView_InsertItem(hwndList, &lvi); + } + + // Allocate a reasonable amount of space in the status bar + { int partWidth[3]; + SIZE textSize; + HDC hdc; + + hdc=GetDC(GetDlgItem(hwndDlg,IDC_STATUSBAR)); + SelectObject(hdc,(HFONT)SendDlgItemMessage(hwndDlg,IDC_STATUSBAR,WM_GETFONT,0,0)); + GetTextExtentPoint32(hdc,TranslateT("Searching"),lstrlen(TranslateT("Searching")),&textSize); + partWidth[0]=textSize.cx; + GetTextExtentPoint32(hdc,_T("01234567890123456789"), 20, &textSize ); + partWidth[0]+=textSize.cx; + ReleaseDC(GetDlgItem(hwndDlg,IDC_STATUSBAR),hdc); + partWidth[1]=partWidth[0]+150; + partWidth[2]=-1; + SendDlgItemMessage(hwndDlg,IDC_STATUSBAR,SB_SETPARTS,SIZEOF(partWidth),(LPARAM)partWidth); + SendDlgItemMessage(hwndDlg,IDC_STATUSBAR,SB_SETTEXT,1|SBT_OWNERDRAW,0); + SetStatusBarSearchInfo(GetDlgItem(hwndDlg,IDC_STATUSBAR),dat); + } + { + TCHAR *szProto = NULL; + int index = 0; + DBVARIANT dbv={0}; + HDC hdc; + SIZE textSize; + RECT rect; + int cbwidth = 0; + + if ( !DBGetContactSettingTString( NULL, "FindAdd", "LastSearched", &dbv )) + szProto = dbv.ptszVal; + + for( i=0, netProtoCount=0; i < accounts.getCount(); i++ ) { + if (!Proto_IsAccountEnabled( accounts[i] )) continue; + DWORD caps = (DWORD)CallProtoService( accounts[i]->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0 ); + if (caps & PF1_BASICSEARCH || caps & PF1_EXTSEARCH || caps & PF1_SEARCHBYEMAIL || caps & PF1_SEARCHBYNAME) + netProtoCount++; + } + dat->himlComboIcons=ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),(IsWinVerXPPlus()?ILC_COLOR32:ILC_COLOR16)|ILC_MASK,netProtoCount+1,netProtoCount+1); + SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CBEM_SETIMAGELIST,0,(LPARAM)dat->himlComboIcons); + cbei.mask=CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_TEXT|CBEIF_LPARAM; + cbei.iItem=0; + hdc=GetDC(hwndDlg); + SelectObject(hdc,(HFONT)SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,WM_GETFONT,0,0)); + if(netProtoCount>1) { + cbei.pszText=TranslateT("All Networks"); + GetTextExtentPoint32(hdc,cbei.pszText,lstrlen(cbei.pszText),&textSize); + if (textSize.cx>cbwidth) cbwidth = textSize.cx; + cbei.iImage=cbei.iSelectedImage=ImageList_AddIcon_IconLibLoaded(dat->himlComboIcons, SKINICON_OTHER_SEARCHALL); + cbei.lParam=0; + SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CBEM_INSERTITEM,0,(LPARAM)&cbei); + cbei.iItem++; + } + for( i=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if (!Proto_IsAccountEnabled(pa)) continue; + DWORD caps=(DWORD)CallProtoService( pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0 ); + if ( !(caps&PF1_BASICSEARCH) && !(caps&PF1_EXTSEARCH) && !(caps&PF1_SEARCHBYEMAIL) && !(caps&PF1_SEARCHBYNAME)) + continue; + + cbei.pszText = pa->tszAccountName; + GetTextExtentPoint32(hdc,cbei.pszText,lstrlen(cbei.pszText),&textSize); + if (textSize.cx>cbwidth) cbwidth = textSize.cx; + hIcon=(HICON)CallProtoService( pa->szModuleName,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0); + cbei.iImage=cbei.iSelectedImage=ImageList_AddIcon(dat->himlComboIcons,hIcon); + DestroyIcon(hIcon); + cbei.lParam=(LPARAM)pa->szModuleName; + SendDlgItemMessageA(hwndDlg,IDC_PROTOLIST,CBEM_INSERTITEM,0,(LPARAM)&cbei); + if (szProto && cbei.pszText && !lstrcmp( szProto, pa->tszAccountName )) + index = cbei.iItem; + cbei.iItem++; + } + cbwidth+=32; + SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETDROPPEDCONTROLRECT,0,(LPARAM)&rect); + if ((rect.right-rect.left)notSearchedYet) { + RECT rc; + GetClientRect(hwndList,&rc); + ListView_SetColumnWidth(hwndList,0,rc.right); + } + } + //fall through + case WM_MOVE: + if (dat && dat->hwndAdvSearch) + { + RECT rc; + GetWindowRect(hwndList,&rc); + SetWindowPos(dat->hwndAdvSearch,0,rc.left,rc.top,0,0,SWP_NOZORDER|SWP_NOSIZE); + } + break; + case WM_GETMINMAXINFO: + { MINMAXINFO *mmi=(MINMAXINFO*)lParam; + RECT rc,rc2; + GetWindowRect(hwndList,&rc); + GetWindowRect(hwndDlg,&rc2); + mmi->ptMinTrackSize.x=rc.left-rc2.left+10+GetSystemMetrics(SM_CXFRAME); + GetClientRect(GetDlgItem(hwndDlg,IDC_MOREOPTIONS),&rc); + mmi->ptMinTrackSize.x+=rc.right+5; + GetClientRect(GetDlgItem(hwndDlg,IDC_ADD),&rc); + mmi->ptMinTrackSize.x+=rc.right+5; + GetClientRect(GetDlgItem(hwndDlg,IDC_STATUSBAR),&rc); + mmi->ptMinTrackSize.y=dat->minDlgHeight+20+GetSystemMetrics(SM_CYCAPTION)+2*GetSystemMetrics(SM_CYFRAME); + GetClientRect(GetDlgItem(hwndDlg,IDC_STATUSBAR),&rc); + mmi->ptMinTrackSize.y+=rc.bottom; + return 0; + } + case M_SETGROUPVISIBILITIES: + { char *szProto; + int i; + DWORD protoCaps; + MINMAXINFO mmi; + RECT rc; + int checkmarkVisible; + + dat->showAdvanced = dat->showEmail = dat->showName = dat->showProtoId = dat->showTiny = 0; + szProto=(char*)SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCURSEL,0,0),0); + if ( szProto == (char *)CB_ERR ) + break; + if ( szProto == NULL ) { + for ( i=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if (!Proto_IsAccountEnabled(pa)) continue; + protoCaps=(DWORD)CallProtoService(pa->szModuleName,PS_GETCAPS,PFLAGNUM_1,0); + if(protoCaps&PF1_SEARCHBYEMAIL) dat->showEmail=1; + if(protoCaps&PF1_SEARCHBYNAME) dat->showName=1; + } + } + else { + protoCaps=(DWORD)CallProtoService(szProto,PS_GETCAPS,PFLAGNUM_1,0); + if(protoCaps&PF1_BASICSEARCH) dat->showProtoId=1; + if(protoCaps&PF1_SEARCHBYEMAIL) dat->showEmail=1; + if(protoCaps&PF1_SEARCHBYNAME) dat->showName=1; + if(protoCaps&PF1_EXTSEARCH && !(protoCaps&PF1_EXTSEARCHUI)) dat->showTiny=1; + if(protoCaps&PF1_EXTSEARCHUI) dat->showAdvanced=1; + if(protoCaps&PF1_USERIDISEMAIL && dat->showProtoId) {dat->showProtoId=0; dat->showEmail=1;} + if(dat->showProtoId) { + char *szUniqueId; + szUniqueId=(char*)CallProtoService(szProto,PS_GETCAPS,PFLAG_UNIQUEIDTEXT,0); + if(szUniqueId) { + #if defined( _UNICODE ) + TCHAR* p = mir_a2u(szUniqueId); + SetDlgItemText(hwndDlg,IDC_BYPROTOID,p); + mir_free(p); + #else + SetDlgItemTextA(hwndDlg,IDC_BYPROTOID,szUniqueId); + #endif + } + else SetDlgItemText(hwndDlg,IDC_BYPROTOID,TranslateT("Handle")); + if(protoCaps&PF1_NUMERICUSERID) SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_PROTOID),GWL_STYLE,GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_PROTOID),GWL_STYLE)|ES_NUMBER); + else SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_PROTOID),GWL_STYLE,GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_PROTOID),GWL_STYLE)&~ES_NUMBER); + } + } + + if (dat->showTiny) + ShowTinySearchDlg(hwndDlg, dat); + else { + if (dat->hwndTinySearch) { + DestroyWindow(dat->hwndTinySearch); + dat->hwndTinySearch=NULL; + } } + +#define en(id,t) ShowWindow(GetDlgItem(hwndDlg,IDC_##id),dat->show##t?SW_SHOW:SW_HIDE) + en(PROTOIDGROUP,ProtoId); en(BYPROTOID,ProtoId); en(PROTOID,ProtoId); + en(EMAILGROUP,Email); en(BYEMAIL,Email); en(EMAIL,Email); + en(NAMEGROUP,Name); en(BYNAME,Name); + en(STNAMENICK,Name); en(NAMENICK,Name); + en(STNAMEFIRST,Name); en(NAMEFIRST,Name); + en(STNAMELAST,Name); en(NAMELAST,Name); + en(ADVANCEDGROUP,Advanced); en(BYADVANCED,Advanced); en(ADVANCED,Advanced); + en(BYCUSTOM, Tiny); en(TINYEXTENDEDGROUP, Tiny); +#undef en + + checkmarkVisible=(dat->showAdvanced && IsDlgButtonChecked(hwndDlg,IDC_BYADVANCED)) || + (dat->showEmail && IsDlgButtonChecked(hwndDlg,IDC_BYEMAIL)) || + (dat->showTiny && IsDlgButtonChecked(hwndDlg,IDC_BYCUSTOM)) || + (dat->showName && IsDlgButtonChecked(hwndDlg,IDC_BYNAME)) || + (dat->showProtoId && IsDlgButtonChecked(hwndDlg,IDC_BYPROTOID)); + if(!checkmarkVisible) { + if(dat->showProtoId) CheckSearchTypeRadioButton(hwndDlg,IDC_BYPROTOID); + else if(dat->showEmail) CheckSearchTypeRadioButton(hwndDlg,IDC_BYEMAIL); + else if(dat->showName) CheckSearchTypeRadioButton(hwndDlg,IDC_BYNAME); + else if(dat->showAdvanced) CheckSearchTypeRadioButton(hwndDlg,IDC_BYADVANCED); + else if(dat->showTiny) CheckSearchTypeRadioButton(hwndDlg,IDC_BYCUSTOM); + } + + SendMessage(hwndDlg,WM_SIZE,0,0); + SendMessage(hwndDlg,WM_GETMINMAXINFO,0,(LPARAM)&mmi); + GetWindowRect(hwndDlg,&rc); + if(rc.bottom-rc.topthrobbing,&dat->pivot); + ReleaseDC(GetDlgItem(hwndDlg,IDC_STATUSBAR),hdc); + } + break; + case WM_DRAWITEM: + { DRAWITEMSTRUCT *dis=(DRAWITEMSTRUCT*)lParam; + if(dis->CtlID==IDC_STATUSBAR && dis->itemID==1) { + RenderThrobber(dis->hDC,&dis->rcItem,&dat->throbbing,&dat->pivot); + return TRUE; + } + break; + } + case WM_NOTIFY: + if (wParam == IDC_RESULTS) { + switch(((LPNMHDR)lParam)->code) { + case LVN_ITEMCHANGED: + { int count=ListView_GetSelectedCount(hwndList); + if(dat->notSearchedYet) count=0; + EnableResultButtons(hwndDlg,count); + break; + } + case LVN_COLUMNCLICK: + { + LPNMLISTVIEW nmlv=(LPNMLISTVIEW)lParam; + HDITEM hdi; + + hdi.mask=HDI_BITMAP|HDI_FORMAT; + hdi.fmt=HDF_LEFT|HDF_STRING; + Header_SetItem(ListView_GetHeader(hwndList),dat->iLastColumnSortIndex,&hdi); + + if(nmlv->iSubItem!=dat->iLastColumnSortIndex) + { + dat->bSortAscending=TRUE; + dat->iLastColumnSortIndex=nmlv->iSubItem; + } + else dat->bSortAscending=!dat->bSortAscending; + + hdi.fmt=HDF_LEFT|HDF_BITMAP|HDF_STRING|HDF_BITMAP_ON_RIGHT; + hdi.hbm=dat->bSortAscending?dat->hBmpSortDown:dat->hBmpSortUp; + Header_SetItem(ListView_GetHeader(hwndList),dat->iLastColumnSortIndex,&hdi); + + ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); + } + break; + } } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_PROTOLIST: + if(HIWORD(wParam)==CBN_SELCHANGE) { + HideAdvancedSearchDlg(hwndDlg,dat); + if(dat->hwndAdvSearch) { + DestroyWindow(dat->hwndAdvSearch); + dat->hwndAdvSearch=NULL; + } + if(dat->hwndTinySearch) { + DestroyWindow(dat->hwndTinySearch); + dat->hwndTinySearch=NULL; + } + SendMessage(hwndDlg,M_SETGROUPVISIBILITIES,0,0); + } + break; + case IDC_BYPROTOID: + EnableWindow(GetDlgItem(hwndDlg,IDC_ADD),TRUE); + HideAdvancedSearchDlg(hwndDlg,dat); + break; + case IDC_BYEMAIL: + case IDC_BYNAME: + EnableWindow(GetDlgItem(hwndDlg,IDC_ADD), ListView_GetSelectedCount(hwndList) > 0); + HideAdvancedSearchDlg(hwndDlg,dat); + break; + case IDC_PROTOID: + if(HIWORD(wParam)==EN_CHANGE) { + HideAdvancedSearchDlg(hwndDlg,dat); + CheckSearchTypeRadioButton(hwndDlg,IDC_BYPROTOID); + EnableWindow(GetDlgItem(hwndDlg,IDC_ADD),TRUE); + } + break; + case IDC_EMAIL: + if(HIWORD(wParam)==EN_CHANGE) { + HideAdvancedSearchDlg(hwndDlg,dat); + CheckSearchTypeRadioButton(hwndDlg,IDC_BYEMAIL); + } + break; + case IDC_NAMENICK: + case IDC_NAMEFIRST: + case IDC_NAMELAST: + if(HIWORD(wParam)==EN_CHANGE) { + HideAdvancedSearchDlg(hwndDlg,dat); + CheckSearchTypeRadioButton(hwndDlg,IDC_BYNAME); + } + break; + case IDC_ADVANCED: + EnableWindow(GetDlgItem(hwndDlg,IDC_ADD), ListView_GetSelectedCount(hwndList) > 0); + if(IsDlgButtonChecked(hwndDlg,IDC_ADVANCED)) + ShowAdvancedSearchDlg(hwndDlg,dat); + else + HideAdvancedSearchDlg(hwndDlg,dat); + CheckSearchTypeRadioButton(hwndDlg,IDC_BYADVANCED); + break; + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + case IDOK: + { + HideAdvancedSearchDlg(hwndDlg,dat); + if(dat->searchCount) { //cancel search + SetDlgItemText(hwndDlg,IDOK,TranslateT("&Search")); + if(dat->hResultHook) {UnhookEvent(dat->hResultHook); dat->hResultHook=NULL;} + if(dat->search) {mir_free(dat->search); dat->search=NULL;} + dat->searchCount=0; + StopThrobber(hwndDlg,dat); + SetStatusBarSearchInfo(GetDlgItem(hwndDlg,IDC_STATUSBAR),dat); + break; + } + char *szProto=(char*)SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCURSEL,0,0),0); + if(dat->search) {mir_free(dat->search); dat->search=NULL;} + dat->searchCount=0; + dat->hResultHook=HookEventMessage(ME_PROTO_ACK,hwndDlg,HM_SEARCHACK); + if (IsDlgButtonChecked(hwndDlg,IDC_BYCUSTOM)) + { + BeginSearch(hwndDlg,dat,szProto,PS_SEARCHBYADVANCED,PF1_EXTSEARCHUI,dat->hwndTinySearch); + } + else if(IsDlgButtonChecked(hwndDlg,IDC_BYPROTOID)) { + TCHAR str[256]; + GetDlgItemText(hwndDlg,IDC_PROTOID,str,SIZEOF(str)); + rtrim(str); + if(str[0]==0) + MessageBox(hwndDlg,sttErrMsg,sttErrTitle,MB_OK); + else + BeginSearch(hwndDlg,dat,szProto,PS_BASICSEARCHT,PF1_BASICSEARCH,str); + } + else if(IsDlgButtonChecked(hwndDlg,IDC_BYEMAIL)) { + TCHAR str[256]; + GetDlgItemText(hwndDlg,IDC_EMAIL,str,SIZEOF(str)); + rtrim(str); + if(str[0]==0) + MessageBox(hwndDlg,sttErrMsg,sttErrTitle,MB_OK); + else + BeginSearch(hwndDlg,dat,szProto,PS_SEARCHBYEMAILT,PF1_SEARCHBYEMAIL,str); + } + else if(IsDlgButtonChecked(hwndDlg,IDC_BYNAME)) { + TCHAR nick[256],first[256],last[256]; + PROTOSEARCHBYNAME psbn; + GetDlgItemText(hwndDlg,IDC_NAMENICK,nick,SIZEOF(nick)); + GetDlgItemText(hwndDlg,IDC_NAMEFIRST,first,SIZEOF(first)); + GetDlgItemText(hwndDlg,IDC_NAMELAST,last,SIZEOF(last)); + psbn.pszFirstName = first; + psbn.pszLastName = last; + psbn.pszNick = nick; + if(nick[0]==0 && first[0]==0 && last[0]==0) + MessageBox(hwndDlg,sttErrMsg,sttErrTitle,MB_OK); + else + BeginSearch(hwndDlg,dat,szProto,PS_SEARCHBYNAMET,PF1_SEARCHBYNAME,&psbn); + } + else if(IsDlgButtonChecked(hwndDlg,IDC_BYADVANCED)) { + if(dat->hwndAdvSearch==NULL) + MessageBox(hwndDlg,sttErrMsg,sttErrTitle,MB_OK); + else + BeginSearch(hwndDlg,dat,szProto,PS_SEARCHBYADVANCED,PF1_EXTSEARCHUI,dat->hwndAdvSearch); + } + + if(dat->searchCount==0) { + if(dat->hResultHook) {UnhookEvent(dat->hResultHook); dat->hResultHook=NULL;} + break; + } + + dat->notSearchedYet=0; + FreeSearchResults(hwndList); + + CreateResultsColumns(hwndList,dat,szProto); + SetStatusBarSearchInfo(GetDlgItem(hwndDlg,IDC_STATUSBAR),dat); + SetStatusBarResultInfo(hwndDlg); + StartThrobber(hwndDlg,dat); + SetDlgItemText(hwndDlg, IDOK, TranslateT("Cancel")); + break; + } + case IDC_ADD: + { LVITEM lvi; + struct ListSearchResult *lsr; + ADDCONTACTSTRUCT acs = {0}; + + if (ListView_GetSelectedCount(hwndList) == 1) + { + lvi.mask = LVIF_PARAM; + lvi.iItem = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED); + ListView_GetItem(hwndList, &lvi); + lsr = (struct ListSearchResult*)lvi.lParam; + acs.szProto = lsr->szProto; + acs.psr = &lsr->psr; + } + else + { + TCHAR str[256]; + GetDlgItemText(hwndDlg, IDC_PROTOID, str, SIZEOF(str)); + if (*rtrim(str) == 0) break; + + PROTOSEARCHRESULT psr = {0}; + psr.cbSize = sizeof(psr); + psr.flags = PSR_TCHAR; + psr.id = str; + + acs.psr = &psr; + acs.szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); + } + + acs.handleType = HANDLE_SEARCHRESULT; + CallService(MS_ADDCONTACT_SHOW, (WPARAM)hwndDlg, (LPARAM)&acs); + break; + } + case IDC_MOREOPTIONS: + { RECT rc; + GetWindowRect(GetDlgItem(hwndDlg,IDC_MOREOPTIONS),&rc); + ShowMoreOptionsMenu(hwndDlg,rc.left,rc.bottom); + break; + } + } + if (lParam && dat->hwndTinySearch==(HWND)lParam + && HIWORD(wParam)==EN_SETFOCUS && LOWORD(wParam)==0 + && !IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) { + CheckSearchTypeRadioButton(hwndDlg, IDC_BYCUSTOM); + } + break; + case WM_CONTEXTMENU: + { POINT pt; + LVHITTESTINFO lvhti; + + pt.x=(short)LOWORD(lParam); pt.y=(short)HIWORD(lParam); + lvhti.pt=pt; + ScreenToClient(hwndDlg,&pt); + switch(GetDlgCtrlID(ChildWindowFromPoint(hwndDlg,pt))) { + case IDC_RESULTS: + if(dat->notSearchedYet) return TRUE; + ScreenToClient(hwndList,&lvhti.pt); + if(ListView_HitTest(hwndList,&lvhti)==-1) break; + ShowMoreOptionsMenu(hwndDlg,(short)LOWORD(lParam),(short)HIWORD(lParam)); + return TRUE; + } + break; + } + case HM_SEARCHACK: + { ACKDATA *ack=(ACKDATA*)lParam; + int i; + + if(ack->type!=ACKTYPE_SEARCH) break; + for(i=0;isearchCount;i++) + if(dat->search[i].hProcess==ack->hProcess && dat->search[i].hProcess != NULL && !lstrcmpA(dat->search[i].szProto,ack->szModule)) break; + if(i==dat->searchCount) break; + if(ack->result==ACKRESULT_SUCCESS || ack->result==ACKRESULT_FAILED) { + dat->searchCount--; + memmove(dat->search+i,dat->search+i+1,sizeof(struct ProtoSearchInfo)*(dat->searchCount-i)); + if(dat->searchCount==0) { + mir_free(dat->search); + dat->search=NULL; + UnhookEvent(dat->hResultHook); + dat->hResultHook=NULL; + SetDlgItemText(hwndDlg, IDOK, TranslateT("&Search")); + StopThrobber(hwndDlg,dat); + } + ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); + SetStatusBarSearchInfo(GetDlgItem(hwndDlg,IDC_STATUSBAR),dat); + } + else if(ack->result==ACKRESULT_SEARCHRESULT && ack->lParam) { + + PROTOSEARCHRESULT *psr; + CUSTOMSEARCHRESULTS * csr=(CUSTOMSEARCHRESULTS*)ack->lParam; + dat->bFlexSearchResult=TRUE; + psr=&(csr->psr); + // check if this is column names data (psr->cbSize==0) + if ( psr->cbSize==0 ){ // blob contain info about columns + + int iColumn; + LVCOLUMN lvc={0}; + + //firstly remove all exist items + FreeSearchResults(hwndList); + ListView_DeleteAllItems(hwndList); //not sure if previous delete list items too + //secondly remove all columns + while (ListView_DeleteColumn(hwndList,1)); //will delete fist column till it possible + //now will add columns and captions; + lvc.mask=LVCF_TEXT; + for (iColumn=0; iColumnnFieldCount; iColumn++) + { + lvc.pszText=TranslateTS(csr->pszFields[iColumn]); + ListView_InsertColumn (hwndList, iColumn+1, &lvc) ; + } + // Column inserting Done + } else { // blob contain info about found contacts + + LVITEM lvi = {0}; + int i, col; + struct ListSearchResult *lsr; + char *szComboProto; + COMBOBOXEXITEM cbei = {0}; + + lsr = (struct ListSearchResult*)mir_alloc(offsetof(struct ListSearchResult,psr)+psr->cbSize); + lsr->szProto = ack->szModule; + memcpy(&lsr->psr, psr, psr->cbSize); + + /* Next block is not needed but behavior will be kept */ + bool isUnicode = (psr->flags & PSR_UNICODE) != 0; + if (psr->id) + { + BOOL validPtr = isUnicode ? IsBadStringPtrW((wchar_t*)psr->id, 25) : IsBadStringPtrA((char*)psr->id, 25); + if (!validPtr) + { + isUnicode = false; + lsr->psr.id = NULL; + } + else + lsr->psr.id = isUnicode ? mir_u2t((wchar_t*)psr->id) : mir_a2t((char*)psr->id); + } + + lsr->psr.nick = isUnicode ? mir_u2t((wchar_t*)psr->nick) : mir_a2t((char*)psr->nick); + lsr->psr.firstName = isUnicode ? mir_u2t((wchar_t*)psr->firstName) : mir_a2t((char*)psr->firstName); + lsr->psr.lastName = isUnicode ? mir_u2t((wchar_t*)psr->lastName) : mir_a2t((char*)psr->lastName); + lsr->psr.email = isUnicode ? mir_u2t((wchar_t*)psr->email) : mir_a2t((char*)psr->email); + lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_TCHAR; + + lvi.mask = LVIF_PARAM | LVIF_IMAGE; + lvi.lParam = (LPARAM)lsr; + for (i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--; ) + { + szComboProto=(char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0); + if (szComboProto==NULL) continue; + if (!lstrcmpA(szComboProto,ack->szModule)) + { + cbei.mask = CBEIF_IMAGE; + cbei.iItem = i; + SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CBEM_GETITEM,0,(LPARAM)&cbei); + lvi.iImage = cbei.iImage; + } + } + i = ListView_InsertItem(hwndList, &lvi); + for (col=0; colnFieldCount; col++) { + SetListItemText(hwndList, i, col+1 , csr->pszFields[col] ); + } + ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); + i=0; + while (ListView_SetColumnWidth(hwndList, i++, LVSCW_AUTOSIZE_USEHEADER)); + SetStatusBarResultInfo(hwndDlg); + } + break; + } + else if(ack->result==ACKRESULT_DATA) { + LVITEM lvi={0}; + int i,col; + PROTOSEARCHRESULT *psr=(PROTOSEARCHRESULT*)ack->lParam; + struct ListSearchResult *lsr; + char *szComboProto; + COMBOBOXEXITEM cbei={0}; + dat->bFlexSearchResult=FALSE; + lsr=(struct ListSearchResult*)mir_alloc(offsetof(struct ListSearchResult,psr)+psr->cbSize); + lsr->szProto=ack->szModule; + + CopyMemory(&lsr->psr, psr, psr->cbSize); + lsr->psr.nick = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->nick) : mir_a2t((char*)psr->nick); + lsr->psr.firstName = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->firstName) : mir_a2t((char*)psr->firstName); + lsr->psr.lastName = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->lastName) : mir_a2t((char*)psr->lastName); + lsr->psr.email = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->email) : mir_a2t((char*)psr->email); + lsr->psr.id = psr->flags & PSR_UNICODE ? mir_u2t((wchar_t*)psr->id) : mir_a2t((char*)psr->id); + lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_TCHAR; + + lvi.mask = LVIF_PARAM|LVIF_IMAGE; + lvi.lParam=(LPARAM)lsr; + for(i = SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCOUNT,0,0); i--; ) + { + szComboProto=(char*)SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETITEMDATA,i,0); + if(szComboProto==NULL) continue; + if(!lstrcmpA(szComboProto,ack->szModule)) { + cbei.mask=CBEIF_IMAGE; + cbei.iItem=i; + SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CBEM_GETITEM,0,(LPARAM)&cbei); + lvi.iImage=cbei.iImage; + break; + } + } + i=ListView_InsertItem(hwndList, &lvi); + col=1; + SetListItemText(hwndList, i, col++, lsr->psr.id ); + SetListItemText(hwndList, i, col++, lsr->psr.nick ); + SetListItemText(hwndList, i, col++, lsr->psr.firstName ); + SetListItemText(hwndList, i, col++, lsr->psr.lastName ); + SetListItemText(hwndList, i, col++, lsr->psr.email ); + SetStatusBarResultInfo(hwndDlg); + } + break; + } + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + case WM_DESTROY: + { + TCHAR *szProto; + int len = SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETLBTEXTLEN,SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCURSEL,0,0),0); + szProto = ( TCHAR* )alloca( sizeof(TCHAR)*( len+1 )); + *szProto='\0'; + SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETLBTEXT,SendDlgItemMessage(hwndDlg,IDC_PROTOLIST,CB_GETCURSEL,0,0),(LPARAM)szProto); + DBWriteContactSettingTString(NULL, "FindAdd", "LastSearched", szProto?szProto:_T("")); + } + SaveColumnSizes(hwndList); + if(dat->hResultHook!=NULL) UnhookEvent(dat->hResultHook); + FreeSearchResults(hwndList); + ImageList_Destroy(dat->himlComboIcons); + mir_free(dat->search); + if(dat->hwndAdvSearch) { + DestroyWindow(dat->hwndAdvSearch); + dat->hwndAdvSearch=NULL; + } + if(dat->hwndTinySearch) { + DestroyWindow(dat->hwndTinySearch); + dat->hwndTinySearch=NULL; + } + DeleteObject(dat->hBmpSortDown); + DeleteObject(dat->hBmpSortUp); + mir_free(dat); + Window_FreeIcon_IcoLib(hwndDlg); + Utils_SaveWindowPosition(hwndDlg,NULL,"FindAdd",""); + + break; + } + return FALSE; +} + +static INT_PTR FindAddCommand(WPARAM, LPARAM) +{ + if(IsWindow(hwndFindAdd)) { + ShowWindow(hwndFindAdd,SW_SHOWNORMAL); + SetForegroundWindow(hwndFindAdd); + SetFocus(hwndFindAdd); + } + else { + int netProtoCount, i; + + // Make sure we have some networks to search on. This is not ideal since + // this check will be repeated every time the dialog is requested, but it + // must be done since this service can be called from other places than the menu. + // One alternative would be to only create the service if we have network + // protocols loaded but that would delay the creation until MODULE_LOADED and + // that is not good either... + for ( i=0, netProtoCount=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if (!Proto_IsAccountEnabled(pa)) continue; + int protoCaps=CallProtoService( pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0 ); + if ( protoCaps&PF1_BASICSEARCH || protoCaps&PF1_SEARCHBYEMAIL || protoCaps&PF1_SEARCHBYNAME + || protoCaps&PF1_EXTSEARCHUI ) netProtoCount++; + } + if (netProtoCount > 0) + hwndFindAdd=CreateDialog(hMirandaInst, MAKEINTRESOURCE(IDD_FINDADD), NULL, DlgProcFindAdd); + } + return 0; +} + +int FindAddPreShutdown(WPARAM, LPARAM) +{ + if ( IsWindow( hwndFindAdd )) + DestroyWindow(hwndFindAdd); + hwndFindAdd = NULL; + return 0; +} + +int LoadFindAddModule(void) +{ + CreateServiceFunction(MS_FINDADD_FINDADD,FindAddCommand); + HookEvent(ME_SYSTEM_MODULESLOADED, OnSystemModulesLoaded); + HookEvent(ME_PROTO_ACCLISTCHANGED, OnSystemModulesLoaded); + HookEvent(ME_SYSTEM_PRESHUTDOWN,FindAddPreShutdown); + + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.position = 500020000; + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_FINDUSER ); + mi.pszName = LPGEN("&Find/Add Contacts..."); + mi.pszService = MS_FINDADD_FINDADD; + hMainMenuItem = ( HANDLE )CallService(MS_CLIST_ADDMAINMENUITEM, 0, (LPARAM)&mi); + return 0; +} + +static int OnSystemModulesLoaded(WPARAM, LPARAM) +{ + int netProtoCount, i; + + // Make sure we have some networks to search on. + for ( i=0, netProtoCount=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + int protoCaps = CallProtoService( pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0 ); + if ( protoCaps & ( PF1_BASICSEARCH | PF1_SEARCHBYEMAIL | PF1_SEARCHBYNAME | PF1_EXTSEARCHUI )) + netProtoCount++; + } + + CLISTMENUITEM cmi = { 0 }; + cmi.cbSize = sizeof(cmi); + cmi.flags = CMIM_FLAGS; + if ( netProtoCount == 0 ) + cmi.flags |= CMIF_HIDDEN; + CallService( MS_CLIST_MODIFYMENUITEM, (WPARAM)hMainMenuItem, (LPARAM)&cmi ); + return 0; +} diff --git a/src/modules/findadd/findadd.h b/src/modules/findadd/findadd.h new file mode 100644 index 0000000000..8e5fcb29e1 --- /dev/null +++ b/src/modules/findadd/findadd.h @@ -0,0 +1,59 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ +struct ListSearchResult { + const char *szProto; + PROTOSEARCHRESULT psr; +}; + +struct ProtoSearchInfo { + const char *szProto; + HANDLE hProcess; +}; + +struct FindAddDlgData { + HANDLE hResultHook; + int bSortAscending; + int iLastColumnSortIndex; + HIMAGELIST himlComboIcons; + int showProtoId,showEmail,showName,showAdvanced,showTiny; + int minDlgHeight; + int notSearchedYet; + struct ProtoSearchInfo *search; + int searchCount; + HBITMAP hBmpSortUp,hBmpSortDown; + int throbbing; + int pivot; + HWND hwndAdvSearch; + HWND hwndTinySearch; + BOOL bFlexSearchResult; +}; + +int CALLBACK SearchResultsCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); +void FreeSearchResults(HWND hwndResults); +int BeginSearch(HWND hwndDlg,struct FindAddDlgData *dat,const char *szProto,const char *szSearchService,DWORD requiredCapability,void *pvSearchParams); +void SetStatusBarSearchInfo(HWND hwndStatus,struct FindAddDlgData *dat); +void SetStatusBarResultInfo(HWND hwndDlg); +void CreateResultsColumns(HWND hwndResults,struct FindAddDlgData *dat,char *szProto); +void EnableResultButtons(HWND hwndDlg,int enable); +void ShowMoreOptionsMenu(HWND hwndDlg,int x,int y); +void SaveColumnSizes(HWND hwndResults); diff --git a/src/modules/findadd/searchresults.cpp b/src/modules/findadd/searchresults.cpp new file mode 100644 index 0000000000..4c8b7cf46c --- /dev/null +++ b/src/modules/findadd/searchresults.cpp @@ -0,0 +1,398 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "findadd.h" + +enum { + COLUMNID_PROTO, + COLUMNID_HANDLE, + COLUMNID_NICK, + COLUMNID_FIRST, + COLUMNID_LAST, + COLUMNID_EMAIL, + NUM_COLUMNID +}; + +void SaveColumnSizes(HWND hwndResults) +{ + int columnOrder[NUM_COLUMNID]; + int columnCount; + char szSetting[32]; + int i; + struct FindAddDlgData *dat; + + dat=(struct FindAddDlgData*)GetWindowLongPtr(GetParent(hwndResults),GWLP_USERDATA); + columnCount=Header_GetItemCount(ListView_GetHeader(hwndResults)); + if (columnCount != NUM_COLUMNID) return; + ListView_GetColumnOrderArray(hwndResults,columnCount,columnOrder); + for(i=0; i < NUM_COLUMNID; i++) { + mir_snprintf(szSetting, SIZEOF(szSetting), "ColOrder%d", i); + DBWriteContactSettingByte(NULL,"FindAdd",szSetting,(BYTE)columnOrder[i]); + if(i>=columnCount) continue; + mir_snprintf(szSetting, SIZEOF(szSetting), "ColWidth%d", i); + DBWriteContactSettingWord(NULL,"FindAdd",szSetting,(WORD)ListView_GetColumnWidth(hwndResults,i)); + } + DBWriteContactSettingByte(NULL,"FindAdd","SortColumn",(BYTE)dat->iLastColumnSortIndex); + DBWriteContactSettingByte(NULL,"FindAdd","SortAscending",(BYTE)dat->bSortAscending); +} + +static const TCHAR *szColumnNames[] = { NULL, NULL, _T("Nick"), _T("First Name"), _T("Last Name"), _T("E-mail") }; +static int defaultColumnSizes[]={0,90,100,100,100,2000}; +void LoadColumnSizes(HWND hwndResults,const char *szProto) +{ + HDITEM hdi; + int columnOrder[NUM_COLUMNID]; + int columnCount; + char szSetting[32]; + int i; + FindAddDlgData *dat; + bool colOrdersValid; + + defaultColumnSizes[COLUMNID_PROTO] = GetSystemMetrics(SM_CXSMICON) + 4; + dat = (FindAddDlgData*)GetWindowLongPtr(GetParent(hwndResults), GWLP_USERDATA); + + columnCount = NUM_COLUMNID; + colOrdersValid = true; + for(i=0; i < NUM_COLUMNID; i++) + { + LVCOLUMN lvc; + if( i < columnCount ) + { + int bNeedsFree = FALSE; + lvc.mask = LVCF_TEXT | LVCF_WIDTH; + if( szColumnNames[i] != NULL ) + lvc.pszText = TranslateTS( szColumnNames[i] ); + else if( i == COLUMNID_HANDLE ) + { + if (szProto) + { + #if defined( _UNICODE ) + bNeedsFree = TRUE; + lvc.pszText = mir_a2t((char*)CallProtoService(szProto,PS_GETCAPS,PFLAG_UNIQUEIDTEXT,0)); + #else + lvc.pszText = (char*)CallProtoService(szProto,PS_GETCAPS,PFLAG_UNIQUEIDTEXT,0); + #endif + } + else + lvc.pszText = _T("ID"); + } + else lvc.mask &= ~LVCF_TEXT; + mir_snprintf(szSetting, SIZEOF(szSetting), "ColWidth%d", i); + lvc.cx = DBGetContactSettingWord(NULL, "FindAdd", szSetting, defaultColumnSizes[i]); + ListView_InsertColumn(hwndResults, i, (LPARAM)&lvc); + #if defined( _UNICODE ) + if (bNeedsFree) + mir_free(lvc.pszText); + #endif + } + mir_snprintf(szSetting, SIZEOF(szSetting), "ColOrder%d", i); + columnOrder[i] = DBGetContactSettingByte(NULL, "FindAdd", szSetting, -1); + if (columnOrder[i] == -1 || columnOrder[i] >= NUM_COLUMNID) colOrdersValid = false; + } + + if (colOrdersValid) + ListView_SetColumnOrderArray(hwndResults, columnCount, columnOrder); + + dat->iLastColumnSortIndex = DBGetContactSettingByte(NULL, "FindAdd", "SortColumn", COLUMNID_NICK); + if (dat->iLastColumnSortIndex >= columnCount) dat->iLastColumnSortIndex = COLUMNID_NICK; + dat->bSortAscending = DBGetContactSettingByte(NULL, "FindAdd", "SortAscending", TRUE); + + hdi.mask = HDI_BITMAP | HDI_FORMAT; + hdi.fmt = HDF_LEFT | HDF_BITMAP | HDF_STRING | HDF_BITMAP_ON_RIGHT; + hdi.hbm = dat->bSortAscending ? dat->hBmpSortDown : dat->hBmpSortUp; + Header_SetItem(ListView_GetHeader(hwndResults), dat->iLastColumnSortIndex, &hdi); +} + +static LPARAM ListView_GetItemLParam(HWND hwndList, int idx) +{ + LVITEM lv; + lv.iItem = idx; + lv.mask = LVIF_PARAM; + ListView_GetItem(hwndList,&lv); + return lv.lParam; +} + +int CALLBACK SearchResultsCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + struct FindAddDlgData *dat=(struct FindAddDlgData*)GetWindowLongPtr((HWND) lParamSort, GWLP_USERDATA); + int sortMultiplier; + int sortCol; + struct ListSearchResult *lsr1, *lsr2; + HWND hList=GetDlgItem((HWND) lParamSort, IDC_RESULTS); + + sortMultiplier=dat->bSortAscending?1:-1; + sortCol=dat->iLastColumnSortIndex; + if (!dat->bFlexSearchResult) + { + lsr1=(struct ListSearchResult*)ListView_GetItemLParam(hList, (int)lParam1); + lsr2=(struct ListSearchResult*)ListView_GetItemLParam(hList, (int)lParam2); + + if ( lsr1 == NULL || lsr2 == NULL ) return 0; + switch(sortCol) + { + case COLUMNID_PROTO: + return lstrcmpA(lsr1->szProto, lsr2->szProto)*sortMultiplier; + case COLUMNID_HANDLE: + return lstrcmpi(lsr1->psr.id, lsr2->psr.id)*sortMultiplier; + case COLUMNID_NICK: + return lstrcmpi(lsr1->psr.nick, lsr2->psr.nick)*sortMultiplier; + case COLUMNID_FIRST: + return lstrcmpi(lsr1->psr.firstName, lsr2->psr.firstName)*sortMultiplier; + case COLUMNID_LAST: + return lstrcmpi(lsr1->psr.lastName, lsr2->psr.lastName)*sortMultiplier; + case COLUMNID_EMAIL: + return lstrcmpi(lsr1->psr.email, lsr2->psr.email)*sortMultiplier; + } + } + else + { + TCHAR szText1[100]; + TCHAR szText2[100]; + ListView_GetItemText(hList,(int)lParam1,sortCol,szText1,SIZEOF(szText1)); + ListView_GetItemText(hList,(int)lParam2,sortCol,szText2,SIZEOF(szText2)); + return _tcsicmp(szText1, szText2)*sortMultiplier; + } + return 0; +} + +void FreeSearchResults(HWND hwndResults) +{ + LV_ITEM lvi; + struct ListSearchResult *lsr; + for(lvi.iItem=ListView_GetItemCount(hwndResults)-1;lvi.iItem>=0;lvi.iItem--) { + lvi.mask=LVIF_PARAM; + ListView_GetItem(hwndResults,&lvi); + lsr=(struct ListSearchResult*)lvi.lParam; + if(lsr==NULL) continue; + mir_free(lsr->psr.id); + mir_free(lsr->psr.email); + mir_free(lsr->psr.nick); + mir_free(lsr->psr.firstName); + mir_free(lsr->psr.lastName); + mir_free(lsr); + } + ListView_DeleteAllItems(hwndResults); + EnableResultButtons(GetParent(hwndResults),0); +} + +// on its own thread +static void BeginSearchFailed(void * arg) +{ + TCHAR buf[128]; + if ( arg != NULL ) { + const TCHAR* protoName = (TCHAR*)arg; + mir_sntprintf(buf,SIZEOF(buf), + TranslateT("Could not start a search on '%s', there was a problem - is %s connected?"), + protoName,protoName); + mir_free((char*)arg); + } + else lstrcpyn(buf,TranslateT("Could not search on any of the protocols, are you online?"),SIZEOF(buf)); + MessageBox(0,buf,TranslateT("Problem with search"),MB_OK | MB_ICONERROR); +} + +int BeginSearch(HWND,struct FindAddDlgData *dat,const char *szProto,const char *szSearchService,DWORD requiredCapability,void *pvSearchParams) +{ + int i; + if ( szProto == NULL ) { + int failures = 0; + dat->searchCount = 0; + dat->search = (struct ProtoSearchInfo*)mir_calloc(sizeof(struct ProtoSearchInfo) * accounts.getCount()); + for( i=0; i < accounts.getCount();i++) { + PROTOACCOUNT* pa = accounts[i]; + if (!Proto_IsAccountEnabled(pa)) continue; + DWORD caps=(DWORD)CallProtoService(pa->szModuleName,PS_GETCAPS,PFLAGNUM_1,0); + if(!(caps&requiredCapability)) continue; + dat->search[dat->searchCount].hProcess = (HANDLE)CallProtoService(pa->szModuleName,szSearchService,0,(LPARAM)pvSearchParams); + dat->search[dat->searchCount].szProto = pa->szModuleName; + if ( dat->search[dat->searchCount].hProcess == NULL ) failures++; + else dat->searchCount++; + } + if(failures) { + //infuriatingly vague error message. fixme. + if(dat->searchCount==0) { + forkthread(BeginSearchFailed,0,NULL); + mir_free(dat->search); + dat->search=NULL; + return 1; + } } + } + else { + dat->search=(struct ProtoSearchInfo*)mir_alloc(sizeof(struct ProtoSearchInfo)); + dat->searchCount=1; + dat->search[0].hProcess=(HANDLE)CallProtoService(szProto,szSearchService,0,(LPARAM)pvSearchParams); + dat->search[0].szProto=szProto; + if(dat->search[0].hProcess==NULL) { + //infuriatingly vague error message. fixme. + PROTOACCOUNT* pa = Proto_GetAccount(szProto); + forkthread(BeginSearchFailed, 0, mir_tstrdup(pa->tszAccountName)); + mir_free(dat->search); + dat->search=NULL; + dat->searchCount=0; + return 1; + } + } + return 0; +} + +// !!!!!!!! this code is dangerous like a hell +void SetStatusBarSearchInfo(HWND hwndStatus,struct FindAddDlgData *dat) +{ + TCHAR str[256]; + + if (dat->searchCount != 0 ) { + int i; + + lstrcpy( str, TranslateT("Searching")); + for( i=0; i < dat->searchCount; i++ ) { + PROTOACCOUNT* pa = Proto_GetAccount( dat->search[i].szProto ); + if ( !pa ) + continue; + + lstrcat(str, i ? _T(",") : _T( " " )); + lstrcat(str, pa->tszAccountName ); + } } + else lstrcpy(str, TranslateT("Idle")); + + SendMessage( hwndStatus, SB_SETTEXT, 0, (LPARAM)str ); +} + +struct ProtoResultsSummary { + const char *szProto; + int count; +}; +void SetStatusBarResultInfo(HWND hwndDlg) +{ + HWND hwndStatus=GetDlgItem(hwndDlg,IDC_STATUSBAR); + HWND hwndResults=GetDlgItem(hwndDlg,IDC_RESULTS); + LV_ITEM lvi; + struct ListSearchResult *lsr; + struct ProtoResultsSummary *subtotal=NULL; + int subtotalCount=0; + int i,total; + TCHAR str[256]; + + total=ListView_GetItemCount(hwndResults); + for(lvi.iItem=total-1;lvi.iItem>=0;lvi.iItem--) { + lvi.mask=LVIF_PARAM; + ListView_GetItem(hwndResults,&lvi); + lsr=(struct ListSearchResult*)lvi.lParam; + if(lsr==NULL) continue; + for(i=0;iszProto) { + subtotal[i].count++; + break; + } + } + if(i==subtotalCount) { + subtotal=(struct ProtoResultsSummary*)mir_realloc(subtotal,sizeof(struct ProtoResultsSummary)*(subtotalCount+1)); + subtotal[subtotalCount].szProto=lsr->szProto; + subtotal[subtotalCount++].count=1; + } + } + if ( total != 0 ) { + TCHAR substr[64]; + PROTOACCOUNT* pa = Proto_GetAccount( subtotal[0].szProto ); + if ( pa == NULL ) + return; + + if ( subtotalCount == 1 ) { + if(total==1) mir_sntprintf( str, SIZEOF(str), TranslateT("1 %s user found"), pa->tszAccountName ); + else mir_sntprintf( str, SIZEOF(str), TranslateT("%d %s users found"), total, pa->tszAccountName ); + } + else { + mir_sntprintf( str, SIZEOF(str), TranslateT("%d users found ("),total); + for( i=0; i < subtotalCount; i++ ) { + if ( i ) { + if (( pa = Proto_GetAccount( subtotal[i].szProto )) == NULL ) + return; + lstrcat( str, _T(", ")); + } + mir_sntprintf( substr, SIZEOF(substr), _T("%d %s"), subtotal[i].count, pa->tszAccountName ); + lstrcat( str, substr ); + } + lstrcat( str, _T(")")); + } + mir_free(subtotal); + } + else lstrcpy(str, TranslateT("No users found")); + SendMessage(hwndStatus, SB_SETTEXT, 2, (LPARAM)str ); +} + +void CreateResultsColumns(HWND hwndResults,struct FindAddDlgData *dat,char *szProto) +{ + SaveColumnSizes(hwndResults); + while(ListView_DeleteColumn(hwndResults,0)); + ListView_SetImageList(hwndResults,dat->himlComboIcons,LVSIL_SMALL); + LoadColumnSizes(hwndResults,szProto); +} + +void ShowMoreOptionsMenu(HWND hwndDlg,int x,int y) +{ + struct FindAddDlgData *dat; + HMENU hPopupMenu,hMenu; + int commandId; + struct ListSearchResult *lsr; + + dat=(struct FindAddDlgData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + + { LVITEM lvi; + if(ListView_GetSelectedCount(GetDlgItem(hwndDlg,IDC_RESULTS))!=1) return; + lvi.mask=LVIF_PARAM; + lvi.iItem=ListView_GetNextItem(GetDlgItem(hwndDlg,IDC_RESULTS),-1,LVNI_ALL|LVNI_SELECTED); + ListView_GetItem(GetDlgItem(hwndDlg,IDC_RESULTS),&lvi); + lsr=(struct ListSearchResult*)lvi.lParam; + } + + hMenu=LoadMenu(hMirandaInst,MAKEINTRESOURCE(IDR_CONTEXT)); + hPopupMenu=GetSubMenu(hMenu,4); + CallService(MS_LANGPACK_TRANSLATEMENU,(WPARAM)hPopupMenu,0); + commandId=TrackPopupMenu(hPopupMenu,TPM_RIGHTBUTTON|TPM_RETURNCMD,x,y,0,hwndDlg,NULL); + switch(commandId) { + case IDC_ADD: + { ADDCONTACTSTRUCT acs; + + acs.handle=NULL; + acs.handleType=HANDLE_SEARCHRESULT; + acs.szProto=lsr->szProto; + acs.psr=&lsr->psr; + CallService(MS_ADDCONTACT_SHOW,(WPARAM)hwndDlg,(LPARAM)&acs); + break; + } + case IDC_DETAILS: + { HANDLE hContact; + hContact=(HANDLE)CallProtoService(lsr->szProto,PS_ADDTOLIST,PALF_TEMPORARY,(LPARAM)&lsr->psr); + CallService(MS_USERINFO_SHOWDIALOG,(WPARAM)hContact,0); + break; + } + case IDC_SENDMESSAGE: + { HANDLE hContact; + hContact=(HANDLE)CallProtoService(lsr->szProto,PS_ADDTOLIST,PALF_TEMPORARY,(LPARAM)&lsr->psr); + CallService(MS_MSG_SENDMESSAGE,(WPARAM)hContact,(LPARAM)(const char*)NULL); + break; + } + } + DestroyMenu(hPopupMenu); + DestroyMenu(hMenu); +} + + diff --git a/src/modules/fonts/FontOptions.cpp b/src/modules/fonts/FontOptions.cpp new file mode 100644 index 0000000000..cbd19d23de --- /dev/null +++ b/src/modules/fonts/FontOptions.cpp @@ -0,0 +1,1523 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "FontService.h" + +// *_w2 is working copy of list +// *_w3 is stores initial configuration + +static int sttCompareFont( const TFontID* p1, const TFontID* p2 ) +{ + int result = _tcscmp( p1->group, p2->group ); + if ( result != 0 ) + return result; + result = p1->order - p2->order; + if ( result != 0 ) + return result; + return _tcscmp( TranslateTS(p1->name), TranslateTS(p2->name) ); +} + +OBJLIST font_id_list( 20, sttCompareFont ), font_id_list_w2( 20, sttCompareFont ), font_id_list_w3( 20, sttCompareFont ); + +static int sttCompareColour( const TColourID* p1, const TColourID* p2 ) +{ + int result = _tcscmp( p1->group, p2->group ); + if ( result != 0 ) + return result; + result = p1->order - p2->order; + if ( result != 0 ) + return result; + + return _tcscmp( TranslateTS(p1->name), TranslateTS(p2->name) ); +} + +OBJLIST colour_id_list( 10, sttCompareColour ), colour_id_list_w2( 10, sttCompareColour ), colour_id_list_w3( 10, sttCompareColour ); + + +static int sttCompareEffect( const TEffectID* p1, const TEffectID* p2 ) +{ + int result = _tcscmp( p1->group, p2->group ); + if ( result != 0 ) + return result; + result = p1->order - p2->order; + if ( result != 0 ) + return result; + + return _tcscmp( TranslateTS(p1->name), TranslateTS(p2->name) ); +} + +OBJLIST effect_id_list( 10, sttCompareEffect ), effect_id_list_w2( 10, sttCompareEffect ), effect_id_list_w3( 10, sttCompareEffect ); + +typedef struct DrawTextWithEffectParam_tag +{ + int cbSize; + HDC hdc; // handle to DC + LPCTSTR lpchText; // text to draw + int cchText; // length of text to draw + LPRECT lprc; // rectangle coordinates + UINT dwDTFormat; // formatting options + FONTEFFECT * pEffect; // effect to be drawn on + +} DrawTextWithEffectParam; + +#define MS_DRAW_TEXT_WITH_EFFECTA "Modern/SkinEngine/DrawTextWithEffectA" +#define MS_DRAW_TEXT_WITH_EFFECTW "Modern/SkinEngine/DrawTextWithEffectW" + +#ifdef _UNICODE + #define MS_DRAW_TEXT_WITH_EFFECT MS_DRAW_TEXT_WITH_EFFECTW +#else + #define MS_DRAW_TEXT_WITH_EFFECT MS_DRAW_TEXT_WITH_EFFECTA +#endif + +// Helper +int __inline DrawTextWithEffect( HDC hdc, LPCTSTR lpchText, int cchText, RECT * lprc, UINT dwDTFormat, FONTEFFECT * pEffect ) +{ + DrawTextWithEffectParam params; + static BYTE bIfServiceExists = ServiceExists( MS_DRAW_TEXT_WITH_EFFECT ) ? 1 : 0; + + if ( pEffect == NULL || pEffect->effectIndex == 0 ) + return DrawText ( hdc, lpchText, cchText, lprc, dwDTFormat ); // If no effect specified draw by GDI it just more careful with ClearType + + if ( bIfServiceExists == 0) + return DrawText ( hdc, lpchText, cchText, lprc, dwDTFormat ); + + // else + params.cbSize = sizeof( DrawTextWithEffectParam ); + params.hdc = hdc; + params.lpchText = lpchText; + params.cchText = cchText; + params.lprc = lprc; + params.dwDTFormat = dwDTFormat; + params.pEffect = pEffect; + return CallService( MS_DRAW_TEXT_WITH_EFFECT, (WPARAM)¶ms, 0 ); +} + + +#define UM_SETFONTGROUP (WM_USER + 101) +#define TIMER_ID 11015 + +#define FSUI_COLORBOXWIDTH 50 +#define FSUI_COLORBOXLEFT 5 +#define FSUI_FONTFRAMEHORZ 5 +#define FSUI_FONTFRAMEVERT 4 +#define FSUI_FONTLEFT (FSUI_COLORBOXLEFT+FSUI_COLORBOXWIDTH+5) + +extern void UpdateFontSettings(TFontID *font_id, TFontSettings *fontsettings); +extern void UpdateColourSettings(TColourID *colour_id, COLORREF *colour); +extern void UpdateEffectSettings(TEffectID* effect_id, TEffectSettings* effectsettings); + +void WriteLine(HANDLE fhand, char *line) +{ + DWORD wrote; + strcat(line, "\r\n"); + WriteFile(fhand, line, (DWORD)strlen(line), &wrote, 0); +} + +BOOL ExportSettings(HWND hwndDlg, TCHAR *filename, OBJLIST& flist, OBJLIST& clist, OBJLIST& elist) +{ + int i; + char header[512], buff[1024], abuff[1024]; + + HANDLE fhand = CreateFile(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if(fhand == INVALID_HANDLE_VALUE) { + MessageBox(hwndDlg, filename, TranslateT("Failed to create file"), MB_ICONWARNING | MB_OK); + return FALSE; + } + + header[0] = 0; + + strcpy(buff, "SETTINGS:\r\n"); + WriteLine(fhand, buff); + + for ( i = 0; i < flist.getCount(); i++ ) { + TFontID& F = flist[i]; + + mir_snprintf(buff, SIZEOF(buff), "\r\n[%s]", F.dbSettingsGroup); + if ( strcmp( buff, header ) != 0) { + strcpy(header, buff); + WriteLine(fhand, buff); + } + + if ( F.flags & FIDF_APPENDNAME ) + mir_snprintf( buff, SIZEOF(buff), "%sName=s", F.prefix ); + else + mir_snprintf( buff, SIZEOF(buff), "%s=s", F.prefix ); + + #if defined( _UNICODE ) + WideCharToMultiByte(code_page, 0, F.value.szFace, -1, abuff, 1024, 0, 0); + abuff[1023]=0; + strcat( buff, abuff ); + #else + strcat( buff, F.value.szFace ); + #endif + WriteLine(fhand, buff); + + mir_snprintf(buff, SIZEOF(buff), "%sSize=b", F.prefix); + if ( F.flags & FIDF_SAVEACTUALHEIGHT ) { + HDC hdc; + SIZE size; + HFONT hFont, hOldFont; + LOGFONT lf; + CreateFromFontSettings( &F.value, &lf ); + hFont = CreateFontIndirect(&lf); + + hdc = GetDC(hwndDlg); + hOldFont = (HFONT)SelectObject(hdc, hFont); + GetTextExtentPoint32(hdc, _T("_W"), 2, &size); + ReleaseDC(hwndDlg, hdc); + SelectObject(hdc, hOldFont); + DeleteObject(hFont); + + strcat(buff, _itoa((BYTE)size.cy, abuff, 10)); + } + else if(F.flags & FIDF_SAVEPOINTSIZE) { + HDC hdc = GetDC(hwndDlg); + strcat(buff, _itoa((BYTE)-MulDiv(F.value.size, 72, GetDeviceCaps(hdc, LOGPIXELSY)), abuff, 10)); + ReleaseDC(hwndDlg, hdc); + } + else strcat(buff, _itoa((BYTE)F.value.size, abuff, 10)); + + WriteLine(fhand, buff); + + mir_snprintf(buff, SIZEOF(buff), "%sSty=b%d", F.prefix, (BYTE)F.value.style); + WriteLine(fhand, buff); + mir_snprintf(buff, SIZEOF(buff), "%sSet=b%d", F.prefix, (BYTE)F.value.charset); + WriteLine(fhand, buff); + mir_snprintf(buff, SIZEOF(buff), "%sCol=d%d", F.prefix, (DWORD)F.value.colour); + WriteLine(fhand, buff); + if(F.flags & FIDF_NOAS) { + mir_snprintf(buff, SIZEOF(buff), "%sAs=w%d", F.prefix, (WORD)0x00FF); + WriteLine(fhand, buff); + } + mir_snprintf(buff, SIZEOF(buff), "%sFlags=w%d", F.prefix, (WORD)F.flags); + WriteLine(fhand, buff); + } + + header[0] = 0; + for ( i=0; i < clist.getCount(); i++ ) { + TColourID& C = clist[i]; + + mir_snprintf(buff, SIZEOF(buff), "\r\n[%s]", C.dbSettingsGroup ); + if(strcmp(buff, header) != 0) { + strcpy(header, buff); + WriteLine(fhand, buff); + } + mir_snprintf(buff, SIZEOF(buff), "%s=d%d", C.setting, (DWORD)C.value ); + WriteLine(fhand, buff); + } + + header[0] = 0; + for ( i=0; i < elist.getCount(); i++ ) { + TEffectID& E = elist[i]; + + mir_snprintf(buff, SIZEOF(buff), "\r\n[%s]", E.dbSettingsGroup ); + if(strcmp(buff, header) != 0) { + strcpy(header, buff); + WriteLine(fhand, buff); + } + mir_snprintf(buff, SIZEOF(buff), "%sEffect=b%d", E.setting, E.value.effectIndex ); + WriteLine(fhand, buff); + mir_snprintf(buff, SIZEOF(buff), "%sEffectCol1=d%d", E.setting, E.value.baseColour ); + WriteLine(fhand, buff); + mir_snprintf(buff, SIZEOF(buff), "%sEffectCol2=d%d", E.setting, E.value.secondaryColour ); + WriteLine(fhand, buff); + } + + + CloseHandle(fhand); + return TRUE; +} + +void OptionsChanged() +{ + NotifyEventHooks(hFontReloadEvent, 0, 0); + NotifyEventHooks(hColourReloadEvent, 0, 0); +} + +TOOLINFO ti; +int x, y; + +UINT_PTR CALLBACK CFHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) { + switch(uiMsg) { + case WM_INITDIALOG: { + CHOOSEFONT* cf = (CHOOSEFONT *)lParam; + + TranslateDialogDefault(hdlg); + ShowWindow(GetDlgItem(hdlg, 1095), SW_HIDE); + if(cf && (cf->lCustData & FIDF_DISABLESTYLES)) { + EnableWindow(GetDlgItem(hdlg, 1137), FALSE); + ShowWindow(GetDlgItem(hdlg, 1137), SW_HIDE); + ShowWindow(GetDlgItem(hdlg, 1095), SW_SHOW); + } + return 0; + } + } + + return 0; +} + +struct FSUIListItemData +{ + int font_id; + int colour_id; + int effect_id; +}; + + +static BOOL sttFsuiBindColourIdToFonts(HWND hwndList, const TCHAR *name, const TCHAR *backgroundGroup, const TCHAR *backgroundName, int colourId) +{ + int i; + BOOL res = FALSE; + for (i = SendMessage(hwndList, LB_GETCOUNT, 0, 0); i--; ) + { + FSUIListItemData *itemData = (FSUIListItemData *)SendMessage(hwndList, LB_GETITEMDATA, i, 0); + if ( itemData && itemData->font_id >= 0) { + TFontID& F = font_id_list_w2[itemData->font_id]; + + if ( name && !_tcscmp( F.name, name )) { + itemData->colour_id = colourId; + res = TRUE; + } + + if ( backgroundGroup && backgroundName && !_tcscmp( F.backgroundGroup, backgroundGroup) && !_tcscmp( F.backgroundName, backgroundName)) { + itemData->colour_id = colourId; + res = TRUE; + } } } + + return res; +} + +static BOOL sttFsuiBindEffectIdToFonts(HWND hwndList, const TCHAR *name, int effectId) +{ + int i; + BOOL res = FALSE; + for (i = SendMessage(hwndList, LB_GETCOUNT, 0, 0); i--; ) + { + FSUIListItemData *itemData = (FSUIListItemData *)SendMessage(hwndList, LB_GETITEMDATA, i, 0); + if ( itemData && itemData->font_id >= 0) { + TFontID& F = font_id_list_w2[itemData->font_id]; + + if ( name && !_tcscmp( F.name, name )) { + itemData->effect_id = effectId; + res = TRUE; + } + + } } + + return res; +} + +static HTREEITEM sttFindNamedTreeItemAt(HWND hwndTree, HTREEITEM hItem, const TCHAR *name) +{ + TVITEM tvi = {0}; + TCHAR str[MAX_PATH]; + + if (hItem) + tvi.hItem = TreeView_GetChild(hwndTree, hItem); + else + tvi.hItem = TreeView_GetRoot(hwndTree); + + if (!name) + return tvi.hItem; + + tvi.mask = TVIF_TEXT; + tvi.pszText = str; + tvi.cchTextMax = MAX_PATH; + + while (tvi.hItem) + { + TreeView_GetItem(hwndTree, &tvi); + + if (!lstrcmp(tvi.pszText, name)) + return tvi.hItem; + + tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem); + } + return NULL; +} + +static void sttFsuiCreateSettingsTreeNode(HWND hwndTree, const TCHAR *groupName) +{ + TCHAR itemName[1024]; + TCHAR* sectionName; + int sectionLevel = 0; + + HTREEITEM hSection = NULL; + lstrcpy(itemName, groupName); + sectionName = itemName; + + while (sectionName) { + // allow multi-level tree + TCHAR* pItemName = sectionName; + HTREEITEM hItem; + + if (sectionName = _tcschr(sectionName, '/')) { + // one level deeper + *sectionName = 0; + } + + pItemName = TranslateTS( pItemName ); + + hItem = sttFindNamedTreeItemAt(hwndTree, hSection, pItemName); + if (!sectionName || !hItem) { + if (!hItem) { + TVINSERTSTRUCT tvis = {0}; + TreeItem *treeItem = (TreeItem *)mir_alloc(sizeof(TreeItem)); + treeItem->groupName = sectionName ? NULL : mir_tstrdup(groupName); + treeItem->paramName = mir_t2a(itemName); + + tvis.hParent = hSection; + tvis.hInsertAfter = TVI_SORT;//TVI_LAST; + tvis.item.mask = TVIF_TEXT|TVIF_PARAM; + tvis.item.pszText = pItemName; + tvis.item.lParam = (LPARAM)treeItem; + + hItem = TreeView_InsertItem(hwndTree, &tvis); + + ZeroMemory(&tvis.item, sizeof(tvis.item)); + tvis.item.hItem = hItem; + tvis.item.mask = TVIF_HANDLE|TVIF_STATE; + tvis.item.state = tvis.item.stateMask = DBGetContactSettingByte(NULL, "FontServiceUI", treeItem->paramName, TVIS_EXPANDED ); + TreeView_SetItem(hwndTree, &tvis.item); + } } + + if (sectionName) { + *sectionName = '/'; + sectionName++; + } + + sectionLevel++; + + hSection = hItem; + } +} + +static void sttSaveCollapseState( HWND hwndTree ) +{ + HTREEITEM hti; + TVITEM tvi; + + hti = TreeView_GetRoot( hwndTree ); + while( hti != NULL ) { + HTREEITEM ht; + TreeItem *treeItem; + + tvi.mask = TVIF_STATE | TVIF_HANDLE | TVIF_CHILDREN | TVIF_PARAM; + tvi.hItem = hti; + tvi.stateMask = (DWORD)-1; + TreeView_GetItem( hwndTree, &tvi ); + + if( tvi.cChildren > 0 ) { + treeItem = (TreeItem *)tvi.lParam; + if ( tvi.state & TVIS_EXPANDED ) + DBWriteContactSettingByte(NULL, "FontServiceUI", treeItem->paramName, TVIS_EXPANDED ); + else + DBWriteContactSettingByte(NULL, "FontServiceUI", treeItem->paramName, 0 ); + } + + ht = TreeView_GetChild( hwndTree, hti ); + if( ht == NULL ) { + ht = TreeView_GetNextSibling( hwndTree, hti ); + while( ht == NULL ) { + hti = TreeView_GetParent( hwndTree, hti ); + if( hti == NULL ) break; + ht = TreeView_GetNextSibling( hwndTree, hti ); + } } + + hti = ht; +} } + +static void sttFreeListItems(HWND hList) +{ + int idx = 0; + LRESULT res; + FSUIListItemData *itemData; + int count = SendMessage( hList, LB_GETCOUNT, 0, 0 ); + if ( count > 0 ) { + while ( idx < count) { + res = SendMessage( hList, LB_GETITEMDATA, idx++, 0 ); + itemData = (FSUIListItemData *)res; + if ( itemData && res != LB_ERR ) + mir_free( itemData ); + } + } +} + +static BOOL ShowEffectButton( HWND hwndDlg, BOOL bShow ) +{ + ShowWindow(GetDlgItem(hwndDlg, IDC_BKGCOLOUR), bShow ? SW_HIDE : SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_BKGCOLOUR_STATIC), bShow ? SW_HIDE : SW_SHOW); + + ShowWindow(GetDlgItem(hwndDlg, IDC_EFFECT), bShow ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_EFFECT_STATIC), bShow ? SW_SHOW : SW_HIDE); + return TRUE; +} + +static INT_PTR CALLBACK ChooseEffectDlgProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + static TEffectSettings * pEffect = NULL; + + switch (uMsg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + pEffect = ( TEffectSettings*) lParam; + { + int i; + TCHAR * ModernEffectNames[]= + { + _T(""), + _T("Shadow at left"), + _T("Shadow at right"), + _T("Outline"), + _T("Outline smooth"), + _T("Smooth bump"), + _T("Contour thin"), + _T("Contour heavy"), + }; + + for ( i=0; ieffectIndex ) { + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COMBO,CB_SETCURSEL, i, 0 ); + break; + } } } + + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR1,CPM_SETCOLOUR,0,pEffect->baseColour&0x00FFFFFF); + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR2,CPM_SETCOLOUR,0,pEffect->secondaryColour&0x00FFFFFF); + + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR_SPIN1,UDM_SETRANGE,0,MAKELONG(255,0)); + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR_SPIN2,UDM_SETRANGE,0,MAKELONG(255,0)); + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR_SPIN1,UDM_SETPOS,0,MAKELONG((BYTE)~((BYTE)((pEffect->baseColour&0xFF000000)>>24)),0)); + SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR_SPIN2,UDM_SETPOS,0,MAKELONG((BYTE)~((BYTE)((pEffect->secondaryColour&0xFF000000)>>24)),0)); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + { + int i = SendDlgItemMessage(hwndDlg,IDC_EFFECT_COMBO,CB_GETCURSEL, 0, 0 ); + pEffect->effectIndex=(BYTE)SendDlgItemMessage(hwndDlg,IDC_EFFECT_COMBO,CB_GETITEMDATA,i,0); + pEffect->baseColour=SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR1,CPM_GETCOLOUR,0,0)|((~(BYTE)SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR_SPIN1,UDM_GETPOS,0,0))<<24); + pEffect->secondaryColour=SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR2,CPM_GETCOLOUR,0,0)|((~(BYTE)SendDlgItemMessage(hwndDlg,IDC_EFFECT_COLOUR_SPIN2,UDM_GETPOS,0,0))<<24);; + } + EndDialog( hwndDlg, IDOK ); + return TRUE; + + case IDCANCEL: + EndDialog( hwndDlg, IDCANCEL ); + return TRUE; + } + break; + case WM_DESTROY: + pEffect = NULL; + return TRUE; + } + return FALSE; +} + +static BOOL ChooseEffectDialog( HWND hwndParent, TEffectSettings * es) +{ + return ( DialogBoxParam( hMirandaInst, MAKEINTRESOURCE(IDD_CHOOSE_FONT_EFFECT), hwndParent, ChooseEffectDlgProc, (LPARAM) es ) == IDOK ); +} + +static void sttSaveFontData(HWND hwndDlg, TFontID &F) +{ + LOGFONT lf; + char str[128]; + + if ( F.flags & FIDF_APPENDNAME ) + mir_snprintf(str, SIZEOF(str), "%sName", F.prefix); + else + mir_snprintf(str, SIZEOF(str), "%s", F.prefix); + + if ( DBWriteContactSettingTString( NULL, F.dbSettingsGroup, str, F.value.szFace )) { + #if defined( _UNICODE ) + char buff[1024]; + WideCharToMultiByte(code_page, 0, F.value.szFace, -1, buff, 1024, 0, 0); + DBWriteContactSettingString(NULL, F.dbSettingsGroup, str, buff); + #endif + } + + mir_snprintf(str, SIZEOF(str), "%sSize", F.prefix); + if ( F.flags & FIDF_SAVEACTUALHEIGHT ) { + HDC hdc; + SIZE size; + HFONT hFont, hOldFont; + CreateFromFontSettings( &F.value, &lf ); + hFont = CreateFontIndirect( &lf ); + hdc = GetDC(hwndDlg); + hOldFont = (HFONT)SelectObject( hdc, hFont ); + GetTextExtentPoint32( hdc, _T("_W"), 2, &size); + ReleaseDC(hwndDlg, hdc); + SelectObject(hdc, hOldFont); + DeleteObject(hFont); + + DBWriteContactSettingByte(NULL, F.dbSettingsGroup, str, (char)size.cy); + } + else if ( F.flags & FIDF_SAVEPOINTSIZE ) { + HDC hdc = GetDC(hwndDlg); + DBWriteContactSettingByte(NULL, F.dbSettingsGroup, str, (BYTE)-MulDiv(F.value.size, 72, GetDeviceCaps(hdc, LOGPIXELSY))); + ReleaseDC(hwndDlg, hdc); + } + else DBWriteContactSettingByte(NULL, F.dbSettingsGroup, str, F.value.size); + + mir_snprintf(str, SIZEOF(str), "%sSty", F.prefix); + DBWriteContactSettingByte(NULL, F.dbSettingsGroup, str, F.value.style); + mir_snprintf(str, SIZEOF(str), "%sSet", F.prefix); + DBWriteContactSettingByte(NULL, F.dbSettingsGroup, str, F.value.charset); + mir_snprintf(str, SIZEOF(str), "%sCol", F.prefix); + DBWriteContactSettingDword(NULL, F.dbSettingsGroup, str, F.value.colour); + if ( F.flags & FIDF_NOAS ) { + mir_snprintf(str, SIZEOF(str), "%sAs", F.prefix); + DBWriteContactSettingWord(NULL, F.dbSettingsGroup, str, (WORD)0x00FF); + } + mir_snprintf(str, SIZEOF(str), "%sFlags", F.prefix); + DBWriteContactSettingWord(NULL, F.dbSettingsGroup, str, (WORD)F.flags); +} + +static INT_PTR CALLBACK DlgProcLogOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int i; + LOGFONT lf; + + static HBRUSH hBkgColourBrush = 0; + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + font_id_list_w2 = font_id_list; + font_id_list_w3 = font_id_list; + + colour_id_list_w2 = colour_id_list; + colour_id_list_w3 = colour_id_list; + + effect_id_list_w2 = effect_id_list; + effect_id_list_w3 = effect_id_list; + + for ( i = 0; i < font_id_list_w2.getCount(); i++ ) { + TFontID& F = font_id_list_w2[i]; + // sync settings with database + UpdateFontSettings( &F, &F.value ); + sttFsuiCreateSettingsTreeNode(GetDlgItem(hwndDlg, IDC_FONTGROUP), F.group); + } + + for ( i = 0; i < colour_id_list_w2.getCount(); i++ ) { + TColourID& C = colour_id_list_w2[i]; + + // sync settings with database + UpdateColourSettings( &C, &C.value ); + sttFsuiCreateSettingsTreeNode(GetDlgItem(hwndDlg, IDC_FONTGROUP), C.group); + } + + for ( i = 0; i < effect_id_list_w2.getCount(); i++ ) { + TEffectID& E = effect_id_list_w2[i]; + + // sync settings with database + UpdateEffectSettings( &E, &E.value ); + sttFsuiCreateSettingsTreeNode(GetDlgItem(hwndDlg, IDC_FONTGROUP), E.group); + } + } + + SendDlgItemMessage(hwndDlg, IDC_BKGCOLOUR, CPM_SETDEFAULTCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOW)); + return TRUE; + + case UM_SETFONTGROUP: + { + TreeItem *treeItem; + TCHAR *group_buff = NULL; + TVITEM tvi = {0}; + tvi.hItem = TreeView_GetSelection(GetDlgItem(hwndDlg, IDC_FONTGROUP)); + tvi.mask = TVIF_HANDLE|TVIF_PARAM; + TreeView_GetItem(GetDlgItem(hwndDlg, IDC_FONTGROUP), &tvi); + treeItem = (TreeItem *)tvi.lParam; + group_buff = treeItem->groupName; + + sttFreeListItems(GetDlgItem(hwndDlg, IDC_FONTLIST)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_RESETCONTENT, 0, 0); + + if (group_buff) { + BOOL need_restart = FALSE; + int fontId = 0, itemId; + int first_font_index = -1; + int colourId = 0; + int first_colour_index = -1; + int effectId = 0; + int first_effect_index = -1; + + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, WM_SETREDRAW, FALSE, 0); + + for ( fontId = 0; fontId < font_id_list_w2.getCount(); fontId++ ) { + TFontID& F = font_id_list_w2[fontId]; + if ( _tcsncmp( F.group, group_buff, 64 ) == 0 ) { + FSUIListItemData *itemData = ( FSUIListItemData* )mir_alloc(sizeof(FSUIListItemData)); + itemData->colour_id = -1; + itemData->effect_id = -1; + itemData->font_id = fontId; + + if ( first_font_index == -1 ) + first_font_index = fontId; + + itemId = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_ADDSTRING, (WPARAM)-1, (LPARAM)itemData); + need_restart |= (F.flags & FIDF_NEEDRESTART); + } } + +// ShowWindow( GetDlgItem(hwndDlg, IDC_STAT_RESTART), (need_restart ? SW_SHOW : SW_HIDE)); + + if ( hBkgColourBrush ) { + DeleteObject( hBkgColourBrush ); + hBkgColourBrush = 0; + } + + for ( colourId = 0; colourId < colour_id_list_w2.getCount(); colourId++ ) { + TColourID& C = colour_id_list_w2[colourId]; + if ( _tcsncmp( C.group, group_buff, 64 ) == 0 ) { + FSUIListItemData *itemData = NULL; + if ( first_colour_index == -1 ) + first_colour_index = colourId; + + if (!sttFsuiBindColourIdToFonts(GetDlgItem(hwndDlg, IDC_FONTLIST), C.name, C.group, C.name, colourId)) { + itemData = ( FSUIListItemData* )mir_alloc(sizeof(FSUIListItemData)); + itemData->colour_id = colourId; + itemData->font_id = -1; + itemData->effect_id = -1; + + itemId = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_ADDSTRING, (WPARAM)-1, (LPARAM)itemData); + } + + if ( _tcscmp( C.name, _T("Background") ) == 0 ) + hBkgColourBrush = CreateSolidBrush( C.value ); + } } + + if ( !hBkgColourBrush ) + hBkgColourBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW)); + + for ( effectId = 0; effectId < effect_id_list_w2.getCount(); effectId++ ) { + TEffectID& E = effect_id_list_w2[effectId]; + if ( _tcsncmp( E.group, group_buff, 64 ) == 0 ) { + FSUIListItemData *itemData = NULL; + if ( first_effect_index == -1 ) + first_effect_index = effectId; + + if (!sttFsuiBindEffectIdToFonts(GetDlgItem(hwndDlg, IDC_FONTLIST), E.name, effectId)) { + itemData = ( FSUIListItemData* )mir_alloc(sizeof(FSUIListItemData)); + itemData->effect_id = effectId; + itemData->font_id = -1; + itemData->colour_id = -1; + + itemId = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_ADDSTRING, (WPARAM)-1, (LPARAM)itemData); + } } } + + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, WM_SETREDRAW, TRUE, 0); + UpdateWindow(GetDlgItem(hwndDlg, IDC_FONTLIST)); + + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_SETSEL, TRUE, 0); + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_FONTLIST, LBN_SELCHANGE), 0); + } + else { + EnableWindow(GetDlgItem(hwndDlg, IDC_BKGCOLOUR), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_FONTCOLOUR), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_CHOOSEFONT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_RESET), FALSE); + ShowEffectButton(hwndDlg, FALSE); + } + return TRUE; + } + + case WM_MEASUREITEM: + { + MEASUREITEMSTRUCT *mis = (MEASUREITEMSTRUCT *)lParam; + HFONT hFont = NULL, hoFont = NULL; + SIZE fontSize; + BOOL bIsFont = FALSE; + FSUIListItemData *itemData = (FSUIListItemData *)mis->itemData; + TCHAR *itemName = NULL; + HDC hdc; + + if ((mis->CtlID != IDC_FONTLIST) || (mis->itemID == -1)) + break; + + if (!itemData) return FALSE; + + if (itemData->font_id >= 0) { + int iItem = itemData->font_id; + bIsFont = TRUE; + CreateFromFontSettings( &font_id_list_w2[iItem].value, &lf ); + hFont = CreateFontIndirect(&lf); + itemName = TranslateTS(font_id_list_w2[iItem].name); + } + + if (itemData->colour_id >= 0) { + int iItem = itemData->colour_id; + if ( !itemName ) + itemName = TranslateTS( colour_id_list_w2[iItem].name ); + } + + hdc = GetDC(GetDlgItem(hwndDlg, mis->CtlID)); + if ( hFont ) + hoFont = (HFONT) SelectObject(hdc, hFont); + else + hoFont = (HFONT) SelectObject(hdc, (HFONT)SendDlgItemMessage(hwndDlg, mis->CtlID, WM_GETFONT, 0, 0)); + + GetTextExtentPoint32(hdc, itemName, lstrlen(itemName), &fontSize); + if (hoFont) SelectObject(hdc, hoFont); + if (hFont) DeleteObject(hFont); + ReleaseDC(GetDlgItem(hwndDlg, mis->CtlID), hdc); + mis->itemWidth = fontSize.cx + 2*FSUI_FONTFRAMEHORZ + 4; + mis->itemHeight = fontSize.cy + 2*FSUI_FONTFRAMEVERT + 4; + return TRUE; + } + + case WM_DRAWITEM: + { + DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *) lParam; + HFONT hFont = NULL, hoFont = NULL; + COLORREF clBack = (COLORREF)-1; + COLORREF clText = GetSysColor(COLOR_WINDOWTEXT); + BOOL bIsFont = FALSE; + TCHAR *itemName = NULL; + + FSUIListItemData *itemData = (FSUIListItemData *)dis->itemData; + + FONTEFFECT Effect; + FONTEFFECT * pEffect = NULL; + + if(dis->CtlID != IDC_FONTLIST) + break; + + if (!itemData) return FALSE; + + if ( itemData->font_id >= 0 ) { + int iItem = itemData->font_id; + bIsFont = TRUE; + CreateFromFontSettings(&font_id_list_w2[iItem].value, &lf ); + hFont = CreateFontIndirect(&lf); + itemName = TranslateTS(font_id_list_w2[iItem].name); + clText = font_id_list_w2[iItem].value.colour; + } + + if ( itemData->colour_id >= 0 ) { + int iItem = itemData->colour_id; + if (bIsFont) + clBack = colour_id_list_w2[iItem].value; + else { + clText = colour_id_list_w2[iItem].value; + itemName = TranslateTS(colour_id_list_w2[iItem].name); + + } } + + if ( itemData->effect_id >= 0 ) { + int iItem = itemData->effect_id; + + Effect.effectIndex = effect_id_list_w2[iItem].value.effectIndex; + Effect.baseColour = effect_id_list_w2[iItem].value.baseColour; + Effect.secondaryColour = effect_id_list_w2[iItem].value.secondaryColour; + pEffect = &Effect; + + if (!bIsFont) + itemName = TranslateTS(effect_id_list_w2[iItem].name); + } + + if (hFont) + hoFont = (HFONT) SelectObject(dis->hDC, hFont); + else + hoFont = (HFONT) SelectObject(dis->hDC, (HFONT)SendDlgItemMessage(hwndDlg, dis->CtlID, WM_GETFONT, 0, 0)); + + SetBkMode(dis->hDC, TRANSPARENT); + + if (dis->itemState & ODS_SELECTED) { + SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_HIGHLIGHT)); + } + else { + SetTextColor(dis->hDC, bIsFont?clText:GetSysColor(COLOR_WINDOWTEXT)); + if (bIsFont && (clBack != (COLORREF)-1)) { + HBRUSH hbrTmp = CreateSolidBrush(clBack); + FillRect(dis->hDC, &dis->rcItem, hbrTmp); + DeleteObject(hbrTmp); + } + else FillRect(dis->hDC, &dis->rcItem, bIsFont ? hBkgColourBrush : GetSysColorBrush(COLOR_WINDOW)); + } + + if ( bIsFont ) { + HBRUSH hbrBack; + RECT rc; + + if (clBack != (COLORREF)-1) + hbrBack = CreateSolidBrush(clBack); + else { + LOGBRUSH lb; + GetObject(hBkgColourBrush, sizeof(lf), &lb); + hbrBack = CreateBrushIndirect(&lb); + } + + SetRect(&rc, + dis->rcItem.left+FSUI_COLORBOXLEFT, + dis->rcItem.top+FSUI_FONTFRAMEVERT, + dis->rcItem.left+FSUI_COLORBOXLEFT+FSUI_COLORBOXWIDTH, + dis->rcItem.bottom-FSUI_FONTFRAMEVERT); + + FillRect(dis->hDC, &rc, hbrBack); + DeleteObject(hbrBack); + + FrameRect(dis->hDC, &rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + rc.left += 1; + rc.top += 1; + rc.right -= 1; + rc.bottom -= 1; + FrameRect(dis->hDC, &rc, GetSysColorBrush(COLOR_HIGHLIGHTTEXT)); + + SetTextColor(dis->hDC, clText); + + DrawTextWithEffect(dis->hDC, _T("abc"), 3, &rc, DT_CENTER|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER|DT_WORD_ELLIPSIS, pEffect ); + + if (dis->itemState & ODS_SELECTED) { + SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + pEffect = NULL; // Do not draw effect on selected item name text + } + rc = dis->rcItem; + rc.left += FSUI_FONTLEFT; + DrawTextWithEffect(dis->hDC, itemName, (int)_tcslen(itemName), &rc, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER|DT_WORD_ELLIPSIS, pEffect ); + } else + { + RECT rc; + HBRUSH hbrTmp; + SetRect(&rc, + dis->rcItem.left+FSUI_COLORBOXLEFT, + dis->rcItem.top+FSUI_FONTFRAMEVERT, + dis->rcItem.left+FSUI_COLORBOXLEFT+FSUI_COLORBOXWIDTH, + dis->rcItem.bottom-FSUI_FONTFRAMEVERT); + + hbrTmp = CreateSolidBrush(clText); + FillRect(dis->hDC, &rc, hbrTmp); + DeleteObject(hbrTmp); + + FrameRect(dis->hDC, &rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + rc.left += 1; + rc.top += 1; + rc.right -= 1; + rc.bottom -= 1; + FrameRect(dis->hDC, &rc, GetSysColorBrush(COLOR_HIGHLIGHTTEXT)); + + rc = dis->rcItem; + rc.left += FSUI_FONTLEFT; + + DrawTextWithEffect(dis->hDC, itemName, (int)_tcslen(itemName), &rc, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER|DT_WORD_ELLIPSIS, pEffect ); + } + if (hoFont) SelectObject(dis->hDC, hoFont); + if (hFont) DeleteObject(hFont); + return TRUE; + } + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_FONTLIST: + if (HIWORD(wParam) == LBN_SELCHANGE) { + int selCount, i; + + char bEnableFont = 1; + char bEnableClText = 1; + char bEnableClBack = 1; + char bEnableEffect = 1; + char bEnableReset = 1; + + COLORREF clBack = 0xffffffff; + COLORREF clText = 0xffffffff; + + if (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, (WPARAM)0, (LPARAM)0)) { + int *selItems = (int *)mir_alloc(font_id_list_w2.getCount() * sizeof(int)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELITEMS, (WPARAM)selCount, (LPARAM)selItems); + for (i = 0; i < selCount; ++i) { + FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + if (IsBadReadPtr(itemData, sizeof(FSUIListItemData))) continue; // prevent possible problems with corrupted itemData + + if (bEnableClBack && (itemData->colour_id < 0)) + bEnableClBack = 0; + if (bEnableEffect && (itemData->effect_id < 0)) + bEnableEffect = 0; + if (bEnableFont && (itemData->font_id < 0)) + bEnableFont = 0; + if (!bEnableFont || bEnableClText && (itemData->font_id < 0)) + bEnableClText = 0; + if (bEnableReset && (itemData->font_id >= 0) && !(font_id_list_w2[itemData->font_id].flags&FIDF_DEFAULTVALID)) + bEnableReset = 0; + + if (bEnableClBack && (itemData->colour_id >= 0) && (clBack == 0xffffffff)) + clBack = colour_id_list_w2[itemData->colour_id].value; + if (bEnableClText && (itemData->font_id >= 0) && (clText == 0xffffffff)) + clText = font_id_list_w2[itemData->font_id].value.colour; + } + mir_free(selItems); + } + else { + bEnableFont = 0; + bEnableClText = 0; + bEnableClBack = 0; + bEnableReset = 0; + bEnableEffect = 0; + } + + EnableWindow(GetDlgItem(hwndDlg, IDC_BKGCOLOUR), bEnableClBack); + ShowEffectButton( hwndDlg, bEnableEffect && !bEnableClBack ); + + EnableWindow(GetDlgItem(hwndDlg, IDC_FONTCOLOUR), bEnableClText); + EnableWindow(GetDlgItem(hwndDlg, IDC_CHOOSEFONT), bEnableFont); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_RESET), bEnableReset); + + if (bEnableClBack) SendDlgItemMessage(hwndDlg, IDC_BKGCOLOUR, CPM_SETCOLOUR, 0, clBack); + if (bEnableClText) SendDlgItemMessage(hwndDlg, IDC_FONTCOLOUR, CPM_SETCOLOUR, 0, clText); + + return TRUE; + } + + if (HIWORD(wParam) != LBN_DBLCLK) + return TRUE; + + //fall through + + case IDC_CHOOSEFONT: + { + int selCount; + if (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, 0, 0)) { + FSUIListItemData *itemData; + CHOOSEFONT cf = { 0 }; + int i; + int *selItems = (int *)mir_alloc(selCount * sizeof(int)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELITEMS, (WPARAM)selCount, (LPARAM) selItems); + itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[0], 0); + if (itemData->font_id < 0) { + mir_free(selItems); + if (itemData->colour_id >= 0) + SendDlgItemMessage(hwndDlg, IDC_BKGCOLOUR, WM_LBUTTONUP, 0, 0); + return TRUE; + } + + TFontID& F = font_id_list_w2[itemData->font_id]; + + CreateFromFontSettings(&F.value, &lf ); + + cf.lStructSize = sizeof(cf); + cf.hwndOwner = hwndDlg; + cf.lpLogFont = &lf; + cf.lCustData = 0; + + if ( F.flags & FIDF_ALLOWEFFECTS ) + { + cf.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS | CF_ENABLETEMPLATE | CF_ENABLEHOOK; + // use custom font dialog to disable colour selection + cf.hInstance = hMirandaInst; + cf.lpTemplateName = MAKEINTRESOURCE(IDD_CUSTOM_FONT); + cf.lpfnHook = CFHookProc; + } + else if ( F.flags & FIDF_DISABLESTYLES ) { // no style selection, mutually exclusive with FIDF_ALLOWEFFECTS + cf.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_ENABLETEMPLATE | CF_ENABLEHOOK | CF_TTONLY | CF_NOOEMFONTS; + cf.lCustData = F.flags; + cf.hInstance = hMirandaInst; + cf.lpTemplateName = MAKEINTRESOURCE(IDD_CUSTOM_FONT); + cf.lpfnHook = CFHookProc; + lf.lfWeight = FW_NORMAL; + lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE; + } + else cf.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS; + + if (ChooseFont(&cf)) { + for (i = 0; i < selCount; ++i) { + FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + if (itemData->font_id < 0) + continue; + + TFontID& F1 = font_id_list_w2[itemData->font_id]; + F1.value.size = (char)lf.lfHeight; + F1.value.style = (lf.lfWeight >= FW_BOLD ? DBFONTF_BOLD : 0) | (lf.lfItalic ? DBFONTF_ITALIC : 0) | (lf.lfUnderline ? DBFONTF_UNDERLINE : 0) | (lf.lfStrikeOut ? DBFONTF_STRIKEOUT : 0); + F1.value.charset = lf.lfCharSet; + _tcscpy(F1.value.szFace, lf.lfFaceName); + + MEASUREITEMSTRUCT mis = { 0 }; + mis.CtlID = IDC_FONTLIST; + mis.itemID = selItems[i]; + mis.itemData = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + SendMessage(hwndDlg, WM_MEASUREITEM, 0, (LPARAM) & mis); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_SETITEMHEIGHT, selItems[i], mis.itemHeight); + } + InvalidateRect(GetDlgItem(hwndDlg, IDC_FONTLIST), NULL, TRUE); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNDO), TRUE); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + + mir_free(selItems); + } + return TRUE; + } + case IDC_EFFECT: + { + int selCount; + if (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, 0, 0)) { + FSUIListItemData *itemData; + TEffectSettings es = { 0 }; + int i; + int *selItems = (int *)mir_alloc(selCount * sizeof(int)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELITEMS, (WPARAM)selCount, (LPARAM) selItems); + itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[0], 0); + TEffectID& E = effect_id_list_w2[itemData->effect_id]; + es = E.value; + if ( ChooseEffectDialog(hwndDlg, &es) ) { + for (i = 0; i < selCount; ++i) { + FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + if (itemData->effect_id < 0) + continue; + + TEffectID& E1 = effect_id_list_w2[itemData->effect_id]; + E1.value = es; + } + InvalidateRect(GetDlgItem(hwndDlg, IDC_FONTLIST), NULL, TRUE); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNDO), TRUE); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + + mir_free(selItems); + } + break; + } + case IDC_FONTCOLOUR: + { + int selCount, i; + if (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, 0, 0)) { + int *selItems = (int *)mir_alloc(selCount * sizeof(int)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELITEMS, (WPARAM)selCount, (LPARAM) selItems); + for (i = 0; i < selCount; i++) { + FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + if (itemData->font_id < 0) continue; + font_id_list_w2[itemData->font_id].value.colour = SendDlgItemMessage(hwndDlg, IDC_FONTCOLOUR, CPM_GETCOLOUR, 0, 0); + } + mir_free(selItems); + InvalidateRect(GetDlgItem(hwndDlg, IDC_FONTLIST), NULL, FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNDO), TRUE); + } + break; + } + case IDC_BKGCOLOUR: + { + int selCount, i; + if (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, 0, 0)) { + int *selItems = (int *)mir_alloc(selCount * sizeof(int)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELITEMS, (WPARAM)selCount, (LPARAM) selItems); + for (i = 0; i < selCount; i++) { + FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + if (itemData->colour_id < 0) continue; + colour_id_list_w2[itemData->colour_id].value = SendDlgItemMessage(hwndDlg, IDC_BKGCOLOUR, CPM_GETCOLOUR, 0, 0); + + if ( _tcscmp( colour_id_list_w2[itemData->colour_id].name, _T("Background") ) == 0 ) + { + if ( hBkgColourBrush ) DeleteObject( hBkgColourBrush ); + hBkgColourBrush = CreateSolidBrush( colour_id_list_w2[itemData->colour_id].value ); + } + } + mir_free(selItems); + InvalidateRect(GetDlgItem(hwndDlg, IDC_FONTLIST), NULL, FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNDO), TRUE); + } + break; + } + case IDC_BTN_RESET: + { + int selCount; + if (font_id_list_w2.getCount() && (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, (WPARAM)0, (LPARAM)0))) { + int *selItems = (int *)mir_alloc(font_id_list_w2.getCount() * sizeof(int)); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELITEMS, (WPARAM)selCount, (LPARAM)selItems); + for (i = 0; i < selCount; ++i) { + FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + if (IsBadReadPtr(itemData, sizeof(FSUIListItemData))) continue; // prevent possible problems with corrupted itemData + + if((itemData->font_id >= 0) && (font_id_list_w2[itemData->font_id].flags & FIDF_DEFAULTVALID)) { + font_id_list_w2[itemData->font_id].value = font_id_list_w2[itemData->font_id].deffontsettings; + + MEASUREITEMSTRUCT mis = { 0 }; + mis.CtlID = IDC_FONTLIST; + mis.itemID = selItems[i]; + mis.itemData = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0); + SendMessage(hwndDlg, WM_MEASUREITEM, 0, (LPARAM) & mis); + SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_SETITEMHEIGHT, selItems[i], mis.itemHeight); + } + + if (itemData->colour_id >= 0) + colour_id_list_w2[itemData->colour_id].value = colour_id_list_w2[itemData->colour_id].defcolour; + + if (itemData->effect_id >= 0) + effect_id_list_w2[itemData->effect_id].value = effect_id_list_w2[itemData->effect_id].defeffect; + + } + mir_free(selItems); + InvalidateRect(GetDlgItem(hwndDlg, IDC_FONTLIST), NULL, TRUE); + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_FONTLIST, LBN_SELCHANGE), 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNDO), TRUE); + } + break; + } + case IDC_BTN_EXPORT: + { + TCHAR fname_buff[MAX_PATH], filter[MAX_PATH]; + mir_sntprintf(filter, SIZEOF(filter), _T("%s (*.ini)%c*.ini%c%s (*.txt)%c*.TXT%c%s (*.*)%c*.*%c"), TranslateT("Configuration Files"), 0, 0, TranslateT("Text Files"), 0, 0, TranslateT("All Files"), 0, 0); + + OPENFILENAME ofn = {0}; + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = fname_buff; + ofn.lpstrFile[0] = '\0'; + ofn.nMaxFile = MAX_PATH; + ofn.hwndOwner = hwndDlg; + ofn.Flags = OFN_NOREADONLYRETURN | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT; + ofn.lpstrFilter = filter; + ofn.nFilterIndex = 1; + + ofn.lpstrDefExt = _T("ini"); + + if ( GetSaveFileName( &ofn ) == TRUE ) + if ( !ExportSettings( hwndDlg, ofn.lpstrFile, font_id_list, colour_id_list, effect_id_list )) + MessageBox(hwndDlg, TranslateT("Error writing file"), TranslateT("Error"), MB_ICONWARNING | MB_OK); + return TRUE; + } + case IDC_BTN_UNDO: + font_id_list_w2 = font_id_list_w3; + colour_id_list_w2 = colour_id_list_w3; + effect_id_list_w2 = effect_id_list_w3; + EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UNDO), FALSE); + + SendMessage(hwndDlg, UM_SETFONTGROUP, 0, 0); + break; + + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + + case WM_NOTIFY: + if (((LPNMHDR) lParam)->idFrom == 0 && ((LPNMHDR) lParam)->code == PSN_APPLY ) { + char str[32]; + + font_id_list_w3 = font_id_list; + colour_id_list_w3 = colour_id_list; + effect_id_list_w3 = effect_id_list; + + EnableWindow( GetDlgItem(hwndDlg, IDC_BTN_UNDO), TRUE ); + + font_id_list = font_id_list_w2; + colour_id_list = colour_id_list_w2; + effect_id_list = effect_id_list_w2; + + for ( i=0; i < font_id_list_w2.getCount(); i++ ) { + TFontID& F = font_id_list_w2[i]; + sttSaveFontData(hwndDlg, F); + } + + for ( i=0; i < colour_id_list_w2.getCount(); i++ ) { + TColourID& C = colour_id_list_w2[i]; + + mir_snprintf(str, SIZEOF(str), "%s", C.setting); + DBWriteContactSettingDword(NULL, C.dbSettingsGroup, str, C.value); + } + + for ( i=0; i < effect_id_list_w2.getCount(); i++ ) { + TEffectID& E = effect_id_list_w2[i]; + + mir_snprintf(str, SIZEOF(str), "%sEffect", E.setting); + DBWriteContactSettingByte(NULL, E.dbSettingsGroup, str, E.value.effectIndex); + + mir_snprintf(str, SIZEOF(str), "%sEffectCol1", E.setting); + DBWriteContactSettingDword(NULL, E.dbSettingsGroup, str, E.value.baseColour); + + mir_snprintf(str, SIZEOF(str), "%sEffectCol2", E.setting); + DBWriteContactSettingDword(NULL, E.dbSettingsGroup, str, E.value.secondaryColour); + } + + OptionsChanged(); + return TRUE; + } + + if (((LPNMHDR) lParam)->idFrom == IDC_FONTGROUP) { + switch(((NMHDR*)lParam)->code) { + case TVN_SELCHANGEDA: // !!!! This needs to be here - both !! + case TVN_SELCHANGEDW: + SendMessage(hwndDlg, UM_SETFONTGROUP, 0, 0); + break; + + case TVN_DELETEITEMA: // no idea why both TVN_SELCHANGEDA/W should be there but let's keep this both too... + case TVN_DELETEITEMW: + { + TreeItem *treeItem = (TreeItem *)(((LPNMTREEVIEW)lParam)->itemOld.lParam); + if (treeItem) { + mir_free(treeItem->groupName); + mir_free(treeItem->paramName); + mir_free(treeItem); + } + break; + } + } + } + break; + + case WM_DESTROY: + KillTimer(hwndDlg, TIMER_ID); + sttSaveCollapseState(GetDlgItem(hwndDlg, IDC_FONTGROUP)); + DeleteObject(hBkgColourBrush); + font_id_list_w2.destroy(); + font_id_list_w3.destroy(); + colour_id_list_w2.destroy(); + colour_id_list_w3.destroy(); + effect_id_list_w2.destroy(); + effect_id_list_w3.destroy(); + sttFreeListItems(GetDlgItem(hwndDlg, IDC_FONTLIST)); + break; + } + return FALSE; +} + +int OptInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {0}; + + odp.cbSize = sizeof(odp); + odp.cbSize = OPTIONPAGE_OLD_SIZE2; + odp.position = -790000000; + odp.hInstance = hMirandaInst;; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_FONTS); + odp.pszTitle = LPGEN("Fonts & Colors"); + odp.pszGroup = LPGEN("Customize"); + odp.flags = ODPF_BOLDGROUPS; + odp.nIDBottomSimpleControl = 0; + odp.pfnDlgProc = DlgProcLogOptions; + CallService( MS_OPT_ADDPAGE, wParam,( LPARAM )&odp ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static TFontID *sttFindFont(OBJLIST &fonts, char *module, char *prefix) +{ + for ( int i = 0; i < fonts.getCount(); i++ ) + { + TFontID& F = fonts[i]; + if ( !lstrcmpA(F.dbSettingsGroup, module) && !lstrcmpA(F.prefix, prefix) ) + return &F; + } + + return 0; +} + +static INT_PTR CALLBACK DlgProcModernOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int i; + LOGFONT lf; + + static TFontID fntHeader={0}, fntGeneral={0}, fntSmall={0}; + + switch (msg) { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwndDlg); + + fntHeader = *sttFindFont(font_id_list, "Fonts", "Header"); + UpdateFontSettings(&fntHeader, &fntHeader.value); + fntGeneral = *sttFindFont(font_id_list, "Fonts", "Generic"); + UpdateFontSettings(&fntGeneral, &fntGeneral.value); + fntSmall = *sttFindFont(font_id_list, "Fonts", "Small"); + UpdateFontSettings(&fntSmall, &fntSmall.value); + + return TRUE; + } + + case WM_DRAWITEM: + { + TFontID *pf = 0; + DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *) lParam; + switch (dis->CtlID) + { + case IDC_PREVIEWHEADER: + pf = &fntHeader; + break; + case IDC_PREVIEWGENERAL: + pf = &fntGeneral; + break; + case IDC_PREVIEWSMALL: + pf = &fntSmall; + break; + } + + if (!pf) break; + + HFONT hFont = NULL, hoFont = NULL; + COLORREF clText = GetSysColor(COLOR_WINDOWTEXT); + CreateFromFontSettings(&pf->value, &lf); + hFont = CreateFontIndirect(&lf); + hoFont = (HFONT) SelectObject(dis->hDC, hFont); + SetBkMode(dis->hDC, TRANSPARENT); + SetTextColor(dis->hDC, GetSysColor(COLOR_BTNTEXT)); + FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_BTNFACE)); + DrawText(dis->hDC, TranslateT("Sample Text"), (int)_tcslen(TranslateT("Sample Text")), &dis->rcItem, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER|DT_WORD_ELLIPSIS|DT_CENTER); + if (hoFont) SelectObject(dis->hDC, hoFont); + return TRUE; + } + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_CHOOSEFONTHEADER: + case IDC_CHOOSEFONTGENERAL: + case IDC_CHOOSEFONTSMALL: + { + CHOOSEFONT cf = { 0 }; + TFontID *pf = NULL; + switch (LOWORD(wParam)) + { + case IDC_CHOOSEFONTHEADER: + pf = &fntHeader; + break; + case IDC_CHOOSEFONTGENERAL: + pf = &fntGeneral; + break; + case IDC_CHOOSEFONTSMALL: + pf = &fntSmall; + break; + }; + + CreateFromFontSettings(&pf->value, &lf); + + cf.lStructSize = sizeof(cf); + cf.hwndOwner = hwndDlg; + cf.lpLogFont = &lf; + cf.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS; + if ( pf->flags & FIDF_ALLOWEFFECTS ) + { + cf.Flags |= CF_EFFECTS | CF_ENABLETEMPLATE | CF_ENABLEHOOK; + // use custom font dialog to disable colour selection + cf.hInstance = hMirandaInst; + cf.lpTemplateName = MAKEINTRESOURCE(IDD_CUSTOM_FONT); + cf.lpfnHook = CFHookProc; + } + + if (ChooseFont(&cf)) + { + pf->value.size = (char)lf.lfHeight; + pf->value.style = (lf.lfWeight >= FW_BOLD ? DBFONTF_BOLD : 0) | (lf.lfItalic ? DBFONTF_ITALIC : 0) | (lf.lfUnderline ? DBFONTF_UNDERLINE : 0) | (lf.lfStrikeOut ? DBFONTF_STRIKEOUT : 0); + pf->value.charset = lf.lfCharSet; + _tcscpy(pf->value.szFace, lf.lfFaceName); + + InvalidateRect(GetDlgItem(hwndDlg, IDC_PREVIEWHEADER), NULL, TRUE); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PREVIEWGENERAL), NULL, TRUE); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PREVIEWSMALL), NULL, TRUE); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + return TRUE; + } + } + break; + + case WM_NOTIFY: + if (((LPNMHDR) lParam)->idFrom == 0 && ((LPNMHDR) lParam)->code == PSN_APPLY ) { + for ( i=0; i < font_id_list.getCount(); i++ ) + { + TFontID &F = font_id_list[i]; + if (F.deffontsettings.charset == SYMBOL_CHARSET) continue; + + COLORREF cl = F.value.colour; + if ((F.flags&FIDF_CLASSMASK) == FIDF_CLASSHEADER || + (F.flags&FIDF_CLASSMASK) == 0 && + (_tcsstr(F.name, _T("Incoming nick")) || + _tcsstr(F.name, _T("Outgoing nick")) || + _tcsstr(F.name, _T("Incoming timestamp")) || + _tcsstr(F.name, _T("Outgoing timestamp"))) + ) + { + F.value = fntHeader.value; + } else + if ((F.flags&FIDF_CLASSMASK) == FIDF_CLASSSMALL) + { + F.value = fntSmall.value; + } else + { + F.value = fntGeneral.value; + } + F.value.colour = cl; + sttSaveFontData(hwndDlg, F); + } + + OptionsChanged(); + } + break; + } + return FALSE; +} + +INT_PTR CALLBACK AccMgrDlgProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK DlgPluginOpt(HWND, UINT, WPARAM, LPARAM); + +int FontsModernOptInit(WPARAM wParam, LPARAM lParam) +{ + static int iBoldControls[] = + { + IDC_TXT_TITLE1, IDC_TXT_TITLE2, IDC_TXT_TITLE3, + MODERNOPT_CTRL_LAST + }; + + MODERNOPTOBJECT obj = {0}; + obj.cbSize = sizeof(obj); + obj.dwFlags = MODEROPT_FLG_TCHAR|MODEROPT_FLG_NORESIZE; + obj.hIcon = LoadSkinnedIcon(SKINICON_OTHER_MIRANDA); + obj.hInstance = hMirandaInst; + obj.iSection = MODERNOPT_PAGE_SKINS; + obj.iType = MODERNOPT_TYPE_SUBSECTIONPAGE; + obj.iBoldControls = iBoldControls; + obj.lptzSubsection = LPGENT("Fonts"); + obj.lpzClassicGroup = "Customize"; + obj.lpzClassicPage = "Fonts"; + obj.lpzHelpUrl = "http://wiki.miranda-im.org/"; + + obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_FONTS); + obj.pfnDlgProc = DlgProcModernOptions; + CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj); + + obj.iSection = MODERNOPT_PAGE_ACCOUNTS; + obj.iType = MODERNOPT_TYPE_SECTIONPAGE; + obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_ACCOUNTS); + obj.pfnDlgProc = AccMgrDlgProc; + obj.lpzClassicGroup = NULL; + obj.lpzClassicPage = "Network"; + obj.lpzHelpUrl = "http://wiki.miranda-im.org/"; + CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj); + + obj.iSection = MODERNOPT_PAGE_MODULES; + obj.iType = MODERNOPT_TYPE_SECTIONPAGE; +// obj.lptzSubsection = LPGENT("Installed Plugins"); + obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_MODULES); + obj.pfnDlgProc = DlgPluginOpt; + obj.iBoldControls = iBoldControls; + obj.lpzClassicGroup = NULL; + obj.lpzClassicPage = NULL; + obj.lpzHelpUrl = "http://wiki.miranda-im.org/"; + CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj); + return 0; +} diff --git a/src/modules/fonts/FontService.cpp b/src/modules/fonts/FontService.cpp new file mode 100644 index 0000000000..f0f1afae16 --- /dev/null +++ b/src/modules/fonts/FontService.cpp @@ -0,0 +1,126 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "m_fontservice.h" + +#include "FontService.h" + +int code_page = CP_ACP; +HANDLE hFontReloadEvent, hColourReloadEvent; + +int OptInit( WPARAM, LPARAM ); +int FontsModernOptInit(WPARAM wParam, LPARAM lParam); + +INT_PTR RegisterFont(WPARAM wParam, LPARAM lParam); +INT_PTR RegisterFontW(WPARAM wParam, LPARAM lParam); + +INT_PTR GetFont(WPARAM wParam, LPARAM lParam); +INT_PTR GetFontW(WPARAM wParam, LPARAM lParam); + +INT_PTR RegisterColour(WPARAM wParam, LPARAM lParam); +INT_PTR RegisterColourW(WPARAM wParam, LPARAM lParam); + +INT_PTR GetColour(WPARAM wParam, LPARAM lParam); +INT_PTR GetColourW(WPARAM wParam, LPARAM lParam); + +INT_PTR RegisterEffect(WPARAM wParam, LPARAM lParam); +INT_PTR RegisterEffectW(WPARAM wParam, LPARAM lParam); + +INT_PTR GetEffect(WPARAM wParam, LPARAM lParam); +INT_PTR GetEffectW(WPARAM wParam, LPARAM lParam); + +static int OnModulesLoaded(WPARAM, LPARAM) +{ + HookEvent(ME_OPT_INITIALISE, OptInit); + HookEvent(ME_MODERNOPT_INITIALIZE, FontsModernOptInit); + return 0; +} + +static int OnPreShutdown(WPARAM, LPARAM) +{ + DestroyHookableEvent(hFontReloadEvent); + DestroyHookableEvent(hColourReloadEvent); + + font_id_list.destroy(); + colour_id_list.destroy(); + return 0; +} + +int LoadFontserviceModule( void ) +{ + code_page = LangPackGetDefaultCodePage(); + + CreateServiceFunction(MS_FONT_REGISTER, RegisterFont); + CreateServiceFunction(MS_FONT_GET, GetFont); + + CreateServiceFunction(MS_COLOUR_REGISTER, RegisterColour); + CreateServiceFunction(MS_COLOUR_GET, GetColour); + + CreateServiceFunction(MS_EFFECT_REGISTER, RegisterEffect); + CreateServiceFunction(MS_EFFECT_GET, GetEffect); + +#if defined( _UNICODE ) + CreateServiceFunction(MS_FONT_REGISTERW, RegisterFontW); + CreateServiceFunction(MS_FONT_GETW, GetFontW); + + CreateServiceFunction(MS_COLOUR_REGISTERW, RegisterColourW); + CreateServiceFunction(MS_COLOUR_GETW, GetColourW); + + CreateServiceFunction(MS_EFFECT_REGISTERW, RegisterEffectW); + CreateServiceFunction(MS_EFFECT_GETW, GetEffectW); +#endif + + hFontReloadEvent = CreateHookableEvent(ME_FONT_RELOAD); + hColourReloadEvent = CreateHookableEvent(ME_COLOUR_RELOAD); + + // cretae generic fonts + FontIDT fontid = {0}; + + fontid.cbSize = sizeof(FontID); + strncpy(fontid.dbSettingsGroup, "Fonts", sizeof(fontid.dbSettingsGroup)); + _tcsncpy(fontid.group, _T("General"), SIZEOF(fontid.group)); + + _tcsncpy(fontid.name, _T("Headers"), SIZEOF(fontid.name)); + fontid.flags = FIDF_APPENDNAME | FIDF_NOAS | FIDF_SAVEPOINTSIZE | FIDF_ALLOWEFFECTS | FIDF_CLASSHEADER; + strncpy(fontid.prefix, "Header", SIZEOF(fontid.prefix)); + fontid.order = 0; + FontRegisterT( &fontid ); + + _tcsncpy(fontid.name, _T("Generic text"), SIZEOF(fontid.name)); + fontid.flags = FIDF_APPENDNAME | FIDF_NOAS | FIDF_SAVEPOINTSIZE | FIDF_ALLOWEFFECTS | FIDF_CLASSGENERAL; + strncpy(fontid.prefix, "Generic", SIZEOF(fontid.prefix)); + fontid.order = 0; + FontRegisterT( &fontid ); + + _tcsncpy(fontid.name, _T("Small text"), SIZEOF(fontid.name)); + fontid.flags = FIDF_APPENDNAME | FIDF_NOAS | FIDF_SAVEPOINTSIZE | FIDF_ALLOWEFFECTS | FIDF_CLASSSMALL; + strncpy(fontid.prefix, "Small", SIZEOF(fontid.prefix)); + fontid.order = 0; + FontRegisterT( &fontid ); + + // do last for silly dyna plugin + HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded); + HookEvent(ME_SYSTEM_PRESHUTDOWN, OnPreShutdown); + return 0; +} diff --git a/src/modules/fonts/FontService.h b/src/modules/fonts/FontService.h new file mode 100644 index 0000000000..4f665ae6d3 --- /dev/null +++ b/src/modules/fonts/FontService.h @@ -0,0 +1,112 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "m_fontservice.h" + + +// settings to be used for the value of 'deffontsettings' in the FontID structure below - i.e. defaults +typedef struct TFontSettings_tag +{ + COLORREF colour; + char size; + BYTE style; // see the DBFONTF_* flags above + BYTE charset; + TCHAR szFace[LF_FACESIZE]; +} + TFontSettings; + +// a font identifier structure - used for registering a font, and getting one out again + +struct TFontID +{ + int cbSize; + TCHAR group[64]; // group the font belongs to - this is the 'Font Group' list in the options page + TCHAR name[64]; // this is the name of the font setting - e.g. 'contacts' in the 'contact list' group + char dbSettingsGroup[32]; // the 'module' in the database where the font data is stored + char prefix[32]; // this is prepended to the settings used to store this font's data in the db + DWORD flags; // bitwise OR of the FIDF_* flags above + TFontSettings deffontsettings; // defaults, valid if flags & FIDF_DEFAULTVALID + int order; // controls the order in the font group in which the fonts are listed in the UI (if order fields are equal, + // they will be ordered alphabetically by name) + TCHAR backgroundGroup[64]; + TCHAR backgroundName[64]; + TFontSettings value; +}; + +struct TColourID +{ + int cbSize; + TCHAR group[64]; + TCHAR name[64]; + char dbSettingsGroup[32]; + char setting[32]; + DWORD flags; + COLORREF defcolour; + int order; + + COLORREF value; +}; + +// clist_modern related tune-up, adding clist_modern effects to FontService + +typedef struct TEffectSettings_tag +{ + BYTE effectIndex; + DWORD baseColour; // ARGB + DWORD secondaryColour; // ARGB +} +TEffectSettings; + + +struct TEffectID +{ + int cbSize; + TCHAR group[64]; + TCHAR name[64]; + char dbSettingsGroup[32]; + char setting[32]; + DWORD flags; + TEffectSettings defeffect; + int order; + + TEffectSettings value; +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// global data & functions + +typedef struct +{ + char *paramName; + TCHAR *groupName; +} + TreeItem; + +extern OBJLIST font_id_list; +extern OBJLIST colour_id_list; +extern OBJLIST effect_id_list; + +extern int code_page; +extern HANDLE hFontReloadEvent, hColourReloadEvent; + +int CreateFromFontSettings(TFontSettings *fs, LOGFONT *lf ); diff --git a/src/modules/fonts/services.cpp b/src/modules/fonts/services.cpp new file mode 100644 index 0000000000..3106060d8b --- /dev/null +++ b/src/modules/fonts/services.cpp @@ -0,0 +1,534 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "FontService.h" + +COLORREF GetColorFromDefault(COLORREF cl); + +#if defined( _UNICODE ) +void ConvertFontSettings( FontSettings* fs, TFontSettings* fsw) +{ + fsw->colour = fs->colour; + fsw->size = fs->size; + fsw->style = fs->style; + fsw->charset = fs->charset; + + MultiByteToWideChar( code_page, 0, fs->szFace, -1, fsw->szFace, LF_FACESIZE); +} + +void ConvertFontID( FontID *fid, TFontID* fidw ) +{ + memset(fidw, 0, sizeof(TFontID)); + fidw->cbSize = sizeof(TFontID); + strcpy(fidw->dbSettingsGroup, fid->dbSettingsGroup); + strcpy(fidw->prefix, fid->prefix); + fidw->flags = fid->flags; + fidw->order = fid->order; + ConvertFontSettings(&fid->deffontsettings, &fidw->deffontsettings); + + MultiByteToWideChar( code_page, 0, fid->group, -1, fidw->group, 64); + MultiByteToWideChar( code_page, 0, fid->name, -1, fidw->name, 64); + if (fid->cbSize >= FontID_SIZEOF_V2A) { + MultiByteToWideChar( code_page, 0, fid->backgroundGroup, -1, fidw->backgroundGroup, 64); + MultiByteToWideChar( code_page, 0, fid->backgroundName, -1, fidw->backgroundName, 64); + } +} + +void ConvertColourID(ColourID *cid, TColourID* cidw) +{ + cidw->cbSize = sizeof(TColourID); + + strcpy(cidw->dbSettingsGroup, cid->dbSettingsGroup); + strcpy(cidw->setting, cid->setting); + cidw->flags = cid->flags; + cidw->defcolour = cid->defcolour; + cidw->order = cid->order; + + MultiByteToWideChar( code_page, 0, cid->group, -1, cidw->group, 64); + MultiByteToWideChar( code_page, 0, cid->name, -1, cidw->name, 64); +} + +void ConvertEffectID(EffectID *eid, TEffectID* eidw) +{ + eidw->cbSize = sizeof(TEffectID); + + strcpy(eidw->dbSettingsGroup, eid->dbSettingsGroup); + strcpy(eidw->setting, eid->setting); + eidw->flags = eid->flags; + eidw->defeffect.effectIndex = eid->defeffect.effectIndex; + eidw->defeffect.baseColour = eid->defeffect.baseColour; + eidw->defeffect.secondaryColour = eid->defeffect.secondaryColour; + eidw->order = eid->order; + + MultiByteToWideChar( code_page, 0, eid->group, -1, eidw->group, 64); + MultiByteToWideChar( code_page, 0, eid->name, -1, eidw->name, 64); +} + + +void ConvertLOGFONT(LOGFONTW *lfw, LOGFONTA *lfa) +{ + lfa->lfHeight = lfw->lfHeight; + lfa->lfWidth = lfw->lfWidth; + lfa->lfEscapement = lfw->lfEscapement; + lfa->lfOrientation = lfw->lfOrientation; + lfa->lfWeight = lfw->lfWeight; + lfa->lfItalic = lfw->lfItalic; + lfa->lfUnderline = lfw->lfUnderline; + lfa->lfStrikeOut = lfw->lfStrikeOut; + lfa->lfCharSet = lfw->lfCharSet; + lfa->lfOutPrecision = lfw->lfOutPrecision; + lfa->lfClipPrecision = lfw->lfClipPrecision; + lfa->lfQuality = lfw->lfQuality; + lfa->lfPitchAndFamily = lfw->lfPitchAndFamily; + + WideCharToMultiByte( code_page, 0, lfw->lfFaceName, -1, lfa->lfFaceName, LF_FACESIZE, 0, 0); +} +#endif + +static void GetDefaultFontSetting(LOGFONT* lf, COLORREF* colour) +{ + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), lf, FALSE); + if ( colour ) + *colour = GetSysColor(COLOR_WINDOWTEXT); + + lf->lfHeight = 10; + + HDC hdc = GetDC(0); + lf->lfHeight = -MulDiv(lf->lfHeight,GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(0, hdc); +} + +int GetFontSettingFromDB(char *settings_group, char *prefix, LOGFONT* lf, COLORREF * colour, DWORD flags) +{ + DBVARIANT dbv; + char idstr[256]; + BYTE style; + int retval = 0; + + GetDefaultFontSetting(lf, colour); + + if(flags & FIDF_APPENDNAME) mir_snprintf(idstr, SIZEOF(idstr), "%sName", prefix); + else mir_snprintf(idstr, SIZEOF(idstr), "%s", prefix); + + if ( !DBGetContactSettingTString(NULL, settings_group, idstr, &dbv )) { + _tcscpy(lf->lfFaceName, dbv.ptszVal); + DBFreeVariant(&dbv); + } + else retval = 1; + + if (colour) { + mir_snprintf(idstr, SIZEOF(idstr), "%sCol", prefix); + *colour = DBGetContactSettingDword(NULL, settings_group, idstr, *colour); + } + + mir_snprintf(idstr, SIZEOF(idstr), "%sSize", prefix); + lf->lfHeight = (char)DBGetContactSettingByte(NULL, settings_group, idstr, lf->lfHeight); + + + //wsprintf(idstr, "%sFlags", prefix); + //if(DBGetContactSettingDword(NULL, settings_group, idstr, 0) & FIDF_SAVEACTUALHEIGHT) { + // HDC hdc = GetDC(0); + // lf->lfHeight = -lf->lfHeight; + // ReleaseDC(0, hdc); + //} + + mir_snprintf(idstr, SIZEOF(idstr), "%sSty", prefix); + style = (BYTE) DBGetContactSettingByte(NULL, settings_group, idstr, + (lf->lfWeight == FW_NORMAL ? 0 : DBFONTF_BOLD) | (lf->lfItalic ? DBFONTF_ITALIC : 0) | (lf->lfUnderline ? DBFONTF_UNDERLINE : 0) | lf->lfStrikeOut ? DBFONTF_STRIKEOUT : 0); + + lf->lfWidth = lf->lfEscapement = lf->lfOrientation = 0; + lf->lfWeight = style & DBFONTF_BOLD ? FW_BOLD : FW_NORMAL; + lf->lfItalic = (style & DBFONTF_ITALIC) != 0; + lf->lfUnderline = (style & DBFONTF_UNDERLINE) != 0; + lf->lfStrikeOut = (style & DBFONTF_STRIKEOUT) != 0; + + mir_snprintf(idstr, SIZEOF(idstr), "%sSet", prefix); + lf->lfCharSet = DBGetContactSettingByte(NULL, settings_group, idstr, lf->lfCharSet); + + lf->lfOutPrecision = OUT_DEFAULT_PRECIS; + lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf->lfQuality = DEFAULT_QUALITY; + lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + + if(lf->lfHeight > 0) { + HDC hdc = GetDC(0); + if(flags & FIDF_SAVEPOINTSIZE) { + lf->lfHeight = -MulDiv(lf->lfHeight,GetDeviceCaps(hdc, LOGPIXELSY), 72); + } else { // assume SAVEACTUALHEIGHT + TEXTMETRIC tm; + HFONT hFont = CreateFontIndirect(lf); + HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); + + GetTextMetrics(hdc, &tm); + + lf->lfHeight = -(lf->lfHeight - tm.tmInternalLeading); + + SelectObject(hdc, hOldFont); + DeleteObject(hFont); + } + //lf->lfHeight = -MulDiv(lf->lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(0, hdc); + } + + return retval; +} + +int CreateFromFontSettings(TFontSettings* fs, LOGFONT* lf ) +{ + GetDefaultFontSetting(lf, 0); + + _tcscpy(lf->lfFaceName, fs->szFace); + + lf->lfWidth = lf->lfEscapement = lf->lfOrientation = 0; + lf->lfWeight = fs->style & DBFONTF_BOLD ? FW_BOLD : FW_NORMAL; + lf->lfItalic = (fs->style & DBFONTF_ITALIC) != 0; + lf->lfUnderline = (fs->style & DBFONTF_UNDERLINE) != 0; + lf->lfStrikeOut = (fs->style & DBFONTF_STRIKEOUT) != 0;; + lf->lfCharSet = fs->charset; + lf->lfOutPrecision = OUT_DEFAULT_PRECIS; + lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf->lfQuality = DEFAULT_QUALITY; + lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + + lf->lfHeight = fs->size; + return 0; +} + +void UpdateFontSettings(TFontID* font_id, TFontSettings* fontsettings) +{ + LOGFONT lf; + COLORREF colour; + if ( GetFontSettingFromDB(font_id->dbSettingsGroup, font_id->prefix, &lf, &colour, font_id->flags) && (font_id->flags & FIDF_DEFAULTVALID)) { + CreateFromFontSettings(&font_id->deffontsettings, &lf ); + colour = GetColorFromDefault(font_id->deffontsettings.colour); + } + + fontsettings->style = + (lf.lfWeight == FW_NORMAL ? 0 : DBFONTF_BOLD) | (lf.lfItalic ? DBFONTF_ITALIC : 0) | (lf.lfUnderline ? DBFONTF_UNDERLINE : 0) | (lf.lfStrikeOut ? DBFONTF_STRIKEOUT : 0); + + fontsettings->size = (char)lf.lfHeight; + fontsettings->charset = lf.lfCharSet; + fontsettings->colour = colour; + _tcscpy(fontsettings->szFace, lf.lfFaceName); +} + +static COLORREF sttMixColor(COLORREF cl1, COLORREF cl2, int q) +{ + return RGB( + (GetRValue(cl1) * q + GetRValue(cl2) * (255 - q)) / 255, + (GetGValue(cl1) * q + GetGValue(cl2) * (255 - q)) / 255, + (GetBValue(cl1) * q + GetBValue(cl2) * (255 - q)) / 255 + ); +} + +COLORREF GetColorFromDefault(COLORREF cl) +{ +/* + if (cl & 0x80000000) + return GetSysColor(cl & 0x7fffffff); + + if (cl & 0x40000000) + { + switch (cl) + { + case MIRCOLOR_BTNHALF: return sttMixColor(GetSysColor(COLOR_BTNFACE), GetSysColor(COLOR_BTNTEXT), 128); + case MIRCOLOR_WNDHALF: return sttMixColor(GetSysColor(COLOR_WINDOW), GetSysColor(COLOR_WINDOWTEXT), 128); + case MIRCOLOR_SELHALF: return sttMixColor(GetSysColor(COLOR_HIGHLIGHT), GetSysColor(COLOR_HIGHLIGHTTEXT), 128); + case MIRCOLOR_INBACK: return sttMixColor(GetSysColor(COLOR_WINDOW), RGB(0,0,255), 245); + case MIRCOLOR_INTEXT: return GetSysColor(COLOR_WINDOWTEXT); + case MIRCOLOR_INHALF: return sttMixColor(GetColorFromDefault(MIRCOLOR_INBACK), GetColorFromDefault(MIRCOLOR_INTEXT), 128); + case MIRCOLOR_OUTBACK: return sttMixColor(GetSysColor(COLOR_WINDOW), RGB(0,255,0), 245); + case MIRCOLOR_OUTTEXT: return GetSysColor(COLOR_WINDOWTEXT); + case MIRCOLOR_OUTHALF: return sttMixColor(GetColorFromDefault(MIRCOLOR_OUTBACK), GetColorFromDefault(MIRCOLOR_OUTTEXT), 128); + } + } +*/ + return cl; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// RegisterFont service + +static int sttRegisterFontWorker( TFontID* font_id ) +{ + for ( int i = 0; i < font_id_list.getCount(); i++ ) { + TFontID& F = font_id_list[i]; + if ( !lstrcmp( F.group, font_id->group ) && !lstrcmp( F.name, font_id->name ) && !( F.flags & FIDF_ALLOWREREGISTER )) + return 1; + } + + char idstr[256]; + mir_snprintf(idstr, SIZEOF(idstr), "%sFlags", font_id->prefix); + DBWriteContactSettingDword(0, font_id->dbSettingsGroup, idstr, font_id->flags); + { + TFontID* newItem = new TFontID; + memset( newItem, 0, sizeof( TFontID )); + memcpy( newItem, font_id, font_id->cbSize); + + if (!lstrcmp(newItem->deffontsettings.szFace, _T("MS Shell Dlg"))) + { + LOGFONT lf; + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, FALSE); + lstrcpyn(newItem->deffontsettings.szFace, lf.lfFaceName, SIZEOF(newItem->deffontsettings.szFace)); + if (!newItem->deffontsettings.size) + newItem->deffontsettings.size = lf.lfHeight; + } + + UpdateFontSettings( font_id, &newItem->value ); + font_id_list.insert( newItem ); + } + return 0; +} + +#if defined( _UNICODE ) +INT_PTR RegisterFontW(WPARAM wParam, LPARAM ) +{ + return sttRegisterFontWorker(( TFontID* )wParam ); +} +#endif + +INT_PTR RegisterFont(WPARAM wParam, LPARAM) +{ + #if defined( _UNICODE ) + TFontID temp; + ConvertFontID( ( FontID* )wParam, &temp ); + return sttRegisterFontWorker( &temp ); + #else + return sttRegisterFontWorker(( TFontID* )wParam ); + #endif +} + +///////////////////////////////////////////////////////////////////////////////////////// +// GetFont service + +static int sttGetFontWorker( TFontID* font_id, LOGFONT* lf ) +{ + COLORREF colour; + + for ( int i = 0; i < font_id_list.getCount(); i++ ) { + TFontID& F = font_id_list[i]; + if ( !_tcsncmp( F.name, font_id->name, SIZEOF(F.name)) && !_tcsncmp( F.group, font_id->group, SIZEOF(F.group))) { + if ( GetFontSettingFromDB( F.dbSettingsGroup, F.prefix, lf, &colour, F.flags) && ( F.flags & FIDF_DEFAULTVALID )) { + CreateFromFontSettings( &F.deffontsettings, lf ); + colour = GetColorFromDefault(F.deffontsettings.colour); + } + + return (int)colour; + } } + + GetDefaultFontSetting( lf, &colour ); + return (int)colour; +} + +#if defined( _UNICODE ) +INT_PTR GetFontW(WPARAM wParam, LPARAM lParam) +{ + return sttGetFontWorker(( TFontID* )wParam, ( LOGFONT* )lParam ); +} +#endif + +INT_PTR GetFont(WPARAM wParam, LPARAM lParam) +{ + #if defined( _UNICODE ) + TFontID temp; + LOGFONT lftemp; + ConvertFontID((FontID *)wParam, &temp); + { int ret = sttGetFontWorker( &temp, &lftemp ); + ConvertLOGFONT( &lftemp, ( LOGFONTA* )lParam ); + return ret; + } + #else + return sttGetFontWorker(( TFontID* )wParam, ( LOGFONT* )lParam ); + #endif +} + +///////////////////////////////////////////////////////////////////////////////////////// +// RegisterColour service + +void UpdateColourSettings( TColourID* colour_id, COLORREF *colour) +{ + *colour = ( COLORREF )DBGetContactSettingDword(NULL, colour_id->dbSettingsGroup, colour_id->setting, GetColorFromDefault(colour_id->defcolour) ); +} + +static int sttRegisterColourWorker( TColourID* colour_id ) +{ + for ( int i = 0; i < colour_id_list.getCount(); i++ ) { + TColourID& C = colour_id_list[i]; + if ( !_tcscmp( C.group, colour_id->group ) && !_tcscmp( C.name, colour_id->name )) + return 1; + } + + TColourID* newItem = new TColourID; + memcpy( newItem, colour_id, sizeof( TColourID )); + UpdateColourSettings( colour_id, &newItem->value ); + colour_id_list.insert( newItem ); + return 0; +} + +#if defined( _UNICODE ) +INT_PTR RegisterColourW(WPARAM wParam, LPARAM) +{ + return sttRegisterColourWorker(( TColourID* )wParam ); +} +#endif + +INT_PTR RegisterColour(WPARAM wParam, LPARAM) +{ + #if defined( _UNICODE ) + TColourID temp; + ConvertColourID(( ColourID* )wParam, &temp ); + return sttRegisterColourWorker( &temp ); + #else + return sttRegisterColourWorker(( TColourID* )wParam ); + #endif +} + +///////////////////////////////////////////////////////////////////////////////////////// +// GetColour service + +static int sttGetColourWorker( TColourID* colour_id ) +{ + int i; + + for ( i = 0; i < colour_id_list.getCount(); i++ ) { + TColourID& C = colour_id_list[i]; + if ( !_tcscmp( C.group, colour_id->group ) && !_tcscmp( C.name, colour_id->name )) + return (int)DBGetContactSettingDword(NULL, C.dbSettingsGroup, C.setting, GetColorFromDefault(C.defcolour)); + } + + return -1; +} + +#if defined( _UNICODE ) +INT_PTR GetColourW(WPARAM wParam, LPARAM) +{ + return sttGetColourWorker(( TColourID* )wParam ); +} +#endif + +INT_PTR GetColour(WPARAM wParam, LPARAM) +{ + #if defined( _UNICODE ) + TColourID temp; + ConvertColourID(( ColourID* )wParam, &temp ); + return sttGetColourWorker( &temp ); + #else + return sttGetColourWorker(( TColourID* )wParam ); + #endif +} + + +////////////////////////////////////////////////////////////////////////// +// Effects + +void UpdateEffectSettings(TEffectID* effect_id, TEffectSettings* effectsettings) +{ + char str[256]; + + mir_snprintf(str, SIZEOF(str), "%sEffect", effect_id->setting); + effectsettings->effectIndex = DBGetContactSettingByte(NULL, effect_id->dbSettingsGroup, str, effect_id->defeffect.effectIndex); + + mir_snprintf(str, SIZEOF(str), "%sEffectCol1", effect_id->setting); + effectsettings->baseColour = DBGetContactSettingDword(NULL, effect_id->dbSettingsGroup, str, effect_id->defeffect.baseColour); + + mir_snprintf(str, SIZEOF(str), "%sEffectCol2", effect_id->setting); + effectsettings->secondaryColour = DBGetContactSettingDword(NULL, effect_id->dbSettingsGroup, str, effect_id->defeffect.secondaryColour); + +} + +///////////////////////////////////////////////////////////////////////////////////////// +// RegisterFont service + +static int sttRegisterEffectWorker( TEffectID* effect_id ) +{ + for ( int i = 0; i < effect_id_list.getCount(); i++ ) { + TEffectID& E = effect_id_list[i]; + if ( !_tcscmp( E.group, effect_id->group ) && !_tcscmp( E.name, effect_id->name )) + return 1; + } + + TEffectID* newItem = new TEffectID; + memcpy( newItem, effect_id, sizeof( TEffectID )); + UpdateEffectSettings( effect_id, &newItem->value ); + effect_id_list.insert( newItem ); + return 0; +} + +#if defined( _UNICODE ) +INT_PTR RegisterEffectW(WPARAM wParam, LPARAM lParam) +{ + return sttRegisterEffectWorker(( TEffectID* )wParam ); +} +#endif + +INT_PTR RegisterEffect(WPARAM wParam, LPARAM lParam) +{ +#if defined( _UNICODE ) + TEffectID temp; + ConvertEffectID( ( EffectID* )wParam, &temp ); + return sttRegisterEffectWorker( &temp ); +#else + return sttRegisterEffectWorker(( TEffectID* )wParam ); +#endif +} + +///////////////////////////////////////////////////////////////////////////////////////// +// GetEffect service + +static int sttGetEffectWorker( TEffectID* effect_id, FONTEFFECT* effect ) +{ + for ( int i = 0; i < effect_id_list.getCount(); i++ ) { + TEffectID& E = effect_id_list[i]; + if ( !_tcsncmp( E.name, effect_id->name, SIZEOF(E.name)) && !_tcsncmp( E.group, effect_id->group, SIZEOF(E.group))) + { + TEffectSettings temp; + UpdateEffectSettings( effect_id, &temp ); + + effect->effectIndex = temp.effectIndex; + effect->baseColour = temp.baseColour; + effect->secondaryColour = temp.secondaryColour; + + return (int) TRUE; + } } + + return (int)FALSE; +} + +#if defined( _UNICODE ) +INT_PTR GetEffectW(WPARAM wParam, LPARAM lParam) +{ + return sttGetEffectWorker(( TEffectID* )wParam, ( FONTEFFECT* )lParam ); +} +#endif + +INT_PTR GetEffect(WPARAM wParam, LPARAM lParam) +{ +#if defined( _UNICODE ) + TEffectID temp; + ConvertEffectID((EffectID *)wParam, &temp); + return sttGetEffectWorker( &temp, ( FONTEFFECT* )lParam ); +#else + return sttGetEffectWorker(( TEffectID* )wParam, ( FONTEFFECT* )lParam ); +#endif +} diff --git a/src/modules/help/about.cpp b/src/modules/help/about.cpp new file mode 100644 index 0000000000..37c949260a --- /dev/null +++ b/src/modules/help/about.cpp @@ -0,0 +1,148 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#if defined( _UNICODE ) + #define STR_VERSION_FORMAT "%s%S%S" +#else + #define STR_VERSION_FORMAT "%s%s%s" +#endif + +INT_PTR CALLBACK DlgProcAbout(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static int iState = 0; + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { TCHAR filename[MAX_PATH], *productCopyright; + DWORD unused; + DWORD verInfoSize; + UINT blockSize; + PVOID pVerInfo; + + GetModuleFileName(NULL,filename,SIZEOF(filename)); + verInfoSize=GetFileVersionInfoSize(filename,&unused); + pVerInfo=mir_alloc(verInfoSize); + GetFileVersionInfo(filename,0,verInfoSize,pVerInfo); + VerQueryValue(pVerInfo,_T("\\StringFileInfo\\000004b0\\LegalCopyright"),(LPVOID*)&productCopyright,&blockSize); + SetDlgItemText(hwndDlg,IDC_DEVS,productCopyright); + mir_free(pVerInfo); + } + { char productVersion[56], *p; + int isAnsi = 0; + TCHAR str[64]; + CallService(MS_SYSTEM_GETVERSIONTEXT,SIZEOF(productVersion),(LPARAM)productVersion); + // Hide Unicode from version text as it is assumed at this point + p = strstr(productVersion, " Unicode"); + if (p) + *p = '\0'; + else + isAnsi = 1; + mir_sntprintf(str,SIZEOF(str),_T(STR_VERSION_FORMAT), TranslateT("v"), productVersion, isAnsi?" ANSI":""); + { + TCHAR oldTitle[256], newTitle[256]; + GetDlgItemText( hwndDlg, IDC_HEADERBAR, oldTitle, SIZEOF( oldTitle )); + mir_sntprintf( newTitle, SIZEOF(newTitle), oldTitle, str ); + SetDlgItemText( hwndDlg, IDC_HEADERBAR, newTitle ); + } + + mir_sntprintf(str,SIZEOF(str),TranslateT("Built %s %s"),_T(__DATE__),_T(__TIME__)); + SetDlgItemText(hwndDlg,IDC_BUILDTIME,str); + } + ShowWindow(GetDlgItem(hwndDlg, IDC_CREDITSFILE), SW_HIDE); + { + HRSRC hResInfo = FindResource(hMirandaInst,MAKEINTRESOURCE(IDR_CREDITS),_T("TEXT")); + DWORD ResSize = SizeofResource(hMirandaInst,hResInfo); + HGLOBAL hRes = LoadResource(hMirandaInst,hResInfo); + char* pszMsg = (char*)LockResource(hRes); + if (pszMsg) + { + char* pszMsgt = (char*)alloca(ResSize + 1); + memcpy(pszMsgt, pszMsg, ResSize); pszMsgt[ResSize] = 0; + + TCHAR *ptszMsg; + if (ResSize >=3 && pszMsgt[0] == '\xef' && pszMsgt[1] == '\xbb' && pszMsgt[2] == '\xbf') + ptszMsg = Utf8DecodeT(pszMsgt + 3); + else + ptszMsg = mir_a2t_cp(pszMsgt, 1252); + + SetDlgItemText(hwndDlg, IDC_CREDITSFILE, ptszMsg); + UnlockResource(pszMsg); + mir_free(ptszMsg); + } + FreeResource(hRes); + } + Window_SetIcon_IcoLib(hwndDlg, SKINICON_OTHER_MIRANDA); + return TRUE; + + case WM_COMMAND: + switch( LOWORD( wParam )) { + case IDOK: + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + case IDC_CONTRIBLINK: + if (iState) { + iState = 0; + SetDlgItemText(hwndDlg, IDC_CONTRIBLINK, TranslateT("Credits >")); + ShowWindow(GetDlgItem(hwndDlg, IDC_DEVS), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_BUILDTIME), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_CREDITSFILE), SW_HIDE); + } + else { + iState = 1; + SetDlgItemText(hwndDlg, IDC_CONTRIBLINK, TranslateT("< Copyright")); + ShowWindow(GetDlgItem(hwndDlg, IDC_DEVS), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_BUILDTIME), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CREDITSFILE), SW_SHOW); + } + break; + } + break; + + case WM_CTLCOLOREDIT: + case WM_CTLCOLORSTATIC: + switch ( GetWindowLongPtr(( HWND )lParam, GWL_ID )) { + case IDC_WHITERECT: + case IDC_BUILDTIME: + case IDC_CREDITSFILE: + case IDC_DEVS: + SetTextColor((HDC)wParam,GetSysColor(COLOR_WINDOWTEXT)); + break; + default: + return FALSE; + } + SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + + case WM_DESTROY: + Window_FreeIcon_IcoLib( hwndDlg ); + { + HFONT hFont=(HFONT)SendDlgItemMessage(hwndDlg,IDC_VERSION,WM_GETFONT,0,0); + SendDlgItemMessage(hwndDlg,IDC_VERSION,WM_SETFONT,SendDlgItemMessage(hwndDlg,IDOK,WM_GETFONT,0,0),0); + DeleteObject(hFont); + } + break; + } + return FALSE; +} diff --git a/src/modules/help/help.cpp b/src/modules/help/help.cpp new file mode 100644 index 0000000000..432049e2d1 --- /dev/null +++ b/src/modules/help/help.cpp @@ -0,0 +1,117 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +INT_PTR CALLBACK DlgProcAbout(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +HWND hAboutDlg=NULL; +static HANDLE hBugEvent = NULL; + +static INT_PTR AboutCommand(WPARAM wParam,LPARAM) +{ + if (IsWindow(hAboutDlg)) { + SetForegroundWindow(hAboutDlg); + SetFocus(hAboutDlg); + return 0; + } + hAboutDlg=CreateDialog(hMirandaInst,MAKEINTRESOURCE(IDD_ABOUT),(HWND)wParam,DlgProcAbout); + return 0; +} + +static INT_PTR IndexCommand(WPARAM, LPARAM) +{ + CallService(MS_UTILS_OPENURL,1,(LPARAM)Translate("http://wiki.miranda-im.org/")); + return 0; +} + +static INT_PTR WebsiteCommand(WPARAM, LPARAM) +{ + CallService(MS_UTILS_OPENURL,1,(LPARAM)"http://www.miranda-im.org"); + return 0; +} + +static int BugCommandEvent(WPARAM wParam, LPARAM lParam) { + char *szUrl = (char*)lParam; + + if (szUrl) { + CallService(MS_UTILS_OPENURL,1,(LPARAM)szUrl); + } + return 0; +} + +static INT_PTR BugCommand(WPARAM, LPARAM) +{ + NotifyEventHooks(hBugEvent, 0, (LPARAM)"http://code.google.com/p/miranda/issues/list"); + return 0; +} + + +int ShutdownHelpModule(WPARAM, LPARAM) +{ + if (IsWindow(hAboutDlg)) DestroyWindow(hAboutDlg); + hAboutDlg=NULL; + return 0; +} + +int LoadHelpModule(void) +{ + HookEvent(ME_SYSTEM_PRESHUTDOWN,ShutdownHelpModule); + + CreateServiceFunction("Help/AboutCommand",AboutCommand); + CreateServiceFunction("Help/IndexCommand",IndexCommand); + CreateServiceFunction("Help/WebsiteCommand",WebsiteCommand); + CreateServiceFunction("Help/BugCommand",BugCommand); + + hBugEvent = CreateHookableEvent(ME_HELP_BUGREPORT); + SetHookDefaultForHookableEvent(hBugEvent, BugCommandEvent); + + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_MIRANDA); + mi.pszPopupName = LPGEN("&Help"); + mi.popupPosition = 2000090000; + mi.position = 2000090000; + mi.pszName = LPGEN("&About..."); + mi.pszService = "Help/AboutCommand"; + CallService(MS_CLIST_ADDMAINMENUITEM,0,(LPARAM)&mi); + + mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_HELP); + mi.position = -500050000; + mi.pszName = LPGEN("&Support"); + mi.pszService = "Help/IndexCommand"; + CallService(MS_CLIST_ADDMAINMENUITEM,0,(LPARAM)&mi); + + mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_MIRANDAWEB); + mi.position = 2000050000; + mi.pszName = LPGEN("&Miranda IM Homepage"); + mi.pszService = "Help/WebsiteCommand"; + CallService(MS_CLIST_ADDMAINMENUITEM,0,(LPARAM)&mi); + + mi.icolibItem = GetSkinIconHandle(SKINICON_EVENT_URL); + mi.position = 2000040000; + mi.pszName = LPGEN("&Report Bug"); + mi.pszService = "Help/BugCommand"; + CallService(MS_CLIST_ADDMAINMENUITEM,0,(LPARAM)&mi); + return 0; +} diff --git a/src/modules/history/history.cpp b/src/modules/history/history.cpp new file mode 100644 index 0000000000..3b6b805a7f --- /dev/null +++ b/src/modules/history/history.cpp @@ -0,0 +1,434 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#define SUMMARY 0 +#define DETAIL 1 +#define DM_FINDNEXT (WM_USER+10) +#define DM_HREBUILD (WM_USER+11) + +static INT_PTR CALLBACK DlgProcHistory(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK DlgProcHistoryFind(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +static HANDLE hWindowList=0; + +static INT_PTR UserHistoryCommand(WPARAM wParam, LPARAM) +{ + HWND hwnd = WindowList_Find( hWindowList,( HANDLE )wParam ); + if ( hwnd ) { + SetForegroundWindow(hwnd); + SetFocus(hwnd); + return 0; + } + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_HISTORY),NULL,DlgProcHistory,wParam); + return 0; +} + +static int HistoryContactDelete(WPARAM wParam, LPARAM) +{ + HWND hwnd = WindowList_Find(hWindowList,(HANDLE)wParam); + if ( hwnd != NULL ) + DestroyWindow(hwnd); + return 0; +} + +int PreShutdownHistoryModule(WPARAM, LPARAM) +{ + if (hWindowList) + WindowList_BroadcastAsync(hWindowList,WM_DESTROY,0,0); + return 0; +} + +int LoadHistoryModule(void) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.position = 1000090000; + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_HISTORY ); + mi.pszName = LPGEN("View &History"); + mi.pszService = MS_HISTORY_SHOWCONTACTHISTORY; + CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi); + + CreateServiceFunction(MS_HISTORY_SHOWCONTACTHISTORY,UserHistoryCommand); + hWindowList=(HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST,0,0); + HookEvent(ME_DB_CONTACT_DELETED,HistoryContactDelete); + HookEvent(ME_SYSTEM_PRESHUTDOWN,PreShutdownHistoryModule); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Fills the events list + +static void GetMessageDescription( DBEVENTINFO *dbei, TCHAR* buf, int cbBuf ) +{ + TCHAR* msg = DbGetEventTextT( dbei, CP_ACP ); + _tcsncpy( buf, msg ? msg : TranslateT("Invalid Message"), cbBuf ); + buf[ cbBuf-1 ] = 0; + mir_free( msg ); +} + +static void GetUrlDescription( DBEVENTINFO *dbei, TCHAR* buf, int cbBuf ) +{ + int len = dbei->cbBlob; + if ( len >= cbBuf ) + len = cbBuf-1; + + #if !defined( _UNICODE ) + memcpy( buf, dbei->pBlob, len ); + #else + MultiByteToWideChar( CP_ACP, 0, ( LPCSTR )dbei->pBlob, len, buf, cbBuf ); + #endif + buf[ len ] = 0; + + if ( len < cbBuf-3 ) + _tcscat( buf, _T( "\r\n" )); +} + +static void GetFileDescription( DBEVENTINFO *dbei, TCHAR* buf, int cbBuf ) +{ + int len = dbei->cbBlob - sizeof( DWORD ); + if ( len >= cbBuf ) + len = cbBuf-1; + + #if !defined( _UNICODE ) + memcpy( buf, dbei->pBlob + sizeof( DWORD ), len ); + #else + MultiByteToWideChar( CP_ACP, 0, ( LPCSTR )dbei->pBlob + sizeof( DWORD ), len, buf, cbBuf ); + #endif + buf[ len ] = 0; + + if ( len < cbBuf-3 ) + _tcscat( buf, _T( "\r\n" )); +} + +static void GetObjectDescription( DBEVENTINFO *dbei, TCHAR* str, int cbStr ) +{ + switch( dbei->eventType ) { + case EVENTTYPE_MESSAGE: + GetMessageDescription( dbei, str, cbStr ); + break; + + case EVENTTYPE_URL: + GetUrlDescription( dbei, str, cbStr ); + break; + + case EVENTTYPE_FILE: + GetFileDescription( dbei, str, cbStr ); + break; + + default: + { + DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )CallService( MS_DB_EVENT_GETTYPE, ( WPARAM )dbei->szModule, ( LPARAM )dbei->eventType ); + if ( et && ( et->flags & DETF_HISTORY )) { + GetMessageDescription( dbei, str, cbStr ); + } + else + str[ 0 ] = 0; +} } } + +static void GetObjectSummary( DBEVENTINFO *dbei, TCHAR* str, int cbStr ) +{ + TCHAR* pszSrc, *pszTmp = NULL; + + switch( dbei->eventType ) { + case EVENTTYPE_MESSAGE: + if ( dbei->flags & DBEF_SENT ) pszSrc = TranslateT( "Outgoing Message" ); + else pszSrc = TranslateT( "Incoming Message" ); + break; + + case EVENTTYPE_URL: + if ( dbei->flags & DBEF_SENT ) pszSrc = TranslateT( "Outgoing URL" ); + else pszSrc = TranslateT( "Incoming URL" ); + break; + + case EVENTTYPE_FILE: + if ( dbei->flags & DBEF_SENT ) pszSrc = TranslateT( "Outgoing File" ); + else pszSrc = TranslateT( "Incoming File" ); + break; + + default: + { + DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )CallService( MS_DB_EVENT_GETTYPE, ( WPARAM )dbei->szModule, ( LPARAM )dbei->eventType ); + if ( et && ( et->flags & DETF_HISTORY )) { + pszTmp = mir_a2t( et->descr ); + pszSrc = TranslateTS( pszTmp ); + break; + } + else { + str[ 0 ] = 0; + return; + } } } + + _tcsncpy( str, ( const TCHAR* )pszSrc, cbStr ); + str[ cbStr-1 ] = 0; + + mir_free( pszTmp ); +} + +typedef struct { + HANDLE hContact; + HWND hwnd; +} THistoryThread; + +static void FillHistoryThread(void* param) +{ + TCHAR str[200], eventText[256], strdatetime[64]; + HANDLE hDbEvent; + DBEVENTINFO dbei; + int newBlobSize,oldBlobSize,i; + HWND hwndList; + THistoryThread *hInfo = ( THistoryThread* )param; + + SendDlgItemMessage(hInfo->hwnd,IDC_LIST,LB_RESETCONTENT,0,0); + i=CallService(MS_DB_EVENT_GETCOUNT,(WPARAM)hInfo->hContact,0); + SendDlgItemMessage(hInfo->hwnd,IDC_LIST,LB_INITSTORAGE,i,i*40); + + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + oldBlobSize=0; + hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDLAST,(WPARAM)hInfo->hContact,0); + hwndList = GetDlgItem(hInfo->hwnd,IDC_LIST); + while ( hDbEvent != NULL ) { + if ( !IsWindow( hInfo->hwnd )) + break; + newBlobSize=CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)hDbEvent,0); + if(newBlobSize>oldBlobSize) { + dbei.pBlob=(PBYTE)mir_realloc(dbei.pBlob,newBlobSize); + oldBlobSize=newBlobSize; + } + dbei.cbBlob = oldBlobSize; + CallService( MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei ); + GetObjectSummary(&dbei,str,SIZEOF(str)); + if(str[0]) { + tmi.printTimeStamp(NULL, dbei.timestamp, _T("d t"), strdatetime, SIZEOF(strdatetime), 0); + mir_sntprintf( eventText, SIZEOF(eventText), _T("%s: %s"), strdatetime, str ); + i = SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)eventText ); + SendMessage(hwndList, LB_SETITEMDATA, i, (LPARAM)hDbEvent); + } + hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDPREV,(WPARAM)hDbEvent,0); + } + mir_free(dbei.pBlob); + + SendDlgItemMessage(hInfo->hwnd,IDC_LIST,LB_SETCURSEL,0,0); + SendMessage(hInfo->hwnd,WM_COMMAND,MAKEWPARAM(IDC_LIST,LBN_SELCHANGE),0); + EnableWindow(GetDlgItem(hInfo->hwnd, IDC_LIST), TRUE); + mir_free(hInfo); +} + +static int HistoryDlgResizer(HWND, LPARAM, UTILRESIZECONTROL *urc) +{ + switch(urc->wId) { + case IDC_LIST: + return RD_ANCHORX_WIDTH|RD_ANCHORY_HEIGHT; + case IDC_EDIT: + return RD_ANCHORX_WIDTH|RD_ANCHORY_BOTTOM; + case IDC_FIND: + case IDC_DELETEHISTORY: + return RD_ANCHORX_LEFT|RD_ANCHORY_BOTTOM; + case IDOK: + return RD_ANCHORX_RIGHT|RD_ANCHORY_BOTTOM; + } + return RD_ANCHORX_LEFT|RD_ANCHORY_TOP; +} + +static INT_PTR CALLBACK DlgProcHistory(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact; + + hContact=(HANDLE)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)lParam); + hContact = (HANDLE)lParam; + WindowList_Add(hWindowList,hwndDlg,hContact); + Utils_RestoreWindowPosition(hwndDlg,hContact,"History",""); + { + TCHAR* contactName, str[200]; + contactName = cli.pfnGetContactDisplayName( hContact, 0 ); + mir_sntprintf(str,SIZEOF(str),TranslateT("History for %s"),contactName); + SetWindowText(hwndDlg,str); + } + Window_SetIcon_IcoLib(hwndDlg, SKINICON_OTHER_HISTORY); + SendMessage(hwndDlg,DM_HREBUILD,0,0); + return TRUE; + + case DM_HREBUILD: + { + THistoryThread* hInfo = (THistoryThread*)mir_alloc(sizeof(THistoryThread)); + EnableWindow(GetDlgItem(hwndDlg, IDC_LIST), FALSE); + hInfo->hContact = hContact; + hInfo->hwnd = hwndDlg; + forkthread(FillHistoryThread, 0, hInfo); + } + return TRUE; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Utils_SaveWindowPosition(hwndDlg,hContact,"History",""); + WindowList_Remove(hWindowList,hwndDlg); + return TRUE; + + case WM_GETMINMAXINFO: + ((MINMAXINFO*)lParam)->ptMinTrackSize.x=300; + ((MINMAXINFO*)lParam)->ptMinTrackSize.y=230; + + case WM_SIZE: + { + UTILRESIZEDIALOG urd={0}; + urd.cbSize=sizeof(urd); + urd.hwndDlg=hwndDlg; + urd.hInstance=hMirandaInst; + urd.lpTemplate=MAKEINTRESOURCEA(IDD_HISTORY); + urd.lParam=(LPARAM)NULL; + urd.pfnResizer=HistoryDlgResizer; + CallService(MS_UTILS_RESIZEDIALOG,0,(LPARAM)&urd); + return TRUE; + } + case WM_COMMAND: + switch ( LOWORD( wParam )) { + case IDOK: + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + + case IDC_FIND: + ShowWindow(CreateDialogParam(hMirandaInst, MAKEINTRESOURCE(IDD_HISTORY_FIND), hwndDlg, DlgProcHistoryFind, (LPARAM)hwndDlg), SW_SHOW); + return TRUE; + + case IDC_DELETEHISTORY: + { + HANDLE hDbevent; + int index = SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETCURSEL,0,0); + if ( index == LB_ERR ) + break; + + if ( MessageBox(hwndDlg,TranslateT("Are you sure you want to delete this history item?"),TranslateT("Delete History"),MB_YESNO|MB_ICONQUESTION)==IDYES) { + hDbevent = (HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETITEMDATA,index,0); + CallService(MS_DB_EVENT_DELETE,(WPARAM)hContact,(LPARAM)hDbevent); + SendMessage(hwndDlg,DM_HREBUILD,0,0); + } + return TRUE; + } + case IDC_LIST: + if ( HIWORD(wParam) == LBN_SELCHANGE ) { + TCHAR str[8192],*contactName; + HANDLE hDbEvent; + DBEVENTINFO dbei; + int sel; + sel=SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETCURSEL,0,0); + if(sel==LB_ERR) { EnableWindow(GetDlgItem(hwndDlg,IDC_DELETEHISTORY),FALSE); break; } + EnableWindow(GetDlgItem(hwndDlg,IDC_DELETEHISTORY),TRUE); + contactName = cli.pfnGetContactDisplayName( hContact, 0 ); + hDbEvent=(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETITEMDATA,sel,0); + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + dbei.cbBlob=CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)hDbEvent,0); + if ((int)dbei.cbBlob != -1) + { + dbei.pBlob=(PBYTE)mir_alloc(dbei.cbBlob); + if (CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei) == 0) + { + GetObjectDescription(&dbei,str,SIZEOF(str)); + if ( str[0] ) + SetDlgItemText(hwndDlg, IDC_EDIT, str); + } + mir_free(dbei.pBlob); + } + } + return TRUE; + } + break; + case DM_FINDNEXT: + { + TCHAR str[1024]; + HANDLE hDbEvent,hDbEventStart; + DBEVENTINFO dbei; + int newBlobSize,oldBlobSize; + + int index = SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETCURSEL,0,0); + if ( index == LB_ERR ) + break; + + hDbEventStart=(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETITEMDATA,index,0); + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + dbei.pBlob=NULL; + oldBlobSize=0; + for(;;) { + hDbEvent = (HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,LB_GETITEMDATA,++index,0); + if(hDbEvent == ( HANDLE )LB_ERR) { + index = -1; + continue; + } + if(hDbEvent==hDbEventStart) break; + newBlobSize=CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)hDbEvent,0); + if(newBlobSize>oldBlobSize) { + dbei.pBlob=(PBYTE)mir_realloc(dbei.pBlob,newBlobSize); + oldBlobSize=newBlobSize; + } + dbei.cbBlob=oldBlobSize; + CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei); + GetObjectDescription(&dbei,str,SIZEOF(str)); + if(str[0]) { + CharUpperBuff(str,lstrlen(str)); + if( _tcsstr(str,(const TCHAR*)lParam)!=NULL) { + SendDlgItemMessage(hwndDlg,IDC_LIST,LB_SETCURSEL,index,0); + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_LIST,LBN_SELCHANGE),0); + break; + } } } + + mir_free(dbei.pBlob); + break; + } + } + return FALSE; +} + +static INT_PTR CALLBACK DlgProcHistoryFind(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + return TRUE; + + case WM_COMMAND: + switch ( LOWORD( wParam )) { + case IDOK://find Next + { + TCHAR str[128]; + HWND hwndParent = ( HWND )GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + GetDlgItemText(hwndDlg, IDC_FINDWHAT, str, SIZEOF(str)); + CharUpperBuff(str,lstrlen(str)); + SendMessage(hwndParent,DM_FINDNEXT,0,(LPARAM)str); + return TRUE; + } + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + } + break; + } + return FALSE; +} diff --git a/src/modules/icolib/IcoLib.h b/src/modules/icolib/IcoLib.h new file mode 100644 index 0000000000..72e3aa4122 --- /dev/null +++ b/src/modules/icolib/IcoLib.h @@ -0,0 +1,83 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ +typedef struct +{ + TCHAR* name; + int flags; + int maxOrder; + int ref_count; +} + SectionItem; + +typedef struct +{ + TCHAR* file; + int ref_count; +} + IconSourceFile; + +typedef struct +{ + IconSourceFile* file; + int indx; + int cx, cy; + + int ref_count; + + HICON icon; + int icon_ref_count; + + BYTE* icon_data; + int icon_size; +} + IconSourceItem; + +typedef struct +{ + char* name; + SectionItem* section; + int orderID; + TCHAR* description; + TCHAR* default_file; + int default_indx; + int cx, cy; + + IconSourceItem* source_small; + IconSourceItem* source_big; + IconSourceItem* default_icon; + + TCHAR* temp_file; + HICON temp_icon; + BOOL temp_reset; +} + IconItem; + +typedef struct +{ + char *paramName; + DWORD value; +} + TreeItem; + +// extracticon.c +UINT _ExtractIconEx(LPCTSTR lpszFile, int iconIndex, int cxIcon, int cyIcon, HICON *phicon, UINT flags); diff --git a/src/modules/icolib/extracticon.cpp b/src/modules/icolib/extracticon.cpp new file mode 100644 index 0000000000..a886d6510d --- /dev/null +++ b/src/modules/icolib/extracticon.cpp @@ -0,0 +1,280 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#ifdef _ASSERT +#undef _ASSERT +#endif +#define _ASSERT(n) +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/resources/introductiontoresources/resourcereference/resourcestructures/newheader.asp +typedef struct +{ + WORD Reserved; + WORD ResType; + WORD ResCount; +} + NEWHEADER; + +#define MAGIC_ICON 0 +#define MAGIC_ICO1 1 +#define MAGIC_CUR 2 +#define MAGIC_BMP ((WORD)'B'+((WORD)'M'<<8)) + +#define MAGIC_ANI1 ((WORD)'R'+((WORD)'I'<<8)) +#define MAGIC_ANI2 ((WORD)'F'+((WORD)'F'<<8)) +#define MAGIC_ANI3 ((WORD)'A'+((WORD)'C'<<8)) +#define MAGIC_ANI4 ((WORD)'O'+((WORD)'N'<<8)) + +#define VER30 0x00030000 + +void* _RelativeVirtualAddresstoPtr(IMAGE_DOS_HEADER* pDosHeader, DWORD rva) +{ + IMAGE_NT_HEADERS* pPE = (IMAGE_NT_HEADERS*)((BYTE*)pDosHeader + pDosHeader->e_lfanew); + IMAGE_SECTION_HEADER* pSection = IMAGE_FIRST_SECTION( pPE ); + int i; + + for (i = 0; i < pPE->FileHeader.NumberOfSections; i++) { + IMAGE_SECTION_HEADER* cSection = &pSection[i]; + DWORD size = cSection->Misc.VirtualSize ? cSection->Misc.VirtualSize : cSection->SizeOfRawData; + + if (rva >= cSection->VirtualAddress && rva < cSection->VirtualAddress + size) + return (LPBYTE)pDosHeader + cSection->PointerToRawData + (rva - cSection->VirtualAddress); + } + + return NULL; +} + +void* _GetResourceTable(IMAGE_DOS_HEADER* pDosHeader) +{ + IMAGE_NT_HEADERS* pPE = (IMAGE_NT_HEADERS*)((BYTE*)pDosHeader + pDosHeader->e_lfanew); + + if (pPE->Signature != IMAGE_NT_SIGNATURE) + return NULL; + if (pPE->FileHeader.SizeOfOptionalHeader < 2) + return NULL; + + // The DataDirectory is an array of 16 structures. + // Each array entry has a predefined meaning for what it refers to. + + switch (pPE->OptionalHeader.Magic) + { + case IMAGE_NT_OPTIONAL_HDR32_MAGIC: + if (pPE->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER32)) + return _RelativeVirtualAddresstoPtr(pDosHeader, ((PIMAGE_NT_HEADERS32)pPE)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress); + break; + + case IMAGE_NT_OPTIONAL_HDR64_MAGIC: + if (pPE->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER64)) + return _RelativeVirtualAddresstoPtr(pDosHeader, ((PIMAGE_NT_HEADERS64)pPE)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress); + break; + } + + return NULL; +} + +IMAGE_RESOURCE_DIRECTORY_ENTRY* _FindResourceBase(void* prt, int resType, int* pCount) +{ + IMAGE_RESOURCE_DIRECTORY* pDir = (IMAGE_RESOURCE_DIRECTORY*)prt; + IMAGE_RESOURCE_DIRECTORY_ENTRY* pRes; + int i, count; + + *pCount = 0; + + count = pDir->NumberOfIdEntries + pDir->NumberOfNamedEntries; + pRes = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(pDir+1); + + for(i = 0; i < count; i++) + if (pRes[i].Name == (DWORD)resType) break; + + if (i == count) return NULL; + + pDir = (IMAGE_RESOURCE_DIRECTORY*)((LPBYTE)prt + + (pRes[i].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY)); + + count = pDir->NumberOfIdEntries + pDir->NumberOfNamedEntries; + *pCount = count; + pRes = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(pDir+1); + + return pRes; +} + +int _FindResourceCount(void* prt, int resType) +{ + int count; + + _FindResourceBase(prt, resType, &count); + return count; +} + +void* _FindResource(IMAGE_DOS_HEADER* pDosHeader, void* prt, int resIndex, int resType, DWORD* pcbSize) +{ + int count, index = 0; + IMAGE_RESOURCE_DIRECTORY_ENTRY* pRes; + IMAGE_RESOURCE_DATA_ENTRY* pEntry; + + pRes = _FindResourceBase(prt, resType, &count); + if (resIndex < 0) { + for(index = 0; index < count; index++) + if (pRes[index].Name == (DWORD)(-resIndex)) + break; + } + else index = resIndex; + + if (index >= count) + return NULL; + + if (pRes[index].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY) { + IMAGE_RESOURCE_DIRECTORY* pDir; + pDir = (IMAGE_RESOURCE_DIRECTORY*)((LPBYTE)prt + (pRes[index].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY) ); + pRes = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(pDir+1); + index = 0; + } + + if ( pRes[index].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY ) + return NULL; + + pEntry = (IMAGE_RESOURCE_DATA_ENTRY*)((LPBYTE)prt + pRes[index].OffsetToData); + *pcbSize = pEntry->Size; + return _RelativeVirtualAddresstoPtr(pDosHeader, pEntry->OffsetToData); +} + +UINT _ExtractFromExe(HANDLE hFile, int iconIndex, int cxIconSize, int cyIconSize, HICON *phicon, UINT flags) +{ + int retval = 0; + DWORD fileLen = GetFileSize(hFile, NULL); + HANDLE hFileMap = INVALID_HANDLE_VALUE, pFile = NULL; + IMAGE_DOS_HEADER* pDosHeader; + void* pRes; + DWORD cbSize = 0; + NEWHEADER* pIconDir; + int idIcon; + LPBITMAPINFOHEADER pIcon; + // UINT res = 0; + + hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hFileMap == NULL) goto cleanup; + + pFile = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0); + if (pFile == NULL) goto cleanup; + + pDosHeader = (IMAGE_DOS_HEADER*)(void*)pFile; + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) goto cleanup; + if (pDosHeader->e_lfanew <= 0) goto cleanup; + if ((DWORD)(pDosHeader->e_lfanew) >= fileLen) goto cleanup; + + pRes = _GetResourceTable(pDosHeader); + if (!pRes) goto cleanup; + if (!phicon) { + retval = _FindResourceCount(pRes, (int)RT_GROUP_ICON); + goto cleanup; + } + + pIconDir = (NEWHEADER*)_FindResource(pDosHeader, pRes, iconIndex, (int)RT_GROUP_ICON, &cbSize); + if (!pIconDir) goto cleanup; + if (pIconDir->Reserved || pIconDir->ResType != RES_ICON) goto cleanup; + + idIcon = LookupIconIdFromDirectoryEx((LPBYTE)pIconDir, TRUE, cxIconSize, cyIconSize, flags); + pIcon = (LPBITMAPINFOHEADER)_FindResource(pDosHeader, pRes, -idIcon, (int)RT_ICON, &cbSize); + if (!pIcon) goto cleanup; + + if ( pIcon->biSize != sizeof(BITMAPINFOHEADER) && pIcon->biSize != sizeof(BITMAPCOREHEADER)) { + _ASSERT(0); + goto cleanup; + } + + *phicon = CreateIconFromResourceEx((LPBYTE)pIcon, cbSize, TRUE, VER30, cxIconSize, cyIconSize, flags); + retval = 1; + +cleanup: + if (pFile) UnmapViewOfFile(pFile); + if (hFileMap != INVALID_HANDLE_VALUE) CloseHandle(hFileMap); + + return retval; +} + +UINT _ExtractFromICO( LPCTSTR pFileName, int iconIndex, int cxIcon, int cyIcon, HICON* phicon, UINT flags ) +{ + HICON hicon; + + if ( iconIndex >= 1 ) + return 0; + + // do we just want a count? + if (!phicon) + return 1; + + flags |= LR_LOADFROMFILE; + hicon = (HICON)LoadImage( NULL, pFileName, IMAGE_ICON, cxIcon, cyIcon, flags ); + if (!hicon) + return 0; + + *phicon = hicon; + return 1; +} + +UINT _ExtractIconEx(LPCTSTR lpszFile, int iconIndex, int cxIcon, int cyIcon, HICON *phicon, UINT flags) +{ + HANDLE hFile; + WORD magic[6]; + DWORD read = 0; + UINT res = 0; + + if (cxIcon == GetSystemMetrics(SM_CXICON) && cyIcon == GetSystemMetrics(SM_CYICON)) + res = ExtractIconEx(lpszFile, iconIndex, phicon, NULL, 1); + else if (cxIcon == GetSystemMetrics(SM_CXSMICON) && cyIcon == GetSystemMetrics(SM_CYSMICON)) + res = ExtractIconEx(lpszFile, iconIndex, NULL, phicon, 1); + else if (cxIcon == 0 || cyIcon == 0) + res = ExtractIconEx(lpszFile, iconIndex, NULL, phicon, 1); + // check if the api succeded, if not try our method too + if (res) return res; + + hFile = CreateFile(lpszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (hFile == INVALID_HANDLE_VALUE) + return 0; + + // failed to read file signature + if ( !ReadFile(hFile,&magic, sizeof(magic), &read, NULL ) || (read != sizeof(magic))) { + CloseHandle(hFile); + return 0; + } + + switch ( magic[0] ) { + case IMAGE_DOS_SIGNATURE: + res = _ExtractFromExe(hFile, iconIndex, cxIcon, cyIcon, phicon, flags); + break; + + case MAGIC_ANI1: // ani cursors are RIFF file of type 'ACON' + if (magic[1] == MAGIC_ANI2 && magic[4] == MAGIC_ANI3 && magic[5] == MAGIC_ANI4) + res = _ExtractFromICO(lpszFile, iconIndex, cxIcon, cyIcon, phicon, flags); + break; + + case MAGIC_ICON: + if ((magic[1] == MAGIC_ICO1 || magic[1] == MAGIC_CUR ) && magic[2] >= 1 ) + res = _ExtractFromICO(lpszFile, iconIndex, cxIcon, cyIcon, phicon, flags); + + break; + } + + CloseHandle(hFile); + return res; +} diff --git a/src/modules/icolib/skin2icons.cpp b/src/modules/icolib/skin2icons.cpp new file mode 100644 index 0000000000..a1a3e670b2 --- /dev/null +++ b/src/modules/icolib/skin2icons.cpp @@ -0,0 +1,1992 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#include "IcoLib.h" + +static BOOL bModuleInitialized = FALSE; +static HANDLE hIcons2ChangedEvent, hIconsChangedEvent; +static HICON hIconBlank = NULL; + +HANDLE hIcoLib_AddNewIcon, hIcoLib_RemoveIcon, hIcoLib_GetIcon, hIcoLib_GetIcon2, + hIcoLib_GetIconHandle, hIcoLib_IsManaged, hIcoLib_AddRef, hIcoLib_ReleaseIcon; + +static int iconEventActive = 0; + +static BOOL bNeedRebuild = FALSE; + +struct IcoLibOptsData { + HWND hwndIndex; +}; + +CRITICAL_SECTION csIconList; + +#define SECTIONPARAM_MAKE(index, level, flags) MAKELONG( (index)&0xFFFF, MAKEWORD( level, flags ) ) +#define SECTIONPARAM_INDEX(lparam) LOWORD( lparam ) +#define SECTIONPARAM_LEVEL(lparam) LOBYTE( HIWORD(lparam) ) +#define SECTIONPARAM_FLAGS(lparam) HIBYTE( HIWORD(lparam) ) +#define SECTIONPARAM_HAVEPAGE 0x0001 + +static int sttCompareSections( const SectionItem* p1, const SectionItem* p2 ) +{ return _tcscmp( p1->name, p2->name ); +} + +static LIST sectionList( 20, sttCompareSections ); + +static int sttCompareIconSourceFiles( const IconSourceFile* p1, const IconSourceFile* p2 ) +{ return _tcsicmp( p1->file, p2->file ); +} + +static LIST iconSourceFileList( 10, sttCompareIconSourceFiles ); + +static int sttCompareIconSourceItems( const IconSourceItem* p1, const IconSourceItem* p2 ) +{ if (p1->indx < p2->indx) + return -1; + + if (p1->indx > p2->indx) + return 1; + + if (p1->cx < p2->cx) + return -1; + + if (p1->cx > p2->cx) + return 1; + + if (p1->cy < p2->cy) + return -1; + + if (p1->cy > p2->cy) + return 1; + + if ( p1->file == p2->file ) + return 0; + + return ( p1->file > p2->file ) ? 1 : -1; +} + +static LIST iconSourceList( 20, sttCompareIconSourceItems ); + +static int sttCompareIcons( const IconItem* p1, const IconItem* p2 ) +{ return strcmp( p1->name, p2->name ); +} + +static LIST iconList( 20, sttCompareIcons ); + +///////////////////////////////////////////////////////////////////////////////////////// +// Utility functions + +static void __fastcall MySetCursor(TCHAR* nCursor) +{ SetCursor( LoadCursor( NULL, nCursor )); +} + +static void __fastcall SAFE_FREE(void** p) +{ + if ( *p ) { + mir_free( *p ); + *p = NULL; +} } + +static void __fastcall SafeDestroyIcon( HICON* icon ) +{ + if ( *icon ) { + DestroyIcon( *icon ); + *icon = NULL; +} } + + +// Helper functions to manage Icon resources + +IconSourceFile* IconSourceFile_Get( const TCHAR* file, bool isPath ) +{ + TCHAR fileFull[ MAX_PATH ]; + + if ( !file ) + return NULL; + + if (isPath) + pathToAbsoluteT( file, fileFull, NULL ); + /// TODO: convert path to long - eliminate duplicate items + else + _tcscpy( fileFull, file ); + + IconSourceFile key = { fileFull }; + int ix; + if (( ix = iconSourceFileList.getIndex( &key )) != -1 ) { + iconSourceFileList[ ix ]->ref_count++; + return iconSourceFileList[ ix ]; + } + + IconSourceFile* newItem = (IconSourceFile*)mir_calloc( sizeof( IconSourceFile )); + newItem->file = mir_tstrdup( fileFull ); + newItem->ref_count = 1; + iconSourceFileList.insert( newItem ); + + return newItem; +} + +int IconSourceFile_Release( IconSourceFile** pitem ) +{ + if ( pitem && *pitem && (*pitem)->ref_count ) { + IconSourceFile* item = *pitem; + if ( --item->ref_count <= 0 ) { + int indx; + if (( indx = iconSourceFileList.getIndex( item )) != -1 ) { + SAFE_FREE(( void** )&item->file ); + iconSourceFileList.remove( indx ); + SAFE_FREE(( void** )&item ); + } + } + *pitem = NULL; + return 0; + } + return 1; +} + +static int BytesPerScanLine(int PixelsPerScanline, int BitsPerPixel, int Alignment) +{ Alignment--; + int bytes = ((PixelsPerScanline * BitsPerPixel) + Alignment) & ~Alignment; + return bytes / 8; +} + +static int InitializeBitmapInfoHeader( HBITMAP bitmap, BITMAPINFOHEADER* bi ) +{ + DIBSECTION DS; + int bytes; + + DS.dsBmih.biSize = 0; + bytes = GetObject( bitmap, sizeof(DS), &DS ); + if ( bytes == 0 ) return 1; // Failure + else if (( bytes >= (sizeof(DS.dsBm) + sizeof(DS.dsBmih))) && + (DS.dsBmih.biSize >= DWORD(sizeof(DS.dsBmih)))) + *bi = DS.dsBmih; + else { + memset(bi, 0, sizeof(BITMAPINFOHEADER)); + bi->biSize = sizeof(BITMAPINFOHEADER); + bi->biWidth = DS.dsBm.bmWidth; + bi->biHeight = DS.dsBm.bmHeight; + } + bi->biBitCount = DS.dsBm.bmBitsPixel * DS.dsBm.bmPlanes; + bi->biPlanes = 1; + if ( bi->biClrImportant > bi->biClrUsed ) + bi->biClrImportant = bi->biClrUsed; + if ( !bi->biSizeImage ) + bi->biSizeImage = BytesPerScanLine( bi->biWidth, bi->biBitCount, 32 ) * abs( bi->biHeight ); + return 0; // Success +} + +static int InternalGetDIBSizes( HBITMAP bitmap, int* InfoHeaderSize, int* ImageSize ) +{ + BITMAPINFOHEADER bi; + + if ( InitializeBitmapInfoHeader( bitmap, &bi )) return 1; // Failure + if ( bi.biBitCount > 8 ) { + *InfoHeaderSize = sizeof(BITMAPINFOHEADER); + if ((bi.biCompression & BI_BITFIELDS) != 0 ) + *InfoHeaderSize += 12; + } + else { + if ( bi.biClrUsed == 0 ) + *InfoHeaderSize = sizeof(BITMAPINFOHEADER) + + sizeof(RGBQUAD) * (int)(1 << bi.biBitCount); + else + *InfoHeaderSize = sizeof(BITMAPINFOHEADER) + + sizeof(RGBQUAD) * bi.biClrUsed; + } + *ImageSize = bi.biSizeImage; + return 0; // Success +} + +static int InternalGetDIB( HBITMAP bitmap, HPALETTE palette, void* bitmapInfo, void* Bits ) +{ + HPALETTE oldPal = 0; + + if ( InitializeBitmapInfoHeader( bitmap, (BITMAPINFOHEADER*)bitmapInfo )) return 1; // Failure + + HDC DC = CreateCompatibleDC(0); + if ( palette ) { + oldPal = SelectPalette( DC, palette, FALSE ); + RealizePalette( DC ); + } + int result = GetDIBits( DC, bitmap, 0, ((BITMAPINFOHEADER*)bitmapInfo)->biHeight, Bits, (BITMAPINFO*)bitmapInfo, DIB_RGB_COLORS) == 0; + + if ( oldPal ) SelectPalette( DC, oldPal, FALSE ); + DeleteDC( DC ); + return result; +} + +static int GetIconData( HICON icon, BYTE** data, int* size ) +{ + ICONINFO iconInfo; + int MonoInfoSize, ColorInfoSize; + int MonoBitsSize, ColorBitsSize; + + if ( !data || !size ) return 1; // Failure + + if ( !GetIconInfo( icon, &iconInfo )) return 1; // Failure + + if ( InternalGetDIBSizes( iconInfo.hbmMask, &MonoInfoSize, &MonoBitsSize ) || + InternalGetDIBSizes( iconInfo.hbmColor, &ColorInfoSize, &ColorBitsSize )) { + DeleteObject( iconInfo.hbmColor ); + DeleteObject( iconInfo.hbmMask ); + return 1; // Failure + } + void* MonoInfo = mir_alloc( MonoInfoSize ); + void* MonoBits = mir_alloc( MonoBitsSize ); + void* ColorInfo = mir_alloc( ColorInfoSize ); + void* ColorBits = mir_alloc( ColorBitsSize ); + + if ( InternalGetDIB( iconInfo.hbmMask, 0, MonoInfo, MonoBits ) || + InternalGetDIB( iconInfo.hbmColor, 0, ColorInfo, ColorBits )) { + SAFE_FREE( &MonoInfo ); + SAFE_FREE( &MonoBits ); + SAFE_FREE( &ColorInfo ); + SAFE_FREE( &ColorBits ); + DeleteObject( iconInfo.hbmColor ); + DeleteObject( iconInfo.hbmMask ); + return 1; // Failure + } + + *size = ColorInfoSize + ColorBitsSize + MonoBitsSize; + *data = (BYTE*)mir_alloc(*size); + + BYTE* buf = *data; + ((BITMAPINFOHEADER*)ColorInfo)->biHeight *= 2; // color height includes mono bits + memcpy( buf, ColorInfo, ColorInfoSize ); + buf += ColorInfoSize; + memcpy( buf, ColorBits, ColorBitsSize ); + buf += ColorBitsSize; + memcpy( buf, MonoBits, MonoBitsSize ); + + SAFE_FREE( &MonoInfo ); + SAFE_FREE( &MonoBits ); + SAFE_FREE( &ColorInfo ); + SAFE_FREE( &ColorBits ); + DeleteObject( iconInfo.hbmColor ); + DeleteObject( iconInfo.hbmMask ); + return 0; // Success +} + +#define VER30 0x00030000 + +static HICON IconSourceItem_GetIcon( IconSourceItem* item ) +{ + if ( item->icon ) { + item->icon_ref_count++; + return item->icon; + } + + if ( item->icon_size ) { + item->icon = CreateIconFromResourceEx( item->icon_data, item->icon_size, TRUE, VER30, item->cx, item->cy, LR_COLOR ); + if ( item->icon ) { + item->icon_ref_count++; + return item->icon; + } + } + //SHOULD BE REPLACED WITH GOOD ENOUGH FUNCTION + _ExtractIconEx( item->file->file, item->indx, item->cx, item->cy, &item->icon, LR_COLOR ); + + if ( item->icon ) + item->icon_ref_count++; + + return item->icon; +} + +static int IconSourceItem_ReleaseIcon( IconSourceItem* item ) +{ + if ( item && item->icon_ref_count ) + { + item->icon_ref_count--; + if ( !item->icon_ref_count ) { + if ( !item->icon_size ) + if ( GetIconData( item->icon, &item->icon_data, &item->icon_size )) + item->icon_size = 0; // Failure + SafeDestroyIcon( &item->icon ); + } + return 0; // Success + } + return 1; // Failure +} + +IconSourceItem* GetIconSourceItem( const TCHAR* file, int indx, int cxIcon, int cyIcon ) +{ + if ( !file ) + return NULL; + + IconSourceFile* r_file = IconSourceFile_Get( file, true ); + IconSourceItem key = { r_file, indx, cxIcon, cyIcon }; + int ix; + if (( ix = iconSourceList.getIndex( &key )) != -1 ) { + IconSourceFile_Release( &r_file ); + iconSourceList[ ix ]->ref_count++; + return iconSourceList[ ix ]; + } + + IconSourceItem* newItem = (IconSourceItem*)mir_calloc( sizeof( IconSourceItem )); + newItem->file = r_file; + newItem->indx = indx; + newItem->ref_count = 1; + newItem->cx = cxIcon; + newItem->cy = cyIcon; + iconSourceList.insert( newItem ); + + return newItem; +} + +IconSourceItem* GetIconSourceItemFromPath( const TCHAR* path, int cxIcon, int cyIcon ) +{ + TCHAR *comma; + TCHAR file[ MAX_PATH ]; + int n; + + if ( !path ) + return NULL; + + lstrcpyn( file, path, SIZEOF( file )); + comma = _tcsrchr( file, ',' ); + if ( !comma ) + n = 0; + else { + n = _ttoi( comma+1 ); + *comma = 0; + } + return GetIconSourceItem( file, n, cxIcon, cyIcon ); +} + +IconSourceItem* CreateStaticIconSourceItem( int cxIcon, int cyIcon ) +{ + TCHAR sourceName[ MAX_PATH ]; + IconSourceFile key = { sourceName }; + + int i = 0; + do { // find new unique name + mir_sntprintf( sourceName, SIZEOF( sourceName ), _T("*StaticIcon_%d"), ++i); + } while ( iconSourceFileList.getIndex( &key ) != -1 ); + + IconSourceItem* newItem = (IconSourceItem*)mir_calloc( sizeof( IconSourceItem )); + newItem->file = IconSourceFile_Get( sourceName, false ); + newItem->indx = 0; + newItem->ref_count = 1; + newItem->cx = cxIcon; + newItem->cy = cyIcon; + iconSourceList.insert( newItem ); + + return newItem; +} + +static int IconSourceItem_Release( IconSourceItem** pitem ) +{ + if ( pitem && *pitem && (*pitem)->ref_count ) { + IconSourceItem* item = *pitem; + item->ref_count--; + if ( !item->ref_count ) { + int indx; + if (( indx = iconSourceList.getIndex( item )) != -1 ) { + IconSourceFile_Release( &item->file ); + SafeDestroyIcon( &item->icon ); + SAFE_FREE(( void** )&item->icon_data ); + iconSourceList.remove( indx ); + SAFE_FREE(( void** )&item ); + } + } + *pitem = NULL; + return 0; + } + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Service functions + +static HICON ExtractIconFromPath( const TCHAR *path, int cxIcon, int cyIcon ) +{ + TCHAR *comma; + TCHAR file[ MAX_PATH ], fileFull[ MAX_PATH ]; + int n; + HICON hIcon; + + if ( !path ) + return (HICON)NULL; + + lstrcpyn( file, path, SIZEOF( file )); + comma = _tcsrchr( file, ',' ); + if ( !comma ) + n = 0; + else { + n = _ttoi( comma+1 ); + *comma = 0; + } + pathToAbsoluteT( file, fileFull, NULL ); + hIcon = NULL; + + //SHOULD BE REPLACED WITH GOOD ENOUGH FUNCTION + _ExtractIconEx( fileFull, n, cxIcon, cyIcon, &hIcon, LR_COLOR ); + return hIcon; +} + +static SectionItem* IcoLib_AddSection(TCHAR *sectionName, BOOL create_new) +{ + if ( !sectionName ) + return NULL; + + int indx; + SectionItem key = { sectionName, 0 }; + if (( indx = sectionList.getIndex( &key )) != -1 ) + return sectionList[ indx ]; + + if ( create_new ) { + SectionItem* newItem = ( SectionItem* )mir_calloc( sizeof( SectionItem )); + newItem->name = mir_tstrdup( sectionName ); + newItem->flags = 0; + sectionList.insert( newItem ); + bNeedRebuild = TRUE; + return newItem; + } + + return NULL; +} + +static void IcoLib_RemoveSection(SectionItem* section) +{ + if ( !section ) + return; + + int indx; + + if (( indx = sectionList.getIndex( section )) != -1 ) { + sectionList.remove( indx ); + SAFE_FREE(( void** )§ion->name); + SAFE_FREE(( void** )§ion ); + bNeedRebuild = TRUE; + } +} + +static IconItem* IcoLib_FindIcon(const char* pszIconName) +{ + int indx; + IconItem key = { (char*)pszIconName }; + if (( indx = iconList.getIndex( &key )) != -1 ) + return iconList[ indx ]; + + return NULL; +} + +static IconItem* IcoLib_FindHIcon(HICON hIcon, bool &big) +{ + IconItem* item = NULL; + int indx; + + for ( indx = 0; indx < iconList.getCount(); indx++ ) { + if ( iconList[ indx ]->source_small && iconList[ indx ]->source_small->icon == hIcon) { + item = iconList[ indx ]; + big = false; + break; + } + else if ( iconList[ indx ]->source_big && iconList[ indx ]->source_big->icon == hIcon) { + item = iconList[ indx ]; + big = true; + break; + } + } + + return item; +} + +static void IcoLib_FreeIcon(IconItem* icon) +{ + if ( !icon) return; + + SAFE_FREE(( void** )&icon->name ); + SAFE_FREE(( void** )&icon->description ); + SAFE_FREE(( void** )&icon->default_file ); + SAFE_FREE(( void** )&icon->temp_file ); + if ( icon->section) { + if ( !--icon->section->ref_count) + IcoLib_RemoveSection( icon->section ); + icon->section = NULL; + } + IconSourceItem_Release( &icon->source_small ); + IconSourceItem_Release( &icon->source_big ); + IconSourceItem_Release( &icon->default_icon ); + SafeDestroyIcon( &icon->temp_icon ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_AddNewIcon + +HANDLE IcoLib_AddNewIcon( SKINICONDESC* sid ) +{ + int utf = 0, utf_path = 0; + IconItem* item; + + if ( !sid->cbSize ) + return NULL; + + if ( sid->cbSize < SKINICONDESC_SIZE_V1 ) + return NULL; + + if ( sid->cbSize >= SKINICONDESC_SIZE ) { + utf = sid->flags & SIDF_UNICODE ? 1 : 0; + utf_path = sid->flags & SIDF_PATH_UNICODE ? 1 : 0; + } + + EnterCriticalSection( &csIconList ); + + item = IcoLib_FindIcon( sid->pszName ); + if ( !item ) { + item = ( IconItem* )mir_alloc( sizeof( IconItem )); + item->name = sid->pszName; + iconList.insert( item ); + } + else IcoLib_FreeIcon( item ); + + ZeroMemory( item, sizeof( *item )); + item->name = mir_strdup( sid->pszName ); + if ( utf ) { + item->description = mir_u2t( sid->pwszDescription ); + #ifdef _UNICODE + item->section = IcoLib_AddSection( sid->pwszSection, TRUE ); + #else + char* pszSection = sid->pwszSection ? mir_u2a( sid->pwszSection ) : NULL; + item->section = IcoLib_AddSection( pszSection, TRUE ); + SAFE_FREE(( void** )&pszSection ); + #endif + } + else { + item->description = mir_a2t( sid->pszDescription ); + #ifdef _UNICODE + WCHAR* pwszSection = sid->pszSection ? mir_a2u( sid->pszSection ) : NULL; + + item->section = IcoLib_AddSection( pwszSection, TRUE ); + SAFE_FREE(( void** )&pwszSection ); + #else + item->section = IcoLib_AddSection( sid->pszSection, TRUE ); + #endif + } + if ( item->section ) { + item->section->ref_count++; + item->orderID = ++item->section->maxOrder; + } + else + item->orderID = 0; + + if ( sid->pszDefaultFile ) { + if ( utf_path ) { + #ifdef _UNICODE + WCHAR fileFull[ MAX_PATH ]; + + pathToAbsoluteW( sid->pwszDefaultFile, fileFull, NULL ); + item->default_file = mir_wstrdup( fileFull ); + #else + char* file = mir_u2a( sid->pwszDefaultFile ); + char fileFull[ MAX_PATH ]; + + pathToAbsolute( file, fileFull, NULL ); + SAFE_FREE(( void** )&file ); + item->default_file = mir_strdup( fileFull ); + #endif + } + else { + #ifdef _UNICODE + WCHAR *file = mir_a2u( sid->pszDefaultFile ); + WCHAR fileFull[ MAX_PATH ]; + + pathToAbsoluteW( file, fileFull, NULL ); + SAFE_FREE(( void** )&file ); + item->default_file = mir_wstrdup( fileFull ); + #else + char fileFull[ MAX_PATH ]; + + pathToAbsolute( sid->pszDefaultFile, fileFull, NULL ); + item->default_file = mir_strdup( fileFull ); + #endif + } } + item->default_indx = sid->iDefaultIndex; + + if ( sid->cbSize >= SKINICONDESC_SIZE_V3 ) { + item->cx = sid->cx; + item->cy = sid->cy; + } + + if ( sid->cbSize >= SKINICONDESC_SIZE_V2 && sid->hDefaultIcon ) { + bool big; + IconItem* def_item = IcoLib_FindHIcon( sid->hDefaultIcon, big ); + if ( def_item ) { + item->default_icon = big ? def_item->source_big : def_item->source_small; + item->default_icon->ref_count++; + } + else { + int cx = item->cx ? item->cx : GetSystemMetrics(SM_CXSMICON); + int cy = item->cy ? item->cy : GetSystemMetrics(SM_CYSMICON); + item->default_icon = CreateStaticIconSourceItem( cx, cy ); + if ( GetIconData( sid->hDefaultIcon, &item->default_icon->icon_data, &item->default_icon->icon_size )) { + IconSourceItem_Release( &item->default_icon ); + } + } + } + + if ( sid->cbSize >= SKINICONDESC_SIZE && item->section ) + item->section->flags = sid->flags & SIDF_SORTED; + + LeaveCriticalSection( &csIconList ); + return item; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_RemoveIcon + +static INT_PTR IcoLib_RemoveIcon( WPARAM, LPARAM lParam ) +{ + if ( lParam ) { + int indx; + EnterCriticalSection( &csIconList ); + + if (( indx = iconList.getIndex(( IconItem* )&lParam )) != -1 ) { + IconItem *item = iconList[ indx ]; + IcoLib_FreeIcon( item ); + iconList.remove( indx ); + SAFE_FREE(( void** )&item ); + } + + LeaveCriticalSection( &csIconList ); + return ( indx == -1 ) ? 1 : 0; + } + return 1; // Failed +} + +HICON IconItem_GetDefaultIcon( IconItem* item, bool big ) +{ + HICON hIcon = NULL; + + if ( item->default_icon && !big ) { + IconSourceItem_Release( &item->source_small ); + item->source_small = item->default_icon; + item->source_small->ref_count++; + hIcon = IconSourceItem_GetIcon( item->source_small ); + } + + if ( !hIcon && item->default_file ) { + int cx = item->cx ? item->cx : GetSystemMetrics(big ? SM_CXICON : SM_CXSMICON); + int cy = item->cy ? item->cy : GetSystemMetrics(big ? SM_CYICON : SM_CYSMICON); + IconSourceItem* def_icon = GetIconSourceItem( item->default_file, item->default_indx, cx, cy ); + if ( big ) { + if ( def_icon != item->source_big ) { + IconSourceItem_Release( &item->source_big ); + item->source_big = def_icon; + if ( def_icon ) { + def_icon->ref_count++; + hIcon = IconSourceItem_GetIcon( def_icon ); + } + } + else + IconSourceItem_Release( &def_icon ); + } + else { + if ( def_icon != item->default_icon ) { + IconSourceItem_Release( &item->default_icon ); + item->default_icon = def_icon; + if ( def_icon ) { + IconSourceItem_Release( &item->source_small ); + item->source_small = def_icon; + def_icon->ref_count++; + hIcon = IconSourceItem_GetIcon( def_icon ); + } + } + else + IconSourceItem_Release( &def_icon ); + } + } + return hIcon; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// IconItem_GetIcon + +HICON IconItem_GetIcon( IconItem* item, bool big ) +{ + DBVARIANT dbv = {0}; + HICON hIcon = NULL; + + big = big && !item->cx; + IconSourceItem* &source = big ? item->source_big : item->source_small; + + if ( !source && !DBGetContactSettingTString( NULL, "SkinIcons", item->name, &dbv )) { + int cx = item->cx ? item->cx : GetSystemMetrics(big ? SM_CXICON : SM_CXSMICON); + int cy = item->cy ? item->cy : GetSystemMetrics(big ? SM_CYICON : SM_CYSMICON); + source = GetIconSourceItemFromPath( dbv.ptszVal, cx, cy ); + DBFreeVariant( &dbv ); + } + + if ( source ) + hIcon = IconSourceItem_GetIcon( source ); + + if ( !hIcon ) + hIcon = IconItem_GetDefaultIcon( item, big ); + + if ( !hIcon ) + return hIconBlank; + + return hIcon; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IconItem_GetIcon_Preview + +HICON IconItem_GetIcon_Preview( IconItem* item ) +{ + HICON hIcon = NULL; + + if ( !item->temp_reset ) { + HICON hRefIcon = IconItem_GetIcon( item, false ); + hIcon = CopyIcon( hRefIcon ); + if ( item->source_small && item->source_small->icon == hRefIcon ) + IconSourceItem_ReleaseIcon( item->source_small ); + } + else { + if ( item->default_icon ) { + HICON hRefIcon = IconSourceItem_GetIcon( item->default_icon ); + if ( hRefIcon ) { + hIcon = CopyIcon( hRefIcon ); + if ( item->default_icon->icon == hRefIcon ) + IconSourceItem_ReleaseIcon( item->default_icon ); + } } + + if ( !hIcon && item->default_file ) { + IconSourceItem_Release( &item->default_icon ); + item->default_icon = GetIconSourceItem( item->default_file, item->default_indx, item->cx, item->cy ); + if ( item->default_icon ) { + HICON hRefIcon = IconSourceItem_GetIcon( item->default_icon ); + if ( hRefIcon ) { + hIcon = CopyIcon( hRefIcon ); + if ( item->default_icon->icon == hRefIcon ) + IconSourceItem_ReleaseIcon( item->default_icon ); + } } } + + if ( !hIcon ) + return CopyIcon(hIconBlank); + } + return hIcon; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_GetIcon +// lParam: pszIconName +// wParam: PLOADIMAGEPARAM or NULL. +// if wParam == NULL, default is used: +// uType = IMAGE_ICON +// cx/cyDesired = GetSystemMetrics(SM_CX/CYSMICON) +// fuLoad = 0 + +HICON IcoLib_GetIcon( const char* pszIconName, bool big ) +{ + IconItem* item; + HICON result = NULL; + + if ( !pszIconName ) + return hIconBlank; + + EnterCriticalSection( &csIconList ); + + item = IcoLib_FindIcon( pszIconName ); + if ( item ) { + result = IconItem_GetIcon( item, big ); + } + LeaveCriticalSection( &csIconList ); + return result; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_GetIconHandle +// lParam: pszIconName + +HANDLE IcoLib_GetIconHandle( const char* pszIconName ) +{ + IconItem* item; + + if ( !pszIconName ) + return NULL; + + EnterCriticalSection( &csIconList ); + item = IcoLib_FindIcon( pszIconName ); + LeaveCriticalSection( &csIconList ); + + return (HANDLE)item; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_GetIconByHandle +// lParam: icolib item handle +// wParam: 0 + +HICON IcoLib_GetIconByHandle( HANDLE hItem, bool big ) +{ + if ( hItem == NULL ) + return NULL; + + HICON result = hIconBlank; + IconItem* pi = ( IconItem* )hItem; + + EnterCriticalSection( &csIconList ); + + // we can get any junk here... but getIndex() is MUCH faster than indexOf(). + __try + { + if ( iconList.getIndex( pi ) != -1 ) + result = IconItem_GetIcon( pi, big ); + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + } + + LeaveCriticalSection( &csIconList ); + return result; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_IsManaged +// lParam: NULL +// wParam: HICON + +HANDLE IcoLib_IsManaged( HICON hIcon ) +{ + IconItem* item; + + EnterCriticalSection( &csIconList ); + + bool big; + item = IcoLib_FindHIcon( hIcon, big ); + if ( item ) { + IconSourceItem* source = big && !item->cx ? item->source_big : item->source_small; + if ( source->icon_ref_count == 0 ) + item = NULL; + } + + LeaveCriticalSection( &csIconList ); + return NULL; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_AddRef +// lParam: NULL +// wParam: HICON + +static INT_PTR IcoLib_AddRef( WPARAM wParam, LPARAM ) +{ + EnterCriticalSection( &csIconList ); + + bool big; + IconItem *item = IcoLib_FindHIcon(( HICON )wParam, big); + + INT_PTR res = 1; + if ( item ) { + IconSourceItem* source = big && !item->cx ? item->source_big : item->source_small; + if ( source->icon_ref_count ) { + source->icon_ref_count++; + res = 0; + } + } + + LeaveCriticalSection( &csIconList ); + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib_ReleaseIcon +// lParam: pszIconName or NULL +// wParam: HICON or NULL + +int IcoLib_ReleaseIcon( HICON hIcon, char* szIconName, bool big ) +{ + IconItem *item = NULL; + + EnterCriticalSection(&csIconList); + + if ( szIconName ) + item = IcoLib_FindIcon( szIconName ); + + if ( !item && hIcon ) // find by HICON + item = IcoLib_FindHIcon( hIcon, big ); + + int res = 1; + if ( item ) { + IconSourceItem* source = big && !item->cx ? item->source_big : item->source_small; + if ( source && source->icon_ref_count ) { + if ( iconEventActive ) + source->icon_ref_count--; + else + IconSourceItem_ReleaseIcon( source ); + res = 0; + } + } + + LeaveCriticalSection( &csIconList ); + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib GUI service routines + +static void LoadSectionIcons(TCHAR *filename, SectionItem* sectionActive) +{ + TCHAR path[ MAX_PATH ]; + int suffIndx; + HICON hIcon; + int indx; + + mir_sntprintf( path, SIZEOF(path), _T("%s,"), filename ); + suffIndx = lstrlen( path ); + + EnterCriticalSection( &csIconList ); + + for ( indx = 0; indx < iconList.getCount(); indx++ ) { + IconItem *item = iconList[ indx ]; + + if ( item->default_file && item->section == sectionActive ) { + _itot( item->default_indx, path + suffIndx, 10 ); + hIcon = ExtractIconFromPath( path, item->cx, item->cy ); + if ( hIcon ) { + SAFE_FREE(( void** )&item->temp_file ); + SafeDestroyIcon( &item->temp_icon ); + + item->temp_file = mir_tstrdup( path ); + item->temp_icon = hIcon; + item->temp_reset = FALSE; + } } } + + LeaveCriticalSection( &csIconList ); +} + +void LoadSubIcons(HWND htv, TCHAR *filename, HTREEITEM hItem) +{ + TVITEM tvi; + TreeItem *treeItem; + SectionItem* sectionActive; + + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + tvi.hItem = hItem; + + TreeView_GetItem( htv, &tvi ); + treeItem = (TreeItem *)tvi.lParam; + sectionActive = sectionList[ SECTIONPARAM_INDEX(treeItem->value) ]; + + tvi.hItem = TreeView_GetChild( htv, tvi.hItem ); + while ( tvi.hItem ) { + LoadSubIcons( htv, filename, tvi.hItem ); + tvi.hItem = TreeView_GetNextSibling( htv, tvi.hItem ); + } + + if ( SECTIONPARAM_FLAGS(treeItem->value) & SECTIONPARAM_HAVEPAGE ) + LoadSectionIcons( filename, sectionActive ); +} + +static void UndoChanges( int iconIndx, int cmd ) +{ + IconItem *item = iconList[ iconIndx ]; + + if ( !item->temp_file && !item->temp_icon && item->temp_reset && cmd == ID_CANCELCHANGE ) + item->temp_reset = FALSE; + else + { + SAFE_FREE(( void** )&item->temp_file ); + SafeDestroyIcon( &item->temp_icon ); + } + + if ( cmd == ID_RESET ) + item->temp_reset = TRUE; +} + +void UndoSubItemChanges( HWND htv, HTREEITEM hItem, int cmd ) +{ + TVITEM tvi = {0}; + TreeItem *treeItem; + int indx; + + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + tvi.hItem = hItem; + TreeView_GetItem( htv, &tvi ); + treeItem = (TreeItem *)tvi.lParam; + + if ( SECTIONPARAM_FLAGS( treeItem->value ) & SECTIONPARAM_HAVEPAGE ) { + EnterCriticalSection( &csIconList ); + + for ( indx = 0; indx < iconList.getCount(); indx++ ) + if ( iconList[ indx ]->section == sectionList[ SECTIONPARAM_INDEX(treeItem->value) ]) + UndoChanges( indx, cmd ); + + LeaveCriticalSection( &csIconList ); + } + + tvi.hItem = TreeView_GetChild( htv, tvi.hItem ); + while ( tvi.hItem ) { + UndoSubItemChanges( htv, tvi.hItem, cmd ); + tvi.hItem = TreeView_GetNextSibling( htv, tvi.hItem ); +} } + +static void OpenIconsPage() +{ + CallService( MS_UTILS_OPENURL, 1, (LPARAM)"http://addons.miranda-im.org/index.php?action=display&id=35" ); +} + +static int OpenPopupMenu(HWND hwndDlg) +{ + HMENU hMenu, hPopup; + POINT pt; + int cmd; + + GetCursorPos(&pt); + hMenu = LoadMenu( hMirandaInst, MAKEINTRESOURCE( IDR_ICOLIB_CONTEXT )); + hPopup = GetSubMenu( hMenu, 0 ); + CallService( MS_LANGPACK_TRANSLATEMENU, ( WPARAM )hPopup, 0 ); + cmd = TrackPopupMenu( hPopup, TPM_RIGHTBUTTON|TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL ); + DestroyMenu( hMenu ); + return cmd; +} + +static TCHAR* OpenFileDlg( HWND hParent, const TCHAR* szFile, BOOL bAll ) +{ + OPENFILENAME ofn = {0}; + TCHAR filter[512],*pfilter,file[MAX_PATH*2]; + + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hParent; + + lstrcpy(filter,TranslateT("Icon Sets")); + if (bAll) + lstrcat(filter,_T(" (*.dll;*.icl;*.exe;*.ico)")); + else + lstrcat(filter,_T(" (*.dll)")); + + pfilter=filter+lstrlen(filter)+1; + if (bAll) + lstrcpy(pfilter,_T("*.DLL;*.ICL;*.EXE;*.ICO")); + else + lstrcpy(pfilter,_T("*.DLL")); + + pfilter += lstrlen(pfilter) + 1; + lstrcpy(pfilter, TranslateT("All Files")); + lstrcat(pfilter,_T(" (*)")); + pfilter += lstrlen(pfilter) + 1; + lstrcpy(pfilter,_T("*")); + pfilter += lstrlen(pfilter) + 1; + *pfilter='\0'; + + ofn.lpstrFilter = filter; + ofn.lpstrDefExt = _T("dll"); + lstrcpyn(file, szFile, SIZEOF(file)); + ofn.lpstrFile = file; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + ofn.nMaxFile = MAX_PATH*2; + + if (!GetOpenFileName(&ofn)) + return NULL; + + return mir_tstrdup(file); +} + +// +// User interface +// + +#define DM_REBUILDICONSPREVIEW (WM_USER+10) +#define DM_CHANGEICON (WM_USER+11) +#define DM_CHANGESPECIFICICON (WM_USER+12) +#define DM_UPDATEICONSPREVIEW (WM_USER+13) +#define DM_REBUILD_CTREE (WM_USER+14) + +INT_PTR CALLBACK DlgProcIconImport(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +void DoOptionsChanged(HWND hwndDlg) +{ + SendMessage(hwndDlg, DM_UPDATEICONSPREVIEW, 0, 0); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); +} + +void DoIconsChanged(HWND hwndDlg) +{ + SendMessage(hwndDlg, DM_UPDATEICONSPREVIEW, 0, 0); + + iconEventActive = 1; // Disable icon destroying - performance boost + NotifyEventHooks(hIconsChangedEvent, 0, 0); + NotifyEventHooks(hIcons2ChangedEvent, 0, 0); + iconEventActive = 0; + + EnterCriticalSection(&csIconList); // Destroy unused icons + for (int indx = 0; indx < iconList.getCount(); indx++) { + IconItem *item = iconList[indx]; + if ( item->source_small && !item->source_small->icon_ref_count) { + item->source_small->icon_ref_count++; + IconSourceItem_ReleaseIcon( item->source_small ); + } + if ( item->source_big && !item->source_big->icon_ref_count) { + item->source_big->icon_ref_count++; + IconSourceItem_ReleaseIcon( item->source_big ); + } + } + LeaveCriticalSection(&csIconList); +} + +static HTREEITEM FindNamedTreeItemAt(HWND hwndTree, HTREEITEM hItem, const TCHAR *name) +{ + TVITEM tvi = {0}; + TCHAR str[MAX_PATH]; + + if (hItem) + tvi.hItem = TreeView_GetChild(hwndTree, hItem); + else + tvi.hItem = TreeView_GetRoot(hwndTree); + + if (!name) + return tvi.hItem; + + tvi.mask = TVIF_TEXT; + tvi.pszText = str; + tvi.cchTextMax = MAX_PATH; + + while (tvi.hItem) + { + TreeView_GetItem(hwndTree, &tvi); + + if (!lstrcmp(tvi.pszText, name)) + return tvi.hItem; + + tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem); + } + return NULL; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// icon import dialog's window procedure + +static int IconDlg_Resize(HWND, LPARAM, UTILRESIZECONTROL *urc) +{ + switch (urc->wId) { + case IDC_ICONSET: + return RD_ANCHORX_WIDTH | RD_ANCHORY_TOP; + + case IDC_BROWSE: + return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP; + + case IDC_PREVIEW: + return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; + + case IDC_GETMORE: + return RD_ANCHORX_CENTRE | RD_ANCHORY_BOTTOM; + } + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; // default +} + +INT_PTR CALLBACK DlgProcIconImport(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static HWND hwndParent,hwndDragOver; + static int dragging; + static int dragItem,dropHiLite; + static HWND hPreview = NULL; + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + hwndParent = (HWND)lParam; + hPreview = GetDlgItem(hwndDlg, IDC_PREVIEW); + dragging = dragItem = 0; + ListView_SetImageList(hPreview, ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),ILC_COLOR32|ILC_MASK,0,100), LVSIL_NORMAL); + ListView_SetIconSpacing(hPreview, 56, 67); + { + RECT rcThis, rcParent; + int cxScreen = GetSystemMetrics(SM_CXSCREEN); + + GetWindowRect(hwndDlg,&rcThis); + GetWindowRect(hwndParent,&rcParent); + OffsetRect(&rcThis,rcParent.right-rcThis.left,0); + OffsetRect(&rcThis,0,rcParent.top-rcThis.top); + GetWindowRect(GetParent(hwndParent),&rcParent); + if (rcThis.right > cxScreen) { + OffsetRect(&rcParent,cxScreen-rcThis.right,0); + OffsetRect(&rcThis,cxScreen-rcThis.right,0); + MoveWindow(GetParent(hwndParent),rcParent.left,rcParent.top,rcParent.right-rcParent.left,rcParent.bottom-rcParent.top,TRUE); + } + MoveWindow(hwndDlg,rcThis.left,rcThis.top,rcThis.right-rcThis.left,rcThis.bottom-rcThis.top,FALSE); + GetClientRect(hwndDlg, &rcThis); + SendMessage(hwndDlg, WM_SIZE, 0, MAKELPARAM(rcThis.right-rcThis.left, rcThis.bottom-rcThis.top)); + } + + if (shAutoComplete) shAutoComplete(GetDlgItem(hwndDlg,IDC_ICONSET), 1); + SetDlgItemText(hwndDlg,IDC_ICONSET,_T("icons.dll")); + return TRUE; + + case DM_REBUILDICONSPREVIEW: + { + LVITEM lvi; + TCHAR filename[MAX_PATH],caption[64]; + HIMAGELIST hIml; + int count,i; + HICON hIcon; + + MySetCursor(IDC_WAIT); + ListView_DeleteAllItems(hPreview); + hIml = ListView_GetImageList(hPreview, LVSIL_NORMAL); + ImageList_RemoveAll(hIml); + GetDlgItemText(hwndDlg, IDC_ICONSET, filename, SIZEOF(filename)); + { + RECT rcPreview,rcGroup; + + GetWindowRect(hPreview, &rcPreview); + GetWindowRect(GetDlgItem(hwndDlg,IDC_IMPORTMULTI),&rcGroup); + //SetWindowPos(hPreview,0,0,0,rcPreview.right-rcPreview.left,rcGroup.bottom-rcPreview.top,SWP_NOZORDER|SWP_NOMOVE); + } + + if (_taccess(filename,0) != 0) { + MySetCursor(IDC_ARROW); + break; + } + + lvi.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM; + lvi.iSubItem = 0; + lvi.iItem = 0; + count = (int)_ExtractIconEx( filename, -1, 16,16, NULL, LR_DEFAULTCOLOR ); + for (i = 0; i < count; lvi.iItem++, i++) { + mir_sntprintf(caption, SIZEOF(caption), _T("%d"), i+1); + lvi.pszText = caption; + //hIcon = ExtractIcon(hMirandaInst, filename, i); + _ExtractIconEx( filename, i, 16,16, &hIcon, LR_DEFAULTCOLOR ); + lvi.iImage = ImageList_AddIcon(hIml, hIcon); + DestroyIcon(hIcon); + lvi.lParam = i; + ListView_InsertItem(hPreview, &lvi); + } + MySetCursor(IDC_ARROW); + } + break; + + case WM_COMMAND: + switch( LOWORD( wParam )) { + case IDC_BROWSE: + { + TCHAR str[MAX_PATH]; + TCHAR *file; + + GetDlgItemText(hwndDlg,IDC_ICONSET,str,SIZEOF(str)); + if (!(file = OpenFileDlg(GetParent(hwndDlg), str, TRUE))) break; + SetDlgItemText(hwndDlg,IDC_ICONSET,file); + SAFE_FREE(( void** )&file ); + } + break; + + case IDC_GETMORE: + OpenIconsPage(); + break; + + case IDC_ICONSET: + if (HIWORD(wParam) == EN_CHANGE) + SendMessage(hwndDlg, DM_REBUILDICONSPREVIEW, 0, 0); + break; + } + break; + + case WM_MOUSEMOVE: + if (dragging) { + LVHITTESTINFO lvhti; + int onItem=0; + HWND hwndOver; + RECT rc; + POINT ptDrag; + HWND hPPreview = GetDlgItem(hwndParent, IDC_PREVIEW); + + lvhti.pt.x = (short)LOWORD(lParam); lvhti.pt.y = (short)HIWORD(lParam); + ClientToScreen(hwndDlg, &lvhti.pt); + hwndOver = WindowFromPoint(lvhti.pt); + GetWindowRect(hwndOver, &rc); + ptDrag.x = lvhti.pt.x - rc.left; ptDrag.y = lvhti.pt.y - rc.top; + if (hwndOver != hwndDragOver) { + ImageList_DragLeave(hwndDragOver); + hwndDragOver = hwndOver; + ImageList_DragEnter(hwndDragOver, ptDrag.x, ptDrag.y); + } + + ImageList_DragMove(ptDrag.x, ptDrag.y); + if (hwndOver == hPPreview) { + ScreenToClient(hPPreview, &lvhti.pt); + + if (ListView_HitTest(hPPreview, &lvhti) != -1) { + if (lvhti.iItem != dropHiLite) { + ImageList_DragLeave(hwndDragOver); + if (dropHiLite != -1) + ListView_SetItemState(hPPreview, dropHiLite, 0, LVIS_DROPHILITED); + dropHiLite = lvhti.iItem; + ListView_SetItemState(hPPreview, dropHiLite, LVIS_DROPHILITED, LVIS_DROPHILITED); + UpdateWindow(hPPreview); + ImageList_DragEnter(hwndDragOver, ptDrag.x, ptDrag.y); + } + onItem = 1; + } } + + if (!onItem && dropHiLite != -1) { + ImageList_DragLeave(hwndDragOver); + ListView_SetItemState(hPPreview, dropHiLite, 0, LVIS_DROPHILITED); + UpdateWindow(hPPreview); + ImageList_DragEnter(hwndDragOver, ptDrag.x, ptDrag.y); + dropHiLite = -1; + } + MySetCursor(onItem ? IDC_ARROW : IDC_NO); + } + break; + + case WM_LBUTTONUP: + if (dragging) { + ReleaseCapture(); + ImageList_EndDrag(); + dragging = 0; + if (dropHiLite != -1) { + TCHAR path[MAX_PATH],fullPath[MAX_PATH],filename[MAX_PATH]; + LVITEM lvi; + + GetDlgItemText(hwndDlg, IDC_ICONSET, fullPath, SIZEOF(fullPath)); + CallService(MS_UTILS_PATHTORELATIVET, (WPARAM)fullPath, (LPARAM)filename); + lvi.mask=LVIF_PARAM; + lvi.iItem = dragItem; lvi.iSubItem = 0; + ListView_GetItem(hPreview, &lvi); + mir_sntprintf(path, MAX_PATH, _T("%s,%d"), filename, (int)lvi.lParam); + SendMessage(hwndParent, DM_CHANGEICON, dropHiLite, (LPARAM)path); + ListView_SetItemState(GetDlgItem(hwndParent, IDC_PREVIEW), dropHiLite, 0, LVIS_DROPHILITED); + } } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case IDC_PREVIEW: + switch (((LPNMHDR)lParam)->code) { + case LVN_BEGINDRAG: + SetCapture(hwndDlg); + dragging = 1; + dragItem = ((LPNMLISTVIEW)lParam)->iItem; + dropHiLite = -1; + ImageList_BeginDrag(ListView_GetImageList(hPreview, LVSIL_NORMAL), dragItem, GetSystemMetrics(SM_CXICON)/2, GetSystemMetrics(SM_CYICON)/2); + { + POINT pt; + RECT rc; + + GetCursorPos(&pt); + GetWindowRect(hwndDlg, &rc); + ImageList_DragEnter(hwndDlg, pt.x - rc.left, pt.y - rc.top); + hwndDragOver = hwndDlg; + } + break; + } + break; + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + EnableWindow(GetDlgItem(hwndParent,IDC_IMPORT),TRUE); + break; + + case WM_SIZE: + { // make the dlg resizeable + UTILRESIZEDIALOG urd = {0}; + + if (IsIconic(hwndDlg)) break; + urd.cbSize = sizeof(urd); + urd.hInstance = hMirandaInst; + urd.hwndDlg = hwndDlg; + urd.lParam = 0; // user-defined + urd.lpTemplate = MAKEINTRESOURCEA(IDD_ICOLIB_IMPORT); + urd.pfnResizer = IconDlg_Resize; + CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd); + break; + } } + + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// IcoLib options window procedure + +static int CALLBACK DoSortIconsFunc(LPARAM lParam1, LPARAM lParam2, LPARAM ) +{ return lstrcmpi(TranslateTS(iconList[lParam1]->description), TranslateTS(iconList[lParam2]->description)); +} + +static int CALLBACK DoSortIconsFuncByOrder(LPARAM lParam1, LPARAM lParam2, LPARAM ) +{ return iconList[lParam1]->orderID - iconList[lParam2]->orderID; +} + +static void SaveCollapseState( HWND hwndTree ) +{ + HTREEITEM hti; + TVITEM tvi; + + hti = TreeView_GetRoot( hwndTree ); + while( hti != NULL ) { + HTREEITEM ht; + TreeItem *treeItem; + + tvi.mask = TVIF_STATE | TVIF_HANDLE | TVIF_CHILDREN | TVIF_PARAM; + tvi.hItem = hti; + tvi.stateMask = (DWORD)-1; + TreeView_GetItem( hwndTree, &tvi ); + + if( tvi.cChildren > 0 ) { + treeItem = (TreeItem *)tvi.lParam; + if ( tvi.state & TVIS_EXPANDED ) + DBWriteContactSettingByte(NULL, "SkinIconsUI", treeItem->paramName, TVIS_EXPANDED ); + else + DBWriteContactSettingByte(NULL, "SkinIconsUI", treeItem->paramName, 0 ); + } + + ht = TreeView_GetChild( hwndTree, hti ); + if( ht == NULL ) { + ht = TreeView_GetNextSibling( hwndTree, hti ); + while( ht == NULL ) { + hti = TreeView_GetParent( hwndTree, hti ); + if( hti == NULL ) break; + ht = TreeView_GetNextSibling( hwndTree, hti ); + } } + + hti = ht; +} } + +INT_PTR CALLBACK DlgProcIcoLibOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct IcoLibOptsData *dat; + static HTREEITEM prevItem = 0; + static HWND hPreview = NULL; + + dat = (struct IcoLibOptsData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + hPreview = GetDlgItem(hwndDlg, IDC_PREVIEW); + dat = (struct IcoLibOptsData*)mir_alloc(sizeof(struct IcoLibOptsData)); + dat->hwndIndex = NULL; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + // + // Reset temporary data & upload sections list + // + EnterCriticalSection(&csIconList); + { + int indx; + for (indx = 0; indx < iconList.getCount(); indx++) { + iconList[indx]->temp_file = NULL; + iconList[indx]->temp_icon = NULL; + iconList[indx]->temp_reset = FALSE; + } + bNeedRebuild = FALSE; + } + LeaveCriticalSection(&csIconList); + // + // Setup preview listview + // + ListView_SetUnicodeFormat(hPreview, TRUE); + ListView_SetExtendedListViewStyleEx(hPreview, LVS_EX_INFOTIP, LVS_EX_INFOTIP); + ListView_SetImageList(hPreview, ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),ILC_COLOR32|ILC_MASK,0,30), LVSIL_NORMAL); + ListView_SetIconSpacing(hPreview, 56, 67); + + SendMessage(hwndDlg, DM_REBUILD_CTREE, 0, 0); + return TRUE; + + case DM_REBUILD_CTREE: + { + HWND hwndTree = GetDlgItem(hwndDlg, IDC_CATEGORYLIST); + int indx; + TCHAR itemName[1024]; + HTREEITEM hSection; + + if (!hwndTree) break; + + TreeView_SelectItem(hwndTree, NULL); + TreeView_DeleteAllItems(hwndTree); + + for (indx = 0; indx < sectionList.getCount(); indx++) { + TCHAR* sectionName; + int sectionLevel = 0; + + hSection = NULL; + lstrcpy(itemName, sectionList[indx]->name); + sectionName = itemName; + + while (sectionName) { + // allow multi-level tree + TCHAR* pItemName = sectionName; + HTREEITEM hItem; + + if (sectionName = _tcschr(sectionName, '/')) { + // one level deeper + *sectionName = 0; + } + + pItemName = TranslateTS( pItemName ); + + hItem = FindNamedTreeItemAt(hwndTree, hSection, pItemName); + if (!sectionName || !hItem) { + if (!hItem) { + TVINSERTSTRUCT tvis = {0}; + TreeItem *treeItem = (TreeItem *)mir_alloc(sizeof(TreeItem)); + treeItem->value = SECTIONPARAM_MAKE( indx, sectionLevel, sectionName?0:SECTIONPARAM_HAVEPAGE ); + treeItem->paramName = mir_t2a(itemName); + + tvis.hParent = hSection; + tvis.hInsertAfter = TVI_SORT; //TVI_LAST; + tvis.item.mask = TVIF_TEXT|TVIF_PARAM|TVIF_STATE; + tvis.item.pszText = pItemName; + tvis.item.lParam = (LPARAM) treeItem; + + tvis.item.state = tvis.item.stateMask = DBGetContactSettingByte(NULL, "SkinIconsUI", treeItem->paramName, TVIS_EXPANDED ); + hItem = TreeView_InsertItem(hwndTree, &tvis); + } + else { + TVITEM tvi = {0}; + TreeItem *treeItem; + tvi.hItem = hItem; + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + TreeView_GetItem( hwndTree, &tvi ); + treeItem = (TreeItem *)tvi.lParam; + treeItem->value = SECTIONPARAM_MAKE( indx, sectionLevel, SECTIONPARAM_HAVEPAGE ); + } } + + if (sectionName) { + *sectionName = '/'; + sectionName++; + } + sectionLevel++; + + hSection = hItem; + } } + + ShowWindow(hwndTree, SW_SHOW); + + TreeView_SelectItem(hwndTree, FindNamedTreeItemAt(hwndTree, 0, NULL)); + } + break; + + // Rebuild preview to new section + case DM_REBUILDICONSPREVIEW: + { + LVITEM lvi = {0}; + HIMAGELIST hIml; + HICON hIcon; + SectionItem* sectionActive = ( SectionItem* )lParam; + int indx; + + EnableWindow(hPreview, sectionActive != NULL ); + + ListView_DeleteAllItems(hPreview); + hIml = ListView_GetImageList(hPreview, LVSIL_NORMAL); + ImageList_RemoveAll(hIml); + + if (sectionActive == NULL) + break; + + lvi.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM; + + EnterCriticalSection(&csIconList); + + for (indx = 0; indx < iconList.getCount(); indx++) { + IconItem *item = iconList[indx]; + + if (item->section == sectionActive) { + lvi.pszText = TranslateTS(item->description); + hIcon = item->temp_icon; + if ( !hIcon ) + hIcon = IconItem_GetIcon_Preview( item ); + lvi.iImage = ImageList_AddIcon(hIml, hIcon); + lvi.lParam = indx; + ListView_InsertItem(hPreview, &lvi); + if (hIcon != item->temp_icon) SafeDestroyIcon( &hIcon ); + } } + + LeaveCriticalSection(&csIconList); + + if ( sectionActive->flags & SIDF_SORTED ) + ListView_SortItems(hPreview, DoSortIconsFunc, 0); + else + ListView_SortItems(hPreview, DoSortIconsFuncByOrder, 0); + } + break; + + // Refresh preview to new section + case DM_UPDATEICONSPREVIEW: + { + LVITEM lvi = {0}; + HICON hIcon; + int indx, count; + HIMAGELIST hIml = ListView_GetImageList(hPreview, LVSIL_NORMAL); + + lvi.mask = LVIF_IMAGE|LVIF_PARAM; + count = ListView_GetItemCount(hPreview); + + for (indx = 0; indx < count; indx++) { + lvi.iItem = indx; + ListView_GetItem(hPreview, &lvi); + EnterCriticalSection(&csIconList); + hIcon = iconList[lvi.lParam]->temp_icon; + if (!hIcon) + hIcon = IconItem_GetIcon_Preview( iconList[lvi.lParam] ); + LeaveCriticalSection(&csIconList); + + if (hIcon) + ImageList_ReplaceIcon(hIml, lvi.iImage, hIcon); + if (hIcon != iconList[lvi.lParam]->temp_icon) SafeDestroyIcon( &hIcon ); + } + ListView_RedrawItems(hPreview, 0, count); + } + break; + + // Temporary change icon - only inside options dialog + case DM_CHANGEICON: + { + TCHAR *path=(TCHAR*)lParam; + LVITEM lvi = {0}; + IconItem *item; + + lvi.mask = LVIF_PARAM; + lvi.iItem = wParam; + ListView_GetItem( hPreview, &lvi ); + + EnterCriticalSection( &csIconList ); + item = iconList[ lvi.lParam ]; + + SAFE_FREE(( void** )&item->temp_file ); + SafeDestroyIcon( &item->temp_icon ); + item->temp_file = mir_tstrdup( path ); + item->temp_icon = ( HICON )ExtractIconFromPath( path, item->cx, item->cy ); + item->temp_reset = FALSE; + + LeaveCriticalSection( &csIconList ); + DoOptionsChanged( hwndDlg ); + } + break; + + case WM_COMMAND: + if ( LOWORD(wParam) == IDC_IMPORT ) { + dat->hwndIndex = CreateDialogParam(hMirandaInst, MAKEINTRESOURCE(IDD_ICOLIB_IMPORT), GetParent(hwndDlg), DlgProcIconImport, (LPARAM)hwndDlg); + EnableWindow((HWND)lParam, FALSE); + } + else if ( LOWORD(wParam) == IDC_GETMORE ) { + OpenIconsPage(); + break; + } + else if (LOWORD(wParam) == IDC_LOADICONS ) { + TCHAR filetmp[1] = {0}; + TCHAR *file; + + if ( file = OpenFileDlg( hwndDlg, filetmp, FALSE )) { + HWND htv = GetDlgItem( hwndDlg, IDC_CATEGORYLIST ); + TCHAR filename[ MAX_PATH ]; + + CallService( MS_UTILS_PATHTORELATIVET, ( WPARAM )file, ( LPARAM )filename ); + SAFE_FREE(( void** )&file ); + + MySetCursor( IDC_WAIT ); + LoadSubIcons( htv, filename, TreeView_GetSelection( htv )); + MySetCursor( IDC_ARROW ); + + DoOptionsChanged( hwndDlg ); + } } + break; + + case WM_CONTEXTMENU: + if (( HWND )wParam == hPreview ) { + UINT count = ListView_GetSelectedCount( hPreview ); + + if ( count > 0 ) { + int cmd = OpenPopupMenu( hwndDlg ); + switch( cmd ) { + case ID_CANCELCHANGE: + case ID_RESET: + { + LVITEM lvi = {0}; + int itemIndx = -1; + + while (( itemIndx = ListView_GetNextItem( hPreview, itemIndx, LVNI_SELECTED )) != -1 ) { + lvi.mask = LVIF_PARAM; + lvi.iItem = itemIndx; //lvhti.iItem; + ListView_GetItem( hPreview, &lvi ); + + UndoChanges( lvi.lParam, cmd ); + } + + DoOptionsChanged( hwndDlg ); + break; + } } } + } + else { + HWND htv = GetDlgItem( hwndDlg, IDC_CATEGORYLIST ); + if (( HWND )wParam == htv ) { + int cmd = OpenPopupMenu( hwndDlg ); + + switch( cmd ) { + case ID_CANCELCHANGE: + case ID_RESET: + UndoSubItemChanges( htv, TreeView_GetSelection( htv ), cmd ); + DoOptionsChanged( hwndDlg ); + break; + } } } + break; + + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + switch(((LPNMHDR)lParam)->code) { + case PSN_APPLY: + { + int indx; + + EnterCriticalSection(&csIconList); + + for (indx = 0; indx < iconList.getCount(); indx++) { + IconItem *item = iconList[indx]; + if (item->temp_reset) { + DBDeleteContactSetting(NULL, "SkinIcons", item->name); + if (item->source_small != item->default_icon) { + IconSourceItem_Release( &item->source_small ); + } + } + else if (item->temp_file) { + DBWriteContactSettingTString(NULL, "SkinIcons", item->name, item->temp_file); + IconSourceItem_Release( &item->source_small ); + SafeDestroyIcon( &item->temp_icon ); + } + } + LeaveCriticalSection(&csIconList); + + DoIconsChanged(hwndDlg); + return TRUE; + } } + break; + + case IDC_PREVIEW: + if(((LPNMHDR)lParam)->code == LVN_GETINFOTIP) + { + IconItem *item; + NMLVGETINFOTIP *pInfoTip = (NMLVGETINFOTIP *)lParam; + LVITEM lvi; + lvi.mask = LVIF_PARAM; + lvi.iItem = pInfoTip->iItem; + ListView_GetItem(pInfoTip->hdr.hwndFrom, &lvi); + + if( lvi.lParam < iconList.getCount() ) { + item = iconList[lvi.lParam]; + if( item->temp_file ) + _tcsncpy( pInfoTip->pszText, item->temp_file, pInfoTip->cchTextMax ); + else if( item->default_file ) + mir_sntprintf( pInfoTip->pszText, pInfoTip->cchTextMax, _T("%s,%d"), item->default_file, item->default_indx ); + } + } + if ( bNeedRebuild ) { + EnterCriticalSection(&csIconList); + bNeedRebuild = FALSE; + LeaveCriticalSection(&csIconList); + SendMessage(hwndDlg, DM_REBUILD_CTREE, 0, 0); + } + break; + + case IDC_CATEGORYLIST: + switch(((NMHDR*)lParam)->code) { + case TVN_SELCHANGEDA: // !!!! This needs to be here - both !! + case TVN_SELCHANGEDW: + { + NMTREEVIEW *pnmtv = (NMTREEVIEW*)lParam; + TVITEM tvi = pnmtv->itemNew; + TreeItem *treeItem = (TreeItem *)tvi.lParam; + if ( treeItem ) + SendMessage(hwndDlg, DM_REBUILDICONSPREVIEW, 0, ( LPARAM )( + (SECTIONPARAM_FLAGS(treeItem->value)&SECTIONPARAM_HAVEPAGE)? + sectionList[ SECTIONPARAM_INDEX(treeItem->value) ] : NULL ) ); + break; + } + case TVN_DELETEITEMA: // no idea why both TVN_SELCHANGEDA/W should be there but let's keep this both too... + case TVN_DELETEITEMW: + { + TreeItem *treeItem = (TreeItem *)(((LPNMTREEVIEW)lParam)->itemOld.lParam); + if (treeItem) { + mir_free(treeItem->paramName); + mir_free(treeItem); + } + break; + } } + if ( bNeedRebuild ) { + EnterCriticalSection(&csIconList); + bNeedRebuild = FALSE; + LeaveCriticalSection(&csIconList); + SendMessage(hwndDlg, DM_REBUILD_CTREE, 0, 0); + } } + break; + case WM_DESTROY: + { + int indx; + + SaveCollapseState( GetDlgItem(hwndDlg, IDC_CATEGORYLIST) ); + DestroyWindow(dat->hwndIndex); + + EnterCriticalSection(&csIconList); + for (indx = 0; indx < iconList.getCount(); indx++) { + IconItem *item = iconList[indx]; + + SAFE_FREE(( void** )&item->temp_file); + SafeDestroyIcon(&item->temp_icon); + } + LeaveCriticalSection(&csIconList); + + SAFE_FREE(( void** )&dat); + break; + } } + + return FALSE; +} + +static UINT iconsExpertOnlyControls[]={IDC_IMPORT}; + +static int SkinOptionsInit( WPARAM wParam,LPARAM ) +{ + OPTIONSDIALOGPAGE odp = {0}; + + odp.cbSize = sizeof(odp); + odp.hInstance = hMirandaInst; + odp.flags = ODPF_BOLDGROUPS; + odp.position = -180000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICOLIB); + odp.pszTitle = LPGEN("Icons"); + odp.pszGroup = LPGEN("Customize"); + odp.pfnDlgProc = DlgProcIcoLibOpts; + odp.expertOnlyControls = iconsExpertOnlyControls; + odp.nExpertOnlyControls = SIZEOF(iconsExpertOnlyControls); + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + return 0; +} + +static int SkinSystemModulesLoaded( WPARAM, LPARAM ) +{ + HookEvent(ME_OPT_INITIALISE, SkinOptionsInit); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Module initialization and finalization procedure + +static INT_PTR sttIcoLib_AddNewIcon( WPARAM, LPARAM lParam ) +{ return (INT_PTR)IcoLib_AddNewIcon(( SKINICONDESC* )lParam ); +} + +static INT_PTR sttIcoLib_GetIcon( WPARAM wParam, LPARAM lParam ) +{ return (INT_PTR)IcoLib_GetIcon(( const char* )lParam, wParam != 0 ); +} + +static INT_PTR sttIcoLib_GetIconHandle( WPARAM, LPARAM lParam ) +{ return (INT_PTR)IcoLib_GetIconHandle(( const char* )lParam ); +} + +static INT_PTR sttIcoLib_GetIconByHandle( WPARAM wParam, LPARAM lParam ) +{ return (INT_PTR)IcoLib_GetIconByHandle(( HANDLE )lParam, wParam != 0 ); +} + +static INT_PTR sttIcoLib_ReleaseIcon( WPARAM wParam, LPARAM lParam ) +{ return (INT_PTR)IcoLib_ReleaseIcon(( HICON )wParam, ( char* )lParam, false ); +} + +static INT_PTR sttIcoLib_ReleaseIconBig( WPARAM wParam, LPARAM lParam ) +{ return (INT_PTR)IcoLib_ReleaseIcon(( HICON )wParam, ( char* )lParam, true ); +} + +static INT_PTR sttIcoLib_IsManaged( WPARAM wParam, LPARAM ) +{ return (INT_PTR)IcoLib_IsManaged(( HICON )wParam ); +} + +int LoadIcoLibModule(void) +{ + bModuleInitialized = TRUE; + + hIconBlank = LoadIconEx(NULL, MAKEINTRESOURCE(IDI_BLANK),0); + + InitializeCriticalSection(&csIconList); + hIcoLib_AddNewIcon = CreateServiceFunction(MS_SKIN2_ADDICON, sttIcoLib_AddNewIcon); + hIcoLib_RemoveIcon = CreateServiceFunction(MS_SKIN2_REMOVEICON, IcoLib_RemoveIcon); + hIcoLib_GetIcon = CreateServiceFunction(MS_SKIN2_GETICON, sttIcoLib_GetIcon); + hIcoLib_GetIconHandle = CreateServiceFunction(MS_SKIN2_GETICONHANDLE, sttIcoLib_GetIconHandle); + hIcoLib_GetIcon2 = CreateServiceFunction(MS_SKIN2_GETICONBYHANDLE, sttIcoLib_GetIconByHandle); + hIcoLib_IsManaged = CreateServiceFunction(MS_SKIN2_ISMANAGEDICON, sttIcoLib_IsManaged); + hIcoLib_AddRef = CreateServiceFunction(MS_SKIN2_ADDREFICON, IcoLib_AddRef); + hIcoLib_ReleaseIcon = CreateServiceFunction(MS_SKIN2_RELEASEICON, sttIcoLib_ReleaseIcon); + hIcoLib_ReleaseIcon = CreateServiceFunction(MS_SKIN2_RELEASEICONBIG, sttIcoLib_ReleaseIconBig); + + hIcons2ChangedEvent = CreateHookableEvent(ME_SKIN2_ICONSCHANGED); + hIconsChangedEvent = CreateHookableEvent(ME_SKIN_ICONSCHANGED); + + HookEvent(ME_SYSTEM_MODULESLOADED, SkinSystemModulesLoaded); + + return 0; +} + +void UnloadIcoLibModule(void) +{ + int indx; + + if ( !bModuleInitialized ) return; + + DestroyHookableEvent(hIconsChangedEvent); + DestroyHookableEvent(hIcons2ChangedEvent); + + DestroyServiceFunction(hIcoLib_AddNewIcon); + DestroyServiceFunction(hIcoLib_RemoveIcon); + DestroyServiceFunction(hIcoLib_GetIcon); + DestroyServiceFunction(hIcoLib_GetIconHandle); + DestroyServiceFunction(hIcoLib_GetIcon2); + DestroyServiceFunction(hIcoLib_IsManaged); + DestroyServiceFunction(hIcoLib_AddRef); + DestroyServiceFunction(hIcoLib_ReleaseIcon); + DeleteCriticalSection(&csIconList); + + for (indx = iconList.getCount()-1; indx >= 0; indx-- ) { + IconItem* I = iconList[indx]; + iconList.remove( indx ); + IcoLib_FreeIcon( I ); + mir_free( I ); + } + iconList.destroy(); + + for (indx = iconSourceList.getCount()-1; indx >= 0; indx-- ) { + IconSourceItem* I = iconSourceList[indx]; + iconSourceList.remove( indx ); + IconSourceFile_Release( &I->file ); + SafeDestroyIcon( &I->icon ); + SAFE_FREE(( void** )&I->icon_data ); + SAFE_FREE(( void** )&I ); + } + iconSourceList.destroy(); + + for (indx = iconSourceFileList.getCount()-1; indx >= 0; indx-- ) { + IconSourceFile* I = iconSourceFileList[indx]; + iconSourceFileList.remove( indx ); + SAFE_FREE(( void** )&I->file ); + SAFE_FREE(( void** )&I ); + } + iconSourceFileList.destroy(); + + for (indx = 0; indx < sectionList.getCount(); indx++) { + SAFE_FREE(( void** )§ionList[indx]->name ); + mir_free( sectionList[indx] ); + } + sectionList.destroy(); + + SafeDestroyIcon(&hIconBlank); +} diff --git a/src/modules/idle/idle.cpp b/src/modules/idle/idle.cpp new file mode 100644 index 0000000000..bc8b3ec4ef --- /dev/null +++ b/src/modules/idle/idle.cpp @@ -0,0 +1,520 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2005 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#define IDLEMOD "Idle" +#define IDL_USERIDLECHECK "UserIdleCheck" +#define IDL_IDLEMETHOD "IdleMethod" +#define IDL_IDLETIME1ST "IdleTime1st" +#define IDL_IDLEONSAVER "IdleOnSaver" // IDC_SCREENSAVER +#define IDL_IDLEONFULLSCR "IdleOnFullScr" // IDC_FULLSCREEN +#define IDL_IDLEONLOCK "IdleOnLock" // IDC_LOCKED +#define IDL_IDLEONTSDC "IdleOnTerminalDisconnect" // +#define IDL_IDLEPRIVATE "IdlePrivate" // IDC_IDLEPRIVATE +#define IDL_IDLESTATUSLOCK "IdleStatusLock" // IDC_IDLESTATUSLOCK +#define IDL_AAENABLE "AAEnable" +#define IDL_AASTATUS "AAStatus" + +#define IdleObject_IsIdle(obj) (obj->state&0x1) +#define IdleObject_SetIdle(obj) (obj->state|=0x1) +#define IdleObject_ClearIdle(obj) (obj->state&=~0x1) + +// either use meth 0,1 or figure out which one +#define IdleObject_UseMethod0(obj) (obj->state&=~0x2) +#define IdleObject_UseMethod1(obj) (obj->state|=0x2) +#define IdleObject_GetMethod(obj) (obj->state&0x2) + +#define IdleObject_IdleCheckSaver(obj) (obj->state&0x4) +#define IdleObject_SetSaverCheck(obj) (obj->state|=0x4) + +#define IdleObject_IdleCheckWorkstation(obj) (obj->state&0x8) +#define IdleObject_SetWorkstationCheck(obj) (obj->state|=0x8) + +#define IdleObject_IsPrivacy(obj) (obj->state&0x10) +#define IdleObject_SetPrivacy(obj) (obj->state|=0x10) + +#define IdleObject_SetStatusLock(obj) (obj->state|=0x20) + +#define IdleObject_IdleCheckTerminal(obj) (obj->state&0x40) +#define IdleObject_SetTerminalCheck(obj) (obj->state|=0x40) + +#define IdleObject_IdleCheckFullScr(obj) (obj->state&0x80) +#define IdleObject_SetFullScrCheck(obj) (obj->state|=0x80) + +//#include + +#ifndef _INC_WTSAPI + +#define WTS_CURRENT_SERVER_HANDLE ((HANDLE)NULL) +#define WTS_CURRENT_SESSION ((DWORD)-1) + +typedef enum _WTS_CONNECTSTATE_CLASS { + WTSActive, // User logged on to WinStation + WTSConnected, // WinStation connected to client + WTSConnectQuery, // In the process of connecting to client + WTSShadow, // Shadowing another WinStation + WTSDisconnected, // WinStation logged on without client + WTSIdle, // Waiting for client to connect + WTSListen, // WinStation is listening for connection + WTSReset, // WinStation is being reset + WTSDown, // WinStation is down due to error + WTSInit, // WinStation in initialization +} WTS_CONNECTSTATE_CLASS; + + +typedef enum _WTS_INFO_CLASS { + WTSInitialProgram, + WTSApplicationName, + WTSWorkingDirectory, + WTSOEMId, + WTSSessionId, + WTSUserName, + WTSWinStationName, + WTSDomainName, + WTSConnectState, + WTSClientBuildNumber, + WTSClientName, + WTSClientDirectory, + WTSClientProductId, + WTSClientHardwareId, + WTSClientAddress, + WTSClientDisplay, + WTSClientProtocolType, +} WTS_INFO_CLASS; + +#endif + +VOID (WINAPI *_WTSFreeMemory)(PVOID); +BOOL (WINAPI *_WTSQuerySessionInformation)(HANDLE, DWORD, WTS_INFO_CLASS, PVOID, DWORD*); + +BOOL bIsWTSApiPresent = FALSE; + +static BOOL bModuleInitialized = FALSE; + +BOOL InitWTSAPI() +{ + HMODULE hDll = LoadLibraryA("wtsapi32.dll"); + if (hDll) { + _WTSFreeMemory = (VOID (WINAPI *)(PVOID))GetProcAddress(hDll, "WTSFreeMemory"); + #ifdef UNICODE + _WTSQuerySessionInformation = (BOOL (WINAPI *)(HANDLE, DWORD, WTS_INFO_CLASS, PVOID, DWORD*))GetProcAddress(hDll, "WTSQuerySessionInformationW"); + #else + _WTSQuerySessionInformation = (BOOL (WINAPI *)(HANDLE, DWORD, WTS_INFO_CLASS, PVOID, DWORD*))GetProcAddress(hDll, "WTSQuerySessionInformationA"); + #endif + + if (_WTSFreeMemory && _WTSQuerySessionInformation) return 1; + } + return 0; +} + +BOOL IsTerminalDisconnected() +{ + PVOID pBuffer = NULL; + DWORD pBytesReturned = 0; + BOOL result = FALSE; + + if ( !bIsWTSApiPresent ) + return FALSE; + + if ( _WTSQuerySessionInformation( WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSConnectState, &pBuffer, &pBytesReturned)) { + if ( *( PDWORD )pBuffer == WTSDisconnected) + result = TRUE; + } + else bIsWTSApiPresent = FALSE; + + if ( pBuffer ) + _WTSFreeMemory( pBuffer ); + return result; +} + +typedef struct { + UINT_PTR hTimer; + unsigned int useridlecheck; + unsigned int state; + unsigned int minutes; // user setting, number of minutes of inactivity to wait for + POINT mousepos; + unsigned int mouseidle; + int aastatus; + int idleType; +} + IdleObject; + +static const WORD aa_Status[] = {ID_STATUS_AWAY, ID_STATUS_NA, ID_STATUS_OCCUPIED, ID_STATUS_DND, ID_STATUS_ONTHEPHONE, ID_STATUS_OUTTOLUNCH}; + +static IdleObject gIdleObject; +static HANDLE hIdleEvent; +static BOOL (WINAPI * MyGetLastInputInfo)(PLASTINPUTINFO); + +void CALLBACK IdleTimer(HWND hwnd, UINT umsg, UINT_PTR idEvent, DWORD dwTime); + +static void IdleObject_ReadSettings(IdleObject * obj) +{ + obj->useridlecheck = DBGetContactSettingByte(NULL, IDLEMOD, IDL_USERIDLECHECK, 0); + obj->minutes = DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLETIME1ST, 10); + obj->aastatus = !DBGetContactSettingByte(NULL, IDLEMOD, IDL_AAENABLE, 0) ? 0 : DBGetContactSettingWord(NULL, IDLEMOD, IDL_AASTATUS, 0); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLEMETHOD, 0) ) IdleObject_UseMethod1(obj); + else IdleObject_UseMethod0(obj); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLEONSAVER, 0) ) IdleObject_SetSaverCheck(obj); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLEONFULLSCR, 0) ) IdleObject_SetFullScrCheck(obj); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLEONLOCK, 0 ) ) IdleObject_SetWorkstationCheck(obj); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLEPRIVATE, 0) ) IdleObject_SetPrivacy(obj); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLESTATUSLOCK, 0) ) IdleObject_SetStatusLock(obj); + if ( DBGetContactSettingByte(NULL, IDLEMOD, IDL_IDLEONTSDC, 0) ) IdleObject_SetTerminalCheck(obj); +} + +static void IdleObject_Create(IdleObject * obj) +{ + ZeroMemory(obj, sizeof(IdleObject)); + obj->hTimer=SetTimer(NULL, 0, 2000, IdleTimer); + IdleObject_ReadSettings(obj); +} + +static void IdleObject_Destroy(IdleObject * obj) +{ + if (IdleObject_IsIdle(obj)) + NotifyEventHooks(hIdleEvent, 0, 0); + IdleObject_ClearIdle(obj); + KillTimer(NULL, obj->hTimer); +} + +static int IdleObject_IsUserIdle(IdleObject * obj) +{ + DWORD dwTick; + if ( IdleObject_GetMethod(obj) ) { + CallService(MS_SYSTEM_GETIDLE, 0, (LPARAM)&dwTick); + return GetTickCount() - dwTick > (obj->minutes * 60 * 1000); + } + + if ( MyGetLastInputInfo != NULL ) { + LASTINPUTINFO ii; + ZeroMemory(&ii,sizeof(ii)); + ii.cbSize=sizeof(ii); + if ( MyGetLastInputInfo(&ii) ) + return GetTickCount() - ii.dwTime > (obj->minutes * 60 * 1000); + } + else { + POINT pt; + GetCursorPos(&pt); + if ( pt.x != obj->mousepos.x || pt.y != obj->mousepos.y ) { + obj->mousepos=pt; + obj->mouseidle=0; + } + else obj->mouseidle += 2; + + if ( obj->mouseidle ) + return obj->mouseidle * 1000 >= (obj->minutes * 60 * 1000); + } + return FALSE; +} + +static bool IsWorkstationLocked (void) +{ + bool rc = false; + + if (openInputDesktop != NULL) { + HDESK hDesk = openInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP); + if (hDesk == NULL) + rc = true; + else if (closeDesktop != NULL) + closeDesktop(hDesk); + } + return rc; +} + +static bool IsScreenSaverRunning(void) +{ + BOOL rc = FALSE; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &rc, FALSE); + return rc != 0; +} + +bool IsFullScreen(void) +{ + RECT rcScreen = {0}; + + rcScreen.right = GetSystemMetrics(SM_CXSCREEN); + rcScreen.bottom = GetSystemMetrics(SM_CYSCREEN); + + if (MyMonitorFromWindow) + { + HMONITOR hMon = MyMonitorFromWindow(cli.hwndContactList, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof(mi); + if (MyGetMonitorInfo(hMon, &mi)) + rcScreen = mi.rcMonitor; + } + + HWND hWndDesktop = GetDesktopWindow(); + HWND hWndShell = GetShellWindow(); + + // check foregroundwindow + HWND hWnd = GetForegroundWindow(); + if (hWnd && hWnd != hWndDesktop && hWnd != hWndShell) + { + TCHAR tszClassName[128] = _T(""); + GetClassName(hWnd, tszClassName, SIZEOF(tszClassName)); + if (_tcscmp(tszClassName, _T("WorkerW"))) + { + RECT rect, rectw, recti; + GetWindowRect(hWnd, &rectw); + + GetClientRect(hWnd, &rect); + ClientToScreen(hWnd, (LPPOINT)&rect); + ClientToScreen(hWnd, (LPPOINT)&rect.right); + + if (EqualRect(&rect, &rectw) && IntersectRect(&recti, &rect, &rcScreen) && + EqualRect(&recti, &rcScreen)) + return true; + } + } + + return false; +} + +static void IdleObject_Tick(IdleObject * obj) +{ + bool idle = false; + int idleType = 0, flags = 0; + + if ( obj->useridlecheck && IdleObject_IsUserIdle(obj)) { + idleType = 1; idle = true; + } + else if ( IdleObject_IdleCheckSaver(obj) && IsScreenSaverRunning()) { + idleType = 2; idle = true; + } + else if ( IdleObject_IdleCheckFullScr(obj) && IsFullScreen()) { + idleType = 5; idle = true; + } + else if ( IdleObject_IdleCheckWorkstation(obj) && IsWorkstationLocked()) { + idleType = 3; idle = true; + } + else if ( IdleObject_IdleCheckTerminal(obj) && IsTerminalDisconnected()) { + idleType = 4; idle = true; + } + + if ( IdleObject_IsPrivacy(obj)) + flags |= IDF_PRIVACY; + + if ( !IdleObject_IsIdle(obj) && idle ) { + IdleObject_SetIdle(obj); + obj->idleType = idleType; + NotifyEventHooks(hIdleEvent, 0, IDF_ISIDLE | flags); + } + if ( IdleObject_IsIdle(obj) && !idle ) { + IdleObject_ClearIdle(obj); + obj->idleType = 0; + NotifyEventHooks(hIdleEvent, 0, flags); +} } + +void CALLBACK IdleTimer(HWND, UINT, UINT_PTR idEvent, DWORD) +{ + if ( gIdleObject.hTimer == idEvent ) + IdleObject_Tick( &gIdleObject ); +} + +int IdleGetStatusIndex(WORD status) +{ + int j; + for (j = 0; j < SIZEOF(aa_Status); j++ ) + if ( aa_Status[j] == status ) + return j; + + return 0; +} + +static INT_PTR CALLBACK IdleOptsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch ( msg ) { + case WM_INITDIALOG: + { + int j; + int method = DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLEMETHOD, 0); + TranslateDialogDefault(hwndDlg); + CheckDlgButton(hwndDlg, IDC_IDLESHORT, DBGetContactSettingByte(NULL,IDLEMOD,IDL_USERIDLECHECK,0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_IDLEONWINDOWS, method == 0 ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_IDLEONMIRANDA, method ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_SCREENSAVER, DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLEONSAVER,0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_FULLSCREEN, DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLEONFULLSCR,0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_LOCKED, DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLEONLOCK,0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_IDLEPRIVATE, DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLEPRIVATE,0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_IDLESTATUSLOCK, DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLESTATUSLOCK,0) ? BST_CHECKED : BST_UNCHECKED); + if ( !bIsWTSApiPresent ) + EnableWindow( GetDlgItem( hwndDlg, IDC_IDLETERMINAL ), FALSE ); + else + CheckDlgButton(hwndDlg, IDC_IDLETERMINAL, DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLEONTSDC,0) ? BST_CHECKED : BST_UNCHECKED); + SendDlgItemMessage(hwndDlg, IDC_IDLESPIN, UDM_SETBUDDY, (WPARAM)GetDlgItem(hwndDlg, IDC_IDLE1STTIME), 0); + SendDlgItemMessage(hwndDlg, IDC_IDLESPIN, UDM_SETRANGE32, 1, 60); + SendDlgItemMessage(hwndDlg, IDC_IDLESPIN, UDM_SETPOS, 0, MAKELONG((short) DBGetContactSettingByte(NULL,IDLEMOD,IDL_IDLETIME1ST, 10), 0)); + SendDlgItemMessage(hwndDlg, IDC_IDLE1STTIME, EM_LIMITTEXT, (WPARAM)2, 0); + + CheckDlgButton(hwndDlg, IDC_AASHORTIDLE, DBGetContactSettingByte(NULL, IDLEMOD, IDL_AAENABLE, 0) ? BST_CHECKED:BST_UNCHECKED); + for ( j = 0; j < SIZEOF(aa_Status); j++ ) + SendDlgItemMessage(hwndDlg, IDC_AASTATUS, CB_ADDSTRING, 0, ( LPARAM )cli.pfnGetStatusModeDescription( aa_Status[j], 0 )); + + j = IdleGetStatusIndex((WORD)(DBGetContactSettingWord(NULL, IDLEMOD, IDL_AASTATUS, 0))); + SendDlgItemMessage(hwndDlg, IDC_AASTATUS, CB_SETCURSEL, j, 0); + SendMessage(hwndDlg, WM_USER+2, 0, 0); + return TRUE; + } + case WM_USER+2: + { + BOOL checked = IsDlgButtonChecked(hwndDlg, IDC_IDLESHORT) == BST_CHECKED; + EnableWindow(GetDlgItem(hwndDlg, IDC_IDLEONWINDOWS), checked); + EnableWindow(GetDlgItem(hwndDlg, IDC_IDLEONMIRANDA), checked); + EnableWindow(GetDlgItem(hwndDlg, IDC_IDLE1STTIME), checked); + EnableWindow(GetDlgItem(hwndDlg, IDC_AASTATUS), IsDlgButtonChecked(hwndDlg, IDC_AASHORTIDLE)==BST_CHECKED?1:0); + EnableWindow(GetDlgItem(hwndDlg, IDC_IDLESTATUSLOCK), IsDlgButtonChecked(hwndDlg, IDC_AASHORTIDLE)==BST_CHECKED?1:0); + break; + } + case WM_NOTIFY: + { + NMHDR * hdr = (NMHDR *)lParam; + if ( hdr && hdr->code == PSN_APPLY ) { + int method = IsDlgButtonChecked(hwndDlg, IDC_IDLEONWINDOWS) == BST_CHECKED; + int mins = SendDlgItemMessage(hwndDlg, IDC_IDLESPIN, UDM_GETPOS, 0, 0); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLETIME1ST, (BYTE)(HIWORD(mins) == 0 ? LOWORD(mins) : 10)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_USERIDLECHECK, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_IDLESHORT) == BST_CHECKED)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLEMETHOD, (BYTE)(method ? 0 : 1)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLEONSAVER, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_SCREENSAVER) == BST_CHECKED)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLEONFULLSCR, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_FULLSCREEN) == BST_CHECKED)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLEONLOCK, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_LOCKED) == BST_CHECKED)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLEONTSDC, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_IDLETERMINAL) == BST_CHECKED)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLEPRIVATE, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_IDLEPRIVATE) == BST_CHECKED)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_AAENABLE, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_AASHORTIDLE)==BST_CHECKED?1:0)); + DBWriteContactSettingByte(NULL, IDLEMOD, IDL_IDLESTATUSLOCK, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_IDLESTATUSLOCK)==BST_CHECKED?1:0)); + { + int curSel = SendDlgItemMessage(hwndDlg, IDC_AASTATUS, CB_GETCURSEL, 0, 0); + if ( curSel != CB_ERR ) { + DBWriteContactSettingWord(NULL, IDLEMOD, IDL_AASTATUS, (WORD)(aa_Status[curSel])); + } + } + // destroy any current idle and reset settings. + IdleObject_Destroy(&gIdleObject); + IdleObject_Create(&gIdleObject); + } + break; + } + case WM_COMMAND: + switch ( LOWORD( wParam )) { + case IDC_IDLE1STTIME: + { + int min; + if ( (HWND)lParam != GetFocus() || HIWORD(wParam) != EN_CHANGE ) return FALSE; + min=GetDlgItemInt(hwndDlg, IDC_IDLE1STTIME, NULL, FALSE); + if ( min == 0 && GetWindowTextLength(GetDlgItem(hwndDlg, IDC_IDLE1STTIME)) ) + SendDlgItemMessage(hwndDlg, IDC_IDLESPIN, UDM_SETPOS, 0, MAKELONG((short) 1, 0)); + break; + } + case IDC_IDLESHORT: + case IDC_AASHORTIDLE: + SendMessage(hwndDlg, WM_USER+2, 0, 0); + break; + + case IDC_AASTATUS: + if ( HIWORD(wParam) != CBN_SELCHANGE ) + return TRUE; + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + return FALSE; +} + +static int IdleOptInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.position = 100000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_IDLE); + odp.pszGroup = LPGEN("Status"); + odp.pszTitle = LPGEN("Idle"); + odp.pfnDlgProc = IdleOptsDlgProc; + odp.flags = ODPF_BOLDGROUPS; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} + +static INT_PTR IdleGetInfo(WPARAM, LPARAM lParam) +{ + MIRANDA_IDLE_INFO *mii = ( MIRANDA_IDLE_INFO* )lParam; + if ( !mii || ( mii->cbSize != sizeof(MIRANDA_IDLE_INFO) && mii->cbSize != MIRANDA_IDLE_INFO_SIZE_1 )) + return 1; + + mii->idleTime = gIdleObject.minutes; + mii->privacy = gIdleObject.state&0x10; + mii->aaStatus = gIdleObject.aastatus; + mii->aaLock = gIdleObject.state&0x20; + + if ( mii->cbSize == sizeof(MIRANDA_IDLE_INFO)) + mii->idleType = gIdleObject.idleType; + return 0; +} + +static int IdleModernOptInit(WPARAM wParam, LPARAM) +{ + static const int iBoldControls[] = + { + IDC_TXT_TITLE1, IDC_TXT_TITLE2, IDC_TXT_TITLE3, + MODERNOPT_CTRL_LAST + }; + + MODERNOPTOBJECT obj = {0}; + obj.cbSize = sizeof(obj); + obj.hInstance = hMirandaInst; + obj.dwFlags = MODEROPT_FLG_TCHAR | MODEROPT_FLG_NORESIZE; + obj.iSection = MODERNOPT_PAGE_STATUS; + obj.iType = MODERNOPT_TYPE_SECTIONPAGE; + obj.iBoldControls = (int*)iBoldControls; + obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_IDLE); + obj.pfnDlgProc = IdleOptsDlgProc; +// obj.lpzClassicGroup = "Status"; +// obj.lpzClassicPage = "Messages"; + obj.lpzHelpUrl = "http://wiki.miranda-im.org/"; + CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj); + return 0; +} + +int LoadIdleModule(void) +{ + bModuleInitialized = TRUE; + + bIsWTSApiPresent = InitWTSAPI(); + MyGetLastInputInfo=(BOOL (WINAPI *)(LASTINPUTINFO*))GetProcAddress(GetModuleHandleA("user32"), "GetLastInputInfo"); + hIdleEvent=CreateHookableEvent(ME_IDLE_CHANGED); + IdleObject_Create(&gIdleObject); + CreateServiceFunction(MS_IDLE_GETIDLEINFO, IdleGetInfo); + HookEvent(ME_OPT_INITIALISE, IdleOptInit); + HookEvent(ME_MODERNOPT_INITIALIZE, IdleModernOptInit); + return 0; +} + +void UnloadIdleModule() +{ + if ( !bModuleInitialized ) return; + + IdleObject_Destroy(&gIdleObject); + DestroyHookableEvent(hIdleEvent); + hIdleEvent=NULL; +} diff --git a/src/modules/ignore/ignore.cpp b/src/modules/ignore/ignore.cpp new file mode 100644 index 0000000000..18ded9d972 --- /dev/null +++ b/src/modules/ignore/ignore.cpp @@ -0,0 +1,481 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#define IGNOREEVENT_MAX 7 + +static const DWORD ignoreIdToPf1[IGNOREEVENT_MAX]={PF1_IMRECV,PF1_URLRECV,PF1_FILERECV,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF}; +static const DWORD ignoreIdToPf4[IGNOREEVENT_MAX]={0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,PF4_SUPPORTTYPING}; + +static DWORD GetMask(HANDLE hContact) +{ + DWORD mask=DBGetContactSettingDword(hContact,"Ignore","Mask1",(DWORD)(-1)); + if(mask==(DWORD)(-1)) { + if(hContact==NULL) mask=0; + else { + if(DBGetContactSettingByte(hContact,"CList","Hidden",0) || DBGetContactSettingByte(hContact,"CList","NotOnList",0)) + mask=DBGetContactSettingDword(NULL,"Ignore","Mask1",0); + else + mask=DBGetContactSettingDword(NULL,"Ignore","Default1",0); + } + } + return mask; +} + +static void SetListGroupIcons(HWND hwndList,HANDLE hFirstItem,HANDLE hParentItem,int *groupChildCount) +{ + int typeOfFirst; + int iconOn[IGNOREEVENT_MAX]={1,1,1,1,1,1,1}; + int childCount[IGNOREEVENT_MAX]={0,0,0,0,0,0,0},i; + int iImage; + HANDLE hItem,hChildItem; + + typeOfFirst=SendMessage(hwndList,CLM_GETITEMTYPE,(WPARAM)hFirstItem,0); + //check groups + if(typeOfFirst==CLCIT_GROUP) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hFirstItem); + while(hItem) { + hChildItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_CHILD,(LPARAM)hItem); + if(hChildItem) SetListGroupIcons(hwndList,hChildItem,hItem,childCount); + for(i=0; i < SIZEOF(iconOn); i++) + if(iconOn[i] && SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,i)==0) iconOn[i]=0; + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hItem); + } + //check contacts + if(typeOfFirst==CLCIT_CONTACT) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hFirstItem); + while(hItem) { + for( i=0; i < SIZEOF(iconOn); i++ ) { + iImage=SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,i); + if(iconOn[i] && iImage==0) iconOn[i]=0; + if(iImage!=0xFF) childCount[i]++; + } + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hItem); + } + //set icons + for( i=0; i < SIZEOF(iconOn); i++ ) { + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hParentItem,MAKELPARAM(i,childCount[i]?(iconOn[i]?i+3:0):0xFF)); + if(groupChildCount) groupChildCount[i]+=childCount[i]; + } + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hParentItem,MAKELPARAM(IGNOREEVENT_MAX,1)); + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hParentItem,MAKELPARAM(IGNOREEVENT_MAX+1,2)); +} + +static void SetAllChildIcons(HWND hwndList,HANDLE hFirstItem,int iColumn,int iImage) +{ + int typeOfFirst,iOldIcon; + HANDLE hItem,hChildItem; + + typeOfFirst=SendMessage(hwndList,CLM_GETITEMTYPE,(WPARAM)hFirstItem,0); + //check groups + if(typeOfFirst==CLCIT_GROUP) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hFirstItem); + while(hItem) { + hChildItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_CHILD,(LPARAM)hItem); + if(hChildItem) SetAllChildIcons(hwndList,hChildItem,iColumn,iImage); + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hItem); + } + //check contacts + if(typeOfFirst==CLCIT_CONTACT) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hFirstItem); + while(hItem) { + iOldIcon=SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,iColumn); + if(iOldIcon!=0xFF && iOldIcon!=iImage) SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(iColumn,iImage)); + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hItem); + } +} + +static void ResetListOptions(HWND hwndList) +{ + int i; + + SendMessage(hwndList,CLM_SETBKBITMAP,0,(LPARAM)(HBITMAP)NULL); + SendMessage(hwndList,CLM_SETBKCOLOR,GetSysColor(COLOR_WINDOW),0); + SendMessage(hwndList,CLM_SETGREYOUTFLAGS,0,0); + SendMessage(hwndList,CLM_SETLEFTMARGIN,4,0); + SendMessage(hwndList,CLM_SETINDENT,10,0); + SendMessage(hwndList,CLM_SETHIDEEMPTYGROUPS,1,0); + for(i=0;i<=FONTID_MAX;i++) + SendMessage(hwndList,CLM_SETTEXTCOLOR,i,GetSysColor(COLOR_WINDOWTEXT)); +} + +static void SetIconsForColumn(HWND hwndList,HANDLE hItem,HANDLE hItemAll,int iColumn,int iImage) +{ + int itemType; + + itemType=SendMessage(hwndList,CLM_GETITEMTYPE,(WPARAM)hItem,0); + if(itemType==CLCIT_CONTACT) { + int oldiImage = SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,iColumn); + if (oldiImage!=0xFF&&oldiImage!=iImage) + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(iColumn,iImage)); + } + else if(itemType==CLCIT_INFO) { + if(hItem==hItemAll) SetAllChildIcons(hwndList,hItem,iColumn,iImage); + else SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(iColumn,iImage)); //hItemUnknown + } + else if(itemType==CLCIT_GROUP) { + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_CHILD,(LPARAM)hItem); + if(hItem) SetAllChildIcons(hwndList,hItem,iColumn,iImage); + } +} + +static void InitialiseItem(HWND hwndList,HANDLE hContact,HANDLE hItem,DWORD proto1Caps,DWORD proto4Caps) +{ + DWORD mask; + int i; + + mask=GetMask(hContact); + for(i=0;iidFrom) { + case IDC_LIST: + switch (((LPNMHDR)lParam)->code) + { + case CLN_NEWCONTACT: + case CLN_LISTREBUILT: + SetAllContactIcons(GetDlgItem(hwndDlg,IDC_LIST)); + //fall through + case CLN_CONTACTMOVED: + SetListGroupIcons(GetDlgItem(hwndDlg,IDC_LIST),(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETNEXTITEM,CLGN_ROOT,0),hItemAll,NULL); + break; + case CLN_OPTIONSCHANGED: + ResetListOptions(GetDlgItem(hwndDlg,IDC_LIST)); + break; + case CLN_CHECKCHANGED: + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case NM_CLICK: + { HANDLE hItem; + NMCLISTCONTROL *nm=(NMCLISTCONTROL*)lParam; + DWORD hitFlags; + int iImage; + + if(nm->iColumn==-1) break; + hItem=(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_HITTEST,(WPARAM)&hitFlags,MAKELPARAM(nm->pt.x,nm->pt.y)); + if(hItem==NULL) break; + if(!(hitFlags&CLCHT_ONITEMEXTRA)) break; + if(nm->iColumn==IGNOREEVENT_MAX) { //ignore all + for(iImage=0;iImageiColumn==IGNOREEVENT_MAX+1) { //ignore none + for(iImage=0;iImageiColumn,0)); + if(iImage==0) iImage=nm->iColumn+3; + else if(iImage!=0xFF) iImage=0; + SetIconsForColumn(GetDlgItem(hwndDlg,IDC_LIST),hItem,hItemAll,nm->iColumn,iImage); + } + SetListGroupIcons(GetDlgItem(hwndDlg,IDC_LIST),(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETNEXTITEM,CLGN_ROOT,0),hItemAll,NULL); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + } + break; + case 0: + switch (((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { HANDLE hContact,hItem; + + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST,0,0); + do { + hItem=(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_FINDCONTACT,(WPARAM)hContact,0); + if(hItem) SaveItemMask(GetDlgItem(hwndDlg,IDC_LIST),hContact,hItem,"Mask1"); + if(SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETCHECKMARK,(WPARAM)hItem,0)) + DBDeleteContactSetting(hContact,"CList","Hidden"); + else + DBWriteContactSettingByte(hContact,"CList","Hidden",1); + } while(hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDNEXT,(WPARAM)hContact,0)); + SaveItemMask(GetDlgItem(hwndDlg,IDC_LIST),NULL,hItemAll,"Default1"); + SaveItemMask(GetDlgItem(hwndDlg,IDC_LIST),NULL,hItemUnknown,"Mask1"); + return TRUE; + } + case PSN_EXPERTCHANGED: + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_LIST),GWL_STYLE,((PSHNOTIFY*)lParam)->lParam?GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_LIST),GWL_STYLE)|CLS_CHECKBOXES|CLS_GROUPCHECKBOXES|CLS_SHOWHIDDEN:GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_LIST),GWL_STYLE)&~(CLS_CHECKBOXES|CLS_GROUPCHECKBOXES|CLS_SHOWHIDDEN)); + SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_AUTOREBUILD,0,0); + break; + } + break; + } + break; + case WM_DESTROY: + { int i; + HIMAGELIST hIml; + for( i=0; i < SIZEOF(hIcons); i++ ) + DestroyIcon(hIcons[i]); + hIml=(HIMAGELIST)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETEXTRAIMAGELIST,0,0); + ImageList_Destroy(hIml); + break; + } + } + return FALSE; +} + +static UINT expertOnlyControls[]={IDC_STCHECKMARKS}; +static int IgnoreOptInitialise(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.position = 900000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_IGNORE); + odp.pszTitle = LPGEN("Ignore"); + odp.pszGroup = LPGEN("Events"); + odp.pfnDlgProc = DlgProcIgnoreOpts; + odp.flags = ODPF_BOLDGROUPS; + odp.expertOnlyControls = expertOnlyControls; + odp.nExpertOnlyControls = SIZEOF( expertOnlyControls ); + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp); + return 0; +} + +static INT_PTR IsIgnored(WPARAM wParam,LPARAM lParam) +{ + DWORD mask=GetMask((HANDLE)wParam); + if(lParam<1 || lParam>IGNOREEVENT_MAX) return 1; + return (mask>>(lParam-1))&1; +} + +static INT_PTR Ignore(WPARAM wParam,LPARAM lParam) +{ + DWORD mask=GetMask((HANDLE)wParam); + if((lParam<1 || lParam>IGNOREEVENT_MAX) && lParam!=IGNOREEVENT_ALL) return 1; + if(lParam==IGNOREEVENT_ALL) mask=(1<IGNOREEVENT_MAX) && lParam!=IGNOREEVENT_ALL) return 1; + if(lParam==IGNOREEVENT_ALL) mask=0; + else mask&=~(1<<(lParam-1)); + DBWriteContactSettingDword((HANDLE)wParam,"Ignore","Mask1",mask); + return 0; +} + +static int IgnoreContactAdded(WPARAM wParam, LPARAM) +{ + CallService(MS_PROTO_ADDTOCONTACT,wParam,(LPARAM)"Ignore"); + return 0; +} + +static INT_PTR IgnoreRecvMessage(WPARAM wParam,LPARAM lParam) +{ + if(IsIgnored((WPARAM)((CCSDATA*)lParam)->hContact,IGNOREEVENT_MESSAGE)) return 1; + return CallService(MS_PROTO_CHAINRECV,wParam,lParam); +} + +static INT_PTR IgnoreRecvUrl(WPARAM wParam,LPARAM lParam) +{ + if(IsIgnored((WPARAM)((CCSDATA*)lParam)->hContact,IGNOREEVENT_URL)) return 1; + return CallService(MS_PROTO_CHAINRECV,wParam,lParam); +} + +static INT_PTR IgnoreRecvFile(WPARAM wParam,LPARAM lParam) +{ + if(IsIgnored((WPARAM)((CCSDATA*)lParam)->hContact,IGNOREEVENT_FILE)) return 1; + return CallService(MS_PROTO_CHAINRECV,wParam,lParam); +} + +static INT_PTR IgnoreRecvAuth(WPARAM wParam,LPARAM lParam) +{ + if(IsIgnored((WPARAM)((CCSDATA*)lParam)->hContact,IGNOREEVENT_AUTHORIZATION)) return 1; + return CallService(MS_PROTO_CHAINRECV,wParam,lParam); +} + +static int IgnoreAddedNotify(WPARAM, LPARAM lParam) +{ + DBEVENTINFO *dbei=(DBEVENTINFO*)lParam; + if (dbei && dbei->eventType==EVENTTYPE_ADDED && dbei->pBlob!=NULL) { + HANDLE hContact; + + hContact=*((PHANDLE)(dbei->pBlob+sizeof(DWORD))); + if (CallService(MS_DB_CONTACT_IS,(WPARAM)hContact,0) && IsIgnored((WPARAM)hContact,IGNOREEVENT_YOUWEREADDED)) + return 1; + } + return 0; +} + +static int IgnoreModernOptInit(WPARAM wParam, LPARAM) +{ + static int iBoldControls[] = + { + IDC_TXT_TITLE1, IDC_TXT_TITLE2, IDC_TXT_TITLE3, + MODERNOPT_CTRL_LAST + }; + + MODERNOPTOBJECT obj = {0}; + obj.cbSize = sizeof(obj); + obj.hInstance = hMirandaInst; + obj.dwFlags = MODEROPT_FLG_TCHAR; + obj.iSection = MODERNOPT_PAGE_IGNORE; + obj.iType = MODERNOPT_TYPE_SECTIONPAGE; + obj.iBoldControls = iBoldControls; + obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_IGNORE); + obj.pfnDlgProc = DlgProcIgnoreOpts; +// obj.lpzClassicGroup = "Events"; +// obj.lpzClassicPage = "Ignore"; + CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj); + return 0; +} + +int LoadIgnoreModule(void) +{ + PROTOCOLDESCRIPTOR pd = { 0 }; + pd.cbSize=sizeof(pd); + pd.szName="Ignore"; + pd.type=PROTOTYPE_IGNORE; + CallService(MS_PROTO_REGISTERMODULE,0,(LPARAM)&pd); + + HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST,0,0); + while ( hContact != NULL ) { + if (!CallService(MS_PROTO_ISPROTOONCONTACT,(WPARAM)hContact,(LPARAM)"Ignore")) + CallService(MS_PROTO_ADDTOCONTACT,(WPARAM)hContact,(LPARAM)"Ignore"); + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDNEXT,(WPARAM)hContact,0); + } + + CreateServiceFunction("Ignore"PSR_MESSAGE,IgnoreRecvMessage); + CreateServiceFunction("Ignore"PSR_URL,IgnoreRecvUrl); + CreateServiceFunction("Ignore"PSR_FILE,IgnoreRecvFile); + CreateServiceFunction("Ignore"PSR_AUTH,IgnoreRecvAuth); + CreateServiceFunction(MS_IGNORE_ISIGNORED,IsIgnored); + CreateServiceFunction(MS_IGNORE_IGNORE,Ignore); + CreateServiceFunction(MS_IGNORE_UNIGNORE,Unignore); + + HookEvent(ME_DB_CONTACT_ADDED,IgnoreContactAdded); + HookEvent(ME_DB_EVENT_FILTER_ADD,IgnoreAddedNotify); + HookEvent(ME_MODERNOPT_INITIALIZE, IgnoreModernOptInit); + HookEvent(ME_OPT_INITIALISE,IgnoreOptInitialise); + return 0; +} diff --git a/src/modules/keybindings/keybindings.cpp b/src/modules/keybindings/keybindings.cpp new file mode 100644 index 0000000000..6255cc78e9 --- /dev/null +++ b/src/modules/keybindings/keybindings.cpp @@ -0,0 +1,616 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "keybindings.h" +#include "m_keybindings.h" + +static HANDLE hKeyBindings_Register; +static HANDLE hKeyBindings_Get; +static WNDPROC OldEditProc; +static TCHAR *keySeparator = _T(" + "); +static TreeItem *currentTreeItem = NULL; +static DWORD tempModifiers; +static DWORD modifiers; +static DWORD virtualKey; + +static int addKeyBinding(KEYBINDINGDESC *desc) { + int i, len; + TCHAR *sectionName; + TCHAR *actionName; + KeyBindingItem *item = (KeyBindingItem *)mir_alloc(sizeof(KeyBindingItem)); + ZeroMemory(item, sizeof(KeyBindingItem)); + if (desc->flags & KBDF_UNICODE) { + #ifdef _UNICODE + sectionName = mir_tstrdup(desc->ptszSection); + actionName = mir_tstrdup(desc->ptszActionName); + #else + sectionName = u2a(desc->pwszSection); + actionName = u2a(desc->pwszActionName); + #endif + } else { + #ifdef _UNICODE + sectionName = a2u(desc->pszSection); + actionName = a2u(desc->pszActionName); + #else + sectionName = mir_tstrdup(desc->ptszSection); + actionName = mir_tstrdup(desc->ptszActionName); + #endif + } + len = _tcslen(sectionName) + _tcslen(actionName) + 2; + item->fullActionName = (TCHAR *)mir_alloc(len * sizeof(TCHAR)); + _tcscpy(item->fullActionName, sectionName); + _tcscat(item->fullActionName, _T("/")); + _tcscat(item->fullActionName, actionName); + item->actionName = actionName; + mir_free(sectionName); + item->actionGroupName = mir_strdup(desc->pszActionGroup); + item->action = desc->action; + for (i = 0; i < 5; i++) { + item->defaultKey[i] = desc->key[i]; + item->key[i] = desc->key[i]; + } + + item->next = keyBindingList; + keyBindingList = item; + if (item->next != NULL) + item->next->prev = item; + { + DBVARIANT dbv; + char *paramName = mir_t2a(item->fullActionName); + if ( !DBGetContactSettingString(NULL, "KeyBindings", paramName, &dbv )) { + for (i = 0; i < 5; i++) + item->key[i] = 0; + sscanf(dbv.pszVal, "%X,%X,%X,%X,%X", &item->key[0],&item->key[1],&item->key[2],&item->key[3],&item->key[4]); + DBFreeVariant(&dbv); + } + mir_free(paramName); + } + return 0; +} + +static KeyBindingItem* findKeyBinding(char *actionGroup, DWORD key) +{ + int i; + KeyBindingItem *ptr = NULL; + if (key != 0) { + for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) + if (strcmp(ptr->actionGroupName, actionGroup) == 0) + for (i = 0; i < 5; i++) + if (ptr->key[i] == key) return ptr; + } + return ptr; +} + +static KeyBindingItem* findTempKeyBinding(char *actionGroup, DWORD key) +{ + int i; + KeyBindingItem *ptr = NULL; + if (key != 0) { + for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) + if (strcmp(ptr->actionGroupName, actionGroup) == 0) + for (i = 0; i < 5; i++) + if (ptr->tempKey[i] == key) return ptr; + } + return ptr; +} + +static void removeTempKeyBinding(char *actionGroup, DWORD key) +{ + KeyBindingItem *ptr = NULL; + int i, j; + if (key != 0) { + for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) + if (strcmp(ptr->actionGroupName, actionGroup) == 0) + for (i = 0; i < 5; i++) + if (ptr->tempKey[i] == key) { + for (j = i+1; j < 5; j++) + ptr->tempKey[j-1] = ptr->tempKey[j]; + ptr->tempKey[4] = 0; + } + } +} + +static void loadTempKeyBinding(KeyBindingItem *item) +{ + int i; + for (i = 0; i < 5; i ++) + item->tempKey[i] = item->key[i]; +} + +static void loadTempKeyBindings() +{ + KeyBindingItem *ptr; + for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) + loadTempKeyBinding(ptr); +} + +static void saveKeyBinding(KeyBindingItem *item) +{ + BOOL save = FALSE; + int i; + char buff[128]; + for (i = 0; i < 5; i ++) { + if (item->key[i] != item->tempKey[i]) { + item->key[i] = item->tempKey[i]; + save = TRUE; + } + } + if (save) { + char *paramName = mir_t2a(item->fullActionName); + mir_snprintf(buff, sizeof(buff), "%X,%X,%X,%X,%X", item->key[0],item->key[1],item->key[2],item->key[3],item->key[4]); + DBWriteContactSettingString(NULL, "KeyBindings", paramName, buff); + mir_free(paramName); + } +} + +static void saveKeyBindings() +{ + KeyBindingItem *ptr; + for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) + saveKeyBinding(ptr); +} + +static HTREEITEM findNamedTreeItemAt(HWND hwndTree, HTREEITEM hItem, const TCHAR *name) +{ + TVITEM tvi = {0}; + TCHAR str[MAX_PATH]; + + if (hItem) + tvi.hItem = TreeView_GetChild(hwndTree, hItem); + else + tvi.hItem = TreeView_GetRoot(hwndTree); + + if (!name) + return tvi.hItem; + + tvi.mask = TVIF_TEXT; + tvi.pszText = str; + tvi.cchTextMax = MAX_PATH; + + while (tvi.hItem) + { + TreeView_GetItem(hwndTree, &tvi); + if (!lstrcmp(tvi.pszText, name)) + return tvi.hItem; + + tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem); + } + return NULL; +} + +static void createSettingsTreeNode(HWND hwndTree, KeyBindingItem *keyBindingItem) +{ + TCHAR itemName[1024]; + TCHAR* sectionName; + int sectionLevel = 0; + + HTREEITEM hSection = NULL; + lstrcpy(itemName, keyBindingItem->fullActionName); + sectionName = itemName; + + while (sectionName) { + HTREEITEM hItem; + TCHAR* pTranslatedItemName; + TCHAR* pItemName = sectionName; + + if (sectionName = _tcschr(sectionName, '/')) { + *sectionName = 0; + } + pTranslatedItemName = TranslateTS( pItemName ); + hItem = findNamedTreeItemAt(hwndTree, hSection, pTranslatedItemName); + if (!sectionName || !hItem) { + if (!hItem) { + TVINSERTSTRUCT tvis = {0}; + TreeItem *treeItem = (TreeItem *)mir_alloc(sizeof(TreeItem)); + treeItem->keyBinding = !sectionName ? keyBindingItem : NULL; + treeItem->paramName = mir_t2a(itemName); + tvis.hParent = hSection; + tvis.hInsertAfter = TVI_SORT; //!sectionName ? TVI_LAST : TVI_SORT; + tvis.item.mask = TVIF_TEXT|TVIF_PARAM|TVIF_STATE; + tvis.item.pszText = pTranslatedItemName; + tvis.item.lParam = (LPARAM)treeItem; + tvis.item.state = tvis.item.stateMask = DBGetContactSettingByte(NULL, "KeyBindingsUI", treeItem->paramName, TVIS_EXPANDED ); + hItem = TreeView_InsertItem(hwndTree, &tvis); + } + } + if (sectionName) { + *sectionName = '/'; + sectionName++; + } + sectionLevel++; + hSection = hItem; + } +} + +static void saveCollapseState( HWND hwndTree ) +{ + HTREEITEM hti; + TVITEM tvi; + hti = TreeView_GetRoot( hwndTree ); + while( hti != NULL ) { + HTREEITEM ht; + tvi.mask = TVIF_STATE | TVIF_HANDLE | TVIF_CHILDREN | TVIF_PARAM; + tvi.hItem = hti; + tvi.stateMask = (DWORD)-1; + TreeView_GetItem( hwndTree, &tvi ); + if( tvi.cChildren > 0 ) { + TreeItem *treeItem = (TreeItem *)tvi.lParam; + if ( tvi.state & TVIS_EXPANDED ) + DBWriteContactSettingByte(NULL, "KeyBindingsUI", treeItem->paramName, TVIS_EXPANDED ); + else + DBWriteContactSettingByte(NULL, "KeyBindingsUI", treeItem->paramName, 0 ); + } + ht = TreeView_GetChild( hwndTree, hti ); + if( ht == NULL ) { + ht = TreeView_GetNextSibling( hwndTree, hti ); + while( ht == NULL ) { + hti = TreeView_GetParent( hwndTree, hti ); + if( hti == NULL ) break; + ht = TreeView_GetNextSibling( hwndTree, hti ); + } } + hti = ht; +} } + + +static const TCHAR* getKeyName(DWORD key) { + static TCHAR keyName[64]; + int nameLen = 0; + ZeroMemory(keyName, sizeof(keyName)); + if (key & KB_CTRL_FLAG) { + GetKeyNameText(MAKELPARAM(0, MapVirtualKey(VK_CONTROL, 0)), keyName, 64); + _tcscat(keyName, keySeparator); + nameLen = _tcslen(keyName); + } + if (key & KB_SHIFT_FLAG) { + GetKeyNameText(MAKELPARAM(0, MapVirtualKey(VK_SHIFT, 0)), &keyName[nameLen], 64 - nameLen); + _tcscat(keyName, keySeparator); + nameLen = _tcslen(keyName); + } + if (key & KB_ALT_FLAG) { + GetKeyNameText(MAKELPARAM(0, MapVirtualKey(VK_MENU, 0)), &keyName[nameLen], 64 - nameLen); + _tcscat(keyName, keySeparator); + nameLen = _tcslen(keyName); + } + if ((key & 0xFFFF) != 0) { + DWORD scanCode = MapVirtualKey(key & 0xFFFF, 0); + switch(key & 0xFFFF) { + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_NEXT: + case VK_PRIOR: + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + scanCode |= 0x100; // Add extended bit + } + GetKeyNameText(MAKELPARAM(0, scanCode), &keyName[nameLen], 64 - nameLen); + nameLen = _tcslen(keyName); + } + return keyName; +} + +static refreshPreview(HWND hwnd) +{ + TCHAR warning[1024]; + ZeroMemory(warning, sizeof(warning)); + SetWindowText(hwnd, getKeyName(virtualKey | modifiers)); + if (currentTreeItem != NULL && currentTreeItem->keyBinding != NULL) { + KeyBindingItem *item = findTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, virtualKey | modifiers); + if (item != NULL) + mir_sntprintf(warning, 1024, TranslateT("Shortcut already assigned to \"%s\" action.\nIf you click \"Add\" the shortcut will be reassigned."), item->actionName); + } + SetDlgItemText(GetParent(hwnd), IDC_MESSAGE, warning); + EnableWindow(GetDlgItem(GetParent(hwnd), IDC_ADD), virtualKey != 0); +} + +static LRESULT CALLBACK KeyBindingsEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_CREATE: + virtualKey = 0; + break; + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + if (virtualKey != 0) { + virtualKey = 0; + } + switch (wParam) + { + case VK_SHIFT: + tempModifiers |= KB_SHIFT_FLAG; + break; + case VK_CONTROL: + tempModifiers |= KB_CTRL_FLAG; + break; + case VK_MENU: + tempModifiers |= KB_ALT_FLAG; + break; + default: + virtualKey = wParam; + break; + } + modifiers = tempModifiers; + refreshPreview(hwnd); + return 0; + case WM_KEYUP: + case WM_SYSKEYUP: + switch (wParam) + { + case VK_SHIFT: + tempModifiers &= ~KB_SHIFT_FLAG; + break; + case VK_CONTROL: + tempModifiers &= ~KB_CTRL_FLAG; + break; + case VK_MENU: + tempModifiers &= ~KB_ALT_FLAG; + break; + default: + break; + } + if (virtualKey == 0) { + modifiers = tempModifiers; + refreshPreview(hwnd); + } + case WM_CHAR: + case WM_PASTE: + return 0; + case WM_SETFOCUS: + modifiers = 0; + tempModifiers = 0; + virtualKey = 0; + refreshPreview(hwnd); + break; + case WM_GETDLGCODE: + return DLGC_WANTARROWS|DLGC_WANTALLKEYS| DLGC_WANTTAB; + } + return CallWindowProc(OldEditProc, hwnd, msg, wParam, lParam); +} + +static void buildTree(HWND hwnd) { + KeyBindingItem *ptr; + for (ptr = keyBindingList; ptr != NULL; ptr=ptr->next) { + createSettingsTreeNode(hwnd, ptr); + } +} + +static void refreshListBox(HWND hwnd) { + int count = 0; + BOOL nonDefault = FALSE; + BOOL canUndo = FALSE; + SendDlgItemMessage(hwnd, IDC_LIST, LB_RESETCONTENT, 0, 0); + if (currentTreeItem->keyBinding != NULL) { + int i; + for (i=0; i<5; i++) { + if (currentTreeItem->keyBinding->tempKey[i] != currentTreeItem->keyBinding->defaultKey[i]) nonDefault = TRUE; + if (currentTreeItem->keyBinding->tempKey[i] != currentTreeItem->keyBinding->key[i]) canUndo = TRUE; + if (currentTreeItem->keyBinding->tempKey[i] != 0) { + SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0, (LPARAM)getKeyName(currentTreeItem->keyBinding->tempKey[i])); + count++; + } + } + } + EnableWindow(GetDlgItem(hwnd, IDC_PREVIEW), currentTreeItem->keyBinding != NULL && count < 5); + SetDlgItemText(hwnd, IDC_PREVIEW, _T("")); + SetDlgItemText(hwnd, IDC_MESSAGE, _T("")); + EnableWindow(GetDlgItem(hwnd, IDC_ADD), FALSE); + EnableWindow(GetDlgItem(hwnd, IDC_DELETE), FALSE); + EnableWindow(GetDlgItem(hwnd, IDC_BTN_RESET), nonDefault); + EnableWindow(GetDlgItem(hwnd, IDC_BTN_UNDO), canUndo); +} + +BOOL CALLBACK DlgProcKeyBindingsOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + + switch (msg) { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwndDlg); + OldEditProc = (WNDPROC) SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PREVIEW), GWLP_WNDPROC, (LONG_PTR) KeyBindingsEditProc); + currentTreeItem = NULL; + loadTempKeyBindings(); + buildTree(GetDlgItem(hwndDlg, IDC_CATEGORYLIST)); + EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); + } + return TRUE; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + SendDlgItemMessage(hwndDlg, IDC_PREVIEW, msg, wParam, lParam); + return TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_ADD: + if (currentTreeItem->keyBinding != NULL) { + int i; + removeTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, modifiers | virtualKey); + for (i=0; i<5; i++) { + if (currentTreeItem->keyBinding->tempKey[i] == 0) { + currentTreeItem->keyBinding->tempKey[i] = modifiers | virtualKey; + break; + } + } + } + refreshListBox(hwndDlg); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case IDC_DELETE: + if (currentTreeItem->keyBinding != NULL) { + int index = SendDlgItemMessage(hwndDlg, IDC_LIST, LB_GETCURSEL, 0, 0); + if (index != LB_ERR && index <5) { + if (currentTreeItem->keyBinding->tempKey[index] != 0) { + int i; + for (i = index + 1; i < 5; i++) { + currentTreeItem->keyBinding->tempKey[i-1] = currentTreeItem->keyBinding->tempKey[i]; + } + currentTreeItem->keyBinding->tempKey[4] = 0; + } + } + } + refreshListBox(hwndDlg); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case IDC_BTN_RESET: + if (currentTreeItem->keyBinding != NULL) { + int i; + for (i = 0; i < 5; i++) { + removeTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, currentTreeItem->keyBinding->defaultKey[i]); + currentTreeItem->keyBinding->tempKey[i] = currentTreeItem->keyBinding->defaultKey[i]; + } + } + refreshListBox(hwndDlg); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case IDC_BTN_UNDO: + if (currentTreeItem->keyBinding != NULL) { + int i; + for (i = 0; i < 5; i++) { + removeTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, currentTreeItem->keyBinding->key[i]); + currentTreeItem->keyBinding->tempKey[i] = currentTreeItem->keyBinding->key[i]; + } + } + refreshListBox(hwndDlg); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case IDC_LIST: + if (HIWORD(wParam) == LBN_SELCHANGE) { + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), TRUE); + } + break; + } + break; + case WM_NOTIFY: + if (((LPNMHDR) lParam)->idFrom == IDC_CATEGORYLIST) + { + switch(((NMHDR*)lParam)->code) { + case TVN_SELCHANGEDA: + case TVN_SELCHANGEDW: + { + TVITEM tvi = {0}; + tvi.hItem = TreeView_GetSelection(GetDlgItem(hwndDlg, IDC_CATEGORYLIST)); + tvi.mask = TVIF_HANDLE|TVIF_PARAM; + TreeView_GetItem(GetDlgItem(hwndDlg, IDC_CATEGORYLIST), &tvi); + currentTreeItem = (TreeItem *) tvi.lParam; + refreshListBox(hwndDlg); + break; + } + case TVN_DELETEITEMA: + case TVN_DELETEITEMW: + { + TreeItem *treeItem = (TreeItem *)(((LPNMTREEVIEW)lParam)->itemOld.lParam); + if (treeItem) { + mir_free(treeItem->paramName); + mir_free(treeItem); + } + break; + } + } + } + if (((LPNMHDR) lParam)->idFrom == 0 && ((LPNMHDR) lParam)->code == PSN_APPLY ) { + saveKeyBindings(); + } + break; + case WM_DESTROY: + saveCollapseState(GetDlgItem(hwndDlg, IDC_CATEGORYLIST)); + + } + return FALSE; +} + +static INT_PTR KBRegister( WPARAM wParam, LPARAM lParam ) +{ + return (int)addKeyBinding(( KEYBINDINGDESC* )lParam ); +} + +static INT_PTR KBGet( WPARAM wParam, LPARAM lParam ) +{ + KEYBINDINGDESC* desc = ( KEYBINDINGDESC* )lParam; + KeyBindingItem* item = (KeyBindingItem*)findKeyBinding(desc->pszActionGroup, desc->key[0]); + if (item != NULL) { + desc->action = item->action; + return 0; + } + return 1; +} + +static void InitKeyBinding() +{ + keyBindingList = NULL; + hKeyBindings_Register = CreateServiceFunction(MS_KEYBINDINGS_REGISTER, KBRegister); + hKeyBindings_Get = CreateServiceFunction(MS_KEYBINDINGS_GET, KBGet); +} + +static void UninitKeyBinding() +{ + KeyBindingItem *ptr, *ptr2; + ptr = keyBindingList; + keyBindingList = NULL; + for (; ptr != NULL; ptr = ptr2) { + ptr2 = ptr->next; + mir_free(ptr->actionName); + mir_free(ptr->fullActionName); + mir_free(ptr->actionGroupName); + mir_free(ptr); + } + DestroyServiceFunction(hKeyBindings_Register); + DestroyServiceFunction(hKeyBindings_Get); +} + +static int KeyBindingsOptionsInit(WPARAM wParam,LPARAM lParam) +{ + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.hInstance = GetModuleHandle(NULL); + odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR; + odp.position = -180000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_KEYBINDINGS); + odp.ptszTitle = TranslateT("Key Bindings"); + odp.ptszGroup = TranslateT("Customize"); + odp.pfnDlgProc = DlgProcKeyBindingsOpts; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + return 0; +} + +static int KeyBindingsSystemModulesLoaded(WPARAM wParam,LPARAM lParam) +{ + HookEvent(ME_OPT_INITIALISE, KeyBindingsOptionsInit); + return 0; +} + +static int OnPreShutdown(WPARAM wParam, LPARAM lParam) +{ + UninitKeyBinding(); + return 0; +} + +int LoadKeyBindingsModule( void ) +{ + HookEvent(ME_SYSTEM_MODULESLOADED, KeyBindingsSystemModulesLoaded); + HookEvent(ME_SYSTEM_PRESHUTDOWN, OnPreShutdown); + InitKeyBinding(); + return 0; +} diff --git a/src/modules/keybindings/keybindings.h b/src/modules/keybindings/keybindings.h new file mode 100644 index 0000000000..a4fbdd0c8f --- /dev/null +++ b/src/modules/keybindings/keybindings.h @@ -0,0 +1,43 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +typedef struct KeyBindingItemStruct +{ + TCHAR* actionName; + TCHAR* fullActionName; + char* actionGroupName; + DWORD action; + DWORD defaultKey[5]; + DWORD tempKey[5]; + DWORD key[5]; + struct KeyBindingItemStruct *prev; + struct KeyBindingItemStruct *next; +}KeyBindingItem; + +static KeyBindingItem* keyBindingList = NULL; + +typedef struct +{ + char *paramName; + KeyBindingItem *keyBinding; +}TreeItem; diff --git a/src/modules/langpack/langpack.cpp b/src/modules/langpack/langpack.cpp new file mode 100644 index 0000000000..0603a24b90 --- /dev/null +++ b/src/modules/langpack/langpack.cpp @@ -0,0 +1,569 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#include "../netlib/netlib.h" + +#define LANGPACK_BUF_SIZE 4000 + +int LoadLangPackServices(void); + +struct LangPackMuuid +{ + MUUID muuid; + PLUGININFOEX* pInfo; +}; + +static int CompareMuuids( const LangPackMuuid* p1, const LangPackMuuid* p2 ) +{ + return memcmp( &p1->muuid, &p2->muuid, sizeof( MUUID )); +} + +static LIST lMuuids( 10, CompareMuuids ); +static LangPackMuuid* pCurrentMuuid = NULL; + +static BOOL bModuleInitialized = FALSE; + +struct LangPackEntry { + DWORD englishHash; + char *local; + wchar_t *wlocal; + LangPackMuuid* pMuuid; + LangPackEntry* pNext; // for langpack items with the same hash value +}; + +struct LangPackStruct { + TCHAR filename[MAX_PATH]; + TCHAR filePath[MAX_PATH]; + char language[64]; + char lastModifiedUsing[64]; + char authors[256]; + char authorEmail[128]; + LangPackEntry *entry; + int entryCount, entriesAlloced; + LCID localeID; + UINT defaultANSICp; +} static langPack; + +static int IsEmpty(char *str) +{ + int i = 0; + + while (str[i]) + { + if (str[i]!=' '&&str[i]!='\r'&&str[i]!='\n') + return 0; + i++; + } + return 1; +} + +void ConvertBackslashes(char *str, UINT fileCp) +{ + char *pstr; + for ( pstr = str; *pstr; pstr = CharNextExA( fileCp, pstr, 0 )) { + if( *pstr == '\\' ) { + switch( pstr[1] ) { + case 'n': *pstr = '\n'; break; + case 't': *pstr = '\t'; break; + case 'r': *pstr = '\r'; break; + default: *pstr = pstr[1]; break; + } + memmove(pstr+1, pstr+2, strlen(pstr+2) + 1); +} } } + +#ifdef _DEBUG +//#pragma optimize( "gt", on ) +#endif + +// MurmurHash2 +unsigned int __fastcall hash(const void * key, unsigned int len) +{ + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + const unsigned int m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + unsigned int h = len; + + // Mix 4 bytes at a time into the hash + const unsigned char * data = (const unsigned char *)key; + + while(len >= 4) + { + unsigned int k = *(unsigned int *)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + // Handle the last few bytes of the input array + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +unsigned int __fastcall hashstrW(const char * key) +{ + if (key == NULL) return 0; + const unsigned int len = (unsigned int)wcslen((const wchar_t*)key); + char* buf = (char*)alloca(len + 1); + for (unsigned i = 0; i <= len ; ++i) + buf[i] = key[i << 1]; + return hash(buf, len); +} + +static int SortLangPackHashesProc(LangPackEntry *arg1,LangPackEntry *arg2) +{ + if (arg1->englishHash < arg2->englishHash) return -1; + if (arg1->englishHash > arg2->englishHash) return 1; + + return (arg1->pMuuid < arg2->pMuuid) ? -1 : 1; +} + +static void swapBytes( void* p, size_t iSize ) +{ + char *head = (char *)p; // here + char *tail = head + iSize - 1; + + for (; tail > head; --tail, ++head) { + char temp = *head; + *head = *tail; + *tail = temp; + } +} + +static bool EnterMuuid( const char* p, MUUID& result ) +{ + if ( *p++ != '{' ) + return false; + + BYTE* d = (BYTE*)&result; + + for ( int nBytes = 0; *p && nBytes < 24; p++ ) { + if ( *p == '-' ) + continue; + + if ( *p == '}' ) + break; + + if ( !isxdigit( *p )) + return false; + + if ( !isxdigit( p[1] )) + return false; + + int c = 0; + if ( sscanf( p, "%2x", &c ) != 1 ) + return false; + + *d++ = ( BYTE )c; + nBytes++; + p++; + } + + if ( *p != '}' ) + return false; + + swapBytes( &result.a, sizeof( result.a )); + swapBytes( &result.b, sizeof( result.b )); + swapBytes( &result.c, sizeof( result.c )); + return true; +} + +static void LoadLangPackFile( FILE* fp, char* line, UINT fileCp ) +{ + while ( !feof( fp )) { + if ( fgets( line, LANGPACK_BUF_SIZE, fp ) == NULL ) + break; + + if ( IsEmpty(line) || line[0] == ';' || line[0] == 0 ) + continue; + + rtrim( line ); + + if ( line[0] == '#' ) { + strlwr( line ); + + if ( !memcmp( line+1, "include", 7 )) { + TCHAR tszFileName[ MAX_PATH ]; + TCHAR* fileName = mir_a2t( ltrim( line+9 )); + mir_sntprintf( tszFileName, SIZEOF(tszFileName), _T("%s%s"), langPack.filePath, fileName ); + mir_free( fileName ); + + FILE* p = _tfopen( tszFileName, _T("r")); + if ( p ) { + line[0] = 0; + fgets( line, SIZEOF(line), p ); + + UINT fileCp = CP_ACP; + if (strlen(line) >= 3 && line[0]=='\xef' && line[1]=='\xbb' && line[2]=='\xbf') + { + fileCp = CP_UTF8; + fseek(p, 3, SEEK_SET); + } + else + { + fileCp = langPack.defaultANSICp; + fseek(p, 0, SEEK_SET); + } + + LoadLangPackFile( p, line, fileCp ); + fclose( p ); + } + } + else if ( !memcmp( line+1, "muuid", 5 )) { + MUUID t; + if ( !EnterMuuid( line+7, t )) { + NetlibLogf( NULL, "Invalid MUUID: %s\n", line+7 ); + continue; + } + + LangPackMuuid* pNew = ( LangPackMuuid* )mir_alloc( sizeof( LangPackMuuid )); + memcpy( &pNew->muuid, &t, sizeof( t )); + pNew->pInfo = NULL; + lMuuids.insert( pNew ); + pCurrentMuuid = pNew; + } + + continue; + } + + ConvertBackslashes( line, fileCp ); + + if ( line[0] == '[' && line[ lstrlenA(line)-1 ] == ']' ) { + if ( langPack.entryCount && langPack.entry[ langPack.entryCount-1].local == NULL ) + langPack.entryCount--; + + char* pszLine = line+1; + line[ lstrlenA(line)-1 ] = '\0'; + if ( ++langPack.entryCount > langPack.entriesAlloced ) { + langPack.entriesAlloced += 128; + langPack.entry = ( LangPackEntry* )mir_realloc( langPack.entry, sizeof(LangPackEntry)*langPack.entriesAlloced ); + } + + LangPackEntry* E = &langPack.entry[ langPack.entryCount-1 ]; + E->englishHash = hashstr(pszLine); + E->local = NULL; + E->wlocal = NULL; + E->pMuuid = pCurrentMuuid; + E->pNext = NULL; + continue; + } + + if ( !langPack.entryCount ) + continue; + + LangPackEntry* E = &langPack.entry[ langPack.entryCount-1 ]; + if ( E->local == NULL ) { + E->local = mir_strdup( line ); + if ( fileCp == CP_UTF8 ) + Utf8DecodeCP( E->local, langPack.defaultANSICp, NULL ); + + int iNeeded = MultiByteToWideChar(fileCp, 0, line, -1, 0, 0); + E->wlocal = (wchar_t *)mir_alloc((iNeeded+1) * sizeof(wchar_t)); + MultiByteToWideChar( fileCp, 0, line, -1, E->wlocal, iNeeded ); + } + else { + size_t iOldLenA = strlen( E->local ); + E->local = ( char* )mir_realloc( E->local, iOldLenA + strlen(line) + 2 ); + strcat( E->local, "\n" ); + strcat( E->local, line ); + + if ( fileCp == CP_UTF8 ) + Utf8DecodeCP( E->local + iOldLenA + 1, langPack.defaultANSICp, NULL ); + + int iNeeded = MultiByteToWideChar( fileCp, 0, line, -1, 0, 0 ); + size_t iOldLen = wcslen( E->wlocal ); + E->wlocal = ( wchar_t* )mir_realloc( E->wlocal, ( sizeof(wchar_t) * ( iOldLen + iNeeded + 2))); + wcscat( E->wlocal, L"\n" ); + MultiByteToWideChar( fileCp, 0, line, -1, E->wlocal + iOldLen + 1, iNeeded ); + } + } +} + +static int LoadLangPack(const TCHAR *szLangPack) +{ + int startOfLine=0; + USHORT langID; + + lstrcpy( langPack.filename, szLangPack ); + lstrcpy( langPack.filePath, szLangPack ); + TCHAR* p = _tcsrchr( langPack.filePath, '\\' ); + if ( p ) + p[1] = 0; + + FILE *fp = _tfopen(szLangPack,_T("rt")); + if ( fp == NULL ) + return 1; + + char line[ LANGPACK_BUF_SIZE ] = ""; + fgets( line, SIZEOF(line), fp ); + + UINT fileCp = CP_ACP; + size_t lineLen = strlen(line); + if (lineLen >= 3 && line[0]=='\xef' && line[1]=='\xbb' && line[2]=='\xbf') + { + fileCp = CP_UTF8; + memmove(line, line + 3, lineLen - 2); + } + + lrtrim( line ); + if ( lstrcmpA( line, "Miranda Language Pack Version 1" )) { + fclose(fp); + return 2; + } + + //headers + while ( !feof( fp )) { + startOfLine = ftell( fp ); + if ( fgets( line, SIZEOF(line), fp ) == NULL ) + break; + + lrtrim( line ); + if( IsEmpty( line ) || line[0]==';' || line[0]==0) + continue; + + if ( line[0] == '[' || line[0] == '#' ) + break; + + char* pszColon = strchr( line,':' ); + if ( pszColon == NULL ) { + fclose( fp ); + return 3; + } + + *pszColon++ = 0; + if(!lstrcmpA(line,"Language")) {mir_snprintf(langPack.language,sizeof(langPack.language),"%s",pszColon); lrtrim(langPack.language);} + else if(!lstrcmpA(line,"Last-Modified-Using")) {mir_snprintf(langPack.lastModifiedUsing,sizeof(langPack.lastModifiedUsing),"%s",pszColon); lrtrim(langPack.lastModifiedUsing);} + else if(!lstrcmpA(line,"Authors")) {mir_snprintf(langPack.authors,sizeof(langPack.authors),"%s",pszColon); lrtrim(langPack.authors);} + else if(!lstrcmpA(line,"Author-email")) {mir_snprintf(langPack.authorEmail,sizeof(langPack.authorEmail),"%s",pszColon); lrtrim(langPack.authorEmail);} + else if(!lstrcmpA(line, "Locale")) { + char szBuf[20], *stopped; + + lrtrim(pszColon + 1); + langID = (USHORT)strtol(pszColon, &stopped, 16); + langPack.localeID = MAKELCID(langID, 0); + GetLocaleInfoA(langPack.localeID, LOCALE_IDEFAULTANSICODEPAGE, szBuf, 10); + szBuf[5] = 0; // codepages have max. 5 digits + langPack.defaultANSICp = atoi(szBuf); + if (fileCp == CP_ACP) + fileCp = langPack.defaultANSICp; + } + } + + //body + fseek( fp, startOfLine, SEEK_SET ); + langPack.entriesAlloced = 0; + + LoadLangPackFile( fp, line, fileCp ); + fclose(fp); + + qsort(langPack.entry,langPack.entryCount,sizeof(LangPackEntry),(int(*)(const void*,const void*))SortLangPackHashesProc); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int SortLangPackHashesProc2(LangPackEntry *arg1,LangPackEntry *arg2) +{ + if (arg1->englishHash < arg2->englishHash) return -1; + if (arg1->englishHash > arg2->englishHash) return 1; + return 0; +} + +char *LangPackTranslateString(LangPackMuuid* pUuid, const char *szEnglish, const int W) +{ + if ( langPack.entryCount == 0 || szEnglish == NULL ) + return (char*)szEnglish; + + LangPackEntry key,*entry; + key.englishHash = W ? hashstrW(szEnglish) : hashstr(szEnglish); + entry = (LangPackEntry*)bsearch(&key, langPack.entry, langPack.entryCount, sizeof(LangPackEntry), (int(*)(const void*,const void*))SortLangPackHashesProc2 ); + if ( entry == NULL ) + return (char*)szEnglish; + + // try to find the exact match, otherwise the first entry will be returned + if ( pUuid ) { + for ( LangPackEntry* p = entry->pNext; p != NULL; p = p->pNext ) { + if (p->pMuuid == pUuid) { + entry = p; + break; + } } } + + return W ? (char *)entry->wlocal : entry->local; +} + +int LangPackGetDefaultCodePage() +{ + return langPack.defaultANSICp; +} + +int LangPackGetDefaultLocale() +{ + return (langPack.localeID == 0) ? LOCALE_USER_DEFAULT : langPack.localeID; +} + +TCHAR* LangPackPcharToTchar( const char* pszStr ) +{ + if ( pszStr == NULL ) + return NULL; + + #if defined( _UNICODE ) + { int len = (int)strlen( pszStr ); + TCHAR* result = ( TCHAR* )alloca(( len+1 )*sizeof( TCHAR )); + MultiByteToWideChar( LangPackGetDefaultCodePage(), 0, pszStr, -1, result, len ); + result[len] = 0; + return mir_wstrdup( TranslateW( result )); + } + #else + return mir_strdup( Translate( pszStr )); + #endif +} + +///////////////////////////////////////////////////////////////////////////////////////// + +LangPackMuuid* __fastcall LangPackLookupUuid( WPARAM wParam ) +{ + int idx = (wParam >> 16) & 0xFFFF; + return ( idx > 0 && idx <= lMuuids.getCount()) ? lMuuids[ idx-1 ] : NULL; +} + +int LangPackMarkPluginLoaded( PLUGININFOEX* pInfo ) +{ + LangPackMuuid tmp; tmp.muuid = pInfo->uuid; + int idx = lMuuids.getIndex( &tmp ); + if ( idx == -1 ) + return 0; + + lMuuids[ idx ]->pInfo = pInfo; + return (idx+1) << 16; +} + +void LangPackDropUnusedItems( void ) +{ + if ( langPack.entryCount == 0 ) + return; + + LangPackEntry *s = langPack.entry+1, *d = s, *pLast = langPack.entry; + DWORD dwSavedHash = langPack.entry->englishHash; + bool bSortNeeded = false; + + for ( int i=1; i < langPack.entryCount; i++, s++ ) { + if ( s->pMuuid != NULL && s->pMuuid->pInfo == NULL ) + s->pMuuid = NULL; + + if ( s->englishHash != dwSavedHash ) { + pLast = d; + if ( s != d ) + *d++ = *s; + else + d++; + dwSavedHash = s->englishHash; + } + else { + bSortNeeded = true; + LangPackEntry* p = ( LangPackEntry* )mir_alloc( sizeof( LangPackEntry )); + *p = *s; + pLast->pNext = p; pLast = p; + } + } + + if ( bSortNeeded ) { + langPack.entryCount = ( int )( d - langPack.entry ); + qsort(langPack.entry,langPack.entryCount,sizeof(LangPackEntry),(int(*)(const void*,const void*))SortLangPackHashesProc); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int LoadLangPackModule(void) +{ + HANDLE hFind; + TCHAR szSearch[MAX_PATH]; + WIN32_FIND_DATA fd; + + bModuleInitialized = TRUE; + + ZeroMemory(&langPack,sizeof(langPack)); + LoadLangPackServices(); + pathToAbsoluteT(_T("langpack_*.txt"), szSearch, NULL); + hFind = FindFirstFile( szSearch, &fd ); + if( hFind != INVALID_HANDLE_VALUE ) { + pathToAbsoluteT(fd.cFileName, szSearch, NULL); + FindClose(hFind); + LoadLangPack(szSearch); + } + return 0; +} + +void UnloadLangPackModule() +{ + int i; + + if ( !bModuleInitialized ) return; + + for ( i=0; i < lMuuids.getCount(); i++ ) + mir_free( lMuuids[i] ); + lMuuids.destroy(); + + LangPackEntry* p = langPack.entry; + for ( i=0; i < langPack.entryCount; i++, p++ ) { + if ( p->pNext != NULL ) { + for ( LangPackEntry* p1 = p->pNext; p1 != NULL; ) { + LangPackEntry* p2 = p1; p1 = p1->pNext; + mir_free( p2->local); + mir_free( p2->wlocal); + mir_free( p2 ); + } + } + + mir_free( p->local ); + mir_free( p->wlocal ); + } + + if ( langPack.entryCount ) { + mir_free(langPack.entry); + langPack.entry=0; + langPack.entryCount=0; +} } diff --git a/src/modules/langpack/lpservices.cpp b/src/modules/langpack/lpservices.cpp new file mode 100644 index 0000000000..26586dfda3 --- /dev/null +++ b/src/modules/langpack/lpservices.cpp @@ -0,0 +1,168 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#if defined( _UNICODE ) + #define FLAGS LANG_UNICODE +#else + #define FLAGS 0 +#endif + +LangPackMuuid* __fastcall LangPackLookupUuid( WPARAM ); +int LangPackMarkPluginLoaded( PLUGININFOEX* pInfo ); + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR TranslateString(WPARAM wParam,LPARAM lParam) +{ + return (INT_PTR)LangPackTranslateString( LangPackLookupUuid(wParam), (const char *)lParam, (wParam & LANG_UNICODE) ? 1 : 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR TranslateMenu(WPARAM wParam, LPARAM lParam) +{ + HMENU hMenu = ( HMENU )wParam; + int i; + MENUITEMINFO mii; + TCHAR str[256]; + LangPackMuuid* uuid = LangPackLookupUuid( lParam ); + + mii.cbSize = MENUITEMINFO_V4_SIZE; + for ( i = GetMenuItemCount( hMenu )-1; i >= 0; i--) { + mii.fMask = MIIM_TYPE|MIIM_SUBMENU; + mii.dwTypeData = ( TCHAR* )str; + mii.cch = SIZEOF(str); + GetMenuItemInfo(hMenu, i, TRUE, &mii); + + if ( mii.cch && mii.dwTypeData ) { + TCHAR* result = ( TCHAR* )LangPackTranslateString( uuid, ( const char* )mii.dwTypeData, FLAGS ); + if ( result != mii.dwTypeData ) { + mii.dwTypeData = result; + mii.fMask = MIIM_TYPE; + SetMenuItemInfo( hMenu, i, TRUE, &mii ); + } } + + if ( mii.hSubMenu != NULL ) TranslateMenu(( WPARAM )mii.hSubMenu, lParam ); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static void TranslateWindow( LangPackMuuid* pUuid, HWND hwnd ) +{ + TCHAR title[2048]; + GetWindowText(hwnd, title, SIZEOF( title )); + { + TCHAR* result = ( TCHAR* )LangPackTranslateString( pUuid, ( const char* )title, FLAGS ); + if ( result != title ) + SetWindowText(hwnd, result ); +} } + +static BOOL CALLBACK TranslateDialogEnumProc(HWND hwnd,LPARAM lParam) +{ + LANGPACKTRANSLATEDIALOG *lptd = (LANGPACKTRANSLATEDIALOG*)lParam; + TCHAR szClass[32]; + int i,id = GetDlgCtrlID( hwnd ); + + if ( lptd->ignoreControls != NULL ) + for ( i=0; lptd->ignoreControls[i]; i++ ) + if ( lptd->ignoreControls[i] == id ) + return TRUE; + + LangPackMuuid* uuid = LangPackLookupUuid( lptd->flags ); + + GetClassName( hwnd, szClass, SIZEOF(szClass)); + if(!lstrcmpi(szClass,_T("static")) || !lstrcmpi(szClass,_T("hyperlink")) || !lstrcmpi(szClass,_T("button")) || !lstrcmpi(szClass,_T("MButtonClass")) || !lstrcmpi(szClass,_T("MHeaderbarCtrl"))) + TranslateWindow( uuid, hwnd ); + else if ( !lstrcmpi( szClass,_T("edit"))) { + if( lptd->flags & LPTDF_NOIGNOREEDIT || GetWindowLongPtr(hwnd,GWL_STYLE) & ES_READONLY ) + TranslateWindow( uuid, hwnd ); + } + return TRUE; +} + +static INT_PTR TranslateDialog(WPARAM wParam, LPARAM lParam) +{ + LANGPACKTRANSLATEDIALOG *lptd = (LANGPACKTRANSLATEDIALOG*)lParam; + if ( lptd == NULL || lptd->cbSize != sizeof(LANGPACKTRANSLATEDIALOG)) + return 1; + + if ( !( lptd->flags & LPTDF_NOTITLE )) + TranslateWindow( LangPackLookupUuid( lptd->flags ), lptd->hwndDlg ); + + EnumChildWindows( lptd->hwndDlg, TranslateDialogEnumProc, lParam ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR LPRegister(WPARAM wParam, LPARAM lParam) +{ + *( int* )wParam = LangPackMarkPluginLoaded(( PLUGININFOEX* )lParam ); + return 0; +} + +static INT_PTR GetDefaultCodePage(WPARAM,LPARAM) +{ + return LangPackGetDefaultCodePage(); +} + +static INT_PTR GetDefaultLocale(WPARAM, LPARAM) +{ + return LangPackGetDefaultLocale(); +} + +static INT_PTR PcharToTchar(WPARAM wParam, LPARAM lParam) +{ + char* pszStr = ( char* )lParam; + if ( pszStr == NULL ) + return NULL; + + LangPackMuuid* uuid = LangPackLookupUuid( wParam ); + + #if defined( _UNICODE ) + { int len = (int)strlen( pszStr ); + TCHAR* result = ( TCHAR* )alloca(( len+1 )*sizeof( TCHAR )); + MultiByteToWideChar( LangPackGetDefaultCodePage(), 0, pszStr, -1, result, len ); + result[len] = 0; + return ( INT_PTR )mir_wstrdup(( wchar_t* )LangPackTranslateString( uuid, ( char* )result, 1 )); + } + #else + return ( INT_PTR )mir_strdup( LangPackTranslateString( uuid, pszStr, 0 )); + #endif +} + +int LoadLangPackServices(void) +{ + CreateServiceFunction(MS_LANGPACK_TRANSLATESTRING,TranslateString); + CreateServiceFunction(MS_LANGPACK_TRANSLATEMENU,TranslateMenu); + CreateServiceFunction(MS_LANGPACK_TRANSLATEDIALOG,TranslateDialog); + CreateServiceFunction(MS_LANGPACK_GETCODEPAGE,GetDefaultCodePage); + CreateServiceFunction(MS_LANGPACK_GETLOCALE,GetDefaultLocale); + CreateServiceFunction(MS_LANGPACK_PCHARTOTCHAR,PcharToTchar); + CreateServiceFunction(MS_LANGPACK_REGISTER,LPRegister); + return 0; +} + diff --git a/src/modules/netlib/netlib.cpp b/src/modules/netlib/netlib.cpp new file mode 100644 index 0000000000..000639474f --- /dev/null +++ b/src/modules/netlib/netlib.cpp @@ -0,0 +1,640 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +static BOOL bModuleInitialized = FALSE; + +HANDLE hConnectionHeaderMutex, hConnectionOpenMutex; +DWORD g_LastConnectionTick; +int connectionTimeout; +HANDLE hSendEvent=NULL, hRecvEvent=NULL; + +typedef BOOL (WINAPI *tGetProductInfo)(DWORD, DWORD, DWORD, DWORD, PDWORD); + +static int CompareNetlibUser(const NetlibUser* p1, const NetlibUser* p2) +{ + return strcmp(p1->user.szSettingsModule, p2->user.szSettingsModule); +} + +LIST netlibUser(5, CompareNetlibUser); +CRITICAL_SECTION csNetlibUser; + +SSL_API si; + +void NetlibFreeUserSettingsStruct(NETLIBUSERSETTINGS *settings) +{ + mir_free(settings->szIncomingPorts); + mir_free(settings->szOutgoingPorts); + mir_free(settings->szProxyAuthPassword); + mir_free(settings->szProxyAuthUser); + mir_free(settings->szProxyServer); +} + +void NetlibInitializeNestedCS(struct NetlibNestedCriticalSection *nlncs) +{ + nlncs->dwOwningThreadId= 0; + nlncs->lockCount=0; + nlncs->hMutex=CreateMutex(NULL,FALSE,NULL); +} + +void NetlibDeleteNestedCS(struct NetlibNestedCriticalSection *nlncs) +{ + CloseHandle(nlncs->hMutex); +} + +int NetlibEnterNestedCS(struct NetlibConnection *nlc,int which) +{ + struct NetlibNestedCriticalSection *nlncs; + DWORD dwCurrentThreadId=GetCurrentThreadId(); + + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + if(nlc==NULL || nlc->handleType!=NLH_CONNECTION) { + ReleaseMutex(hConnectionHeaderMutex); + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + nlncs = (which == NLNCS_SEND) ? &nlc->ncsSend : &nlc->ncsRecv; + if(nlncs->lockCount && nlncs->dwOwningThreadId==dwCurrentThreadId) { + nlncs->lockCount++; + ReleaseMutex(hConnectionHeaderMutex); + return 1; + } + InterlockedIncrement(&nlc->dontCloseNow); + ResetEvent(nlc->hOkToCloseEvent); + ReleaseMutex(hConnectionHeaderMutex); + WaitForSingleObject(nlncs->hMutex,INFINITE); + nlncs->dwOwningThreadId=dwCurrentThreadId; + nlncs->lockCount=1; + if(InterlockedDecrement(&nlc->dontCloseNow)==0) + SetEvent(nlc->hOkToCloseEvent); + return 1; +} + +void NetlibLeaveNestedCS(struct NetlibNestedCriticalSection *nlncs) +{ + if(--nlncs->lockCount==0) { + nlncs->dwOwningThreadId=0; + ReleaseMutex(nlncs->hMutex); + } +} + +static INT_PTR GetNetlibUserSettingInt(const char *szUserModule,const char *szSetting,int defValue) +{ + DBVARIANT dbv; + if(DBGetContactSetting(NULL,szUserModule,szSetting,&dbv) + && DBGetContactSetting(NULL,"Netlib",szSetting,&dbv)) + return defValue; + if(dbv.type==DBVT_BYTE) return dbv.bVal; + if(dbv.type==DBVT_WORD) return dbv.wVal; + return dbv.dVal; +} + +static char *GetNetlibUserSettingString(const char *szUserModule,const char *szSetting,int decode) +{ + DBVARIANT dbv; + if(DBGetContactSettingString(NULL,szUserModule,szSetting,&dbv) + && DBGetContactSettingString(NULL,"Netlib",szSetting,&dbv)) { + return NULL; + } + else { + char *szRet; + if(decode) CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + szRet=mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + if(szRet==NULL) SetLastError(ERROR_OUTOFMEMORY); + return szRet; + } +} + +static INT_PTR NetlibRegisterUser(WPARAM,LPARAM lParam) +{ + NETLIBUSER *nlu=(NETLIBUSER*)lParam; + struct NetlibUser *thisUser; + + if(nlu==NULL || nlu->cbSize!=sizeof(NETLIBUSER) || nlu->szSettingsModule==NULL + || (!(nlu->flags&NUF_NOOPTIONS) && nlu->szDescriptiveName==NULL) + || (nlu->flags&NUF_HTTPGATEWAY && (nlu->pfnHttpGatewayInit==NULL))) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + thisUser = (struct NetlibUser*)mir_calloc(sizeof(struct NetlibUser)); + thisUser->handleType = NLH_USER; + thisUser->user = *nlu; + + EnterCriticalSection(&csNetlibUser); + if (netlibUser.getIndex(thisUser) >= 0) + { + LeaveCriticalSection(&csNetlibUser); + mir_free(thisUser); + SetLastError(ERROR_DUP_NAME); + return 0; + } + LeaveCriticalSection(&csNetlibUser); + + if (nlu->szDescriptiveName) { + thisUser->user.ptszDescriptiveName = (thisUser->user.flags&NUF_UNICODE ? mir_u2t((WCHAR*)nlu->ptszDescriptiveName) : mir_a2t(nlu->szDescriptiveName)); + } + if((thisUser->user.szSettingsModule=mir_strdup(nlu->szSettingsModule))==NULL + || (nlu->szDescriptiveName && thisUser->user.ptszDescriptiveName ==NULL) + || (nlu->szHttpGatewayUserAgent && (thisUser->user.szHttpGatewayUserAgent=mir_strdup(nlu->szHttpGatewayUserAgent))==NULL)) + { + mir_free(thisUser); + SetLastError(ERROR_OUTOFMEMORY); + return 0; + } + if (nlu->szHttpGatewayHello) + thisUser->user.szHttpGatewayHello=mir_strdup(nlu->szHttpGatewayHello); + else + thisUser->user.szHttpGatewayHello=NULL; + + thisUser->settings.cbSize=sizeof(NETLIBUSERSETTINGS); + thisUser->settings.useProxy=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLUseProxy",0); + thisUser->settings.proxyType=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLProxyType",PROXYTYPE_SOCKS5); + if(thisUser->user.flags&NUF_NOHTTPSOPTION && thisUser->settings.proxyType==PROXYTYPE_HTTPS) + thisUser->settings.proxyType=PROXYTYPE_HTTP; + if(!(thisUser->user.flags&(NUF_HTTPCONNS|NUF_HTTPGATEWAY)) && thisUser->settings.proxyType==PROXYTYPE_HTTP) { + thisUser->settings.useProxy=0; + thisUser->settings.proxyType=PROXYTYPE_SOCKS5; + } + thisUser->settings.szProxyServer=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLProxyServer",0); + thisUser->settings.wProxyPort=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLProxyPort",1080); + thisUser->settings.useProxyAuth=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLUseProxyAuth",0); + thisUser->settings.szProxyAuthUser=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLProxyAuthUser",0); + thisUser->settings.szProxyAuthPassword=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLProxyAuthPassword",1); + thisUser->settings.dnsThroughProxy=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLDnsThroughProxy",1); + thisUser->settings.specifyIncomingPorts=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLSpecifyIncomingPorts",0); + thisUser->settings.szIncomingPorts=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLIncomingPorts",0); + thisUser->settings.specifyOutgoingPorts=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLSpecifyOutgoingPorts",0); + thisUser->settings.szOutgoingPorts=GetNetlibUserSettingString(thisUser->user.szSettingsModule,"NLOutgoingPorts",0); + thisUser->settings.enableUPnP=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLEnableUPnP",1); //default to on + thisUser->settings.validateSSL=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLValidateSSL",0); + + thisUser->toLog=GetNetlibUserSettingInt(thisUser->user.szSettingsModule,"NLlog",1); + + EnterCriticalSection(&csNetlibUser); + netlibUser.insert(thisUser); + LeaveCriticalSection(&csNetlibUser); + return (INT_PTR)thisUser; +} + +static INT_PTR NetlibGetUserSettings(WPARAM wParam,LPARAM lParam) +{ + NETLIBUSERSETTINGS *nlus=(NETLIBUSERSETTINGS*)lParam; + struct NetlibUser *nlu=(struct NetlibUser*)wParam; + + if(GetNetlibHandleType(nlu)!=NLH_USER || nlus==NULL || nlus->cbSize!=sizeof(NETLIBUSERSETTINGS)) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + *nlus=nlu->settings; + return 1; +} + +static INT_PTR NetlibSetUserSettings(WPARAM wParam,LPARAM lParam) +{ + NETLIBUSERSETTINGS *nlus=(NETLIBUSERSETTINGS*)lParam; + struct NetlibUser *nlu=(struct NetlibUser*)wParam; + + if(GetNetlibHandleType(nlu)!=NLH_USER || nlus==NULL || nlus->cbSize!=sizeof(NETLIBUSERSETTINGS)) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + NetlibSaveUserSettingsStruct(nlu->user.szSettingsModule,nlus); + return 1; +} + +void NetlibDoClose(NetlibConnection *nlc, bool noShutdown) +{ + if (nlc->s == INVALID_SOCKET) return; + + NetlibLogf(nlc->nlu, "(%p:%u) Connection closed internal", nlc, nlc->s); + if (nlc->hSsl) + { + if (!noShutdown) si.shutdown(nlc->hSsl); + si.sfree(nlc->hSsl); + nlc->hSsl = NULL; + } + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; +} + +INT_PTR NetlibCloseHandle(WPARAM wParam, LPARAM) +{ + switch(GetNetlibHandleType(wParam)) { + case NLH_USER: + { struct NetlibUser *nlu=(struct NetlibUser*)wParam; + int i; + + EnterCriticalSection(&csNetlibUser); + i = netlibUser.getIndex(nlu); + if (i >= 0) netlibUser.remove(i); + LeaveCriticalSection(&csNetlibUser); + + NetlibFreeUserSettingsStruct(&nlu->settings); + mir_free(nlu->user.szSettingsModule); + mir_free(nlu->user.szDescriptiveName); + mir_free(nlu->user.szHttpGatewayHello); + mir_free(nlu->user.szHttpGatewayUserAgent); + mir_free(nlu->szStickyHeaders); + break; + } + case NLH_CONNECTION: + { struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + HANDLE waitHandles[4]; + DWORD waitResult; + + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + if (nlc->usingHttpGateway) + { + HttpGatewayRemovePacket(nlc, -1); + } + else + { + if(nlc->s != INVALID_SOCKET) { + NetlibDoClose(nlc); + } + if (nlc->s2 != INVALID_SOCKET) closesocket(nlc->s2); + nlc->s2 = INVALID_SOCKET; + } + ReleaseMutex(hConnectionHeaderMutex); + + waitHandles[0]=hConnectionHeaderMutex; + waitHandles[1]=nlc->hOkToCloseEvent; + waitHandles[2]=nlc->ncsRecv.hMutex; + waitHandles[3]=nlc->ncsSend.hMutex; + waitResult=WaitForMultipleObjects( SIZEOF(waitHandles),waitHandles,TRUE,INFINITE); + if(waitResult= WAIT_OBJECT_0 + SIZEOF(waitHandles)) { + ReleaseMutex(hConnectionHeaderMutex); + SetLastError(ERROR_INVALID_PARAMETER); //already been closed + return 0; + } + nlc->handleType=0; + mir_free(nlc->nlhpi.szHttpPostUrl); + mir_free(nlc->nlhpi.szHttpGetUrl); + mir_free(nlc->dataBuffer); + mir_free((char*)nlc->nloc.szHost); + mir_free(nlc->szNewUrl); + mir_free(nlc->szProxyServer); + NetlibDeleteNestedCS(&nlc->ncsRecv); + NetlibDeleteNestedCS(&nlc->ncsSend); + CloseHandle(nlc->hOkToCloseEvent); + DeleteCriticalSection(&nlc->csHttpSequenceNums); + ReleaseMutex(hConnectionHeaderMutex); + NetlibLogf(nlc->nlu,"(%p:%u) Connection closed",nlc,nlc->s); + break; + } + case NLH_BOUNDPORT: + return NetlibFreeBoundPort((struct NetlibBoundPort*)wParam); + case NLH_PACKETRECVER: + { struct NetlibPacketRecver *nlpr=(struct NetlibPacketRecver*)wParam; + mir_free(nlpr->packetRecver.buffer); + break; + } + default: + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + mir_free((void*)wParam); + return 1; +} + +static INT_PTR NetlibGetSocket(WPARAM wParam, LPARAM) +{ + SOCKET s; + if(wParam==0) { + s=INVALID_SOCKET; + SetLastError(ERROR_INVALID_PARAMETER); + } + else { + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + switch(GetNetlibHandleType(wParam)) { + case NLH_CONNECTION: + s=((struct NetlibConnection*)wParam)->s; + break; + case NLH_BOUNDPORT: + s=((struct NetlibBoundPort*)wParam)->s; + break; + default: + s=INVALID_SOCKET; + SetLastError(ERROR_INVALID_PARAMETER); + break; + } + ReleaseMutex(hConnectionHeaderMutex); + } + return s; +} + +INT_PTR NetlibShutdown(WPARAM wParam, LPARAM) +{ + if (wParam) + { + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + switch(GetNetlibHandleType(wParam)) { + case NLH_CONNECTION: + { + struct NetlibConnection* nlc = (struct NetlibConnection*)wParam; + if (nlc->hSsl) si.shutdown(nlc->hSsl); + if (nlc->s != INVALID_SOCKET) shutdown(nlc->s, 2); + if (nlc->s2 != INVALID_SOCKET) shutdown(nlc->s2, 2); + nlc->termRequested = true; + } + break; + case NLH_BOUNDPORT: + { + struct NetlibBoundPort* nlb = (struct NetlibBoundPort*)wParam; + if (nlb->s != INVALID_SOCKET) shutdown(nlb->s, 2); + } + break; + } + ReleaseMutex(hConnectionHeaderMutex); + + } + return 0; +} + +static const char szHexDigits[]="0123456789ABCDEF"; +INT_PTR NetlibHttpUrlEncode(WPARAM,LPARAM lParam) +{ + unsigned char *szOutput,*szInput=(unsigned char*)lParam; + unsigned char *pszIn,*pszOut; + int outputLen; + + if(szInput==NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return (INT_PTR)(char*)NULL; + } + for(outputLen=0,pszIn=szInput;*pszIn;pszIn++) { + if ( (48 <= *pszIn && *pszIn <= 57) ||//0-9 + (65 <= *pszIn && *pszIn <= 90) ||//ABC...XYZ + (97 <= *pszIn && *pszIn <= 122) ||//abc...xyz + *pszIn == '-' || *pszIn == '_' || *pszIn == '.' || *pszIn == ' ') outputLen++; + else outputLen+=3; + } + szOutput=(unsigned char*)HeapAlloc(GetProcessHeap(),0,outputLen+1); + if(szOutput==NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return (INT_PTR)(unsigned char*)NULL; + } + for(pszOut=szOutput,pszIn=szInput;*pszIn;pszIn++) { + if ( (48 <= *pszIn && *pszIn <= 57) || + (65 <= *pszIn && *pszIn <= 90) || + (97 <= *pszIn && *pszIn <= 122) || + *pszIn == '-' || *pszIn == '_' || *pszIn == '.') *pszOut++=*pszIn; + else if(*pszIn==' ') *pszOut++='+'; + else { + *pszOut++='%'; + *pszOut++=szHexDigits[*pszIn>>4]; + *pszOut++=szHexDigits[*pszIn&0xF]; + } + } + *pszOut='\0'; + return (INT_PTR)szOutput; +} + +static const char base64chars[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +INT_PTR NetlibBase64Encode(WPARAM, LPARAM lParam) +{ + NETLIBBASE64 *nlb64=(NETLIBBASE64*)lParam; + int iIn; + char *pszOut; + PBYTE pbIn; + + if(nlb64==NULL || nlb64->pszEncoded==NULL || nlb64->pbDecoded==NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if(nlb64->cchEncodedcbDecoded)) { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + nlb64->cchEncoded=Netlib_GetBase64EncodedBufferSize(nlb64->cbDecoded); + for(iIn=0,pbIn=nlb64->pbDecoded,pszOut=nlb64->pszEncoded;iIncbDecoded;iIn+=3,pbIn+=3,pszOut+=4) { + pszOut[0]=base64chars[pbIn[0]>>2]; + if(nlb64->cbDecoded-iIn==1) { + pszOut[1]=base64chars[(pbIn[0]&3)<<4]; + pszOut[2]='='; + pszOut[3]='='; + pszOut+=4; + break; + } + pszOut[1]=base64chars[((pbIn[0]&3)<<4)|(pbIn[1]>>4)]; + if(nlb64->cbDecoded-iIn==2) { + pszOut[2]=base64chars[(pbIn[1]&0xF)<<2]; + pszOut[3]='='; + pszOut+=4; + break; + } + pszOut[2]=base64chars[((pbIn[1]&0xF)<<2)|(pbIn[2]>>6)]; + pszOut[3]=base64chars[pbIn[2]&0x3F]; + } + pszOut[0]='\0'; + return 1; +} + +static BYTE Base64CharToInt(char c) +{ + if(c>='A' && c<='Z') return c-'A'; + if(c>='a' && c<='z') return c-'a'+26; + if(c>='0' && c<='9') return c-'0'+52; + if(c=='+') return 62; + if(c=='/') return 63; + if(c=='=') return 64; + return 255; +} + +INT_PTR NetlibBase64Decode(WPARAM, LPARAM lParam) +{ + NETLIBBASE64 *nlb64=(NETLIBBASE64*)lParam; + char *pszIn; + PBYTE pbOut; + BYTE b1,b2,b3,b4; + int iIn; + + if(nlb64==NULL || nlb64->pszEncoded==NULL || nlb64->pbDecoded==NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if(nlb64->cchEncoded&3) { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + if(nlb64->cbDecodedcchEncoded)) { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + nlb64->cbDecoded=Netlib_GetBase64DecodedBufferSize(nlb64->cchEncoded); + for(iIn=0,pszIn=nlb64->pszEncoded,pbOut=nlb64->pbDecoded;iIncchEncoded;iIn+=4,pszIn+=4,pbOut+=3) { + b1=Base64CharToInt(pszIn[0]); + b2=Base64CharToInt(pszIn[1]); + b3=Base64CharToInt(pszIn[2]); + b4=Base64CharToInt(pszIn[3]); + if(b1==255 || b1==64 || b2==255 || b2==64 || b3==255 || b4==255) { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + pbOut[0]=(b1<<2)|(b2>>4); + if(b3==64) {nlb64->cbDecoded-=2; break;} + pbOut[1]=(b2<<4)|(b3>>2); + if(b4==64) {nlb64->cbDecoded--; break;} + pbOut[2]=b4|(b3<<6); + } + return 1; +} + +void UnloadNetlibModule(void) +{ + if (!bModuleInitialized) return; + + if (hConnectionHeaderMutex != NULL) + { + int i; + + NetlibUnloadIeProxy(); + NetlibSecurityDestroy(); + NetlibUPnPDestroy(); + NetlibLogShutdown(); + + DestroyHookableEvent(hRecvEvent); hRecvEvent = NULL; + DestroyHookableEvent(hSendEvent); hSendEvent = NULL; + + for (i = netlibUser.getCount(); i > 0; i--) + NetlibCloseHandle((WPARAM)netlibUser[i-1], 0); + + netlibUser.destroy(); + + CloseHandle(hConnectionHeaderMutex); + if (hConnectionOpenMutex) CloseHandle(hConnectionOpenMutex); + DeleteCriticalSection(&csNetlibUser); + WSACleanup(); + } +} + +int LoadNetlibModule(void) +{ + WSADATA wsadata; + + bModuleInitialized = TRUE; + + WSAStartup(MAKEWORD(2,2), &wsadata); + + HookEvent(ME_OPT_INITIALISE,NetlibOptInitialise); + + InitializeCriticalSection(&csNetlibUser); + hConnectionHeaderMutex=CreateMutex(NULL,FALSE,NULL); + NetlibLogInit(); + + connectionTimeout = 0; + + OSVERSIONINFOEX osvi = {0}; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (GetVersionEx((LPOSVERSIONINFO)&osvi)) + { + // Connection limiting was introduced in Windows XP SP2 and later and set to 10 / sec + if (osvi.dwMajorVersion == 5 && ((osvi.dwMinorVersion == 1 && osvi.wServicePackMajor >= 2) || osvi.dwMinorVersion > 1)) + connectionTimeout = 150; + // Connection limiting has limits based on addition Windows Vista pre SP2 + else if (osvi.dwMajorVersion == 6 && osvi.wServicePackMajor < 2) + { + DWORD dwType = 0; + tGetProductInfo pGetProductInfo = (tGetProductInfo) GetProcAddress(GetModuleHandleA("kernel32"), "GetProductInfo"); + if (pGetProductInfo != NULL) pGetProductInfo(6, 0, 0, 0, &dwType); + switch( dwType ) + { + case 0x01: // Vista Ultimate edition have connection limit of 25 / sec - plenty for Miranda + case 0x1c: + break; + + case 0x02: // Vista Home Basic edition have connection limit of 2 / sec + case 0x05: + connectionTimeout = 1000; + break; + + default: // all other editions have connection limit of 10 / sec + connectionTimeout = 150; + break; + } + } + // Connection limiting is disabled by default and is controlled by registry setting in Windows Vista SP2 and later + else if (osvi.dwMajorVersion >= 6) + { + static const char keyn[] = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; + static const char valn[] = "EnableConnectionRateLimiting"; + + HKEY hSettings; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyn, 0, KEY_QUERY_VALUE, &hSettings) == ERROR_SUCCESS) + { + DWORD tValueLen, enabled; + tValueLen = sizeof(enabled); + if (RegQueryValueExA(hSettings, valn, NULL, NULL, (BYTE*)&enabled, &tValueLen) == ERROR_SUCCESS && enabled) + connectionTimeout = 150; // if enabled limit is set to 10 / sec + RegCloseKey(hSettings); + } + + } + } + + hConnectionOpenMutex = connectionTimeout ? CreateMutex(NULL,FALSE,NULL) : NULL; + g_LastConnectionTick = GetTickCount(); + + CreateServiceFunction(MS_NETLIB_REGISTERUSER,NetlibRegisterUser); + CreateServiceFunction(MS_NETLIB_GETUSERSETTINGS,NetlibGetUserSettings); + CreateServiceFunction(MS_NETLIB_SETUSERSETTINGS,NetlibSetUserSettings); + CreateServiceFunction(MS_NETLIB_CLOSEHANDLE,NetlibCloseHandle); + CreateServiceFunction(MS_NETLIB_BINDPORT,NetlibBindPort); + CreateServiceFunction(MS_NETLIB_OPENCONNECTION,NetlibOpenConnection); + CreateServiceFunction(MS_NETLIB_SETHTTPPROXYINFO,NetlibHttpGatewaySetInfo); + CreateServiceFunction(MS_NETLIB_SETSTICKYHEADERS,NetlibHttpSetSticky); + CreateServiceFunction(MS_NETLIB_GETSOCKET,NetlibGetSocket); + CreateServiceFunction(MS_NETLIB_URLENCODE,NetlibHttpUrlEncode); + CreateServiceFunction(MS_NETLIB_BASE64ENCODE,NetlibBase64Encode); + CreateServiceFunction(MS_NETLIB_BASE64DECODE,NetlibBase64Decode); + CreateServiceFunction(MS_NETLIB_SENDHTTPREQUEST,NetlibHttpSendRequest); + CreateServiceFunction(MS_NETLIB_RECVHTTPHEADERS,NetlibHttpRecvHeaders); + CreateServiceFunction(MS_NETLIB_FREEHTTPREQUESTSTRUCT,NetlibHttpFreeRequestStruct); + CreateServiceFunction(MS_NETLIB_HTTPTRANSACTION,NetlibHttpTransaction); + CreateServiceFunction(MS_NETLIB_SEND,NetlibSend); + CreateServiceFunction(MS_NETLIB_RECV,NetlibRecv); + CreateServiceFunction(MS_NETLIB_SELECT,NetlibSelect); + CreateServiceFunction(MS_NETLIB_SELECTEX,NetlibSelectEx); + CreateServiceFunction(MS_NETLIB_SHUTDOWN,NetlibShutdown); + CreateServiceFunction(MS_NETLIB_CREATEPACKETRECVER,NetlibPacketRecverCreate); + CreateServiceFunction(MS_NETLIB_GETMOREPACKETS,NetlibPacketRecverGetMore); + CreateServiceFunction(MS_NETLIB_SETPOLLINGTIMEOUT,NetlibHttpSetPollingTimeout); + CreateServiceFunction(MS_NETLIB_STARTSSL,NetlibStartSsl); + + hRecvEvent = CreateHookableEvent(ME_NETLIB_FASTRECV); + hSendEvent = CreateHookableEvent(ME_NETLIB_FASTSEND); + + NetlibUPnPInit(); + NetlibSecurityInit(); + NetlibLoadIeProxy(); + + return 0; +} + +void NetlibInitSsl(void) +{ + mir_getSI(&si); +} diff --git a/src/modules/netlib/netlib.h b/src/modules/netlib/netlib.h new file mode 100644 index 0000000000..575e77f18c --- /dev/null +++ b/src/modules/netlib/netlib.h @@ -0,0 +1,209 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ +#define GetNetlibHandleType(h) (h?*(int*)h:NLH_INVALID) +#define NLH_INVALID 0 +#define NLH_USER 'USER' +#define NLH_CONNECTION 'CONN' +#define NLH_BOUNDPORT 'BIND' +#define NLH_PACKETRECVER 'PCKT' + +struct NetlibUser +{ + int handleType; + NETLIBUSER user; + NETLIBUSERSETTINGS settings; + char * szStickyHeaders; + int toLog; + int inportnum; + int outportnum; +}; + +struct NetlibNestedCriticalSection +{ + HANDLE hMutex; + DWORD dwOwningThreadId; + int lockCount; +}; + +struct NetlibHTTPProxyPacketQueue +{ + struct NetlibHTTPProxyPacketQueue *next; + PBYTE dataBuffer; + int dataBufferLen; +}; + +struct NetlibConnection +{ + int handleType; + SOCKET s, s2; + bool usingHttpGateway; + bool usingDirectHttpGateway; + bool proxyAuthNeeded; + bool dnsThroughProxy; + bool termRequested; + struct NetlibUser *nlu; + NETLIBHTTPPROXYINFO nlhpi; + PBYTE dataBuffer; + int dataBufferLen; + CRITICAL_SECTION csHttpSequenceNums; + HANDLE hOkToCloseEvent; + LONG dontCloseNow; + struct NetlibNestedCriticalSection ncsSend,ncsRecv; + HSSL hSsl; + struct NetlibHTTPProxyPacketQueue * pHttpProxyPacketQueue; + char *szNewUrl; + char *szProxyServer; + WORD wProxyPort; + int proxyType; + int pollingTimeout; + unsigned lastPost; + NETLIBOPENCONNECTION nloc; +}; + +struct NetlibBoundPort { + int handleType; + SOCKET s; + WORD wPort; + WORD wExPort; + struct NetlibUser *nlu; + NETLIBNEWCONNECTIONPROC_V2 pfnNewConnectionV2; + HANDLE hThread; + void *pExtra; +}; + +struct NetlibPacketRecver { + int handleType; + struct NetlibConnection *nlc; + NETLIBPACKETRECVER packetRecver; +}; + +//netlib.c +void NetlibFreeUserSettingsStruct(NETLIBUSERSETTINGS *settings); +void NetlibDoClose(NetlibConnection *nlc, bool noShutdown = false); +INT_PTR NetlibCloseHandle(WPARAM wParam,LPARAM lParam); +void NetlibInitializeNestedCS(struct NetlibNestedCriticalSection *nlncs); +void NetlibDeleteNestedCS(struct NetlibNestedCriticalSection *nlncs); +#define NLNCS_SEND 0 +#define NLNCS_RECV 1 +int NetlibEnterNestedCS(struct NetlibConnection *nlc,int which); +void NetlibLeaveNestedCS(struct NetlibNestedCriticalSection *nlncs); +INT_PTR NetlibBase64Encode(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibBase64Decode(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpUrlEncode(WPARAM wParam,LPARAM lParam); + +extern CRITICAL_SECTION csNetlibUser; +extern LIST netlibUser; + +//netlibautoproxy.c +void NetlibLoadIeProxy(void); +void NetlibUnloadIeProxy(void); +char* NetlibGetIeProxy(char *szUrl); +bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps); + +//netlibbind.c +int NetlibFreeBoundPort(struct NetlibBoundPort *nlbp); +INT_PTR NetlibBindPort(WPARAM wParam,LPARAM lParam); +bool BindSocketToPort(const char *szPorts, SOCKET s, int* portn); + +//netlibhttp.c +INT_PTR NetlibHttpSendRequest(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpRecvHeaders(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpFreeRequestStruct(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpTransaction(WPARAM wParam,LPARAM lParam); +void NetlibHttpSetLastErrorUsingHttpResult(int result); +NETLIBHTTPREQUEST* NetlibHttpRecv(NetlibConnection* nlc, DWORD hflags, DWORD dflags, bool isConnect = false); +void NetlibConnFromUrl(const char* szUrl, bool secur, NETLIBOPENCONNECTION &nloc); + +//netlibhttpproxy.c +int NetlibInitHttpConnection(struct NetlibConnection *nlc,struct NetlibUser *nlu,NETLIBOPENCONNECTION *nloc); +int NetlibHttpGatewayRecv(struct NetlibConnection *nlc,char *buf,int len,int flags); +int NetlibHttpGatewayPost(struct NetlibConnection *nlc,const char *buf,int len,int flags); +void HttpGatewayRemovePacket(NetlibConnection *nlc, int pck); + +INT_PTR NetlibHttpGatewaySetInfo(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpSetPollingTimeout(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibHttpSetSticky(WPARAM wParam, LPARAM lParam); + +//netliblog.c +void NetlibLogShowOptions(void); +void NetlibDumpData(struct NetlibConnection *nlc,PBYTE buf,int len,int sent,int flags); +void NetlibLogf(NetlibUser* nlu, const char *fmt, ...); +void NetlibLogInit(void); +void NetlibLogShutdown(void); + +//netlibopenconn.c +DWORD DnsLookup(struct NetlibUser *nlu,const char *szHost); +int WaitUntilReadable(SOCKET s,DWORD dwTimeout, bool check = false); +int WaitUntilWritable(SOCKET s,DWORD dwTimeout); +bool NetlibDoConnect(NetlibConnection *nlc); +bool NetlibReconnect(NetlibConnection *nlc); +INT_PTR NetlibOpenConnection(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibStartSsl(WPARAM wParam, LPARAM lParam); + +//netlibopts.c +int NetlibOptInitialise(WPARAM wParam,LPARAM lParam); +void NetlibSaveUserSettingsStruct(const char *szSettingsModule,NETLIBUSERSETTINGS *settings); + +//netlibpktrecver.c +INT_PTR NetlibPacketRecverCreate(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibPacketRecverGetMore(WPARAM wParam,LPARAM lParam); + +//netlibsock.c +#define NL_SELECT_READ 0x0001 +#define NL_SELECT_WRITE 0x0002 +#define NL_SELECT_ALL (NL_SELECT_READ+NL_SELECT_WRITE) + +INT_PTR NetlibSend(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibRecv(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibSelect(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibSelectEx(WPARAM wParam,LPARAM lParam); +INT_PTR NetlibShutdown(WPARAM wParam,LPARAM lParam); + +//netlibupnp.c +bool NetlibUPnPAddPortMapping(WORD intport, char *proto, + WORD *extport, DWORD *extip, bool search); +void NetlibUPnPDeletePortMapping(WORD extport, char* proto); +void NetlibUPnPCleanup(void*); +void NetlibUPnPInit(void); +void NetlibUPnPDestroy(void); + +//netlibsecurity.c +void NetlibSecurityInit(void); +void NetlibSecurityDestroy(void); +void NetlibDestroySecurityProvider(HANDLE hSecurity); +HANDLE NetlibInitSecurityProvider(const TCHAR* szProvider, const TCHAR* szPrincipal); +#ifdef UNICODE +HANDLE NetlibInitSecurityProvider(const char* szProvider, const char* szPrincipal); +#endif +char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const TCHAR* login, const TCHAR* psw, + bool http, unsigned& complete); + + +static __inline INT_PTR NLSend(struct NetlibConnection *nlc,const char *buf,int len,int flags) { + NETLIBBUFFER nlb={(char*)buf,len,flags}; + return NetlibSend((WPARAM)nlc,(LPARAM)&nlb); +} +static __inline INT_PTR NLRecv(struct NetlibConnection *nlc,char *buf,int len,int flags) { + NETLIBBUFFER nlb={buf,len,flags}; + return NetlibRecv((WPARAM)nlc,(LPARAM)&nlb); +} diff --git a/src/modules/netlib/netlibautoproxy.cpp b/src/modules/netlib/netlibautoproxy.cpp new file mode 100644 index 0000000000..e169cf9c89 --- /dev/null +++ b/src/modules/netlib/netlibautoproxy.cpp @@ -0,0 +1,460 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2010-2011 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +#include +/* +///////////////////////////////////////////////////////////////////// +// ResolveHostName (a helper function) +///////////////////////////////////////////////////////////////////// +DWORD __stdcall ResolveHostName(LPSTR lpszHostName, + LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize) +{ + if (*lpdwIPAddressSize < 17 || lpszIPAddress == NULL) + { + *lpdwIPAddressSize = 17; + return ERROR_INSUFFICIENT_BUFFER; + } + + IN_ADDR ip; + ip.s_addr = inet_addr(lpszHostName); + if (ip.s_addr == INADDR_NONE) + { + PHOSTENT myhost = gethostbyname(lpszHostName); + if (myhost != NULL) + ip = *(PIN_ADDR)myhost->h_addr; + else + return SOCKET_ERROR; + } + mir_snprintf(lpszIPAddress, *lpdwIPAddressSize, "%u.%u.%u.%u", + ip.s_net, ip.s_host, ip.s_lh, ip.s_impno); + + return 0; +} + +///////////////////////////////////////////////////////////////////// +// IsResolvable (a helper function) +///////////////////////////////////////////////////////////////////// +BOOL __stdcall IsResolvable(LPSTR lpszHost) +{ + char szDummy[255]; + DWORD dwDummySize = sizeof (szDummy) - 1; + + if (ResolveHostName(lpszHost, szDummy, &dwDummySize)) + return FALSE; + return TRUE; +} + +///////////////////////////////////////////////////////////////////// +// GetIPAddress (a helper function) +///////////////////////////////////////////////////////////////////// +DWORD __stdcall GetIPAddress(LPSTR lpszIPAddress, LPDWORD lpdwIPAddressSize) +{ + char szHostBuffer[255]; + + if (gethostname(szHostBuffer, sizeof (szHostBuffer) - 1) != ERROR_SUCCESS) + return (ERROR_INTERNET_INTERNAL_ERROR); + return (ResolveHostName(szHostBuffer, lpszIPAddress, lpdwIPAddressSize)); +} + +///////////////////////////////////////////////////////////////////// +// IsInNet (a helper function) +///////////////////////////////////////////////////////////////////// +BOOL __stdcall IsInNet(LPSTR lpszIPAddress, LPSTR lpszDest, LPSTR lpszMask) +{ + DWORD dwDest; + DWORD dwIpAddr; + DWORD dwMask; + + dwIpAddr = inet_addr(lpszIPAddress); + dwDest = inet_addr(lpszDest); + dwMask = inet_addr(lpszMask); + + if ((dwDest == INADDR_NONE) || + (dwIpAddr == INADDR_NONE) || ((dwIpAddr & dwMask) != dwDest)) + return (FALSE); + + return (TRUE); +} + +static const AutoProxyHelperVtbl OurVtbl = +{ + IsResolvable, + GetIPAddress, + ResolveHostName, + IsInNet, + NULL, + NULL, + NULL, + NULL +}; + +static AutoProxyHelperFunctions HelperFunctions = { &OurVtbl }; +*/ + +static char *szProxyHost[3]; +static LIST proxyBypass(5); + +static HMODULE hModJS; + +static pfnInternetInitializeAutoProxyDll pInternetInitializeAutoProxyDll; +static pfnInternetDeInitializeAutoProxyDll pInternetDeInitializeAutoProxyDll; +static pfnInternetGetProxyInfo pInternetGetProxyInfo; + +static bool bEnabled, bOneProxy; + +static void GetFile(char* szUrl, AUTO_PROXY_SCRIPT_BUFFER &buf) +{ + NetlibUser nlu = {0}; + NETLIBHTTPREQUEST nlhr = {0}; + + nlu.handleType = NLH_USER; + nlu.user.flags = NUF_OUTGOING | NUF_HTTPCONNS; + nlu.user.szSettingsModule = "(NULL)"; + nlu.toLog = 1; + + // initialize the netlib request + nlhr.cbSize = sizeof(nlhr); + nlhr.requestType = REQUEST_GET; + nlhr.flags = NLHRF_HTTP11 | NLHRF_DUMPASTEXT | NLHRF_REDIRECT; + nlhr.szUrl = szUrl; + + // download the page + NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpTransaction((WPARAM)&nlu, (LPARAM)&nlhr); + + if (nlhrReply) + { + if (nlhrReply->resultCode == 200) + { + buf.lpszScriptBuffer = nlhrReply->pData; + buf.dwScriptBufferSize = nlhrReply->dataLength + 1; + + nlhrReply->dataLength = 0; + nlhrReply->pData = NULL; + } + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply); + } +} + +bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps) +{ + bool noHttp = false; + bool usingSsl = false; + char szUrl[256] = ""; + + if ((nlc->nloc.flags & (NLOCF_HTTP | NLOCF_HTTPGATEWAY) && nlc->nloc.flags & NLOCF_SSL) || + nlc->nloc.wPort == 443 || forceHttps) + { + mir_snprintf(szUrl, sizeof(szUrl), "https://%s", nlc->nloc.szHost); + usingSsl = true; + } + else if (nlc->nloc.flags & (NLOCF_HTTPGATEWAY | NLOCF_HTTP) || nlc->usingHttpGateway) + mir_snprintf(szUrl, sizeof(szUrl), "http://%s", nlc->nloc.szHost); + else + { + mir_snprintf(szUrl, sizeof(szUrl), "%s", nlc->nloc.szHost); + noHttp = true; + } + + mir_free(nlc->szProxyServer); nlc->szProxyServer = NULL; + nlc->wProxyPort = 0; + nlc->proxyType = 0; + + char *mt = NetlibGetIeProxy(szUrl); + char *m = NEWSTR_ALLOCA(mt); + mir_free(mt); + + if (m == NULL) return false; + + // if multiple servers, use the first one + char *c = strchr(m, ';'); if (c) *c = 0; + + // if 'direct' no proxy + if (_stricmp(lrtrim(m), "direct") == 0) return false; + + // find proxy address + char *h = strchr(m, ' '); + if (h) { *h = 0; ++h; } else return false; + + // find proxy port + char *p = strchr(h, ':'); + if (p) { *p = 0; ++p; } + + lrtrim(h); ltrim(p); + if (_stricmp(m, "proxy") == 0 && h[0]) + { + nlc->proxyType = (usingSsl || noHttp) ? PROXYTYPE_HTTPS : PROXYTYPE_HTTP; + nlc->wProxyPort = p ? atol(p) : 8080; + nlc->szProxyServer = mir_strdup(h); + } + else if (_stricmp(m, "socks") == 0 && h[0]) + { + nlc->proxyType = PROXYTYPE_SOCKS4; + nlc->wProxyPort = p ? atol(p) : 1080; + nlc->szProxyServer = mir_strdup(h); + } + else if (_stricmp(m, "socks5") == 0 && h[0]) + { + nlc->proxyType = PROXYTYPE_SOCKS5; + nlc->wProxyPort = p ? atol(p) : 1080; + nlc->szProxyServer = mir_strdup(h); + } + else + return false; + + return true; +} + +static char szAutoUrlStr[MAX_PATH] = ""; +static AUTO_PROXY_SCRIPT_BUFFER abuf = {0}; +static HANDLE hIeProxyMutex; +static bool bAutoProxyInit; + +static void NetlibInitAutoProxy(void) +{ + if (bAutoProxyInit) return; + + if (!hModJS) + { + if (!(hModJS = LoadLibraryA("jsproxy.dll"))) + return; + + pInternetInitializeAutoProxyDll = (pfnInternetInitializeAutoProxyDll) + GetProcAddress(hModJS, "InternetInitializeAutoProxyDll"); + + pInternetDeInitializeAutoProxyDll = (pfnInternetDeInitializeAutoProxyDll) + GetProcAddress(hModJS, "InternetDeInitializeAutoProxyDll"); + + pInternetGetProxyInfo = (pfnInternetGetProxyInfo) + GetProcAddress(hModJS, "InternetGetProxyInfo"); + } + + if (strstr(szAutoUrlStr, "file://") == NULL && strstr(szAutoUrlStr, "://") != NULL) + { + abuf.dwStructSize = sizeof(abuf); + GetFile(szAutoUrlStr, abuf); + } + bAutoProxyInit = true; +} + +struct IeProxyParam +{ + char *szUrl; + char *szHost; + char *szProxy; +}; + +static unsigned __stdcall NetlibIeProxyThread(void * arg) +{ + IeProxyParam *param = (IeProxyParam*)arg; + param->szProxy = NULL; + + if (!bAutoProxyInit) + { + WaitForSingleObject(hIeProxyMutex, INFINITE); + NetlibInitAutoProxy(); + ReleaseMutex(hIeProxyMutex); + } + + BOOL res; + char *loc = strstr(szAutoUrlStr, "file://"); + if (loc || strstr(szAutoUrlStr, "://") == NULL) + { + NetlibLogf(NULL, "Autoproxy Init file: %s", loc); + loc = loc ? loc + 7 : szAutoUrlStr; + res = pInternetInitializeAutoProxyDll(0, loc, NULL, NULL /*&HelperFunctions*/, NULL); + } + else + { + NetlibLogf(NULL, "Autoproxy Init %d", abuf.dwScriptBufferSize); + if (abuf.dwScriptBufferSize) + res = pInternetInitializeAutoProxyDll(0, NULL, NULL, NULL /*&HelperFunctions*/, &abuf); + else + res = false; + } + + if (res) + { + char proxyBuffer[1024]; + char *proxy = proxyBuffer; + DWORD dwProxyLen = sizeof(proxyBuffer); + + if (pInternetGetProxyInfo(param->szUrl, (DWORD)strlen(param->szUrl), + param->szHost, (DWORD)strlen(param->szHost), &proxy, &dwProxyLen)) + param->szProxy = mir_strdup(lrtrim(proxy)); + + NetlibLogf(NULL, "Autoproxy got response %s, Param: %s %s", param->szProxy, param->szUrl, param->szHost); + pInternetDeInitializeAutoProxyDll(NULL, 0); + } + else + NetlibLogf(NULL, "Autoproxy init failed"); + + return 0; +} + +char* NetlibGetIeProxy(char *szUrl) +{ + char *res = NULL; + char* p = strstr(szUrl, "://"); + if (p) p += 3; else p = szUrl; + + char *szHost = NEWSTR_ALLOCA(p); + p = strchr(szHost, '/'); if (p) *p = 0; + p = strchr(szHost, ':'); if (p) *p = 0; + _strlwr(szHost); + + if (bEnabled) + { + for (int i = 0; i < proxyBypass.getCount(); ++i) + { + if (strcmp(proxyBypass[i], "") == 0) + { + if (strchr(szHost, '.') == NULL) return NULL; + } + else if (wildcmp(szHost, proxyBypass[i])) return NULL; + } + + int ind = -1; + if (strstr(szUrl, "http://")) + ind = szProxyHost[0] ? 0 : 2; + else if (strstr(szUrl, "https://")) + ind = bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2); + else + ind = szProxyHost[2] ? 2 : (bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2)); + + if (ind < 0 || !szProxyHost[ind]) return NULL; + + size_t len = strlen(szHost) + 20; + res = (char*)mir_alloc(len); + mir_snprintf(res, len, "%s %s", ind == 2 ? "SOCKS" : "PROXY", szProxyHost[ind]); + return res; + } + + if (szAutoUrlStr[0]) + { + unsigned dwThreadId; + IeProxyParam param = { szUrl, szHost, NULL }; + HANDLE hThread = (HANDLE)forkthreadex(NULL, 0, NetlibIeProxyThread, 0, ¶m, &dwThreadId); + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + res = param.szProxy; + } + return res; +} + +void NetlibLoadIeProxy(void) +{ + HKEY hSettings; + if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + 0, KEY_QUERY_VALUE, &hSettings)) + return; + + DWORD tValueLen, enabled = 0; + char szHostStr[256] = "", szProxyBypassStr[4096] = ""; + + tValueLen = sizeof(enabled); + int tResult = RegQueryValueExA(hSettings, "ProxyEnable", NULL, NULL, (BYTE*)&enabled, &tValueLen); + bEnabled = enabled && tResult == ERROR_SUCCESS; + + tValueLen = SIZEOF(szHostStr); + tResult = RegQueryValueExA(hSettings, "ProxyServer", NULL, NULL, (BYTE*)szHostStr, &tValueLen); + bEnabled = bEnabled && tResult == ERROR_SUCCESS; + + tValueLen = SIZEOF(szAutoUrlStr); + tResult = RegQueryValueExA(hSettings, "AutoConfigUrl", NULL, NULL, (BYTE*)szAutoUrlStr, &tValueLen); + + tValueLen = SIZEOF(szProxyBypassStr); + tResult = RegQueryValueExA(hSettings, "ProxyOverride", NULL, NULL, (BYTE*)szProxyBypassStr, &tValueLen); + + RegCloseKey(hSettings); + + if (bEnabled) + { + char* szProxy = ltrim(szHostStr); + if (szProxy[0] == 0) { enabled = false; return; } + + for (;;) + { + char *szProxyEnd = strchr(szProxy, ';'); + if (szProxyEnd) *szProxyEnd = 0; + + int ind = -1; + if (strncmp(szProxy, "http=", 5) == 0) { ind = 0; szProxy += 5; } + else if (strncmp(szProxy, "https=", 6) == 0) { ind = 1; szProxy += 6; } + else if (strncmp(szProxy, "socks=", 6) == 0) { ind = 2; szProxy += 6; } + else if (strchr(szProxy, '=')) ind = -2; + + if (ind != -2) + { + bOneProxy = ind < 0; if (ind < 0) ind = 0; + + lrtrim(szProxy); + + if (strchr(szProxy, ':')) + szProxyHost[ind] = mir_strdup(szProxy); + else + { + size_t len = strlen(szProxy) + 10; + szProxyHost[ind] = (char*)mir_alloc(len); + mir_snprintf(szProxyHost[ind], len, "%s:%u", szProxy, ind == 2 ? 1080 : 8080); + } + if (bOneProxy) break; + } + if (szProxyEnd == NULL) break; + szProxy = szProxyEnd + 1; + } + + char* szProxyBypass = szProxyBypassStr; + for(;;) + { + char *szProxyBypassEnd = strchr(szProxyBypass, ';'); + if (szProxyBypassEnd) *szProxyBypassEnd = 0; + + lrtrim(szProxyBypass); + + proxyBypass.insert(_strlwr(mir_strdup(szProxyBypass))); + if (szProxyBypassEnd == NULL) break; + + szProxyBypass = szProxyBypassEnd + 1; + } + } + + if (bEnabled || szAutoUrlStr[0]) + hIeProxyMutex = CreateMutex(NULL, FALSE, NULL); +} + +void NetlibUnloadIeProxy(void) +{ + int i; + + for (i = 0; i < 3; ++i) + mir_free(szProxyHost[i]); + + for (i = 0; i < proxyBypass.getCount(); ++i) + mir_free(proxyBypass[i]); + + proxyBypass.destroy(); + mir_free(abuf.lpszScriptBuffer); + + CloseHandle(hIeProxyMutex); +} diff --git a/src/modules/netlib/netlibbind.cpp b/src/modules/netlib/netlibbind.cpp new file mode 100644 index 0000000000..6c0c4c19e9 --- /dev/null +++ b/src/modules/netlib/netlibbind.cpp @@ -0,0 +1,287 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +bool BindSocketToPort(const char *szPorts, SOCKET s, int* portn) +{ + SOCKADDR_IN sin = {0}; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + EnterCriticalSection(&csNetlibUser); + + if (--*portn < 0 && s != INVALID_SOCKET) + { + BindSocketToPort(szPorts, INVALID_SOCKET, portn); + if (*portn == 0) + { + LeaveCriticalSection(&csNetlibUser); + return false; + } + WORD num; + CallService(MS_UTILS_GETRANDOM, sizeof(WORD), (LPARAM)&num); + *portn = num % *portn; + } + + bool before=false; + for (;;) + { + const char *psz; + char *pszEnd; + int portMin, portMax, port, portnum = 0; + + for(psz=szPorts;*psz;) + { + while (*psz == ' ' || *psz == ',') psz++; + portMin = strtol(psz, &pszEnd, 0); + if (pszEnd == psz) break; + while (*pszEnd == ' ') pszEnd++; + if(*pszEnd == '-') + { + psz = pszEnd + 1; + portMax = strtol(psz, &pszEnd, 0); + if (pszEnd == psz) portMax = 65535; + if (portMin > portMax) + { + port = portMin; + portMin = portMax; + portMax = port; + } + } + else portMax = portMin; + if (portMax >= 1) + { + if (portMin <= 0) portMin = 1; + for (port = portMin; port <= portMax; port++) + { + if (port > 65535) break; + + ++portnum; + + if (s == INVALID_SOCKET) continue; + if (!before && portnum <= *portn) continue; + if (before && portnum >= *portn) + { + LeaveCriticalSection(&csNetlibUser); + return false; + } + + sin.sin_port = htons((WORD)port); + if (bind(s, (SOCKADDR*)&sin, sizeof(sin)) == 0) + { + LeaveCriticalSection(&csNetlibUser); + *portn = portnum + 1; + return true; + } + } + } + psz = pszEnd; + } + if (*portn < 0) + { + *portn = portnum; + LeaveCriticalSection(&csNetlibUser); + return true; + } + else if (*portn >= portnum) + *portn = 0; + else + before = true; + } +} + + +int NetlibFreeBoundPort(struct NetlibBoundPort *nlbp) +{ + closesocket(nlbp->s); + WaitForSingleObject(nlbp->hThread,INFINITE); + CloseHandle(nlbp->hThread); + NetlibLogf(nlbp->nlu, "(%u) Port %u closed for incoming connections", nlbp->s, nlbp->wPort); + mir_free(nlbp); + return 1; +} + +static unsigned __stdcall NetlibBindAcceptThread(void* param) +{ + SOCKET s; + SOCKADDR_IN sin; + int sinLen; + struct NetlibConnection *nlc; + struct NetlibBoundPort *nlbp = (NetlibBoundPort*)param; + + NetlibLogf(nlbp->nlu, "(%u) Port %u opened for incoming connections", nlbp->s, nlbp->wPort); + for(;;) + { + sinLen = sizeof(sin); + s = accept(nlbp->s, (struct sockaddr*)&sin, &sinLen); + if (s == INVALID_SOCKET) break; + NetlibLogf(nlbp->nlu, "New incoming connection on port %u from %s (%d)", nlbp->wPort, inet_ntoa(sin.sin_addr), s); + nlc = (NetlibConnection*)mir_calloc(sizeof(NetlibConnection)); + nlc->handleType = NLH_CONNECTION; + nlc->nlu = nlbp->nlu; + nlc->s = s; + nlc->s2 = INVALID_SOCKET; + InitializeCriticalSection(&nlc->csHttpSequenceNums); + nlc->hOkToCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + nlc->dontCloseNow = 0; + NetlibInitializeNestedCS(&nlc->ncsSend); + NetlibInitializeNestedCS(&nlc->ncsRecv); + nlbp->pfnNewConnectionV2((HANDLE)nlc,ntohl(sin.sin_addr.S_un.S_addr), nlbp->pExtra); + } + NetlibUPnPDeletePortMapping(nlbp->wExPort, "TCP"); + return 0; +} + +INT_PTR NetlibBindPort(WPARAM wParam,LPARAM lParam) +{ + NETLIBBIND *nlb = (NETLIBBIND*)lParam; + struct NetlibUser *nlu = (struct NetlibUser*)wParam; + struct NetlibBoundPort *nlbp; + SOCKADDR_IN sin; + int foundPort = 0; + UINT dwThreadId; + + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_INCOMING) || + nlb == NULL || nlb->pfnNewConnection == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if (nlb->cbSize != sizeof(NETLIBBIND) && + nlb->cbSize != NETLIBBIND_SIZEOF_V2 && + nlb->cbSize != NETLIBBIND_SIZEOF_V1) + { + return 0; + } + nlbp = (NetlibBoundPort*)mir_calloc(sizeof(NetlibBoundPort)); + nlbp->handleType = NLH_BOUNDPORT; + nlbp->nlu = nlu; + nlbp->pfnNewConnectionV2 = nlb->pfnNewConnectionV2; + nlbp->s = socket(AF_INET, SOCK_STREAM, 0); + nlbp->pExtra = (nlb->cbSize != NETLIBBIND_SIZEOF_V1) ? nlb->pExtra : NULL; + if (nlbp->s == INVALID_SOCKET) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"socket",WSAGetLastError()); + mir_free(nlbp); + return 0; + } + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = 0; + + /* if the netlib user wanted a free port given in the range, then + they better have given wPort==0, let's hope so */ + if (nlu->settings.specifyIncomingPorts && nlu->settings.szIncomingPorts && nlb->wPort == 0) + { + if (!BindSocketToPort(nlu->settings.szIncomingPorts, nlbp->s, &nlu->outportnum)) + { + NetlibLogf(nlu, "Netlib bind: Not enough ports for incoming connections specified"); + SetLastError(WSAEADDRINUSE); + } + else + foundPort = 1; + } + else + { + /* if ->wPort==0 then they'll get any free port, otherwise they'll + be asking for whatever was in nlb->wPort*/ + if (nlb->wPort != 0) + { + NetlibLogf(nlu,"%s %d: trying to bind port %d, this 'feature' can be abused, please be sure you want to allow it.",__FILE__,__LINE__,nlb->wPort); + sin.sin_port = htons(nlb->wPort); + } + if (bind(nlbp->s, (PSOCKADDR)&sin, sizeof(sin)) == 0) + foundPort = 1; + } + if (!foundPort) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"bind",WSAGetLastError()); + closesocket(nlbp->s); + mir_free(nlbp); + return 0; + } + + if (listen(nlbp->s, 5)) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"listen",WSAGetLastError()); + closesocket(nlbp->s); + mir_free(nlbp); + return 0; + } + + { int len; + DWORD extIP; + + ZeroMemory(&sin,sizeof(sin)); + len = sizeof(sin); + if (getsockname(nlbp->s,(SOCKADDR *)&sin,&len)) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"getsockname",WSAGetLastError()); + closesocket(nlbp->s); + mir_free(nlbp); + return 0; + } + nlb->wPort = ntohs(sin.sin_port); + nlbp->wPort = nlb->wPort; + nlb->dwInternalIP = ntohl(sin.sin_addr.S_un.S_addr); + + if (nlb->dwInternalIP == 0) + { + char hostname[64]; + struct hostent *he; + + gethostname(hostname, SIZEOF(hostname)); + he = gethostbyname(hostname); + if (he && he->h_addr_list[0]) + nlb->dwInternalIP = ntohl(*(PDWORD)he->h_addr_list[0]); + } + if (nlu->settings.enableUPnP && + NetlibUPnPAddPortMapping(nlb->wPort, "TCP", &nlbp->wExPort, &extIP, nlb->cbSize > NETLIBBIND_SIZEOF_V2)) + { + NetlibLogf(NULL, "UPnP port mapping succeeded. Internal Port: %u External Port: %u\n", + nlb->wPort, nlbp->wExPort); + if (nlb->cbSize > NETLIBBIND_SIZEOF_V2) + { + nlb->wExPort = nlbp->wExPort; + nlb->dwExternalIP = extIP; + } + } + else + { + if (nlu->settings.enableUPnP) + NetlibLogf(NULL, "UPnP port mapping failed. Internal Port: %u\n", nlb->wPort); + else + NetlibLogf(NULL, "UPnP disabled. Internal Port: %u\n", nlb->wPort); + + nlbp->wExPort = 0; + if (nlb->cbSize > NETLIBBIND_SIZEOF_V2) + { + nlb->wExPort = nlb->wPort; + nlb->dwExternalIP = nlb->dwInternalIP; + } + } + } + nlbp->hThread = (HANDLE)forkthreadex(NULL, 0, NetlibBindAcceptThread, 0, nlbp, &dwThreadId); + return (INT_PTR)nlbp; +} diff --git a/src/modules/netlib/netlibhttp.cpp b/src/modules/netlib/netlibhttp.cpp new file mode 100644 index 0000000000..970a28cb10 --- /dev/null +++ b/src/modules/netlib/netlibhttp.cpp @@ -0,0 +1,1331 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "../plugins/zlib/zlib.h" +#include "netlib.h" + +#define HTTPRECVHEADERSTIMEOUT 30000 //in ms +#define HTTPRECVDATATIMEOUT 20000 + +struct ResizableCharBuffer +{ + char *sz; + int iEnd, cbAlloced; +}; + +struct ProxyAuth +{ + char *szServer; + char *szMethod; +// char *szUserName; +// char *szPassword; + + ProxyAuth(const char *pszServer, const char *pszMethod) + { + szServer = mir_strdup(pszServer); + szMethod = mir_strdup(pszMethod); + } + ~ProxyAuth() + { + mir_free(szServer); + mir_free(szMethod); + } + static int Compare(const ProxyAuth* p1, const ProxyAuth* p2 ) + { return lstrcmpiA(p1->szServer, p2->szServer); } +}; + +struct ProxyAuthList : OBJLIST +{ + ProxyAuthList() : OBJLIST(2, ProxyAuth::Compare) {} + + void add(const char *szServer, const char *szMethod) + { + if (szServer == NULL) return; + int i = getIndex((ProxyAuth*)&szServer); + if (i >= 0) + { + ProxyAuth &rec = (*this)[i]; + if (szMethod == NULL) + remove(i); + else if (_stricmp(rec.szMethod, szMethod)) + { + mir_free(rec.szMethod); + rec.szMethod = mir_strdup(szMethod); + } + } + else + insert(new ProxyAuth(szServer, szMethod)); + } + + const char* find(const char *szServer) + { + ProxyAuth * rec = szServer ? OBJLIST::find((ProxyAuth*)&szServer) : NULL; + return rec ? rec->szMethod : NULL; + } +}; + +ProxyAuthList proxyAuthList; + +static void AppendToCharBuffer(struct ResizableCharBuffer *rcb, const char *fmt, ...) +{ + va_list va; + int charsDone; + + if (rcb->cbAlloced == 0) + { + rcb->cbAlloced = 512; + rcb->sz = (char*)mir_alloc(rcb->cbAlloced); + } + va_start(va, fmt); + for (;;) + { + charsDone = mir_vsnprintf(rcb->sz + rcb->iEnd, rcb->cbAlloced-rcb->iEnd, fmt, va); + if(charsDone >= 0) break; + rcb->cbAlloced += 512; + rcb->sz = (char*)mir_realloc(rcb->sz, rcb->cbAlloced); + } + va_end(va); + rcb->iEnd += charsDone; +} + +static int RecvWithTimeoutTime(struct NetlibConnection *nlc, unsigned dwTimeoutTime, char *buf, int len, int flags) +{ + DWORD dwTimeNow; + + if (!si.pending(nlc->hSsl)) + { + while ((dwTimeNow = GetTickCount()) < dwTimeoutTime) + { + unsigned dwDeltaTime = min(dwTimeoutTime - dwTimeNow, 1000); + int res = WaitUntilReadable(nlc->s, dwDeltaTime); + + switch (res) + { + case SOCKET_ERROR: + return SOCKET_ERROR; + + case 1: + return NLRecv(nlc, buf, len, flags); + } + + if (nlc->termRequested || Miranda_Terminated()) return 0; + } + SetLastError(ERROR_TIMEOUT); + return SOCKET_ERROR; + } + return NLRecv(nlc, buf, len, flags); +} + +static char* NetlibHttpFindHeader(NETLIBHTTPREQUEST *nlhrReply, const char *hdr) +{ + for (int i = 0; i < nlhrReply->headersCount; i++) + { + if (_stricmp(nlhrReply->headers[i].szName, hdr) == 0) + { + return nlhrReply->headers[i].szValue; + } + } + return NULL; +} + +static char* NetlibHttpFindAuthHeader(NETLIBHTTPREQUEST *nlhrReply, const char *hdr, const char *szProvider) +{ + char *szBasicHdr = NULL; + char *szNegoHdr = NULL; + char *szNtlmHdr = NULL; + + for (int i = 0; i < nlhrReply->headersCount; i++) + { + if (_stricmp(nlhrReply->headers[i].szName, hdr) == 0) + { + if (_strnicmp(nlhrReply->headers[i].szValue, "Negotiate", 9) == 0) + szNegoHdr = nlhrReply->headers[i].szValue; + else if (_strnicmp(nlhrReply->headers[i].szValue, "NTLM", 4) == 0) + szNtlmHdr = nlhrReply->headers[i].szValue; + else if (_strnicmp(nlhrReply->headers[i].szValue, "Basic", 5) == 0) + szBasicHdr = nlhrReply->headers[i].szValue; + } + } + + if (szNegoHdr && (!szProvider || !_stricmp(szProvider, "Negotiate"))) return szNegoHdr; + if (szNtlmHdr && (!szProvider || !_stricmp(szProvider, "NTLM"))) return szNtlmHdr; + if (!szProvider || !_stricmp(szProvider, "Basic")) return szBasicHdr; + return NULL; +} + +void NetlibConnFromUrl(const char* szUrl, bool secur, NETLIBOPENCONNECTION &nloc) +{ + secur = secur || _strnicmp(szUrl, "https", 5) == 0; + const char* phost = strstr(szUrl, "://"); + + char* szHost = mir_strdup(phost ? phost + 3 : szUrl); + + char* ppath = strchr(szHost, '/'); + if (ppath) *ppath = '\0'; + + memset(&nloc, 0, sizeof(nloc)); + nloc.cbSize = sizeof(nloc); + nloc.szHost = szHost; + + char* pcolon = strrchr(szHost, ':'); + if (pcolon) + { + *pcolon = '\0'; + nloc.wPort = (WORD)strtol(pcolon+1, NULL, 10); + } + else nloc.wPort = secur ? 443 : 80; + nloc.flags = (secur ? NLOCF_SSL : 0); +} + +static NetlibConnection* NetlibHttpProcessUrl(NETLIBHTTPREQUEST *nlhr, NetlibUser *nlu, NetlibConnection* nlc, + const char* szUrl = NULL) +{ + NETLIBOPENCONNECTION nloc; + + if (szUrl == NULL) + NetlibConnFromUrl(nlhr->szUrl, (nlhr->flags & NLHRF_SSL) != 0, nloc); + else + NetlibConnFromUrl(szUrl, false, nloc); + + nloc.flags |= NLOCF_HTTP; + if (nloc.flags & NLOCF_SSL) nlhr->flags |= NLHRF_SSL; else nlhr->flags &= ~NLHRF_SSL; + + if (nlc != NULL) + { + bool httpProxy = !(nloc.flags & NLOCF_SSL) && nlc->proxyType == PROXYTYPE_HTTP; + bool sameHost = lstrcmpA(nlc->nloc.szHost, nloc.szHost) == 0 && nlc->nloc.wPort == nloc.wPort; + + if (!httpProxy && !sameHost) + { + NetlibDoClose(nlc); + + mir_free((char*)nlc->nloc.szHost); + nlc->nloc = nloc; + return NetlibDoConnect(nlc) ? nlc : NULL; + } + } + else + nlc = (NetlibConnection*)NetlibOpenConnection((WPARAM)nlu, (LPARAM)&nloc); + + mir_free((char*)nloc.szHost); + + return nlc; +} + +struct HttpSecurityContext +{ + HANDLE m_hNtlmSecurity; + char *m_szHost; + char *m_szProvider; + + HttpSecurityContext() + { m_hNtlmSecurity = NULL; m_szHost = NULL; m_szProvider = NULL; } + + ~HttpSecurityContext() { Destroy(); } + + void Destroy(void) + { + if (!m_hNtlmSecurity) return; + + NetlibDestroySecurityProvider(m_hNtlmSecurity); + m_hNtlmSecurity = NULL; + mir_free(m_szHost); m_szHost = NULL; + mir_free(m_szProvider); m_szProvider = NULL; + } + + bool TryBasic(void) + { + return m_hNtlmSecurity && m_szProvider && _stricmp(m_szProvider, "Basic"); + } + + char* Execute(NetlibConnection *nlc, char* szHost, const char* szProvider, + const char* szChallenge, unsigned& complete) + { + char* szAuthHdr = NULL; + bool justCreated = false; + + if (m_hNtlmSecurity) + { + bool newAuth = !m_szProvider || !szProvider || _stricmp(m_szProvider, szProvider); + newAuth = newAuth || (m_szHost != szHost && (!m_szHost || !szHost || _stricmp(m_szHost, szHost))); + if (newAuth) + Destroy(); + } + + if (m_hNtlmSecurity == NULL) + { + char szSpnStr[256] = ""; + if (szHost && _stricmp(szProvider, "Basic")) + { + unsigned long ip = inet_addr(szHost); + PHOSTENT host = (ip == INADDR_NONE) ? gethostbyname(szHost) : gethostbyaddr((char*)&ip, 4, AF_INET); + mir_snprintf(szSpnStr, SIZEOF(szSpnStr), "HTTP/%s", host && host->h_name ? host->h_name : szHost); + _strlwr(szSpnStr + 5); + NetlibLogf(nlc->nlu, "Host SPN: %s", szSpnStr); + } + m_hNtlmSecurity = NetlibInitSecurityProvider(szProvider, szSpnStr[0] ? szSpnStr : NULL); + if (m_hNtlmSecurity) + { + m_szProvider = mir_strdup(szProvider); + m_szHost = mir_strdup(szHost); + justCreated = true; + } + } + + if (m_hNtlmSecurity) + { + TCHAR *szLogin = NULL, *szPassw = NULL; + + if (nlc->nlu->settings.useProxyAuth) + { + EnterCriticalSection(&csNetlibUser); + szLogin = mir_a2t(nlc->nlu->settings.szProxyAuthUser); + szPassw = mir_a2t(nlc->nlu->settings.szProxyAuthPassword); + LeaveCriticalSection(&csNetlibUser); + } + + szAuthHdr = NtlmCreateResponseFromChallenge(m_hNtlmSecurity, + szChallenge, szLogin, szPassw, true, complete); + + if (!szAuthHdr) + { + NetlibLogf(NULL, "Security login %s failed, user: " TCHAR_STR_PARAM " pssw: " TCHAR_STR_PARAM, + szProvider, szLogin ? szLogin : _T("(no user)"), szPassw ? _T("(exist)") : _T("(no psw)")); + } + else if (justCreated) + proxyAuthList.add(m_szHost, m_szProvider); + + mir_free(szLogin); + mir_free(szPassw); + } + else + complete = 1; + + return szAuthHdr; + } +}; + +static int HttpPeekFirstResponseLine(NetlibConnection *nlc, DWORD dwTimeoutTime, + DWORD recvFlags, int *resultCode, + char **ppszResultDescr, int *length) +{ + int bytesPeeked; + char buffer[2048]; + char *peol; + + for(;;) + { + bytesPeeked = RecvWithTimeoutTime(nlc, dwTimeoutTime, buffer, SIZEOF(buffer) - 1, + MSG_PEEK | recvFlags); + + if (bytesPeeked == 0) + { + SetLastError(ERROR_HANDLE_EOF); + return 0; + } + if (bytesPeeked == SOCKET_ERROR) + return 0; + + buffer[bytesPeeked] = '\0'; + peol = strchr(buffer, '\n'); + if (peol == NULL) + { + if ((int)strlen(buffer) < bytesPeeked) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + if (bytesPeeked == SIZEOF(buffer) - 1) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + if (Miranda_Terminated()) return 0; + Sleep(10); + } + else + break; + } + + if (peol == buffer) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + + *peol = '\0'; + + if (_strnicmp(buffer, "HTTP/", 5)) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + + size_t off = strcspn(buffer, " \t"); + if (off >= (unsigned)bytesPeeked) + return 0; + + char* pResultCode = buffer + off; + *(pResultCode++) = 0; + + char* pResultDescr; + *resultCode = strtol(pResultCode, &pResultDescr, 10); + + if (ppszResultDescr) + *ppszResultDescr = mir_strdup(lrtrimp(pResultDescr)); + + if (length) *length = peol - buffer + 1; + return 1; +} + +static int SendHttpRequestAndData(struct NetlibConnection *nlc,struct ResizableCharBuffer *httpRequest,NETLIBHTTPREQUEST *nlhr,int sendContentLengthHeader) +{ + bool sendData = (nlhr->requestType==REQUEST_POST || nlhr->requestType==REQUEST_PUT); + + if(sendContentLengthHeader && sendData) + AppendToCharBuffer(httpRequest,"Content-Length: %d\r\n\r\n", nlhr->dataLength); + else + AppendToCharBuffer(httpRequest,"\r\n"); + + DWORD hflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | + (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND | NLHRF_NODUMPHEADERS) ? + MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + int bytesSent = NLSend(nlc, httpRequest->sz, httpRequest->iEnd, hflags); + if (bytesSent != SOCKET_ERROR && sendData && nlhr->dataLength) + { + DWORD sflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | + (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? + MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + int sendResult = NLSend(nlc,nlhr->pData,nlhr->dataLength, sflags); + + bytesSent = sendResult != SOCKET_ERROR ? bytesSent + sendResult : SOCKET_ERROR; + } + mir_free(httpRequest->sz); + memset(httpRequest, 0, sizeof(*httpRequest)); + + return bytesSent; +} + +INT_PTR NetlibHttpSendRequest(WPARAM wParam, LPARAM lParam) +{ + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + NETLIBHTTPREQUEST *nlhr=(NETLIBHTTPREQUEST*)lParam; + NETLIBHTTPREQUEST *nlhrReply = NULL; + HttpSecurityContext httpSecurity; + + struct ResizableCharBuffer httpRequest={0}; + const char *pszRequest, *pszUrl, *pszFullUrl; + char *szHost = NULL, *szNewUrl = NULL; + char *pszProxyAuthHdr = NULL, *pszAuthHdr = NULL; + int i, doneHostHeader, doneContentLengthHeader, doneProxyAuthHeader, doneAuthHeader; + int bytesSent; + bool lastFirstLineFail = false; + + if (nlhr == NULL || nlhr->cbSize < NETLIBHTTPREQUEST_V1_SIZE || nlhr->szUrl == NULL || nlhr->szUrl[0] == '\0') + { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + int hdrTimeout = nlhr->cbSize > NETLIBHTTPREQUEST_V1_SIZE && nlhr->timeout ? nlhr->timeout : HTTPRECVHEADERSTIMEOUT; + + switch(nlhr->requestType) + { + case REQUEST_GET: pszRequest = "GET"; break; + case REQUEST_POST: pszRequest = "POST"; break; + case REQUEST_CONNECT: pszRequest = "CONNECT"; break; + case REQUEST_HEAD: pszRequest = "HEAD"; break; + case REQUEST_PUT: pszRequest = "PUT"; break; + case REQUEST_DELETE: pszRequest = "DELETE"; break; + default: + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + if (!nlc->usingHttpGateway) + { + if (!NetlibEnterNestedCS(nlc, NLNCS_SEND)) + return SOCKET_ERROR; + } + + pszFullUrl = nlhr->szUrl; + pszUrl = NULL; + + unsigned complete = false; + int count = 11; + while (--count) + { + if (!NetlibReconnect(nlc)) + { + bytesSent = SOCKET_ERROR; + break; + } + + if (!pszUrl) + { + pszUrl = pszFullUrl; + if (nlhr->flags & (NLHRF_SMARTREMOVEHOST | NLHRF_REMOVEHOST | NLHRF_GENERATEHOST)) + { + bool usingProxy = nlc->proxyType == PROXYTYPE_HTTP && !(nlhr->flags & NLHRF_SSL); + + mir_free(szHost); + szHost = NULL; + + const char *ppath, *phost; + phost = strstr(pszUrl, "://"); + if (phost == NULL) phost = pszUrl; + else phost += 3; + ppath = strchr(phost, '/'); + if (ppath == phost) phost = NULL; + + if (nlhr->flags & NLHRF_GENERATEHOST) + { + szHost = mir_strdup(phost); + if (ppath && phost) szHost[ppath - phost] = 0; + } + + if (nlhr->flags & NLHRF_REMOVEHOST || (nlhr->flags & NLHRF_SMARTREMOVEHOST && !usingProxy)) + { + pszUrl = ppath ? ppath : "/"; + } + + if (usingProxy && phost && !nlc->dnsThroughProxy) + { + char* tszHost = mir_strdup(phost); + if (ppath && phost) tszHost[ppath - phost] = 0; + char* cln = strchr(tszHost, ':'); if (cln) *cln = 0; + + if (inet_addr(tszHost) == INADDR_NONE) + { + DWORD ip = DnsLookup(nlc->nlu, tszHost); + if (ip && szHost) + { + mir_free(szHost); + szHost = (char*)mir_alloc(30); + if (cln) *cln = ':'; + mir_snprintf(szHost, 30, "%s%s", inet_ntoa(*(PIN_ADDR)&ip), cln ? cln : ""); + } +/* + if (ip && pszUrl[0] != '/') + { + mir_free(szNewUrl); + szNewUrl = (char*)mir_alloc(strlen(pszUrl) + 60); + szNewUrl[0] = 0; + + phost = strstr(pszUrl, "://"); + if (phost) + { + phost += 3; + size_t len = phost - pszUrl; + memcpy(szNewUrl, pszUrl, len); + szNewUrl[len] = 0; + } + strcat(szNewUrl, inet_ntoa(*(PIN_ADDR)&ip)); + ppath = strchr(phost, '/'); + if (ppath) strcat(szNewUrl, ppath); + pszUrl = szNewUrl; + } +*/ + } + mir_free(tszHost); + } + } + } + + if (nlc->proxyAuthNeeded && proxyAuthList.getCount()) + { + if (httpSecurity.m_szProvider == NULL && nlc->szProxyServer) + { + const char* szAuthMethodNlu = proxyAuthList.find(nlc->szProxyServer); + + if (szAuthMethodNlu) + { + mir_free(pszProxyAuthHdr); + pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthMethodNlu, "", complete); + } + } + } + nlc->proxyAuthNeeded = false; + + AppendToCharBuffer(&httpRequest, "%s %s HTTP/1.%d\r\n", pszRequest, pszUrl, (nlhr->flags & NLHRF_HTTP11) != 0); + + //HTTP headers + doneHostHeader = doneContentLengthHeader = doneProxyAuthHeader = doneAuthHeader = 0; + for (i=0; i < nlhr->headersCount; i++) + { + if (!lstrcmpiA(nlhr->headers[i].szName, "Host")) doneHostHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Content-Length")) doneContentLengthHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Proxy-Authorization")) doneProxyAuthHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Authorization")) doneAuthHeader = 1; + else if (!lstrcmpiA(nlhr->headers[i].szName, "Connection")) continue; + if (nlhr->headers[i].szValue == NULL) continue; + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", nlhr->headers[i].szName, nlhr->headers[i].szValue); + } + if (szHost && !doneHostHeader) + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Host", szHost); + if (pszProxyAuthHdr && !doneProxyAuthHeader) + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Proxy-Authorization", pszProxyAuthHdr); + if (pszAuthHdr && !doneAuthHeader) + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Authorization", pszAuthHdr); + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Connection", "Keep-Alive"); + AppendToCharBuffer(&httpRequest, "%s: %s\r\n", "Proxy-Connection", "Keep-Alive"); + + // Add Sticky Headers + if (nlc->nlu->szStickyHeaders != NULL) + AppendToCharBuffer(&httpRequest, "%s\r\n", nlc->nlu->szStickyHeaders); + + //send it + bytesSent = SendHttpRequestAndData(nlc, &httpRequest, nlhr, !doneContentLengthHeader); + if (bytesSent == SOCKET_ERROR) break; + + //ntlm reply + if (!doneContentLengthHeader || nlhr->requestType == REQUEST_HEAD) + { + int resultCode = 0; + + DWORD fflags = MSG_PEEK | MSG_NODUMP | ((nlhr->flags & NLHRF_NOPROXY) ? MSG_RAW : 0); + DWORD dwTimeOutTime = hdrTimeout < 0 ? -1 : GetTickCount() + hdrTimeout; + if (!HttpPeekFirstResponseLine(nlc, dwTimeOutTime, fflags, &resultCode, NULL, NULL)) + { + NetlibLogf(nlc->nlu, "%s %d: %s Failed (%u %u)",__FILE__,__LINE__,"HttpPeekFirstResponseLine",GetLastError(), count); + DWORD err = GetLastError(); + if (err == ERROR_TIMEOUT || err == ERROR_BAD_FORMAT || err == ERROR_BUFFER_OVERFLOW || + lastFirstLineFail || nlc->termRequested || nlhr->requestType == REQUEST_CONNECT) + { + bytesSent = SOCKET_ERROR; + break; + } + else + { + lastFirstLineFail = true; + continue; + } + } + lastFirstLineFail = false; + + DWORD hflags = (nlhr->flags & (NLHRF_NODUMP|NLHRF_NODUMPHEADERS|NLHRF_NODUMPSEND) ? + MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + DWORD dflags = (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? + MSG_NODUMP : MSG_DUMPASTEXT | MSG_DUMPPROXY) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0) | MSG_NODUMP; + + if (resultCode == 100) + { + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + } + else if (resultCode == 307 || ((resultCode == 301 || resultCode == 302) // redirect + && (nlhr->flags & NLHRF_REDIRECT))) + { + pszUrl = NULL; + + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + if (nlhrReply) + { + char* tmpUrl = NetlibHttpFindHeader(nlhrReply, "Location"); + if (tmpUrl) + { + size_t rlen = 0; + if (tmpUrl[0] == '/') + { + const char *ppath, *phost; + phost = strstr(pszFullUrl, "://"); + phost = phost ? phost + 3 : pszFullUrl; + ppath = strchr(phost, '/'); + rlen = ppath ? ppath - pszFullUrl : strlen(pszFullUrl); + } + + nlc->szNewUrl = (char*)mir_realloc(nlc->szNewUrl, rlen + strlen(tmpUrl) * 3 + 1); + + strncpy(nlc->szNewUrl, pszFullUrl, rlen); + strcpy(nlc->szNewUrl + rlen, tmpUrl); + pszFullUrl = nlc->szNewUrl; + pszUrl = NULL; + + if (NetlibHttpProcessUrl(nlhr, nlc->nlu, nlc, pszFullUrl) == NULL) + { + bytesSent = SOCKET_ERROR; + break; + } + } + else + { + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else + { + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else if (resultCode == 401 && !doneAuthHeader) //auth required + { + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + mir_free(pszAuthHdr); pszAuthHdr = NULL; + if (nlhrReply) + { + char *szAuthStr = NULL; + if (!complete) + { + szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", + httpSecurity.m_szProvider); + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (!szChallenge || !*lrtrimp(szChallenge)) complete = true; + } + } + if (complete && httpSecurity.m_hNtlmSecurity) + { + szAuthStr = httpSecurity.TryBasic() ? + NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", "Basic") : NULL; + } + + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); } + + pszAuthHdr = httpSecurity.Execute(nlc, szHost, szAuthStr, szChallenge, complete); + } + } + if (pszAuthHdr == NULL) + { + proxyAuthList.add(szHost, NULL); + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else if (resultCode == 407 && !doneProxyAuthHeader) //proxy auth required + { + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + mir_free(pszProxyAuthHdr); pszProxyAuthHdr = NULL; + if (nlhrReply) + { + char *szAuthStr = NULL; + if (!complete) + { + szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", + httpSecurity.m_szProvider); + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (!szChallenge || !*lrtrimp(szChallenge + 1)) complete = true; + } + } + if (complete && httpSecurity.m_hNtlmSecurity) + { + szAuthStr = httpSecurity.TryBasic() ? + NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", "Basic") : NULL; + } + + if (szAuthStr) + { + char *szChallenge = strchr(szAuthStr, ' '); + if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); } + + pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthStr, szChallenge, complete); + } + } + if (pszProxyAuthHdr == NULL) + { + proxyAuthList.add(nlc->szProxyServer, NULL); + NetlibHttpSetLastErrorUsingHttpResult(resultCode); + bytesSent = SOCKET_ERROR; + break; + } + } + else + break; + + if (pszProxyAuthHdr && resultCode != 407 && !doneProxyAuthHeader) + { + mir_free(pszProxyAuthHdr); pszProxyAuthHdr = NULL; + } + if (pszAuthHdr && resultCode != 401 && !doneAuthHeader) + { + mir_free(pszAuthHdr); pszAuthHdr = NULL; + } + + if (nlhrReply) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + nlhrReply = NULL; + } + } + else + break; + } + if (count == 0) bytesSent = SOCKET_ERROR; + if (nlhrReply) NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + //clean up + mir_free(pszProxyAuthHdr); + mir_free(pszAuthHdr); + mir_free(szHost); + mir_free(szNewUrl); + + if (!nlc->usingHttpGateway) + NetlibLeaveNestedCS(&nlc->ncsSend); + + return bytesSent; +} + +INT_PTR NetlibHttpFreeRequestStruct(WPARAM, LPARAM lParam) +{ + NETLIBHTTPREQUEST *nlhr=(NETLIBHTTPREQUEST*)lParam; + + if (nlhr == NULL || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->requestType != REQUEST_RESPONSE) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if(nlhr->headers) + { + int i; + for(i=0; iheadersCount; i++) + { + mir_free(nlhr->headers[i].szName); + mir_free(nlhr->headers[i].szValue); + } + mir_free(nlhr->headers); + } + mir_free(nlhr->pData); + mir_free(nlhr->szResultDescr); + mir_free(nlhr->szUrl); + mir_free(nlhr); + return 1; +} + +INT_PTR NetlibHttpRecvHeaders(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc = (struct NetlibConnection*)wParam; + NETLIBHTTPREQUEST *nlhr; + char *peol, *pbuffer; + char *buffer = NULL; + DWORD dwRequestTimeoutTime; + int bytesPeeked, firstLineLength = 0; + int headersCount = 0, bufferSize = 8192; + bool headersCompleted = false; + + if(!NetlibEnterNestedCS(nlc,NLNCS_RECV)) + return 0; + + dwRequestTimeoutTime = GetTickCount() + HTTPRECVDATATIMEOUT; + nlhr = (NETLIBHTTPREQUEST*)mir_calloc(sizeof(NETLIBHTTPREQUEST)); + nlhr->cbSize = sizeof(NETLIBHTTPREQUEST); + nlhr->nlc = nlc; // Needed to id connection in the protocol HTTP gateway wrapper functions + nlhr->requestType = REQUEST_RESPONSE; + + if (!HttpPeekFirstResponseLine(nlc, dwRequestTimeoutTime, lParam | MSG_PEEK, + &nlhr->resultCode, &nlhr->szResultDescr, &firstLineLength)) + { + NetlibLeaveNestedCS(&nlc->ncsRecv); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); + return 0; + } + + buffer = (char*)mir_alloc(bufferSize + 1); + bytesPeeked = NLRecv(nlc, buffer, min(firstLineLength, bufferSize), lParam | MSG_DUMPASTEXT); + if (bytesPeeked != firstLineLength) + { + NetlibLeaveNestedCS(&nlc->ncsRecv); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); + if (bytesPeeked != SOCKET_ERROR) SetLastError(ERROR_HANDLE_EOF); + mir_free(buffer); + return 0; + } + + // Make sure all headers arrived + bytesPeeked = 0; + while (!headersCompleted) + { + if (bytesPeeked >= bufferSize) + { + bufferSize += 8192; + mir_free(buffer); + if (bufferSize > 32 * 1024) + { + bytesPeeked = 0; + break; + } + buffer = (char*)mir_alloc(bufferSize + 1); + } + + bytesPeeked = RecvWithTimeoutTime(nlc, dwRequestTimeoutTime, buffer, bufferSize, + MSG_PEEK | MSG_NODUMP | lParam); + if (bytesPeeked == 0) break; + + if (bytesPeeked == SOCKET_ERROR) + { + bytesPeeked = 0; + break; + } + buffer[bytesPeeked] = 0; + + for (pbuffer = buffer, headersCount = 0; ; pbuffer = peol + 1, ++headersCount) + { + peol = strchr(pbuffer, '\n'); + if (peol == NULL) break; + if (peol == pbuffer || (peol == (pbuffer + 1) && *pbuffer == '\r')) + { + bytesPeeked = peol - buffer + 1; + headersCompleted = true; + break; + } + } + } + + // Recieve headers + if (bytesPeeked > 0) + bytesPeeked = NLRecv(nlc, buffer, bytesPeeked, lParam | MSG_DUMPASTEXT); + if (bytesPeeked <= 0) + { + NetlibLeaveNestedCS(&nlc->ncsRecv); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); + mir_free(buffer); + return 0; + } + buffer[bytesPeeked] = 0; + + nlhr->headersCount = headersCount; + nlhr->headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER) * headersCount); + + for (pbuffer = buffer, headersCount = 0; ; pbuffer = peol + 1, ++headersCount) + { + peol = strchr(pbuffer, '\n'); + if (peol == NULL || peol == pbuffer || (peol == (pbuffer + 1) && *pbuffer == '\r')) break; + *peol = 0; + + char *pColon = strchr(pbuffer, ':'); + if (pColon == NULL) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr); nlhr = NULL; + SetLastError(ERROR_INVALID_DATA); + break; + } + + *(pColon++) = 0; + nlhr->headers[headersCount].szName = mir_strdup(rtrim(pbuffer)); + nlhr->headers[headersCount].szValue = mir_strdup(lrtrimp(pColon)); + } + + NetlibLeaveNestedCS(&nlc->ncsRecv); + mir_free(buffer); + return (INT_PTR)nlhr; +} + +INT_PTR NetlibHttpTransaction(WPARAM wParam, LPARAM lParam) +{ + NetlibUser *nlu = (NetlibUser*)wParam; + NETLIBHTTPREQUEST *nlhr = (NETLIBHTTPREQUEST*)lParam, *nlhrReply; + DWORD dflags, hflags; + + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) || + nlhr == NULL || nlhr->cbSize < NETLIBHTTPREQUEST_V1_SIZE || + nlhr->szUrl == NULL || nlhr->szUrl[0] == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + NetlibConnection* nlc = NetlibHttpProcessUrl(nlhr, nlu, (NetlibConnection*)nlhr->nlc); + if (nlc == NULL) return 0; + + { + NETLIBHTTPREQUEST nlhrSend; + char szUserAgent[64]; + + nlhrSend = *nlhr; + nlhrSend.flags &= ~NLHRF_REMOVEHOST; + nlhrSend.flags |= NLHRF_GENERATEHOST | NLHRF_SMARTREMOVEHOST | NLHRF_SMARTAUTHHEADER; + + bool doneUserAgentHeader = NetlibHttpFindHeader(nlhr, "User-Agent") != NULL; + bool doneAcceptEncoding = NetlibHttpFindHeader(nlhr, "Accept-Encoding") != NULL; + + if (!doneUserAgentHeader || !doneAcceptEncoding) + { + nlhrSend.headers = (NETLIBHTTPHEADER*)mir_alloc(sizeof(NETLIBHTTPHEADER) * (nlhrSend.headersCount + 2)); + memcpy(nlhrSend.headers, nlhr->headers, sizeof(NETLIBHTTPHEADER) * nlhr->headersCount); + } + if (!doneUserAgentHeader) + { + char *pspace,szMirandaVer[64]; + + nlhrSend.headers[nlhrSend.headersCount].szName = "User-Agent"; + nlhrSend.headers[nlhrSend.headersCount].szValue = szUserAgent; + ++nlhrSend.headersCount; + CallService(MS_SYSTEM_GETVERSIONTEXT,SIZEOF(szMirandaVer),(LPARAM)szMirandaVer); + pspace=strchr(szMirandaVer,' '); + if(pspace) + { + *pspace++ = '\0'; + mir_snprintf(szUserAgent, SIZEOF(szUserAgent), "Miranda/%s (%s)", szMirandaVer, pspace); + } + else + mir_snprintf(szUserAgent, SIZEOF(szUserAgent), "Miranda/%s", szMirandaVer); + } + if (!doneAcceptEncoding) + { + nlhrSend.headers[nlhrSend.headersCount].szName = "Accept-Encoding"; + nlhrSend.headers[nlhrSend.headersCount].szValue = "deflate, gzip"; + ++nlhrSend.headersCount; + } + if (NetlibHttpSendRequest((WPARAM)nlc, (LPARAM)&nlhrSend) == SOCKET_ERROR) + { + if (!doneUserAgentHeader || !doneAcceptEncoding) mir_free(nlhrSend.headers); + NetlibCloseHandle((WPARAM)nlc, 0); + return 0; + } + if (!doneUserAgentHeader || !doneAcceptEncoding) mir_free(nlhrSend.headers); + } + + dflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT:0) | + (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + hflags = + (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | + (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); + + + if (nlhr->requestType == REQUEST_HEAD) + nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, 0); + else + nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); + + if (nlhrReply) + { + nlhrReply->szUrl = nlc->szNewUrl; + nlc->szNewUrl = NULL; + } + + if ((nlhr->flags & NLHRF_PERSISTENT) == 0 || nlhrReply == NULL) + { + NetlibCloseHandle((WPARAM)nlc, 0); + if (nlhrReply) nlhrReply->nlc = NULL; + } + else + nlhrReply->nlc = nlc; + + return (INT_PTR)nlhrReply; +} + +void NetlibHttpSetLastErrorUsingHttpResult(int result) +{ + if (result >= 200 && result < 300) + { + SetLastError(ERROR_SUCCESS); + return; + } + switch(result) + { + case 400: SetLastError(ERROR_BAD_FORMAT); break; + case 401: + case 402: + case 403: + case 407: SetLastError(ERROR_ACCESS_DENIED); break; + case 404: SetLastError(ERROR_FILE_NOT_FOUND); break; + case 405: + case 406: SetLastError(ERROR_INVALID_FUNCTION); break; + case 408: SetLastError(ERROR_TIMEOUT); break; + default: SetLastError(ERROR_GEN_FAILURE); break; + } +} + +char* gzip_decode(char *gzip_data, int *len_ptr, int window) +{ + if (*len_ptr == 0) return NULL; + + int gzip_len = *len_ptr * 5; + char* output_data = NULL; + + int gzip_err; + z_stream zstr; + + do + { + output_data = (char*)mir_realloc(output_data, gzip_len+1); + + zstr.next_in = (Bytef*)gzip_data; + zstr.avail_in = *len_ptr; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + inflateInit2_(&zstr, window, ZLIB_VERSION, sizeof(z_stream)); + + zstr.next_out = (Bytef*)output_data; + zstr.avail_out = gzip_len; + + gzip_err = inflate(&zstr, Z_FINISH); + + inflateEnd(&zstr); + gzip_len *= 2; + } + while (gzip_err == Z_BUF_ERROR); + + gzip_len = gzip_err == Z_STREAM_END ? zstr.total_out : -1; + + if (gzip_len <= 0) + { + mir_free(output_data); + output_data = NULL; + } + else + output_data[gzip_len] = 0; + + *len_ptr = gzip_len; + return output_data; +} + +static int NetlibHttpRecvChunkHeader(NetlibConnection* nlc, bool first, DWORD flags) +{ + char data[64], *peol1; + + for (;;) + { + int recvResult = NLRecv(nlc, data, 31, MSG_RAW | MSG_PEEK); + if (recvResult <= 0) return SOCKET_ERROR; + + data[recvResult] = 0; + + peol1 = strchr(data, '\n'); + if (peol1 != NULL) + { + char *peol2 = first ? peol1 : strchr(peol1 + 1, '\n'); + if (peol2 != NULL) + { + int sz = peol2 - data + 1; + int r = strtol(first ? data : peol1 + 1, NULL, 16); + if (r == 0) + { + char *peol3 = strchr(peol2 + 1, '\n'); + if (peol3 == NULL) continue; + sz = peol3 - data + 1; + } + NLRecv(nlc, data, sz, MSG_RAW | flags); + return r; + } + else + if (recvResult >= 31) return SOCKET_ERROR; + } + } +} + +NETLIBHTTPREQUEST* NetlibHttpRecv(NetlibConnection* nlc, DWORD hflags, DWORD dflags, bool isConnect) +{ + int dataLen = -1, i, chunkhdr = 0; + bool chunked = false; + int cenc = 0, cenctype = 0, close = 0; + +next: + NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)NetlibHttpRecvHeaders((WPARAM)nlc, hflags); + if (nlhrReply == NULL) + return NULL; + + if (nlhrReply->resultCode == 100) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + goto next; + } + + for (i=0; iheadersCount; i++) + { + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Content-Length")) + dataLen = atoi(nlhrReply->headers[i].szValue); + + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Content-Encoding")) + { + cenc = i; + if (strstr(nlhrReply->headers[i].szValue, "gzip")) + cenctype = 1; + else if (strstr(nlhrReply->headers[i].szValue, "deflate")) + cenctype = 2; + } + + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Connection")) + close = !lstrcmpiA(nlhrReply->headers[i].szValue, "close"); + + if (!lstrcmpiA(nlhrReply->headers[i].szName, "Transfer-Encoding") && + !lstrcmpiA(nlhrReply->headers[i].szValue, "chunked")) + { + chunked = true; + chunkhdr = i; + dataLen = -1; + } + } + + if (nlhrReply->resultCode >= 200 && (dataLen > 0 || (!isConnect && dataLen < 0))) + { + int recvResult, chunksz = -1; + int dataBufferAlloced; + + if (chunked) + { + chunksz = NetlibHttpRecvChunkHeader(nlc, true, dflags); + if (chunksz == SOCKET_ERROR) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return NULL; + } + dataLen = chunksz; + } + dataBufferAlloced = dataLen < 0 ? 2048 : dataLen + 1; + nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); + + while (chunksz != 0) + { + for(;;) + { + recvResult = RecvWithTimeoutTime(nlc, GetTickCount() + HTTPRECVDATATIMEOUT, + nlhrReply->pData + nlhrReply->dataLength, + dataBufferAlloced - nlhrReply->dataLength - 1, + dflags | (cenctype ? MSG_NODUMP : 0)); + + if (recvResult == 0) break; + if (recvResult == SOCKET_ERROR) + { + NetlibHttpFreeRequestStruct(0,(LPARAM)nlhrReply); + return NULL; + } + nlhrReply->dataLength += recvResult; + + if (dataLen >= 0) + { + if (nlhrReply->dataLength >= dataLen) break; + } + else + { + if ((dataBufferAlloced - nlhrReply->dataLength) < 256) + { + dataBufferAlloced += 2048; + nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); + if(nlhrReply->pData == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return NULL; + } + } + } + Sleep(10); + } + + if (chunked) + { + chunksz = NetlibHttpRecvChunkHeader(nlc, false, dflags); + if (chunksz == SOCKET_ERROR) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return NULL; + } + dataLen += chunksz; + dataBufferAlloced += chunksz; + + nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); + } + else + break; + } + + nlhrReply->pData[nlhrReply->dataLength] = '\0'; + } + + if (chunked) + { + nlhrReply->headers[chunkhdr].szName = ( char* )mir_realloc(nlhrReply->headers[chunkhdr].szName, 16); + lstrcpyA(nlhrReply->headers[chunkhdr].szName, "Content-Length"); + + nlhrReply->headers[chunkhdr].szValue = ( char* )mir_realloc(nlhrReply->headers[chunkhdr].szValue, 16); + mir_snprintf(nlhrReply->headers[chunkhdr].szValue, 16, "%u", nlhrReply->dataLength); + } + + if (cenctype) + { + int bufsz = nlhrReply->dataLength; + char* szData = NULL; + + switch (cenctype) + { + case 1: + szData = gzip_decode(nlhrReply->pData, &bufsz, 0x10 | MAX_WBITS); + break; + + case 2: + szData = gzip_decode(nlhrReply->pData, &bufsz, -MAX_WBITS); + if (bufsz < 0) + { + bufsz = nlhrReply->dataLength; + szData = gzip_decode(nlhrReply->pData, &bufsz, MAX_WBITS); + } + break; + } + + if (bufsz > 0) + { + NetlibDumpData(nlc, (PBYTE)szData, bufsz, 0, dflags); + mir_free(nlhrReply->pData); + nlhrReply->pData = szData; + nlhrReply->dataLength = bufsz; + + mir_free(nlhrReply->headers[cenc].szName); + mir_free(nlhrReply->headers[cenc].szValue); + memmove(&nlhrReply->headers[cenc], &nlhrReply->headers[cenc+1], (--nlhrReply->headersCount-cenc)*sizeof(nlhrReply->headers[0])); + } + else if (bufsz == 0) + { + mir_free(nlhrReply->pData); + nlhrReply->pData = NULL; + nlhrReply->dataLength = 0; + } + } + + if (close && + (nlc->proxyType != PROXYTYPE_HTTP || nlc->nloc.flags & NLOCF_SSL) && + (!isConnect || nlhrReply->resultCode != 200)) + NetlibDoClose(nlc); + + return nlhrReply; +} diff --git a/src/modules/netlib/netlibhttpproxy.cpp b/src/modules/netlib/netlibhttpproxy.cpp new file mode 100644 index 0000000000..153d53660f --- /dev/null +++ b/src/modules/netlib/netlibhttpproxy.cpp @@ -0,0 +1,522 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +typedef enum +{ + reqHelloGet, + reqOldGet, + reqOldPost, + reqNewPost, +} +RequestType; + + +static int HttpGatewayReadSetResult(NetlibConnection *nlc, char *buf, int num, int peek) +{ + if (nlc->dataBufferLen == 0) return 0; + + int bytes = min(num, nlc->dataBufferLen); + int rbytes = nlc->dataBufferLen - bytes; + + memcpy(buf, nlc->dataBuffer, bytes); + if (!peek) + { + memmove(nlc->dataBuffer, nlc->dataBuffer + bytes, rbytes); + nlc->dataBufferLen = rbytes; + } + + return bytes; +} + +void HttpGatewayRemovePacket(NetlibConnection *nlc, int pck) +{ + EnterCriticalSection(&nlc->csHttpSequenceNums); + while (pck-- && nlc->pHttpProxyPacketQueue != NULL) + { + NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue; + nlc->pHttpProxyPacketQueue = nlc->pHttpProxyPacketQueue->next; + + mir_free(p->dataBuffer); + mir_free(p); + } + LeaveCriticalSection(&nlc->csHttpSequenceNums); +} + + +static bool NetlibHttpGatewaySend(struct NetlibConnection *nlc, RequestType reqType, const char *buf, int len) +{ + NETLIBHTTPREQUEST nlhrSend = {0}; + char szUrl[512]; + + nlhrSend.cbSize = sizeof(nlhrSend); + nlhrSend.nlc = nlc; + + nlhrSend.pData = (char*)buf; + nlhrSend.dataLength = len; + + nlhrSend.flags = NLHRF_GENERATEHOST | NLHRF_DUMPPROXY | NLHRF_SMARTAUTHHEADER | NLHRF_NOPROXY | NLHRF_REDIRECT; + if (nlc->nlhpi.flags & NLHPIF_HTTP11) nlhrSend.flags |= NLHRF_HTTP11; + + switch (reqType) + { + case reqHelloGet: + nlhrSend.requestType = REQUEST_GET; + nlhrSend.szUrl=nlc->nlu->user.szHttpGatewayHello; + break; + + case reqOldGet: + nlhrSend.requestType = REQUEST_GET; + nlhrSend.timeout = -1; + if ((nlc->nlhpi.flags & NLHPIF_USEGETSEQUENCE) && (nlc->nlhpi.szHttpGetUrl != NULL)) + { + EnterCriticalSection(&nlc->csHttpSequenceNums); + + mir_snprintf(szUrl, SIZEOF(szUrl), "%s%u", nlc->nlhpi.szHttpGetUrl, nlc->nlhpi.firstGetSequence++); + if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE) nlc->nlhpi.firstPostSequence++; + + LeaveCriticalSection(&nlc->csHttpSequenceNums); + nlhrSend.szUrl = szUrl; + } + else + nlhrSend.szUrl = nlc->nlhpi.szHttpGetUrl; + break; + + case reqOldPost: + nlhrSend.requestType = REQUEST_POST; + if ((nlc->nlhpi.flags & NLHPIF_USEPOSTSEQUENCE) && (nlc->nlhpi.szHttpPostUrl != NULL)) + { + mir_snprintf(szUrl, SIZEOF(szUrl), "%s%u", nlc->nlhpi.szHttpPostUrl, nlc->nlhpi.firstPostSequence); + nlhrSend.szUrl = szUrl; + } + else + nlhrSend.szUrl = nlc->nlhpi.szHttpPostUrl; + break; + + case reqNewPost: + nlhrSend.requestType = REQUEST_POST; + nlhrSend.szUrl = nlc->nlhpi.szHttpPostUrl; + break; + } + + if (nlc->usingDirectHttpGateway) + { + NETLIBOPENCONNECTION nloc; + NetlibConnFromUrl(nlhrSend.szUrl, false, nloc); + + bool sameHost = lstrcmpA(nlc->nloc.szHost, nloc.szHost) == 0 && nlc->nloc.wPort == nloc.wPort; + + if (!sameHost) + { + NetlibDoClose(nlc); + + mir_free((char*)nlc->nloc.szHost); + nlc->nloc = nloc; + if (!NetlibDoConnect(nlc)) + return false; + } + else + mir_free((char*)nloc.szHost); + } + + nlhrSend.headersCount = 3; + nlhrSend.headers = (NETLIBHTTPHEADER*)alloca(sizeof(NETLIBHTTPHEADER) * nlhrSend.headersCount); + nlhrSend.headers[0].szName = "User-Agent"; + nlhrSend.headers[0].szValue = nlc->nlu->user.szHttpGatewayUserAgent; + nlhrSend.headers[1].szName = "Cache-Control"; + nlhrSend.headers[1].szValue = "no-cache, no-store "; + nlhrSend.headers[2].szName = "Pragma"; + nlhrSend.headers[2].szValue = "no-cache"; +// nlhrSend.headers[3].szName = "Accept-Encoding"; +// nlhrSend.headers[3].szValue = "deflate, gzip"; + + return NetlibHttpSendRequest((WPARAM)nlc,(LPARAM)&nlhrSend) != SOCKET_ERROR; +} + +static bool NetlibHttpGatewayStdPost(NetlibConnection *nlc, int& numPackets) +{ + int np = 0, len = 0; + + EnterCriticalSection(&nlc->csHttpSequenceNums); + + NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue; + while (p != NULL && np < nlc->nlhpi.combinePackets) { ++np; len += p->dataBufferLen; p = p->next;} + + char *buf = (char*)alloca(len); + + numPackets = np; + int dlen = 0; + + p = nlc->pHttpProxyPacketQueue; + while (np--) + { + memcpy(buf + dlen, p->dataBuffer, p->dataBufferLen); + dlen += p->dataBufferLen; + p = p->next; + } + + LeaveCriticalSection(&nlc->csHttpSequenceNums); + + return NetlibHttpGatewaySend(nlc, reqNewPost, buf, len); +} + +static bool NetlibHttpGatewayOscarPost(NetlibConnection *nlc, const char *buf, int len, int flags) +{ + NETLIBHTTPREQUEST *nlhrReply = NULL; + NetlibConnection nlcSend = {0}; + + nlcSend.handleType = NLH_CONNECTION; + nlcSend.nlu = nlc->nlu; + nlcSend.nlhpi = nlc->nlhpi; + nlcSend.s = nlc->s2; + nlcSend.usingHttpGateway = nlc->usingHttpGateway; + nlcSend.szProxyServer = nlc->szProxyServer; + nlcSend.wProxyPort = nlc->wProxyPort; + nlcSend.proxyType = nlc->proxyType; + + if (!NetlibReconnect(&nlcSend)) return false; + nlc->s2 = nlcSend.s; + + nlcSend.hOkToCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + NetlibInitializeNestedCS(&nlcSend.ncsRecv); + NetlibInitializeNestedCS(&nlcSend.ncsSend); + + bool res = NetlibHttpGatewaySend(&nlcSend, reqOldPost, buf, len); + if (res) + { + NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(&nlcSend, flags | MSG_RAW | MSG_DUMPPROXY, MSG_RAW | MSG_DUMPPROXY); + if (nlhrReply != NULL) + { + if (nlhrReply->resultCode != 200) + { + NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); + res = false; + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + } + else + res = false; + } + + NetlibDeleteNestedCS(&nlcSend.ncsSend); + NetlibDeleteNestedCS(&nlcSend.ncsRecv); + CloseHandle(nlcSend.hOkToCloseEvent); + + nlc->s2 = nlcSend.s; + mir_free((char*)nlcSend.nloc.szHost); + + EnterCriticalSection(&nlc->csHttpSequenceNums); + + nlc->nlhpi.firstPostSequence++; + if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE) nlc->nlhpi.firstGetSequence++; + + LeaveCriticalSection(&nlc->csHttpSequenceNums); + + return res; +} + + int NetlibHttpGatewayPost(struct NetlibConnection *nlc, const char *buf, int len, int flags) +{ + struct NetlibHTTPProxyPacketQueue *p; + + if (nlc->nlhpi.szHttpGetUrl != NULL) + { + return NetlibHttpGatewayOscarPost(nlc, buf, len, flags) ? len : SOCKET_ERROR; + } + + /* + * Gena01 - many changes here, do compare against the other version. + * + * Change #1: simplify to use similar code to GET + * Change #2: we need to allow to parse POST reply if szHttpGetUrl is NULL + * Change #3: Keep connection open if we need to. + * + * Impact: NONE! Since currently miranda doesn't allow szHttpGetUrl to be NULL, it will not connect + * with the new plugins that use this code. + */ + + p = ( NetlibHTTPProxyPacketQueue* )mir_alloc(sizeof(struct NetlibHTTPProxyPacketQueue)); + p->dataBuffer = ( PBYTE )mir_alloc(len); + memcpy(p->dataBuffer, buf, len); + p->dataBufferLen = len; + p->next = NULL; + + /* + * Now check to see where to insert this in our queue + */ + EnterCriticalSection(&nlc->csHttpSequenceNums); + if (nlc->pHttpProxyPacketQueue == NULL) + { + nlc->pHttpProxyPacketQueue = p; + } + else + { + struct NetlibHTTPProxyPacketQueue *t = nlc->pHttpProxyPacketQueue; + + while (t->next != NULL) t = t->next; + t->next = p; + } + LeaveCriticalSection(&nlc->csHttpSequenceNums); + + /* + * Gena01 - fake a Send!! tell 'em all is ok. We catch errors in Recv. + */ + return len; +} + +#define NETLIBHTTP_RETRYCOUNT 3 +#define NETLIBHTTP_RETRYTIMEOUT 2000 + +int NetlibHttpGatewayRecv(struct NetlibConnection *nlc, char *buf, int len, int flags) +{ + bool peek = (flags & MSG_PEEK) != 0; + + if (nlc->dataBufferLen != 0 && (!peek || nlc->dataBufferLen >= len)) + { + return HttpGatewayReadSetResult(nlc, buf, len, peek); + } + + for (int retryCount = 0; retryCount < NETLIBHTTP_RETRYCOUNT; ) + { + if (nlc->nlhpi.szHttpGetUrl == NULL && retryCount == 0) + { + if (nlc->pollingTimeout == 0) nlc->pollingTimeout = 30; + + /* We Need to sleep/wait for the data to send before we do receive */ + for (int pollCount = nlc->pollingTimeout; pollCount--; ) + { + if (nlc->pHttpProxyPacketQueue != NULL && GetTickCount() - nlc->lastPost > 1000) + break; + + if (nlc->termRequested || (SleepEx(1000, TRUE) && Miranda_Terminated())) + return SOCKET_ERROR; + } + + nlc->lastPost = GetTickCount(); + if (nlc->pHttpProxyPacketQueue == NULL && nlc->nlu->user.pfnHttpGatewayWrapSend != NULL) + { + if (nlc->nlu->user.pfnHttpGatewayWrapSend(nlc, (PBYTE)"", 0, MSG_NOHTTPGATEWAYWRAP, NetlibSend) == SOCKET_ERROR) + return SOCKET_ERROR; + } + } + + int numPackets = 0; + if (nlc->nlhpi.szHttpGetUrl) + { + if (!NetlibHttpGatewaySend(nlc, reqOldGet, NULL, 0)) + { + if (GetLastError() == ERROR_ACCESS_DENIED || nlc->termRequested) + break; + + ++retryCount; + continue; + } + } + else + { + if (!NetlibHttpGatewayStdPost(nlc, numPackets)) + { + if (GetLastError() == ERROR_ACCESS_DENIED || nlc->termRequested) + break; + + ++retryCount; + continue; + } + } + NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(nlc, flags | MSG_RAW | MSG_DUMPPROXY, MSG_RAW | MSG_DUMPPROXY); + if (nlhrReply == NULL) return SOCKET_ERROR; + + if (nlc->nlu->user.pfnHttpGatewayUnwrapRecv && !(flags & MSG_NOHTTPGATEWAYWRAP)) + { + nlhrReply->pData = (char*)nlc->nlu->user.pfnHttpGatewayUnwrapRecv(nlhrReply, + (PBYTE)nlhrReply->pData, nlhrReply->dataLength, &nlhrReply->dataLength, mir_realloc); +/* + if (newBuffer == NULL) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return SOCKET_ERROR; + } + else + nlhrReply->pData = (char*)newBuffer; +*/ + } + + if (nlhrReply->resultCode >= 300) + { + int resultCode = nlhrReply->resultCode; + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + if (nlc->nlhpi.szHttpGetUrl && resultCode != 404) + { + NetlibLogf(nlc->nlu, "Error received from proxy, retrying"); + continue; + } + else + { + NetlibLogf(nlc->nlu, "Error received from proxy, retry attempts exceeded (%u)", retryCount); + SetLastError(ERROR_GEN_FAILURE); + return SOCKET_ERROR; + } + } + else + { + retryCount = 0; + HttpGatewayRemovePacket(nlc, numPackets); + } + + if (nlhrReply->dataLength) + { + if (peek) + { + int rbytes = nlc->dataBufferLen + nlhrReply->dataLength; + + nlc->dataBuffer = (PBYTE)mir_realloc(nlc->dataBuffer, rbytes); + memcpy(nlc->dataBuffer + nlc->dataBufferLen, nlhrReply->pData, nlhrReply->dataLength); + nlc->dataBufferLen = rbytes; + + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + return HttpGatewayReadSetResult(nlc, buf, len, peek); + } + else + { + int bytes = min(len, nlhrReply->dataLength); + int rbytes = nlhrReply->dataLength - bytes; + + memcpy(buf, nlhrReply->pData, bytes); + + nlc->dataBuffer = (PBYTE)mir_realloc(nlc->dataBuffer, rbytes); + if (rbytes) memcpy(nlc->dataBuffer, nlhrReply->pData + bytes, rbytes); + nlc->dataBufferLen = rbytes; + + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return bytes; + } + } + else + { + if ((peek && nlc->dataBufferLen != 0) || nlhrReply->pData) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return HttpGatewayReadSetResult(nlc, buf, len, peek); + } + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + } + + SetLastError(ERROR_GEN_FAILURE); + return SOCKET_ERROR; +} + +int NetlibInitHttpConnection(struct NetlibConnection *nlc, struct NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + NETLIBHTTPREQUEST *nlhrReply = NULL; + + nlc->nlhpi.firstGetSequence = 1; + nlc->nlhpi.firstPostSequence = 1; + + if (nlu->user.szHttpGatewayHello != NULL) + { + nlc->usingHttpGateway = true; + if (NetlibHttpGatewaySend(nlc, reqHelloGet, NULL, 0)) + nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW); + nlc->usingHttpGateway = false; + if (nlhrReply == NULL) return 0; + + if (nlhrReply->resultCode != 200) + { + NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return 0; + } + } + if (!nlu->user.pfnHttpGatewayInit(nlc, nloc, nlhrReply)) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return 0; + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + + /* + * Gena01 - Ok, we should be able to use just POST. Needed for Yahoo, NO GET requests + */ + if(nlc->nlhpi.szHttpPostUrl == NULL) + { + SetLastError(ERROR_BAD_FORMAT); + return 0; + } + + nlc->usingHttpGateway = true; + + //now properly connected + if (nlu->user.pfnHttpGatewayBegin && !nlu->user.pfnHttpGatewayBegin(nlc, nloc)) + return 0; + + return 1; +} + +INT_PTR NetlibHttpGatewaySetInfo(WPARAM wParam,LPARAM lParam) +{ + NETLIBHTTPPROXYINFO *nlhpi=(NETLIBHTTPPROXYINFO*)lParam; + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + + if (GetNetlibHandleType(nlc) != NLH_CONNECTION || nlhpi == NULL || + nlhpi->cbSize < (sizeof(NETLIBHTTPPROXYINFO) - sizeof(int)) || + nlhpi->szHttpPostUrl == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + mir_free(nlc->nlhpi.szHttpGetUrl); + mir_free(nlc->nlhpi.szHttpPostUrl); + + nlc->nlhpi.combinePackets = 1; + memcpy(&nlc->nlhpi, nlhpi, min(nlhpi->cbSize, sizeof(*nlhpi))); + if (nlc->nlhpi.combinePackets == 0) nlc->nlhpi.combinePackets = 1; + + nlc->nlhpi.szHttpGetUrl = mir_strdup(nlc->nlhpi.szHttpGetUrl); + nlc->nlhpi.szHttpPostUrl = mir_strdup(nlc->nlhpi.szHttpPostUrl); + + return 1; +} + +INT_PTR NetlibHttpSetSticky(WPARAM wParam, LPARAM lParam) +{ + struct NetlibUser * nu = (struct NetlibUser*)wParam; + if (GetNetlibHandleType(nu)!=NLH_USER) return ERROR_INVALID_PARAMETER; + mir_free(nu->szStickyHeaders); + nu->szStickyHeaders = mir_strdup((char*)lParam); // pointer is ours + return 0; +} + +INT_PTR NetlibHttpSetPollingTimeout(WPARAM wParam, LPARAM lParam) +{ + int oldTimeout; + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + if (GetNetlibHandleType(nlc)!=NLH_CONNECTION) return -1; + oldTimeout = nlc->pollingTimeout; + nlc->pollingTimeout = lParam; + return oldTimeout; +} diff --git a/src/modules/netlib/netliblog.cpp b/src/modules/netlib/netliblog.cpp new file mode 100644 index 0000000000..fb3fe1d5f2 --- /dev/null +++ b/src/modules/netlib/netliblog.cpp @@ -0,0 +1,637 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" +#include "../srfile/file.h" + +#define MS_NETLIB_LOGWIN "Netlib/Log/Win" + +extern HANDLE hConnectionHeaderMutex; + +#define TIMEFORMAT_NONE 0 +#define TIMEFORMAT_HHMMSS 1 +#define TIMEFORMAT_MILLISECONDS 2 +#define TIMEFORMAT_MICROSECONDS 3 +struct { + HWND hwndOpts; + int toOutputDebugString; + int toFile; + int toLog; + TCHAR* szFile; + TCHAR* szUserFile; + int timeFormat; + int showUser; + int dumpSent,dumpRecv,dumpProxy,dumpSsl; + int textDumps,autoDetectText; + CRITICAL_SECTION cs; + int save; +} logOptions = {0}; + +typedef struct { + const char* pszHead; + const char* pszMsg; +} LOGMSG; + +static __int64 mirandaStartTime,perfCounterFreq; +static int bIsActive = TRUE; +static HANDLE hLogEvent = NULL; + +static const TCHAR* szTimeFormats[] = +{ + _T( "No times" ), + _T( "Standard hh:mm:ss times" ), + _T( "Times in milliseconds" ), + _T( "Times in microseconds" ) +}; + +static INT_PTR CALLBACK LogOptionsDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch(message) { + case WM_INITDIALOG: + logOptions.hwndOpts=hwndDlg; + TranslateDialogDefault(hwndDlg); + CheckDlgButton(hwndDlg,IDC_DUMPRECV,logOptions.dumpRecv?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_DUMPSENT,logOptions.dumpSent?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_DUMPPROXY,logOptions.dumpProxy?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_DUMPSSL,logOptions.dumpSsl?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_TEXTDUMPS,logOptions.textDumps?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_AUTODETECTTEXT,logOptions.autoDetectText?BST_CHECKED:BST_UNCHECKED); + { int i; + for( i=0; i < SIZEOF(szTimeFormats); i++ ) + SendDlgItemMessage(hwndDlg,IDC_TIMEFORMAT,CB_ADDSTRING,0,(LPARAM)TranslateTS( szTimeFormats[i] )); + } + SendDlgItemMessage(hwndDlg,IDC_TIMEFORMAT,CB_SETCURSEL,logOptions.timeFormat,0); + CheckDlgButton(hwndDlg,IDC_SHOWNAMES,logOptions.showUser?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_TOOUTPUTDEBUGSTRING,logOptions.toOutputDebugString?BST_CHECKED:BST_UNCHECKED); + CheckDlgButton(hwndDlg,IDC_TOFILE,logOptions.toFile?BST_CHECKED:BST_UNCHECKED); + SetDlgItemText(hwndDlg,IDC_FILENAME,logOptions.szUserFile); + SetDlgItemText(hwndDlg,IDC_PATH,logOptions.szFile); + CheckDlgButton(hwndDlg,IDC_SHOWTHISDLGATSTART,DBGetContactSettingByte(NULL, "Netlib", "ShowLogOptsAtStart",0)?BST_CHECKED:BST_UNCHECKED); + { DBVARIANT dbv; + if(!DBGetContactSettingString(NULL, "Netlib", "RunAtStart",&dbv)) { + SetDlgItemTextA(hwndDlg,IDC_RUNATSTART,dbv.pszVal); + DBFreeVariant(&dbv); + } + } + logOptions.save = 0; + { + TVINSERTSTRUCT tvis = {0}; + int i; + HWND hwndFilter = GetDlgItem(hwndDlg,IDC_FILTER); + + SetWindowLongPtr(hwndFilter, GWL_STYLE, GetWindowLongPtr(hwndFilter, GWL_STYLE) | (TVS_NOHSCROLL | TVS_CHECKBOXES)); + + tvis.hParent=NULL; + tvis.hInsertAfter=TVI_SORT; + tvis.item.mask=TVIF_PARAM|TVIF_TEXT|TVIF_STATE; + tvis.item.stateMask=TVIS_STATEIMAGEMASK; + + for (i = 0; i < netlibUser.getCount(); ++i) + { + tvis.item.pszText=netlibUser[i]->user.ptszDescriptiveName; + tvis.item.lParam=i; + tvis.item.state=INDEXTOSTATEIMAGEMASK( (netlibUser[i]->toLog) ? 2 : 1 ); + TreeView_InsertItem(hwndFilter, &tvis); + } + tvis.item.lParam=-1; + tvis.item.pszText=TranslateT("(Miranda Core Logging)"); + tvis.item.state=INDEXTOSTATEIMAGEMASK( (logOptions.toLog) ? 2 : 1 ); + TreeView_InsertItem(hwndFilter, &tvis); + } + return TRUE; + case WM_COMMAND: + switch(LOWORD(wParam)) { +/* + case IDC_DUMPRECV: + case IDC_DUMPSENT: + case IDC_DUMPPROXY: + case IDC_TEXTDUMPS: + case IDC_AUTODETECTTEXT: + case IDC_TIMEFORMAT: + case IDC_SHOWNAMES: + case IDC_TOOUTPUTDEBUGSTRING: + case IDC_TOFILE: + case IDC_SHOWTHISDLGATSTART: + case IDC_RUNATSTART: + break; +*/ + case IDC_FILENAME: + if(HIWORD(wParam)!=EN_CHANGE) break; + if((HWND)lParam==GetFocus()) + CheckDlgButton(hwndDlg,IDC_TOFILE,BST_CHECKED); + + { + TCHAR path[MAX_PATH]; + GetWindowText((HWND)lParam, path, MAX_PATH); + + TCHAR *pszNewPath = Utils_ReplaceVarsT(path); + pathToAbsoluteT(pszNewPath, path, NULL); + SetDlgItemText(hwndDlg, IDC_PATH, path); + mir_free(pszNewPath); + } + break; + case IDC_FILENAMEBROWSE: + case IDC_RUNATSTARTBROWSE: + { TCHAR str[MAX_PATH+2]; + OPENFILENAME ofn={0}; + TCHAR filter[512],*pfilter; + + GetWindowText(GetWindow((HWND)lParam,GW_HWNDPREV),str,SIZEOF(str)); + ofn.lStructSize=OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner=hwndDlg; + ofn.Flags=OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + if (LOWORD(wParam)==IDC_FILENAMEBROWSE) { + ofn.lpstrTitle=TranslateT("Select where log file will be created"); + } else { + ofn.Flags|=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST; + ofn.lpstrTitle=TranslateT("Select program to be run"); + } + _tcscpy(filter,TranslateT("All Files")); + _tcscat(filter,_T(" (*)")); + pfilter=filter+lstrlen(filter)+1; + _tcscpy(pfilter,_T("*")); + pfilter=pfilter+lstrlen(pfilter)+1; + *pfilter='\0'; + ofn.lpstrFilter=filter; + ofn.lpstrFile=str; + ofn.nMaxFile=SIZEOF(str)-2; + ofn.nMaxFileTitle=MAX_PATH; + if (LOWORD(wParam)==IDC_FILENAMEBROWSE) { + if(!GetSaveFileName(&ofn)) return 1; + } else { + if(!GetOpenFileName(&ofn)) return 1; + } + if(LOWORD(wParam)==IDC_RUNATSTARTBROWSE && _tcschr(str,' ')!=NULL) { + MoveMemory(str+1,str,SIZEOF(str)-2); + str[0]='"'; + lstrcat(str,_T("\"")); + } + SetWindowText(GetWindow((HWND)lParam,GW_HWNDPREV),str); + break; + } + case IDC_RUNNOW: + { TCHAR str[MAX_PATH+1]; + STARTUPINFO si={0}; + PROCESS_INFORMATION pi; + GetDlgItemText(hwndDlg,IDC_RUNATSTART,str,MAX_PATH); + si.cb=sizeof(si); + if(str[0]) CreateProcess(NULL,str,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi); + } + break; + case IDC_SAVE: + logOptions.save = 1; + // + case IDOK: + { + TCHAR str[MAX_PATH]; + + GetDlgItemText(hwndDlg, IDC_RUNATSTART, str, MAX_PATH); + DBWriteContactSettingTString(NULL, "Netlib", "RunAtStart",str); + DBWriteContactSettingByte(NULL, "Netlib", "ShowLogOptsAtStart",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_SHOWTHISDLGATSTART)); + + EnterCriticalSection(&logOptions.cs); + + mir_free(logOptions.szUserFile); + GetWindowText(GetDlgItem(hwndDlg,IDC_FILENAME), str, MAX_PATH ); + logOptions.szUserFile = mir_tstrdup(str); + + mir_free(logOptions.szFile); + GetWindowText(GetDlgItem(hwndDlg,IDC_PATH), str, MAX_PATH ); + logOptions.szFile = mir_tstrdup(str); + + logOptions.dumpRecv=IsDlgButtonChecked(hwndDlg,IDC_DUMPRECV); + logOptions.dumpSent=IsDlgButtonChecked(hwndDlg,IDC_DUMPSENT); + logOptions.dumpProxy=IsDlgButtonChecked(hwndDlg,IDC_DUMPPROXY); + logOptions.dumpSsl=IsDlgButtonChecked(hwndDlg,IDC_DUMPSSL); + logOptions.textDumps=IsDlgButtonChecked(hwndDlg,IDC_TEXTDUMPS); + logOptions.autoDetectText=IsDlgButtonChecked(hwndDlg,IDC_AUTODETECTTEXT); + logOptions.timeFormat=SendDlgItemMessage(hwndDlg,IDC_TIMEFORMAT,CB_GETCURSEL,0,0); + logOptions.showUser=IsDlgButtonChecked(hwndDlg,IDC_SHOWNAMES); + logOptions.toOutputDebugString=IsDlgButtonChecked(hwndDlg,IDC_TOOUTPUTDEBUGSTRING); + logOptions.toFile=IsDlgButtonChecked(hwndDlg,IDC_TOFILE); + + LeaveCriticalSection(&logOptions.cs); + } + { + HWND hwndFilter = GetDlgItem(logOptions.hwndOpts, IDC_FILTER); + TVITEM tvi={0}; + BOOL checked; + + tvi.mask=TVIF_HANDLE|TVIF_PARAM|TVIF_STATE|TVIF_TEXT; + tvi.hItem=TreeView_GetRoot(hwndFilter); + + while(tvi.hItem) + { + TreeView_GetItem(hwndFilter,&tvi); + checked = ((tvi.state&TVIS_STATEIMAGEMASK)>>12==2); + + if (tvi.lParam == -1) { + logOptions.toLog = checked; + if ( logOptions.save ) + DBWriteContactSettingDword(NULL, "Netlib", "NLlog",checked); + } + else + if (tvi.lParam < netlibUser.getCount()) { + netlibUser[tvi.lParam]->toLog = checked; + if ( logOptions.save ) + DBWriteContactSettingDword(NULL,netlibUser[tvi.lParam]->user.szSettingsModule,"NLlog",checked); + } + + tvi.hItem=TreeView_GetNextSibling(hwndFilter,tvi.hItem); + } + } + + if ( logOptions.save ) { + DBWriteContactSettingByte(NULL, "Netlib", "DumpRecv",(BYTE)logOptions.dumpRecv); + DBWriteContactSettingByte(NULL, "Netlib", "DumpSent",(BYTE)logOptions.dumpSent); + DBWriteContactSettingByte(NULL, "Netlib", "DumpProxy",(BYTE)logOptions.dumpProxy); + DBWriteContactSettingByte(NULL, "Netlib", "DumpSsl",(BYTE)logOptions.dumpSsl); + DBWriteContactSettingByte(NULL, "Netlib", "TextDumps",(BYTE)logOptions.textDumps); + DBWriteContactSettingByte(NULL, "Netlib", "AutoDetectText",(BYTE)logOptions.autoDetectText); + DBWriteContactSettingByte(NULL, "Netlib", "TimeFormat",(BYTE)logOptions.timeFormat); + DBWriteContactSettingByte(NULL, "Netlib", "ShowUser",(BYTE)logOptions.showUser); + DBWriteContactSettingByte(NULL, "Netlib", "ToOutputDebugString",(BYTE)logOptions.toOutputDebugString); + DBWriteContactSettingByte(NULL, "Netlib", "ToFile",(BYTE)logOptions.toFile); + DBWriteContactSettingTString(NULL, "Netlib", "File", logOptions.szFile ? logOptions.szUserFile: _T("")); + logOptions.save = 0; + } + else + DestroyWindow(hwndDlg); + + break; + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + } + break; + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + case WM_DESTROY: + ImageList_Destroy(TreeView_GetImageList(GetDlgItem(hwndDlg, IDC_FILTER), TVSIL_STATE)); + logOptions.hwndOpts=NULL; + break; + } + return FALSE; +} + +void NetlibLogShowOptions(void) +{ + if(logOptions.hwndOpts==NULL) + logOptions.hwndOpts=CreateDialog(hMirandaInst,MAKEINTRESOURCE(IDD_NETLIBLOGOPTS),NULL,LogOptionsDlgProc); + SetForegroundWindow(logOptions.hwndOpts); +} + +static INT_PTR ShowOptions(WPARAM, LPARAM) +{ + NetlibLogShowOptions(); + return 0; +} + +static INT_PTR NetlibLog(WPARAM wParam, LPARAM lParam) +{ + struct NetlibUser *nlu = (struct NetlibUser*)wParam; + struct NetlibUser nludummy; + const char *pszMsg = (const char*)lParam; + char szTime[32], szHead[128]; + LARGE_INTEGER liTimeNow; + DWORD dwOriginalLastError; + + if (!bIsActive) + return 0; + + if ((nlu != NULL && GetNetlibHandleType(nlu) != NLH_USER) || pszMsg == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + if (nlu == NULL) /* if the Netlib user handle is NULL, just pretend its not */ + { + if (!logOptions.toLog) + return 1; + nlu = &nludummy; + nlu->user.szSettingsModule = "(NULL)"; + } + else if (!nlu->toLog) + return 1; + + dwOriginalLastError = GetLastError(); + switch (logOptions.timeFormat) + { + case TIMEFORMAT_HHMMSS: + GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, + NULL, NULL, szTime, SIZEOF(szTime)); + break; + + case TIMEFORMAT_MILLISECONDS: + QueryPerformanceCounter(&liTimeNow); + liTimeNow.QuadPart -= mirandaStartTime; + mir_snprintf(szTime, SIZEOF(szTime), "%I64u.%03I64u", liTimeNow.QuadPart / perfCounterFreq, + 1000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); + break; + + case TIMEFORMAT_MICROSECONDS: + QueryPerformanceCounter(&liTimeNow); + liTimeNow.QuadPart -= mirandaStartTime; + mir_snprintf(szTime, SIZEOF(szTime), "%I64u.%06I64u", liTimeNow.QuadPart / perfCounterFreq, + 1000000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); + break; + + default: + szTime[0] = '\0'; + break; + } + if(logOptions.timeFormat || logOptions.showUser) + mir_snprintf(szHead, SIZEOF(szHead) - 1, "[%s%s%s] ", szTime, + (logOptions.showUser && logOptions.timeFormat) ? " " : "", + logOptions.showUser ? nlu->user.szSettingsModule : ""); + else + szHead[0]=0; + + if(logOptions.toOutputDebugString) + { + if (szHead[0]) + OutputDebugStringA(szHead); + OutputDebugStringA(pszMsg); + OutputDebugStringA("\n"); + } + + if (logOptions.toFile && logOptions.szFile[0]) + { + EnterCriticalSection(&logOptions.cs); + + FILE *fp; + fp = _tfopen(logOptions.szFile, _T("ab")); + if (!fp) + { + CreatePathToFileT(logOptions.szFile); + fp = _tfopen(logOptions.szFile, _T("at")); + } + if (fp) + { + size_t len = strlen(pszMsg); + fprintf(fp,"%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n"); + fclose(fp); + } + LeaveCriticalSection(&logOptions.cs); + } + + if (((THook*)hLogEvent)->subscriberCount) + { + LOGMSG logMsg = { szHead, pszMsg }; + CallHookSubscribers(hLogEvent, (WPARAM)nlu, (LPARAM)&logMsg); + } + + SetLastError(dwOriginalLastError); + return 1; +} + +static INT_PTR NetlibLogW(WPARAM wParam, LPARAM lParam) +{ + const wchar_t *pszMsg = (const wchar_t*)lParam; + char* szMsg = Utf8EncodeUcs2(pszMsg); + INT_PTR res = NetlibLog(wParam, (LPARAM)szMsg); + mir_free(szMsg); + return res; +} + +void NetlibLogf(NetlibUser* nlu, const char *fmt, ...) +{ + if (nlu == NULL) + { + if (!logOptions.toLog) + return; + } + else if (!nlu->toLog) + return; + + va_list va; + char szText[1024]; + + va_start(va,fmt); + mir_vsnprintf(szText, sizeof(szText), fmt, va); + va_end(va); + + NetlibLog((WPARAM)nlu, (LPARAM)szText); +} + + +void NetlibDumpData(struct NetlibConnection *nlc,PBYTE buf,int len,int sent,int flags) +{ + int isText=1; + char szTitleLine[128]; + char *szBuf; + int titleLineLen; + struct NetlibUser *nlu; + bool useStack = false; + + // This section checks a number of conditions and aborts + // the dump if the data should not be written to the log + + // Check packet flags + if (flags & (MSG_PEEK | MSG_NODUMP)) + return; + + // Check user's log settings + if (!(logOptions.toOutputDebugString || + ((THook*)hLogEvent)->subscriberCount || + (logOptions.toFile && logOptions.szFile[0]))) + return; + if ((sent && !logOptions.dumpSent) || + (!sent && !logOptions.dumpRecv)) + return; + if ((flags & MSG_DUMPPROXY) && !logOptions.dumpProxy) + return; + if ((flags & MSG_DUMPSSL) && !logOptions.dumpSsl) + return; + + WaitForSingleObject(hConnectionHeaderMutex, INFINITE); + nlu = nlc ? nlc->nlu : NULL; + titleLineLen = mir_snprintf(szTitleLine, SIZEOF(szTitleLine), "(%p:%u) Data %s%s\r\n", + nlc, nlc ? nlc->s : 0, sent ? "sent" : "received", flags & MSG_DUMPPROXY ? " (proxy)" : ""); + ReleaseMutex(hConnectionHeaderMutex); + + // check filter settings + if (nlu == NULL) + { + if (!logOptions.toLog) + return; + } + else if (!nlu->toLog) + return; + + if (!logOptions.textDumps) + isText = 0; + else if (!(flags&MSG_DUMPASTEXT)) + { + if (logOptions.autoDetectText) + { + int i; + for(i = 0; i=0x80) + { + isText = 0; + break; + } + } + } + else + isText = 0; + } + + // Text data + if ( isText ) { + int sz = titleLineLen + len + 1; + useStack = sz <= 8192; + szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz)); + CopyMemory( szBuf, szTitleLine, titleLineLen ); + CopyMemory( szBuf + titleLineLen, (const char*)buf, len ); + szBuf[titleLineLen + len] = '\0'; + } + // Binary data + else { + int line, col, colsInLine; + char *pszBuf; + int sz = titleLineLen + ((len+16)>>4) * 78 + 1; + useStack = sz <= 8192; + + szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz)); + CopyMemory(szBuf, szTitleLine, titleLineLen); + pszBuf = szBuf + titleLineLen; + for ( line = 0; ; line += 16 ) { + colsInLine = min(16, len - line); + + if (colsInLine == 16) { + PBYTE p = buf + line; + pszBuf += wsprintfA( + pszBuf, "%08X: %02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X ", + line, p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15] ); + } + else { + pszBuf += wsprintfA(pszBuf, "%08X: ", line); + // Dump data as hex + for (col = 0; col < colsInLine; col++) + pszBuf += wsprintfA(pszBuf, "%02X%c", buf[line + col], ((col&3)==3 && col != 15)?'-':' '); + // Fill out last line with blanks + for ( ; col<16; col++) + { + lstrcpyA(pszBuf, " "); + pszBuf += 3; + } + *pszBuf++ = ' '; + } + + for (col = 0; col < colsInLine; col++) + *pszBuf++ = buf[line+col]<' '?'.':(char)buf[line+col]; + + if (len-line<=16) + break; + + *pszBuf++ = '\r'; // End each line with a break + *pszBuf++ = '\n'; // End each line with a break + } + *pszBuf = '\0'; + } + + NetlibLog((WPARAM)nlu,(LPARAM)szBuf); + if (!useStack) mir_free(szBuf); +} + +void NetlibLogInit(void) +{ + DBVARIANT dbv; + LARGE_INTEGER li; + + QueryPerformanceFrequency( &li ); + perfCounterFreq = li.QuadPart; + QueryPerformanceCounter( &li ); + mirandaStartTime = li.QuadPart; + + CreateServiceFunction( MS_NETLIB_LOGWIN, ShowOptions ); + CreateServiceFunction( MS_NETLIB_LOG, NetlibLog ); + CreateServiceFunction( MS_NETLIB_LOGW, NetlibLogW ); + hLogEvent = CreateHookableEvent( ME_NETLIB_FASTDUMP ); + + InitializeCriticalSection(&logOptions.cs); + logOptions.dumpRecv = DBGetContactSettingByte( NULL, "Netlib", "DumpRecv", 1 ); + logOptions.dumpSent = DBGetContactSettingByte( NULL, "Netlib", "DumpSent", 1 ); + logOptions.dumpProxy = DBGetContactSettingByte( NULL, "Netlib", "DumpProxy", 1 ); + logOptions.dumpSsl = DBGetContactSettingByte( NULL, "Netlib", "DumpSsl", 0 ); + logOptions.textDumps = DBGetContactSettingByte( NULL, "Netlib", "TextDumps", 1 ); + logOptions.autoDetectText = DBGetContactSettingByte( NULL, "Netlib", "AutoDetectText", 1 ); + logOptions.timeFormat = DBGetContactSettingByte( NULL, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS ); + logOptions.showUser = DBGetContactSettingByte( NULL, "Netlib", "ShowUser", 1 ); + logOptions.toOutputDebugString = DBGetContactSettingByte( NULL, "Netlib", "ToOutputDebugString", 0 ); + logOptions.toFile = DBGetContactSettingByte( NULL, "Netlib", "ToFile", 0 ); + logOptions.toLog = DBGetContactSettingDword( NULL, "Netlib", "NLlog", 1 ); + + if (!DBGetContactSettingTString(NULL, "Netlib", "File", &dbv)) + { + logOptions.szUserFile = mir_tstrdup(dbv.ptszVal); + TCHAR *pszNewPath = Utils_ReplaceVarsT(dbv.ptszVal); + + TCHAR path[MAX_PATH]; + pathToAbsoluteT(pszNewPath, path, NULL); + logOptions.szFile = mir_tstrdup(path); + + mir_free(pszNewPath); + DBFreeVariant(&dbv); + } + else + { + logOptions.szUserFile = mir_tstrdup(_T("%miranda_logpath%\\netlog.txt")); + logOptions.szFile = Utils_ReplaceVarsT(logOptions.szUserFile); + } + + if ( logOptions.toFile && logOptions.szFile[0] ) { + FILE *fp; + fp = _tfopen( logOptions.szFile, _T("wt")); + if ( fp ) + fclose(fp); + } + + if ( DBGetContactSettingByte( NULL, "Netlib", "ShowLogOptsAtStart", 0 )) + NetlibLogShowOptions(); + + if ( !DBGetContactSettingTString( NULL, "Netlib", "RunAtStart", &dbv )) { + STARTUPINFO si = { 0 }; + PROCESS_INFORMATION pi; + si.cb = sizeof( si ); + if ( dbv.ptszVal[0] ) + CreateProcess( NULL, dbv.ptszVal, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ); + DBFreeVariant( &dbv ); + } +} + +void NetlibLogShutdown(void) +{ + bIsActive = FALSE; + DestroyHookableEvent( hLogEvent ); hLogEvent = NULL; + if ( IsWindow( logOptions.hwndOpts )) + DestroyWindow( logOptions.hwndOpts ); + DeleteCriticalSection( &logOptions.cs ); + mir_free( logOptions.szFile ); + mir_free( logOptions.szUserFile ); +} diff --git a/src/modules/netlib/netlibopenconn.cpp b/src/modules/netlib/netlibopenconn.cpp new file mode 100644 index 0000000000..b9d4753b9a --- /dev/null +++ b/src/modules/netlib/netlibopenconn.cpp @@ -0,0 +1,944 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +extern CRITICAL_SECTION csNetlibUser; +extern HANDLE hConnectionOpenMutex; +extern DWORD g_LastConnectionTick; +extern int connectionTimeout; +static int iUPnPCleanup = 0; + +#define RECV_DEFAULT_TIMEOUT 60000 + +//returns in network byte order +DWORD DnsLookup(struct NetlibUser *nlu,const char *szHost) +{ + HOSTENT* host; + DWORD ip = inet_addr(szHost); + if (ip != INADDR_NONE) + return ip; + + __try + { + host = gethostbyname(szHost); + if ( host ) + return *(u_long*)host->h_addr_list[0]; + + NetlibLogf(nlu,"%s %d: %s() for host %s failed (%u)",__FILE__,__LINE__,"gethostbyname", szHost, WSAGetLastError()); + } + __except(EXCEPTION_EXECUTE_HANDLER) {} + + return 0; +} + +int WaitUntilReadable(SOCKET s, DWORD dwTimeout, bool check) +{ + fd_set readfd; + TIMEVAL tv; + + if (s == INVALID_SOCKET) return SOCKET_ERROR; + + tv.tv_sec = dwTimeout / 1000; + tv.tv_usec = (dwTimeout % 1000) * 1000; + + FD_ZERO(&readfd); + FD_SET(s, &readfd); + + int result = select(0, &readfd, 0, 0, &tv); + if (result == 0 && !check) SetLastError(ERROR_TIMEOUT); + return result; +} + +int WaitUntilWritable(SOCKET s,DWORD dwTimeout) +{ + fd_set writefd; + TIMEVAL tv; + + tv.tv_sec = dwTimeout / 1000; + tv.tv_usec = (dwTimeout % 1000) * 1000; + + FD_ZERO(&writefd); + FD_SET(s, &writefd); + + switch(select(0, 0, &writefd, 0, &tv)) + { + case 0: + SetLastError(ERROR_TIMEOUT); + case SOCKET_ERROR: + return 0; + } + return 1; +} + +bool RecvUntilTimeout(struct NetlibConnection *nlc, char *buf, int len, int flags, DWORD dwTimeout) +{ + int nReceived = 0; + DWORD dwTimeNow, dwCompleteTime = GetTickCount() + dwTimeout; + + while ((dwTimeNow = GetTickCount()) < dwCompleteTime) + { + if (WaitUntilReadable(nlc->s, dwCompleteTime - dwTimeNow) <= 0) return false; + nReceived = NLRecv(nlc, buf, len, flags); + if (nReceived <= 0) return false; + + buf += nReceived; + len -= nReceived; + if (len <= 0) return true; + } + SetLastError( ERROR_TIMEOUT ); + return false; +} + +static int NetlibInitSocks4Connection(NetlibConnection *nlc, NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + // http://www.socks.nec.com/protocol/socks4.protocol and http://www.socks.nec.com/protocol/socks4a.protocol + if (!nloc->szHost || !nloc->szHost[0]) return 0; + + size_t nHostLen = strlen(nloc->szHost) + 1; + size_t nUserLen = nlu->settings.szProxyAuthUser ? strlen(nlu->settings.szProxyAuthUser) + 1 : 1; + size_t len = 8 + nUserLen; + + char* pInit = (char*)alloca(len + nHostLen); + pInit[0] = 4; // SOCKS4 + pInit[1] = 1; //connect + *(PWORD)&pInit[2] = htons(nloc->wPort); + + if (nUserLen <= 1) pInit[8] = 0; + else memcpy(&pInit[8], nlu->settings.szProxyAuthUser, nUserLen); + + //if cannot resolve host, try resolving through proxy (requires SOCKS4a) + DWORD ip = DnsLookup(nlu, nloc->szHost); + *(PDWORD)&pInit[4] = ip ? ip : 0x01000000; + if (!ip) + { + memcpy(&pInit[len], nloc->szHost, nHostLen); + len += nHostLen; + } + + if (NLSend(nlc, pInit, (int)len, MSG_DUMPPROXY) == SOCKET_ERROR) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + return 0; + } + + char reply[8]; + if (!RecvUntilTimeout(nlc, reply, sizeof(reply), MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) + { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + + switch ((BYTE)reply[1]) + { + case 90: return 1; + case 91: SetLastError(ERROR_ACCESS_DENIED); break; + case 92: SetLastError(ERROR_CONNECTION_UNAVAIL); break; + case 93: SetLastError(ERROR_INVALID_ACCESS); break; + default: SetLastError(ERROR_INVALID_DATA); break; + } + NetlibLogf(nlu,"%s %d: Proxy connection failed (%x %u)",__FILE__,__LINE__, (BYTE)reply[1], GetLastError()); + return 0; +} + +static int NetlibInitSocks5Connection(struct NetlibConnection *nlc,struct NetlibUser *nlu,NETLIBOPENCONNECTION *nloc) +{ //rfc1928 + BYTE buf[258]; + + buf[0]=5; //yep, socks5 + buf[1]=1; //one auth method + buf[2]=nlu->settings.useProxyAuth?2:0; + if(NLSend(nlc,(char*)buf,3,MSG_DUMPPROXY)==SOCKET_ERROR) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + return 0; + } + + //confirmation of auth method + if (!RecvUntilTimeout(nlc,(char*)buf,2,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + if((buf[1]!=0 && buf[1]!=2)) { + SetLastError(ERROR_INVALID_ID_AUTHORITY); + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLRecv",GetLastError()); + return 0; + } + + if(buf[1]==2) { //rfc1929 + int nUserLen,nPassLen; + PBYTE pAuthBuf; + + nUserLen=lstrlenA(nlu->settings.szProxyAuthUser); + nPassLen=lstrlenA(nlu->settings.szProxyAuthPassword); + pAuthBuf=(PBYTE)mir_alloc(3+nUserLen+nPassLen); + pAuthBuf[0]=1; //auth version + pAuthBuf[1]=nUserLen; + memcpy(pAuthBuf+2,nlu->settings.szProxyAuthUser,nUserLen); + pAuthBuf[2+nUserLen]=nPassLen; + memcpy(pAuthBuf+3+nUserLen,nlu->settings.szProxyAuthPassword,nPassLen); + if(NLSend(nlc,(char*)pAuthBuf,3+nUserLen+nPassLen,MSG_DUMPPROXY)==SOCKET_ERROR) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + mir_free(pAuthBuf); + return 0; + } + mir_free(pAuthBuf); + + if (!RecvUntilTimeout(nlc,(char*)buf,2,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + if(buf[1]) { + SetLastError(ERROR_ACCESS_DENIED); + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + } + + { + PBYTE pInit; + int nHostLen; + DWORD hostIP; + + if(nlc->dnsThroughProxy) { + if((hostIP=inet_addr(nloc->szHost))==INADDR_NONE) + nHostLen=lstrlenA(nloc->szHost)+1; + else nHostLen=4; + } + else { + if((hostIP=DnsLookup(nlu,nloc->szHost))==0) + return 0; + nHostLen=4; + } + pInit=(PBYTE)mir_alloc(6+nHostLen); + pInit[0]=5; //SOCKS5 + pInit[1]= nloc->flags & NLOCF_UDP ? 3 : 1; //connect or UDP + pInit[2]=0; //reserved + if(hostIP==INADDR_NONE) { //DNS lookup through proxy + pInit[3]=3; + pInit[4]=nHostLen-1; + memcpy(pInit+5,nloc->szHost,nHostLen-1); + } + else { + pInit[3]=1; + *(PDWORD)(pInit+4)=hostIP; + } + *(PWORD)(pInit+4+nHostLen)=htons(nloc->wPort); + if(NLSend(nlc,(char*)pInit,6+nHostLen,MSG_DUMPPROXY)==SOCKET_ERROR) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"NLSend",GetLastError()); + mir_free(pInit); + return 0; + } + mir_free(pInit); + } + + if (!RecvUntilTimeout(nlc,(char*)buf,5,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + + if ( buf[0]!=5 || buf[1] ) { + const char* err = "Unknown response"; + if ( buf[0] != 5 ) + SetLastError(ERROR_BAD_FORMAT); + else + { + switch(buf[1]) + { + case 1: SetLastError(ERROR_GEN_FAILURE); err = "General failure"; break; + case 2: SetLastError(ERROR_ACCESS_DENIED); err = "Connection not allowed by ruleset"; break; + case 3: SetLastError(WSAENETUNREACH); err = "Network unreachable"; break; + case 4: SetLastError(WSAEHOSTUNREACH); err = "Host unreachable"; break; + case 5: SetLastError(WSAECONNREFUSED); err = "Connection refused by destination host"; break; + case 6: SetLastError(WSAETIMEDOUT); err = "TTL expired"; break; + case 7: SetLastError(ERROR_CALL_NOT_IMPLEMENTED); err = "Command not supported / protocol error"; break; + case 8: SetLastError(ERROR_INVALID_ADDRESS); err = "Address type not supported"; break; + default: SetLastError(ERROR_INVALID_DATA); break; + } + } + NetlibLogf(nlu,"%s %d: Proxy conection failed. %s.",__FILE__,__LINE__, err); + return 0; + } + { + int nRecvSize = 0; + switch( buf[3] ) { + case 1:// ipv4 addr + nRecvSize = 5; + break; + case 3:// dns name addr + nRecvSize = buf[4] + 2; + break; + case 4:// ipv6 addr + nRecvSize = 17; + break; + default: + NetlibLogf(nlu,"%s %d: %s() unknown address type (%u)",__FILE__,__LINE__,"NetlibInitSocks5Connection",(int)buf[3]); + return 0; + } + if (!RecvUntilTimeout(nlc,(char*)buf,nRecvSize,MSG_DUMPPROXY,RECV_DEFAULT_TIMEOUT)) { + NetlibLogf(nlu,"%s %d: %s() failed (%u)",__FILE__,__LINE__,"RecvUntilTimeout",GetLastError()); + return 0; + } + } + + //connected + return 1; +} + +static bool NetlibInitHttpsConnection(struct NetlibConnection *nlc, struct NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ //rfc2817 + NETLIBHTTPREQUEST nlhrSend = {0}, *nlhrReply; + char szUrl[512]; + + nlhrSend.cbSize = sizeof(nlhrSend); + nlhrSend.requestType = REQUEST_CONNECT; + nlhrSend.flags = NLHRF_GENERATEHOST | NLHRF_DUMPPROXY | NLHRF_SMARTAUTHHEADER | NLHRF_HTTP11 | NLHRF_NOPROXY | NLHRF_REDIRECT; + if (nlc->dnsThroughProxy) + { + mir_snprintf(szUrl, SIZEOF(szUrl), "%s:%u", nloc->szHost, nloc->wPort); + } + else + { + DWORD ip = DnsLookup(nlu, nloc->szHost); + if (ip == 0) return false; + mir_snprintf(szUrl, SIZEOF(szUrl), "%s:%u", inet_ntoa(*(PIN_ADDR)&ip), nloc->wPort); + } + nlhrSend.szUrl = szUrl; + + nlc->usingHttpGateway = true; + + if (NetlibHttpSendRequest((WPARAM)nlc, (LPARAM)&nlhrSend) == SOCKET_ERROR) + { + nlc->usingHttpGateway = false; + return 0; + } + nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW, true); + nlc->usingHttpGateway = false; + if (nlhrReply == NULL) return false; + if (nlhrReply->resultCode < 200 || nlhrReply->resultCode >= 300) + { + if (nlhrReply->resultCode == 403 && nlc->dnsThroughProxy) + { + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + nlc->dnsThroughProxy = 0; + return NetlibInitHttpsConnection(nlc, nlu, nloc); + } + + NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); + NetlibLogf(nlu,"%s %d: %s request failed (%u %s)",__FILE__,__LINE__,nlu->settings.proxyType==PROXYTYPE_HTTP?"HTTP":"HTTPS",nlhrReply->resultCode,nlhrReply->szResultDescr); + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + return 0; + } + NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply); + //connected + return true; +} + +static void FreePartiallyInitedConnection(struct NetlibConnection *nlc) +{ + DWORD dwOriginalLastError=GetLastError(); + + if (nlc->s!=INVALID_SOCKET) closesocket(nlc->s); + mir_free(nlc->nlhpi.szHttpPostUrl); + mir_free(nlc->nlhpi.szHttpGetUrl); + mir_free((char*)nlc->nloc.szHost); + mir_free(nlc->szProxyServer); + NetlibDeleteNestedCS(&nlc->ncsSend); + NetlibDeleteNestedCS(&nlc->ncsRecv); + CloseHandle(nlc->hOkToCloseEvent); + DeleteCriticalSection(&nlc->csHttpSequenceNums); + mir_free(nlc); + SetLastError(dwOriginalLastError); +} + +static bool my_connectIPv4(NetlibConnection *nlc, NETLIBOPENCONNECTION * nloc) +{ + int rc = 0, retrycnt = 0; + u_long notblocking = 1; + DWORD lasterr = 0; + static const TIMEVAL tv = { 1, 0 }; + + unsigned int dwTimeout = (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2) ? nloc->timeout : 0; + // if dwTimeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway + if (dwTimeout == 0) dwTimeout = 30; + + // this is for XP SP2 where there is a default connection attempt limit of 10/second + if (connectionTimeout) + { + WaitForSingleObject(hConnectionOpenMutex, 10000); + int waitdiff = GetTickCount() - g_LastConnectionTick; + if (waitdiff < connectionTimeout) SleepEx(connectionTimeout, TRUE); + g_LastConnectionTick = GetTickCount(); + ReleaseMutex(hConnectionOpenMutex); + + // might of died in between the wait + if (Miranda_Terminated()) return false; + } + + SOCKADDR_IN sin = {0}; + sin.sin_family = AF_INET; + + if (nlc->proxyType) + { + if (!nlc->szProxyServer) return false; + + if (nloc) + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, nloc->szHost, nloc->wPort); + else + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort); + + sin.sin_port = htons(nlc->wProxyPort); + sin.sin_addr.s_addr = DnsLookup(nlc->nlu, nlc->szProxyServer); + } + else + { + if (!nloc || !nloc->szHost) return false; + NetlibLogf(nlc->nlu,"(%p) Connecting to server %s:%d....", nlc, nloc->szHost, nloc->wPort); + + sin.sin_port = htons(nloc->wPort); + sin.sin_addr.s_addr = DnsLookup(nlc->nlu, nloc->szHost); + } + +retry: + nlc->s = socket(AF_INET,nloc->flags & NLOCF_UDP ? SOCK_DGRAM : SOCK_STREAM, 0); + if (nlc->s == INVALID_SOCKET) return false; + + // return the socket to non blocking + if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) return false; + + if (nlc->nlu->settings.specifyOutgoingPorts && nlc->nlu->settings.szOutgoingPorts && nlc->nlu->settings.szOutgoingPorts[0]) + { + if (!BindSocketToPort(nlc->nlu->settings.szOutgoingPorts, nlc->s, &nlc->nlu->inportnum)) + NetlibLogf(nlc->nlu,"Netlib connect: Not enough ports for outgoing connections specified"); + } + + // try a connect + if (connect(nlc->s, (LPSOCKADDR)&sin, sizeof(sin)) == 0) + { + goto unblock; + } + + // didn't work, was it cos of nonblocking? + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + rc = SOCKET_ERROR; + goto unblock; + } + + for (;;) + { + fd_set r, w, e; + FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e); + FD_SET(nlc->s, &r); + FD_SET(nlc->s, &w); + FD_SET(nlc->s, &e); + if ((rc = select(0, &r, &w, &e, &tv)) == SOCKET_ERROR) + break; + + if (rc > 0) + { + if (FD_ISSET(nlc->s, &w)) + { + // connection was successful + rc = 0; + } + if (FD_ISSET(nlc->s, &r)) + { + // connection was closed + rc = SOCKET_ERROR; + lasterr = WSAECONNRESET; + } + if (FD_ISSET(nlc->s, &e)) + { + // connection failed. + int len = sizeof(lasterr); + rc = SOCKET_ERROR; + getsockopt(nlc->s, SOL_SOCKET, SO_ERROR, (char*)&lasterr, &len); + if (lasterr == WSAEADDRINUSE && ++retrycnt <= 2) + { + closesocket(nlc->s); + goto retry; + } + } + break; + } + else if (Miranda_Terminated()) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + else if (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2 && + nloc->waitcallback != NULL && nloc->waitcallback(&dwTimeout) == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + if (--dwTimeout == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + } + +unblock: + notblocking = 0; + ioctlsocket(nlc->s, FIONBIO, ¬blocking); + if (lasterr) SetLastError(lasterr); + return rc == 0; +} + +static bool my_connectIPv6(NetlibConnection *nlc, NETLIBOPENCONNECTION * nloc) +{ + int rc = SOCKET_ERROR, retrycnt = 0; + u_long notblocking = 1; + DWORD lasterr = 0; + static const TIMEVAL tv = { 1, 0 }; + + unsigned int dwTimeout = (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2) ? nloc->timeout : 0; + // if dwTimeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway + if (dwTimeout == 0) dwTimeout = 30; + + // this is for XP SP2 where there is a default connection attempt limit of 10/second + if (connectionTimeout) + { + WaitForSingleObject(hConnectionOpenMutex, 10000); + int waitdiff = GetTickCount() - g_LastConnectionTick; + if (waitdiff < connectionTimeout) SleepEx(connectionTimeout, TRUE); + g_LastConnectionTick = GetTickCount(); + ReleaseMutex(hConnectionOpenMutex); + + // might of died in between the wait + if (Miranda_Terminated()) return false; + } + + char szPort[6]; + addrinfo *air = NULL, *ai, hints = {0}; + + hints.ai_family = AF_UNSPEC; + + if (nloc->flags & NLOCF_UDP) + { + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + } + else + { + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + } + + if (nlc->proxyType) + { + if (!nlc->szProxyServer) return false; + + if (nloc) + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, nloc->szHost, nloc->wPort); + else + NetlibLogf(nlc->nlu,"(%p) Connecting to proxy %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort); + + _itoa(nlc->wProxyPort, szPort, 10); + if (MyGetaddrinfo(nlc->szProxyServer, szPort, &hints, &air)) + { + NetlibLogf(nlc->nlu,"%s %d: %s() for host %s failed (%u)",__FILE__,__LINE__,"getaddrinfo", nlc->szProxyServer, WSAGetLastError()); + return false; + } + } + else + { + if (!nloc || !nloc->szHost) return false; + NetlibLogf(nlc->nlu,"(%p) Connecting to server %s:%d....", nlc, nloc->szHost, nloc->wPort); + + _itoa(nlc->nloc.wPort, szPort, 10); + + if (MyGetaddrinfo(nlc->nloc.szHost, szPort, &hints, &air)) + { + NetlibLogf(nlc->nlu,"%s %d: %s() for host %s failed (%u)",__FILE__,__LINE__,"getaddrinfo", nlc->nloc.szHost, WSAGetLastError()); + return false; + } + } + + for (ai = air; ai && !Miranda_Terminated(); ai = ai->ai_next) + { +retry: + nlc->s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (nlc->s == INVALID_SOCKET) return false; + + // return the socket to non blocking + if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) return false; + + if (nlc->nlu->settings.specifyOutgoingPorts && nlc->nlu->settings.szOutgoingPorts && nlc->nlu->settings.szOutgoingPorts[0]) + { + if (!BindSocketToPort(nlc->nlu->settings.szOutgoingPorts, nlc->s, &nlc->nlu->inportnum)) + NetlibLogf(nlc->nlu,"Netlib connect: Not enough ports for outgoing connections specified"); + } + + // try a connect + if (connect(nlc->s, ai->ai_addr, (int)ai->ai_addrlen) == 0) + { + rc = 0; + break; + } + + // didn't work, was it cos of nonblocking? + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + rc = SOCKET_ERROR; + break; + } + + for (;;) // timeout loop + { + fd_set r, w, e; + FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e); + FD_SET(nlc->s, &r); + FD_SET(nlc->s, &w); + FD_SET(nlc->s, &e); + if ((rc = select(0, &r, &w, &e, &tv)) == SOCKET_ERROR) + break; + + if (rc > 0) + { + if (FD_ISSET(nlc->s, &w)) + { + // connection was successful + rc = 0; + lasterr = 0; + } + if (FD_ISSET(nlc->s, &r)) + { + // connection was closed + rc = SOCKET_ERROR; + lasterr = WSAECONNRESET; + } + if (FD_ISSET(nlc->s, &e)) + { + // connection failed. + int len = sizeof(lasterr); + rc = SOCKET_ERROR; + getsockopt(nlc->s, SOL_SOCKET, SO_ERROR, (char*)&lasterr, &len); + if (lasterr == WSAEADDRINUSE && ++retrycnt <= 2) + { + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + goto retry; + } + } + break; + } + else if (Miranda_Terminated()) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + else if (nloc->cbSize == sizeof(NETLIBOPENCONNECTION) && nloc->flags & NLOCF_V2 && + nloc->waitcallback != NULL && nloc->waitcallback(&dwTimeout) == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + if (--dwTimeout == 0) + { + rc = SOCKET_ERROR; + lasterr = ERROR_TIMEOUT; + break; + } + } + + if (rc == 0) break; + + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + } + + MyFreeaddrinfo(air); + + notblocking = 0; + if (nlc->s != INVALID_SOCKET) ioctlsocket(nlc->s, FIONBIO, ¬blocking); + if (rc && lasterr) SetLastError(lasterr); + return rc == 0; +} + +static bool my_connect(NetlibConnection *nlc, NETLIBOPENCONNECTION * nloc) +{ + return MyGetaddrinfo && MyFreeaddrinfo ? my_connectIPv6(nlc, nloc) : my_connectIPv4(nlc, nloc); +} + +static int NetlibHttpFallbackToDirect(struct NetlibConnection *nlc, struct NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + NetlibDoClose(nlc, true); + + NetlibLogf(nlu,"Fallback to direct connection"); + + nlc->proxyAuthNeeded = false; + nlc->proxyType = 0; + mir_free(nlc->szProxyServer); nlc->szProxyServer = NULL; + if (!my_connect(nlc, nloc)) + { + NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); + return false; + } + return true; +} + +bool NetlibDoConnect(NetlibConnection *nlc) +{ + NETLIBOPENCONNECTION *nloc = &nlc->nloc; + NetlibUser *nlu = nlc->nlu; + + mir_free(nlc->szProxyServer); nlc->szProxyServer = NULL; + + bool usingProxy = false, forceHttps = false; + if (nlu->settings.useProxy) + { + if (nlu->settings.proxyType == PROXYTYPE_IE) + { + usingProxy = NetlibGetIeProxyConn(nlc, false); + } + else + { + if (nlu->settings.szProxyServer && nlu->settings.szProxyServer[0]) + { + nlc->szProxyServer = mir_strdup(nlu->settings.szProxyServer); + nlc->wProxyPort = nlu->settings.wProxyPort; + nlc->proxyType = nlu->settings.proxyType; + usingProxy = true; + } + } + } + +retry: + if (usingProxy) + { + if (!my_connect(nlc, nloc)) + { + usingProxy = false; + nlc->proxyType = 0; + } + } + if (!usingProxy) + { + my_connect(nlc, nloc); + } + + if (nlc->s == INVALID_SOCKET) + { + if (usingProxy && (nlc->proxyType == PROXYTYPE_HTTPS || nlc->proxyType == PROXYTYPE_HTTP)) + { + usingProxy = false; + if (!NetlibHttpFallbackToDirect(nlc, nlu, nloc)) + { + NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); + return false; + } + } + else + { + if (nlu->settings.useProxy && !usingProxy && nlu->settings.proxyType == PROXYTYPE_IE && !forceHttps) + { + forceHttps = true; + usingProxy = NetlibGetIeProxyConn(nlc, true); + if (usingProxy) goto retry; + } + NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); + return false; + } + } + + if (usingProxy && !((nloc->flags & (NLOCF_HTTP | NLOCF_SSL)) == NLOCF_HTTP && + (nlc->proxyType == PROXYTYPE_HTTP || nlc->proxyType == PROXYTYPE_HTTPS))) + { + if (!WaitUntilWritable(nlc->s, 30000)) return false; + + switch (nlc->proxyType) + { + case PROXYTYPE_SOCKS4: + if (!NetlibInitSocks4Connection(nlc, nlu, nloc)) return false; + break; + + case PROXYTYPE_SOCKS5: + if (!NetlibInitSocks5Connection(nlc, nlu, nloc)) return false; + break; + + case PROXYTYPE_HTTPS: + nlc->proxyAuthNeeded = true; + if (!NetlibInitHttpsConnection(nlc, nlu, nloc)) + { + usingProxy = false; + if (!NetlibHttpFallbackToDirect(nlc, nlu, nloc)) + return false; + } + break; + + case PROXYTYPE_HTTP: + nlc->proxyAuthNeeded = true; + if (!(nlu->user.flags & NUF_HTTPGATEWAY || nloc->flags & NLOCF_HTTPGATEWAY) || nloc->flags & NLOCF_SSL) + { + //NLOCF_HTTP not specified and no HTTP gateway available: try HTTPS + if (!NetlibInitHttpsConnection(nlc, nlu, nloc)) + { + //can't do HTTPS: try direct + usingProxy = false; + if (!NetlibHttpFallbackToDirect(nlc, nlu, nloc)) + return false; + } + } + else + { + if (!NetlibInitHttpConnection(nlc, nlu, nloc)) return false; + } + break; + + default: + SetLastError(ERROR_INVALID_PARAMETER); + FreePartiallyInitedConnection(nlc); + return false; + } + } + else if (nloc->flags & NLOCF_HTTPGATEWAY) + { + if (!NetlibInitHttpConnection(nlc, nlu, nloc)) return false; + nlc->usingDirectHttpGateway = true; + } + + NetlibLogf(nlu,"(%d) Connected to %s:%d", nlc->s, nloc->szHost, nloc->wPort); + + if (NLOCF_SSL & nloc->flags) + { + return NetlibStartSsl((WPARAM)nlc, 0) != 0; + } + + return true; +} + +bool NetlibReconnect(NetlibConnection *nlc) +{ + char buf[4]; + bool opened = nlc->s != INVALID_SOCKET; + + if (opened) + { + switch (WaitUntilReadable(nlc->s, 0, true)) + { + case SOCKET_ERROR: + opened = false; + break; + + case 0: + opened = true; + break; + + case 1: + opened = recv(nlc->s, buf, 1, MSG_PEEK) > 0; + break; + } + + if (!opened) + NetlibDoClose(nlc, true); + } + + if (!opened) + { + if (Miranda_Terminated()) return false; + if (nlc->usingHttpGateway) + { + nlc->proxyAuthNeeded = true; + return my_connect(nlc, &nlc->nloc); + } + else + return NetlibDoConnect(nlc); + } + return true; +} + +INT_PTR NetlibOpenConnection(WPARAM wParam,LPARAM lParam) +{ + NETLIBOPENCONNECTION *nloc = (NETLIBOPENCONNECTION*)lParam; + struct NetlibUser *nlu = (struct NetlibUser*)wParam; + struct NetlibConnection *nlc; + + NetlibLogf(nlu,"Connection request to %s:%d (Flags %x)....", nloc->szHost, nloc->wPort, nloc->flags); + + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) || nloc == NULL || + (nloc->cbSize != NETLIBOPENCONNECTION_V1_SIZE && nloc->cbSize != sizeof(NETLIBOPENCONNECTION)) || + nloc->szHost == NULL || nloc->wPort == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + nlc = (struct NetlibConnection*)mir_calloc(sizeof(struct NetlibConnection)); + nlc->handleType = NLH_CONNECTION; + nlc->nlu = nlu; + nlc->nloc = *nloc; + nlc->nloc.szHost = mir_strdup(nloc->szHost); + nlc->s = INVALID_SOCKET; + nlc->s2 = INVALID_SOCKET; + nlc->dnsThroughProxy = nlu->settings.dnsThroughProxy != 0; + + InitializeCriticalSection(&nlc->csHttpSequenceNums); + nlc->hOkToCloseEvent = CreateEvent(NULL,TRUE,TRUE,NULL); + nlc->dontCloseNow = 0; + NetlibInitializeNestedCS(&nlc->ncsSend); + NetlibInitializeNestedCS(&nlc->ncsRecv); + + if (!NetlibDoConnect(nlc)) + { + FreePartiallyInitedConnection(nlc); + return 0; + } + + if (iUPnPCleanup == 0) + { + EnterCriticalSection(&csNetlibUser); + if (iUPnPCleanup == 0) + { + iUPnPCleanup = 1; + forkthread(NetlibUPnPCleanup, 0, NULL); + } + LeaveCriticalSection(&csNetlibUser); + } + + return (INT_PTR)nlc; +} + +INT_PTR NetlibStartSsl(WPARAM wParam, LPARAM lParam) +{ + NetlibConnection *nlc = (NetlibConnection*)wParam; + if (nlc == NULL) return 0; + + NETLIBSSL *sp = (NETLIBSSL*)lParam; + const char *szHost = sp ? sp->host : nlc->nloc.szHost; + + NetlibLogf(nlc->nlu, "(%d %s) Starting SSL negotiation", nlc->s, szHost); + nlc->hSsl = si.connect(nlc->s, szHost, nlc->nlu->settings.validateSSL); + + if (nlc->hSsl == NULL) + NetlibLogf(nlc->nlu,"(%d %s) Failure to negotiate SSL connection", nlc->s, szHost); + else + NetlibLogf(nlc->nlu, "(%d %s) SSL negotiation successful", nlc->s, szHost); + + return nlc->hSsl != NULL; +} diff --git a/src/modules/netlib/netlibopts.cpp b/src/modules/netlib/netlibopts.cpp new file mode 100644 index 0000000000..0c562a5cff --- /dev/null +++ b/src/modules/netlib/netlibopts.cpp @@ -0,0 +1,556 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +struct NetlibTempSettings +{ + DWORD flags; + char *szSettingsModule; + NETLIBUSERSETTINGS settings; +}; + +static LIST tempSettings(5); + +static const UINT outgoingConnectionsControls[] = +{ + IDC_STATIC12, + IDC_USEPROXY, + IDC_STATIC21,IDC_PROXYTYPE, + IDC_STATIC22,IDC_PROXYHOST,IDC_STATIC23,IDC_PROXYPORT,IDC_STOFTENPORT, + IDC_PROXYAUTH, + IDC_STATIC31,IDC_PROXYUSER,IDC_STATIC32,IDC_PROXYPASS, + IDC_PROXYDNS, + IDC_SPECIFYPORTSO, + IDC_PORTSRANGEO, + IDC_STATIC54, + IDC_VALIDATESSL}; +static const UINT useProxyControls[]={ + IDC_STATIC21,IDC_PROXYTYPE, + IDC_STATIC22,IDC_PROXYHOST,IDC_STATIC23,IDC_PROXYPORT,IDC_STOFTENPORT, + IDC_PROXYAUTH, + IDC_STATIC31,IDC_PROXYUSER,IDC_STATIC32,IDC_PROXYPASS, + IDC_PROXYDNS}; +static const UINT specifyOPortsControls[]={ + IDC_PORTSRANGEO, + IDC_STATIC54 +}; +static const UINT incomingConnectionsControls[]={ + IDC_STATIC43, + IDC_SPECIFYPORTS, + IDC_PORTSRANGE, + IDC_STATIC52, + IDC_ENABLEUPNP}; +static const UINT specifyPortsControls[]={ + IDC_PORTSRANGE, + IDC_STATIC52}; + +static const TCHAR* szProxyTypes[]={_T(""),_T("SOCKS4"),_T("SOCKS5"),_T("HTTP"),_T("HTTPS"),_T("Internet Explorer")}; +static const WORD oftenProxyPorts[]={1080,1080,1080,8080,8080,8080}; + +#define M_REFRESHALL (WM_USER+100) +#define M_REFRESHENABLING (WM_USER+101) + +static void ShowMultipleControls(HWND hwndDlg,const UINT *controls,int cControls,int state) +{ + int i; + for(i=0;iszIncomingPorts) dest->szIncomingPorts=mir_strdup(dest->szIncomingPorts); + if(dest->szOutgoingPorts) dest->szOutgoingPorts=mir_strdup(dest->szOutgoingPorts); + if(dest->szProxyAuthPassword) dest->szProxyAuthPassword=mir_strdup(dest->szProxyAuthPassword); + if(dest->szProxyAuthUser) dest->szProxyAuthUser=mir_strdup(dest->szProxyAuthUser); + if(dest->szProxyServer) dest->szProxyServer=mir_strdup(dest->szProxyServer); +} + +static void CombineSettingsStrings(char **dest,char **source) +{ + if(*dest!=NULL && (*source==NULL || lstrcmpiA(*dest,*source))) {mir_free(*dest); *dest=NULL;} +} + +static void CombineSettingsStructs(NETLIBUSERSETTINGS *dest,DWORD *destFlags,NETLIBUSERSETTINGS *source,DWORD sourceFlags) +{ + if(sourceFlags&NUF_OUTGOING) { + if(*destFlags&NUF_OUTGOING) { + if(dest->validateSSL!=source->validateSSL) dest->validateSSL=2; + if(dest->useProxy!=source->useProxy) dest->useProxy=2; + if(dest->proxyType!=source->proxyType) dest->proxyType=0; + CombineSettingsStrings(&dest->szProxyServer,&source->szProxyServer); + if(dest->wProxyPort!=source->wProxyPort) dest->wProxyPort=0; + if(dest->useProxyAuth!=source->useProxyAuth) dest->useProxyAuth=2; + CombineSettingsStrings(&dest->szProxyAuthUser,&source->szProxyAuthUser); + CombineSettingsStrings(&dest->szProxyAuthPassword,&source->szProxyAuthPassword); + if(dest->dnsThroughProxy!=source->dnsThroughProxy) dest->dnsThroughProxy=2; + if(dest->specifyOutgoingPorts!=source->specifyOutgoingPorts) dest->specifyOutgoingPorts=2; + CombineSettingsStrings(&dest->szOutgoingPorts,&source->szOutgoingPorts); + } + else { + dest->validateSSL=source->validateSSL; + dest->useProxy=source->useProxy; + dest->proxyType=source->proxyType; + dest->szProxyServer=source->szProxyServer; + if(dest->szProxyServer) dest->szProxyServer=mir_strdup(dest->szProxyServer); + dest->wProxyPort=source->wProxyPort; + dest->useProxyAuth=source->useProxyAuth; + dest->szProxyAuthUser=source->szProxyAuthUser; + if(dest->szProxyAuthUser) dest->szProxyAuthUser=mir_strdup(dest->szProxyAuthUser); + dest->szProxyAuthPassword=source->szProxyAuthPassword; + if(dest->szProxyAuthPassword) dest->szProxyAuthPassword=mir_strdup(dest->szProxyAuthPassword); + dest->dnsThroughProxy=source->dnsThroughProxy; + dest->specifyOutgoingPorts=source->specifyOutgoingPorts; + dest->szOutgoingPorts=source->szOutgoingPorts; + if (dest->szOutgoingPorts) dest->szOutgoingPorts=mir_strdup(dest->szOutgoingPorts); + } + } + if(sourceFlags&NUF_INCOMING) { + if(*destFlags&NUF_INCOMING) { + if(dest->enableUPnP!=source->enableUPnP) dest->enableUPnP=2; + if(dest->specifyIncomingPorts!=source->specifyIncomingPorts) dest->specifyIncomingPorts=2; + CombineSettingsStrings(&dest->szIncomingPorts,&source->szIncomingPorts); + } + else { + dest->enableUPnP=source->enableUPnP; + dest->specifyIncomingPorts=source->specifyIncomingPorts; + dest->szIncomingPorts=source->szIncomingPorts; + if(dest->szIncomingPorts) dest->szIncomingPorts=mir_strdup(dest->szIncomingPorts); + } + } + if((*destFlags&NUF_NOHTTPSOPTION)!=(sourceFlags&NUF_NOHTTPSOPTION)) + *destFlags=(*destFlags|sourceFlags)&~NUF_NOHTTPSOPTION; + else *destFlags|=sourceFlags; +} + +static void ChangeSettingIntByCheckbox(HWND hwndDlg,UINT ctrlId,int iUser,int memberOffset) +{ + int newValue,i; + + newValue=IsDlgButtonChecked(hwndDlg,ctrlId)!=BST_CHECKED; + CheckDlgButton(hwndDlg,ctrlId,newValue?BST_CHECKED:BST_UNCHECKED); + if (iUser == -1) + { + for (i=0; iflags & NUF_NOOPTIONS)) + *(int*)(((PBYTE)&tempSettings[i]->settings) + memberOffset) = newValue; + } + else *(int*)(((PBYTE)&tempSettings[iUser]->settings) + memberOffset)=newValue; + SendMessage(hwndDlg,M_REFRESHENABLING,0,0); +} + +static void ChangeSettingStringByEdit(HWND hwndDlg,UINT ctrlId,int iUser,int memberOffset) +{ + int i,newValueLen; + char *szNewValue,**ppszNew; + + newValueLen=GetWindowTextLength(GetDlgItem(hwndDlg,ctrlId)); + szNewValue=(char*)mir_alloc(newValueLen+1); + GetDlgItemTextA(hwndDlg,ctrlId,szNewValue,newValueLen+1); + if (iUser == -1) + { + for (i=0; iflags & NUF_NOOPTIONS)) + { + ppszNew=(char**)(((PBYTE)&tempSettings[i]->settings)+memberOffset); + if(*ppszNew) mir_free(*ppszNew); + *ppszNew=mir_strdup(szNewValue); + } + mir_free(szNewValue); + } + else { + ppszNew=(char**)(((PBYTE)&tempSettings[iUser]->settings)+memberOffset); + if(*ppszNew) mir_free(*ppszNew); + *ppszNew=szNewValue; + } +} + +static void WriteSettingsStructToDb(const char *szSettingsModule,NETLIBUSERSETTINGS *settings,DWORD flags) +{ + if(flags&NUF_OUTGOING) { + char szEncodedPassword[512]; + DBWriteContactSettingByte(NULL,szSettingsModule,"NLValidateSSL",(BYTE)settings->validateSSL); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLUseProxy",(BYTE)settings->useProxy); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLProxyType",(BYTE)settings->proxyType); + DBWriteContactSettingString(NULL,szSettingsModule,"NLProxyServer",settings->szProxyServer?settings->szProxyServer:""); + DBWriteContactSettingWord(NULL,szSettingsModule,"NLProxyPort",(WORD)settings->wProxyPort); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLUseProxyAuth",(BYTE)settings->useProxyAuth); + DBWriteContactSettingString(NULL,szSettingsModule,"NLProxyAuthUser",settings->szProxyAuthUser?settings->szProxyAuthUser:""); + lstrcpynA(szEncodedPassword,settings->szProxyAuthPassword?settings->szProxyAuthPassword:"",SIZEOF(szEncodedPassword)); + CallService(MS_DB_CRYPT_ENCODESTRING,SIZEOF(szEncodedPassword),(LPARAM)szEncodedPassword); + DBWriteContactSettingString(NULL,szSettingsModule,"NLProxyAuthPassword",szEncodedPassword); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLDnsThroughProxy",(BYTE)settings->dnsThroughProxy); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLSpecifyOutgoingPorts",(BYTE)settings->specifyOutgoingPorts); + DBWriteContactSettingString(NULL,szSettingsModule,"NLOutgoingPorts",settings->szOutgoingPorts?settings->szOutgoingPorts:""); + } + if(flags&NUF_INCOMING) { + DBWriteContactSettingByte(NULL,szSettingsModule,"NLEnableUPnP",(BYTE)settings->enableUPnP); + DBWriteContactSettingByte(NULL,szSettingsModule,"NLSpecifyIncomingPorts",(BYTE)settings->specifyIncomingPorts); + DBWriteContactSettingString(NULL,szSettingsModule,"NLIncomingPorts",settings->szIncomingPorts?settings->szIncomingPorts:""); + } +} + +void NetlibSaveUserSettingsStruct(const char *szSettingsModule,NETLIBUSERSETTINGS *settings) +{ + int i; + NETLIBUSERSETTINGS combinedSettings={0}; + DWORD flags; + + EnterCriticalSection(&csNetlibUser); + + NetlibUser *thisUser, tUser; + tUser.user.szSettingsModule = (char*)szSettingsModule; + thisUser = netlibUser.find(&tUser); + + if (thisUser == NULL) + { + LeaveCriticalSection(&csNetlibUser); + return; + } + + NetlibFreeUserSettingsStruct(&thisUser->settings); + CopySettingsStruct(&thisUser->settings, settings); + WriteSettingsStructToDb(thisUser->user.szSettingsModule, &thisUser->settings, thisUser->user.flags); + combinedSettings.cbSize = sizeof(combinedSettings); + for (i=0, flags=0; i < netlibUser.getCount(); ++i) + { + if (thisUser->user.flags & NUF_NOOPTIONS) continue; + CombineSettingsStructs(&combinedSettings, &flags, &thisUser->settings, thisUser->user.flags); + } + if(combinedSettings.validateSSL==2) combinedSettings.validateSSL=0; + if(combinedSettings.useProxy==2) combinedSettings.useProxy=0; + if(combinedSettings.proxyType==0) combinedSettings.proxyType=PROXYTYPE_SOCKS5; + if(combinedSettings.useProxyAuth==2) combinedSettings.useProxyAuth=0; + if(combinedSettings.dnsThroughProxy==2) combinedSettings.dnsThroughProxy=1; + if(combinedSettings.enableUPnP==2) combinedSettings.enableUPnP=1; + if(combinedSettings.specifyIncomingPorts==2) combinedSettings.specifyIncomingPorts=0; + WriteSettingsStructToDb("Netlib",&combinedSettings,flags); + NetlibFreeUserSettingsStruct(&combinedSettings); + LeaveCriticalSection(&csNetlibUser); +} + +static INT_PTR CALLBACK DlgProcNetlibOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_INITDIALOG: + { int iUser,iItem; + + TranslateDialogDefault(hwndDlg); + iItem=SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_ADDSTRING,0,(LPARAM)TranslateT("")); + SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_SETITEMDATA,iItem,(LPARAM)-1); + SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_SETCURSEL,iItem,0); + + EnterCriticalSection(&csNetlibUser); + for (iUser = 0; iUser < netlibUser.getCount(); ++iUser) + { + NetlibTempSettings *thisSettings = (NetlibTempSettings*)mir_calloc(sizeof(NetlibTempSettings)); + thisSettings->flags = netlibUser[iUser]->user.flags; + thisSettings->szSettingsModule = mir_strdup(netlibUser[iUser]->user.szSettingsModule); + CopySettingsStruct(&thisSettings->settings, &netlibUser[iUser]->settings); + tempSettings.insert(thisSettings); + + if (netlibUser[iUser]->user.flags & NUF_NOOPTIONS) continue; + iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, + (LPARAM)netlibUser[iUser]->user.ptszDescriptiveName); + SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS,CB_SETITEMDATA, iItem, iUser); + } + LeaveCriticalSection(&csNetlibUser); + + SendMessage(hwndDlg,M_REFRESHALL,0,0); + return TRUE; + } + case M_REFRESHALL: + { int iUser=SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETCURSEL,0,0),0); + NETLIBUSERSETTINGS settings = {0}; + DWORD flags; + + if (iUser == -1) + { + int i; + settings.cbSize=sizeof(settings); + for (i = 0, flags = 0; i < tempSettings.getCount(); ++i) + { + if (tempSettings[i]->flags & NUF_NOOPTIONS) continue; + CombineSettingsStructs(&settings, &flags, &tempSettings[i]->settings, tempSettings[i]->flags); + } + } + else + { + NetlibFreeUserSettingsStruct(&settings); + CopySettingsStruct(&settings, &tempSettings[iUser]->settings); + flags = tempSettings[iUser]->flags; + } + ShowMultipleControls(hwndDlg,outgoingConnectionsControls,SIZEOF(outgoingConnectionsControls),flags&NUF_OUTGOING?SW_SHOW:SW_HIDE); + CheckDlgButton(hwndDlg,IDC_USEPROXY,settings.useProxy); + SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_RESETCONTENT,0,0); + if (settings.proxyType == 0) AddProxyTypeItem(hwndDlg,0,settings.proxyType); + AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS4, settings.proxyType); + AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS5, settings.proxyType); + if (flags & (NUF_HTTPCONNS | NUF_HTTPGATEWAY)) AddProxyTypeItem(hwndDlg,PROXYTYPE_HTTP,settings.proxyType); + if (!(flags & NUF_NOHTTPSOPTION)) AddProxyTypeItem(hwndDlg,PROXYTYPE_HTTPS,settings.proxyType); + if (flags & (NUF_HTTPCONNS | NUF_HTTPGATEWAY) || !(flags & NUF_NOHTTPSOPTION)) + AddProxyTypeItem(hwndDlg,PROXYTYPE_IE,settings.proxyType); + SetDlgItemTextA(hwndDlg,IDC_PROXYHOST,settings.szProxyServer?settings.szProxyServer:""); + if (settings.wProxyPort) SetDlgItemInt(hwndDlg,IDC_PROXYPORT,settings.wProxyPort,FALSE); + else SetDlgItemTextA(hwndDlg,IDC_PROXYPORT,""); + CheckDlgButton(hwndDlg,IDC_PROXYAUTH,settings.useProxyAuth); + SetDlgItemTextA(hwndDlg,IDC_PROXYUSER,settings.szProxyAuthUser?settings.szProxyAuthUser:""); + SetDlgItemTextA(hwndDlg,IDC_PROXYPASS,settings.szProxyAuthPassword?settings.szProxyAuthPassword:""); + CheckDlgButton(hwndDlg,IDC_PROXYDNS,settings.dnsThroughProxy); + CheckDlgButton(hwndDlg,IDC_VALIDATESSL,settings.validateSSL); + + ShowMultipleControls(hwndDlg,incomingConnectionsControls,SIZEOF(incomingConnectionsControls),flags&NUF_INCOMING?SW_SHOW:SW_HIDE); + CheckDlgButton(hwndDlg,IDC_SPECIFYPORTS,settings.specifyIncomingPorts); + SetDlgItemTextA(hwndDlg,IDC_PORTSRANGE,settings.szIncomingPorts?settings.szIncomingPorts:""); + + CheckDlgButton(hwndDlg,IDC_SPECIFYPORTSO,settings.specifyOutgoingPorts); + SetDlgItemTextA(hwndDlg,IDC_PORTSRANGEO,settings.szOutgoingPorts?settings.szOutgoingPorts:""); + + CheckDlgButton(hwndDlg,IDC_ENABLEUPNP,settings.enableUPnP); + + NetlibFreeUserSettingsStruct(&settings); + SendMessage(hwndDlg,M_REFRESHENABLING,0,0); + break; + } + case M_REFRESHENABLING: + { int selectedProxyType; + TCHAR str[80]; + + selectedProxyType=SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETCURSEL,0,0),0); + mir_sntprintf(str, SIZEOF(str), TranslateT("(often %d)"),oftenProxyPorts[selectedProxyType]); + SetDlgItemText(hwndDlg,IDC_STOFTENPORT,str); + if (IsDlgButtonChecked(hwndDlg,IDC_USEPROXY) != BST_UNCHECKED) + { + int enableAuth = 0, enableUser = 0, enablePass = 0, enableServer = 1; + EnableMultipleControls(hwndDlg, useProxyControls, SIZEOF(useProxyControls), TRUE); + if (selectedProxyType == 0) + { + int i; + for (i = 0; i < tempSettings.getCount(); ++i) + { + if (!tempSettings[i]->settings.useProxy || + tempSettings[i]->flags & NUF_NOOPTIONS || !(tempSettings[i]->flags & NUF_OUTGOING)) + continue; + + if (tempSettings[i]->settings.proxyType==PROXYTYPE_SOCKS4) enableUser=1; + else + { + enableAuth=1; + if (tempSettings[i]->settings.useProxyAuth) + { + enableUser=enablePass=1; + } + } + } + } + else + { + if (selectedProxyType == PROXYTYPE_SOCKS4) enableUser=1; + else + { + if (selectedProxyType == PROXYTYPE_IE) enableServer=0; + enableAuth=1; + if (IsDlgButtonChecked(hwndDlg,IDC_PROXYAUTH) != BST_UNCHECKED) + enableUser = enablePass = 1; + } + } + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYAUTH), enableAuth); + EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC31), enableUser); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYUSER), enableUser); + EnableWindow(GetDlgItem(hwndDlg,IDC_STATIC32), enablePass); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYPASS), enablePass); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYHOST), enableServer); + EnableWindow(GetDlgItem(hwndDlg,IDC_PROXYPORT), enableServer); + } + else EnableMultipleControls(hwndDlg,useProxyControls,SIZEOF(useProxyControls),FALSE); + EnableMultipleControls(hwndDlg,specifyPortsControls,SIZEOF(specifyPortsControls),IsDlgButtonChecked(hwndDlg,IDC_SPECIFYPORTS)!=BST_UNCHECKED); + EnableMultipleControls(hwndDlg,specifyOPortsControls,SIZEOF(specifyOPortsControls),IsDlgButtonChecked(hwndDlg,IDC_SPECIFYPORTSO)!=BST_UNCHECKED); + break; + } + case WM_COMMAND: + { int iUser=SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_NETLIBUSERS,CB_GETCURSEL,0,0),0); + switch(LOWORD(wParam)) + { + case IDC_NETLIBUSERS: + if(HIWORD(wParam)==CBN_SELCHANGE) SendMessage(hwndDlg,M_REFRESHALL,0,0); + return 0; + + case IDC_LOGOPTIONS: + NetlibLogShowOptions(); + return 0; + + case IDC_PROXYTYPE: + if (HIWORD(wParam) != CBN_SELCHANGE) return 0; + { int newValue,i; + newValue = SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_PROXYTYPE,CB_GETCURSEL,0,0),0); + if (iUser == -1) + { + if (newValue == 0) return 0; + for (i = 0; i < tempSettings.getCount(); ++i) + { + if (tempSettings[i]->flags & NUF_NOOPTIONS) continue; + if (newValue == PROXYTYPE_HTTP && !(tempSettings[i]->flags & (NUF_HTTPCONNS|NUF_HTTPGATEWAY))) + tempSettings[i]->settings.proxyType = PROXYTYPE_HTTPS; + else if (newValue == PROXYTYPE_HTTPS && tempSettings[i]->flags & NUF_NOHTTPSOPTION) + tempSettings[i]->settings.proxyType = PROXYTYPE_HTTP; + else tempSettings[i]->settings.proxyType = newValue; + } + SendMessage(hwndDlg, M_REFRESHALL, 0, 0); + } + else + { + tempSettings[iUser]->settings.proxyType = newValue; + SendMessage(hwndDlg,M_REFRESHENABLING,0,0); + } + } + break; + case IDC_USEPROXY: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,useProxy)); + break; + case IDC_PROXYAUTH: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,useProxyAuth)); + break; + case IDC_PROXYDNS: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,dnsThroughProxy)); + break; + case IDC_SPECIFYPORTS: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,specifyIncomingPorts)); + break; + case IDC_SPECIFYPORTSO: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,specifyOutgoingPorts)); + break; + case IDC_ENABLEUPNP: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,enableUPnP)); + break; + case IDC_VALIDATESSL: + ChangeSettingIntByCheckbox(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,validateSSL)); + break; + case IDC_PROXYHOST: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szProxyServer)); + break; + case IDC_PROXYPORT: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + { int newValue,i; + newValue=GetDlgItemInt(hwndDlg,LOWORD(wParam),NULL,FALSE); + if (iUser == -1) + { + for (i = 0; i < tempSettings.getCount(); ++i) + if (!(tempSettings[i]->flags & NUF_NOOPTIONS)) + tempSettings[i]->settings.wProxyPort = newValue; + } + else tempSettings[iUser]->settings.wProxyPort = newValue; + } + break; + case IDC_PROXYUSER: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szProxyAuthUser)); + break; + case IDC_PROXYPASS: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szProxyAuthPassword)); + break; + case IDC_PORTSRANGE: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szIncomingPorts)); + break; + case IDC_PORTSRANGEO: + if(HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()) return 0; + ChangeSettingStringByEdit(hwndDlg,LOWORD(wParam),iUser,offsetof(NETLIBUSERSETTINGS,szOutgoingPorts)); + break; + } + ShowWindow(GetDlgItem(hwndDlg,IDC_RECONNECTREQD),SW_SHOW); + SendMessage(GetParent(hwndDlg),PSM_CHANGED,0,0); + break; + } + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { int iUser; + for (iUser = 0; iUser < tempSettings.getCount(); iUser++) + NetlibSaveUserSettingsStruct(tempSettings[iUser]->szSettingsModule, + &tempSettings[iUser]->settings); + return TRUE; + } + } + break; + } + break; + case WM_DESTROY: + { int iUser; + for (iUser = 0; iUser < tempSettings.getCount(); ++iUser) + { + mir_free(tempSettings[iUser]->szSettingsModule); + NetlibFreeUserSettingsStruct(&tempSettings[iUser]->settings); + mir_free(tempSettings[iUser]); + } + tempSettings.destroy(); + break; + } + } + return FALSE; +} + +static UINT expertOnlyControls[]={IDC_LOGOPTIONS}; +int NetlibOptInitialise(WPARAM wParam,LPARAM) +{ + int optionsCount = 0; + EnterCriticalSection(&csNetlibUser); + for (int i = 0; i < netlibUser.getCount(); ++i) + if (!(netlibUser[i]->user.flags & NUF_NOOPTIONS)) ++optionsCount; + LeaveCriticalSection(&csNetlibUser); + if (optionsCount == 0) return 0; + + OPTIONSDIALOGPAGE odp = { 0 }; + + odp.cbSize = sizeof(odp); + odp.position = 900000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_NETLIB); + odp.pszTitle = LPGEN("Network"); + odp.pfnDlgProc = DlgProcNetlibOpts; + odp.flags = ODPF_BOLDGROUPS; + odp.expertOnlyControls = expertOnlyControls; + odp.nExpertOnlyControls = SIZEOF( expertOnlyControls ); + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} diff --git a/src/modules/netlib/netlibpktrecver.cpp b/src/modules/netlib/netlibpktrecver.cpp new file mode 100644 index 0000000000..9722324a4f --- /dev/null +++ b/src/modules/netlib/netlibpktrecver.cpp @@ -0,0 +1,85 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +INT_PTR NetlibPacketRecverCreate(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + struct NetlibPacketRecver *nlpr; + + if(GetNetlibHandleType(nlc)!=NLH_CONNECTION || lParam==0) { + SetLastError(ERROR_INVALID_PARAMETER); + return (INT_PTR)(struct NetlibPacketRecver*)NULL; + } + nlpr=(struct NetlibPacketRecver*)mir_calloc(sizeof(struct NetlibPacketRecver)); + if(nlpr==NULL) { + SetLastError(ERROR_OUTOFMEMORY); + return (INT_PTR)(struct NetlibPacketRecver*)NULL; + } + nlpr->handleType=NLH_PACKETRECVER; + nlpr->nlc=nlc; + nlpr->packetRecver.cbSize=sizeof(nlpr->packetRecver); + nlpr->packetRecver.bufferSize=lParam; + nlpr->packetRecver.buffer=(PBYTE)mir_alloc(nlpr->packetRecver.bufferSize); + nlpr->packetRecver.bytesUsed=0; + nlpr->packetRecver.bytesAvailable=0; + return (INT_PTR)nlpr; +} + +INT_PTR NetlibPacketRecverGetMore(WPARAM wParam,LPARAM lParam) +{ + struct NetlibPacketRecver *nlpr=(struct NetlibPacketRecver*)wParam; + NETLIBPACKETRECVER *nlprParam=(NETLIBPACKETRECVER*)lParam; + INT_PTR recvResult; + + if(GetNetlibHandleType(nlpr)!=NLH_PACKETRECVER || nlprParam==NULL || nlprParam->cbSize!=sizeof(NETLIBPACKETRECVER) || nlprParam->bytesUsed>nlpr->packetRecver.bytesAvailable) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + if (Miranda_Terminated()) { /* HACK: Lame, break while loops of protocols that can't kill their while loops, (cough, ICQ, cough) */ + SetLastError(ERROR_TIMEOUT); + return SOCKET_ERROR; + } + nlpr->packetRecver.dwTimeout=nlprParam->dwTimeout; + if(nlprParam->bytesUsed==0) { + if(nlpr->packetRecver.bytesAvailable==nlpr->packetRecver.bufferSize) { + nlpr->packetRecver.bytesAvailable=0; + NetlibLogf(nlpr->nlc->nlu,"Packet recver: packet overflowed buffer, ditching"); + } + } + else { + MoveMemory(nlpr->packetRecver.buffer,nlpr->packetRecver.buffer+nlprParam->bytesUsed,nlpr->packetRecver.bytesAvailable-nlprParam->bytesUsed); + nlpr->packetRecver.bytesAvailable-=nlprParam->bytesUsed; + } + if(nlprParam->dwTimeout!=INFINITE) { + if(!si.pending(nlpr->nlc->hSsl) && WaitUntilReadable(nlpr->nlc->s,nlprParam->dwTimeout) <= 0) { + *nlprParam=nlpr->packetRecver; + return SOCKET_ERROR; + } + } + recvResult=NLRecv(nlpr->nlc, (char*)nlpr->packetRecver.buffer+nlpr->packetRecver.bytesAvailable,nlpr->packetRecver.bufferSize-nlpr->packetRecver.bytesAvailable,0); + if(recvResult>0) nlpr->packetRecver.bytesAvailable+=recvResult; + *nlprParam=nlpr->packetRecver; + return recvResult; +} diff --git a/src/modules/netlib/netlibsecurity.cpp b/src/modules/netlib/netlibsecurity.cpp new file mode 100644 index 0000000000..c20dcf10a1 --- /dev/null +++ b/src/modules/netlib/netlibsecurity.cpp @@ -0,0 +1,570 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +#define SECURITY_WIN32 +#include +#include + +static HMODULE g_hSecurity = NULL; +static PSecurityFunctionTable g_pSSPI = NULL; + +typedef struct +{ + CtxtHandle hClientContext; + CredHandle hClientCredential; + TCHAR* szProvider; + TCHAR* szPrincipal; + unsigned cbMaxToken; + bool hasDomain; +} + NtlmHandleType; + +typedef struct +{ + WORD len; + WORD allocedSpace; + DWORD offset; +} + NTLM_String; + +typedef struct +{ + char sign[8]; + DWORD type; // == 2 + NTLM_String targetName; + DWORD flags; + BYTE challenge[8]; + BYTE context[8]; + NTLM_String targetInfo; +} + NtlmType2packet; + +static unsigned secCnt = 0, ntlmCnt = 0; +static HANDLE hSecMutex; + +static void ReportSecError(SECURITY_STATUS scRet, int line) +{ + char szMsgBuf[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, scRet, LANG_USER_DEFAULT, szMsgBuf, SIZEOF(szMsgBuf), NULL); + + char *p = strchr(szMsgBuf, 13); if (p) *p = 0; + + NetlibLogf(NULL, "Security error 0x%x on line %u (%s)", scRet, line, szMsgBuf); +} + +static void LoadSecurityLibrary(void) +{ + INIT_SECURITY_INTERFACE pInitSecurityInterface; + + g_hSecurity = LoadLibraryA("secur32.dll"); + if (g_hSecurity == NULL) + g_hSecurity = LoadLibraryA("security.dll"); + + if (g_hSecurity == NULL) + return; + + pInitSecurityInterface = (INIT_SECURITY_INTERFACE)GetProcAddress(g_hSecurity, SECURITY_ENTRYPOINT_ANSI); + if (pInitSecurityInterface != NULL) + { + g_pSSPI = pInitSecurityInterface(); + } + + if (g_pSSPI == NULL) + { + FreeLibrary(g_hSecurity); + g_hSecurity = NULL; + } +} + +static void FreeSecurityLibrary(void) +{ + FreeLibrary(g_hSecurity); + g_hSecurity = NULL; + g_pSSPI = NULL; +} + +HANDLE NetlibInitSecurityProvider(const TCHAR* szProvider, const TCHAR* szPrincipal) +{ + HANDLE hSecurity = NULL; + + if (_tcsicmp(szProvider, _T("Basic")) == 0) + { + NtlmHandleType* hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType)); + hNtlm->szProvider = mir_tstrdup(szProvider); + SecInvalidateHandle(&hNtlm->hClientContext); + SecInvalidateHandle(&hNtlm->hClientCredential); + ntlmCnt++; + + return hNtlm; + } + + WaitForSingleObject(hSecMutex, INFINITE); + + if (secCnt == 0 ) + { + LoadSecurityLibrary(); + secCnt += g_hSecurity != NULL; + } + else secCnt++; + + if (g_pSSPI != NULL) + { + PSecPkgInfo ntlmSecurityPackageInfo; + bool isGSSAPI = _tcsicmp(szProvider, _T("GSSAPI")) == 0; + const TCHAR *szProviderC = isGSSAPI ? _T("Kerberos") : szProvider; + SECURITY_STATUS sc = g_pSSPI->QuerySecurityPackageInfo((LPTSTR)szProviderC, &ntlmSecurityPackageInfo); + if (sc == SEC_E_OK) + { + NtlmHandleType* hNtlm; + + hSecurity = hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType)); + hNtlm->cbMaxToken = ntlmSecurityPackageInfo->cbMaxToken; + g_pSSPI->FreeContextBuffer(ntlmSecurityPackageInfo); + + hNtlm->szProvider = mir_tstrdup(szProvider); + hNtlm->szPrincipal = mir_tstrdup(szPrincipal ? szPrincipal : _T("")); + SecInvalidateHandle(&hNtlm->hClientContext); + SecInvalidateHandle(&hNtlm->hClientCredential); + ntlmCnt++; + } + } + + ReleaseMutex(hSecMutex); + return hSecurity; +} + +#ifdef UNICODE +HANDLE NetlibInitSecurityProvider(const char* szProvider, const char* szPrincipal) +{ + return NetlibInitSecurityProvider(StrConvT(szProvider), StrConvT(szPrincipal)); +} +#endif + +void NetlibDestroySecurityProvider(HANDLE hSecurity) +{ + if (hSecurity == NULL) return; + + WaitForSingleObject(hSecMutex, INFINITE); + + if (ntlmCnt != 0) + { + NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; + if (SecIsValidHandle(&hNtlm->hClientContext)) g_pSSPI->DeleteSecurityContext(&hNtlm->hClientContext); + if (SecIsValidHandle(&hNtlm->hClientCredential)) g_pSSPI->FreeCredentialsHandle(&hNtlm->hClientCredential); + mir_free(hNtlm->szProvider); + mir_free(hNtlm->szPrincipal); + + --ntlmCnt; + + mir_free(hNtlm); + } + + if (secCnt && --secCnt == 0) + FreeSecurityLibrary(); + + ReleaseMutex(hSecMutex); +} + +char* CompleteGssapi(HANDLE hSecurity, unsigned char *szChallenge, unsigned chlsz) +{ + if (!szChallenge || !szChallenge[0]) return NULL; + + NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; + unsigned char inDataBuffer[1024]; + + SecBuffer inBuffers[2] = + { + { sizeof(inDataBuffer), SECBUFFER_DATA, inDataBuffer }, + { chlsz, SECBUFFER_STREAM, szChallenge }, + }; + + SecBufferDesc inBuffersDesc = { SECBUFFER_VERSION, 2, inBuffers }; + + unsigned long qop = 0; + SECURITY_STATUS sc = g_pSSPI->DecryptMessage(&hNtlm->hClientContext, &inBuffersDesc, 0, &qop); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + unsigned char LayerMask = inDataBuffer[0]; + unsigned int MaxMessageSize = htonl(*(unsigned*)&inDataBuffer[1]); + + SecPkgContext_Sizes sizes; + sc = g_pSSPI->QueryContextAttributes(&hNtlm->hClientContext, SECPKG_ATTR_SIZES, &sizes); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + unsigned char *tokenBuffer = (unsigned char*)alloca(sizes.cbSecurityTrailer); + unsigned char *paddingBuffer = (unsigned char*)alloca(sizes.cbBlockSize); + + unsigned char outDataBuffer[4] = { 1, 0, 16, 0 }; + + SecBuffer outBuffers[3] = + { + { sizes.cbSecurityTrailer, SECBUFFER_TOKEN, tokenBuffer }, + { sizeof(outDataBuffer), SECBUFFER_DATA, outDataBuffer }, + { sizes.cbBlockSize, SECBUFFER_PADDING, paddingBuffer } + }; + SecBufferDesc outBuffersDesc = { SECBUFFER_VERSION, 3, outBuffers }; + + sc = g_pSSPI->EncryptMessage(&hNtlm->hClientContext, SECQOP_WRAP_NO_ENCRYPT, &outBuffersDesc, 0); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + unsigned i, ressz = 0; + for (i = 0; i < outBuffersDesc.cBuffers; i++) + ressz += outBuffersDesc.pBuffers[i].cbBuffer; + + + unsigned char *response = (unsigned char*)alloca(ressz), *p = response; + for (i = 0; i < outBuffersDesc.cBuffers; i++) + { + memcpy(p, outBuffersDesc.pBuffers[i].pvBuffer, outBuffersDesc.pBuffers[i].cbBuffer); + p += outBuffersDesc.pBuffers[i].cbBuffer; + } + + NETLIBBASE64 nlb64; + nlb64.cbDecoded = ressz; + nlb64.pbDecoded = response; + nlb64.cchEncoded = Netlib_GetBase64EncodedBufferSize(nlb64.cbDecoded); + nlb64.pszEncoded = (char*)alloca(nlb64.cchEncoded); + if (!NetlibBase64Encode(0,(LPARAM)&nlb64)) return NULL; + + return mir_strdup(nlb64.pszEncoded); +} + +char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const TCHAR* login, const TCHAR* psw, + bool http, unsigned& complete) +{ + SECURITY_STATUS sc; + SecBufferDesc outputBufferDescriptor,inputBufferDescriptor; + SecBuffer outputSecurityToken,inputSecurityToken; + TimeStamp tokenExpiration; + ULONG contextAttributes; + NETLIBBASE64 nlb64 = { 0 }; + + NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; + + if (hSecurity == NULL || ntlmCnt == 0) return NULL; + + if (_tcsicmp(hNtlm->szProvider, _T("Basic"))) + { + bool isGSSAPI = _tcsicmp(hNtlm->szProvider, _T("GSSAPI")) == 0; + TCHAR *szProvider = isGSSAPI ? _T("Kerberos") : hNtlm->szProvider; + bool hasChallenge = szChallenge != NULL && szChallenge[0] != '\0'; + if (hasChallenge) + { + nlb64.cchEncoded = lstrlenA(szChallenge); + nlb64.pszEncoded = (char*)szChallenge; + nlb64.cbDecoded = Netlib_GetBase64DecodedBufferSize(nlb64.cchEncoded); + nlb64.pbDecoded = (PBYTE)alloca(nlb64.cbDecoded); + if (!NetlibBase64Decode(0, (LPARAM)&nlb64)) return NULL; + + if (isGSSAPI && complete) + return CompleteGssapi(hSecurity, nlb64.pbDecoded, nlb64.cbDecoded); + + inputBufferDescriptor.cBuffers = 1; + inputBufferDescriptor.pBuffers = &inputSecurityToken; + inputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + inputSecurityToken.BufferType = SECBUFFER_TOKEN; + inputSecurityToken.cbBuffer = nlb64.cbDecoded; + inputSecurityToken.pvBuffer = nlb64.pbDecoded; + + // try to decode the domain name from the NTLM challenge + if (login != NULL && login[0] != '\0' && !hNtlm->hasDomain) + { + NtlmType2packet* pkt = ( NtlmType2packet* )nlb64.pbDecoded; + if (!strncmp(pkt->sign, "NTLMSSP", 8) && pkt->type == 2) + { +#ifdef UNICODE + wchar_t* domainName = (wchar_t*)&nlb64.pbDecoded[pkt->targetName.offset]; + int domainLen = pkt->targetName.len; + + // Negotiate ANSI? if yes, convert the ANSI name to unicode + if ((pkt->flags & 1) == 0) + { + int bufsz = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, NULL, 0); + wchar_t* buf = (wchar_t*)alloca(bufsz * sizeof(wchar_t)); + domainLen = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, buf, bufsz) - 1; + domainName = buf; + } + else + domainLen /= sizeof(wchar_t); +#else + char* domainName = (char*)&nlb64.pbDecoded[pkt->targetName.offset]; + int domainLen = pkt->targetName.len; + + // Negotiate Unicode? if yes, convert the unicode name to ANSI + if (pkt->flags & 1) + { + int bufsz = WideCharToMultiByte(CP_ACP, 0, (WCHAR*)domainName, domainLen, NULL, 0, NULL, NULL); + char* buf = (char*)alloca(bufsz); + domainLen = WideCharToMultiByte(CP_ACP, 0, (WCHAR*)domainName, domainLen, buf, bufsz, NULL, NULL) - 1; + domainName = buf; + } +#endif + + if (domainLen) + { + size_t newLoginLen = _tcslen(login) + domainLen + 1; + TCHAR *newLogin = (TCHAR*)alloca(newLoginLen * sizeof(TCHAR)); + + _tcsncpy(newLogin, domainName, domainLen); + newLogin[domainLen] = '\\'; + _tcscpy(newLogin + domainLen + 1, login); + + char* szChl = NtlmCreateResponseFromChallenge(hSecurity, NULL, newLogin, psw, http, complete); + mir_free(szChl); + } + } + } + } + else + { + if (SecIsValidHandle(&hNtlm->hClientContext)) g_pSSPI->DeleteSecurityContext(&hNtlm->hClientContext); + if (SecIsValidHandle(&hNtlm->hClientCredential)) g_pSSPI->FreeCredentialsHandle(&hNtlm->hClientCredential); + + SEC_WINNT_AUTH_IDENTITY auth; + + if (login != NULL && login[0] != '\0') + { + memset(&auth, 0, sizeof(auth)); +#ifdef _UNICODE + NetlibLogf(NULL, "Security login requested, user: %S pssw: %s", login, psw ? "(exist)" : "(no psw)"); +#else + NetlibLogf(NULL, "Security login requested, user: %s pssw: %s", login, psw ? "(exist)" : "(no psw)"); +#endif + + const TCHAR* loginName = login; + const TCHAR* domainName = _tcschr(login, '\\'); + int domainLen = 0; + int loginLen = lstrlen(loginName); + if (domainName != NULL) + { + loginName = domainName + 1; + loginLen = lstrlen(loginName); + domainLen = domainName - login; + domainName = login; + } + else if ((domainName = _tcschr(login, '@')) != NULL) + { + loginName = login; + loginLen = domainName - login; + domainLen = lstrlen(++domainName); + } + +#ifdef UNICODE + auth.User = (PWORD)loginName; + auth.UserLength = loginLen; + auth.Password = (PWORD)psw; + auth.PasswordLength = lstrlen(psw); + auth.Domain = (PWORD)domainName; + auth.DomainLength = domainLen; + auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; +#else + auth.User = (PBYTE)loginName; + auth.UserLength = loginLen; + auth.Password = (PBYTE)psw; + auth.PasswordLength = lstrlen(psw); + auth.Domain = (PBYTE)domainName; + auth.DomainLength = domainLen; + auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; +#endif + + hNtlm->hasDomain = domainLen != 0; + } + + sc = g_pSSPI->AcquireCredentialsHandle(NULL, szProvider, + SECPKG_CRED_OUTBOUND, NULL, hNtlm->hasDomain ? &auth : NULL, NULL, NULL, + &hNtlm->hClientCredential, &tokenExpiration); + if (sc != SEC_E_OK) + { + ReportSecError(sc, __LINE__); + return NULL; + } + } + + outputBufferDescriptor.cBuffers = 1; + outputBufferDescriptor.pBuffers = &outputSecurityToken; + outputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + outputSecurityToken.BufferType = SECBUFFER_TOKEN; + outputSecurityToken.cbBuffer = hNtlm->cbMaxToken; + outputSecurityToken.pvBuffer = alloca(outputSecurityToken.cbBuffer); + + sc = g_pSSPI->InitializeSecurityContext(&hNtlm->hClientCredential, + hasChallenge ? &hNtlm->hClientContext : NULL, + hNtlm->szPrincipal, isGSSAPI ? ISC_REQ_MUTUAL_AUTH | ISC_REQ_STREAM : 0, 0, SECURITY_NATIVE_DREP, + hasChallenge ? &inputBufferDescriptor : NULL, 0, &hNtlm->hClientContext, + &outputBufferDescriptor, &contextAttributes, &tokenExpiration); + + complete = (sc != SEC_I_COMPLETE_AND_CONTINUE && sc != SEC_I_CONTINUE_NEEDED); + + if (sc == SEC_I_COMPLETE_NEEDED || sc == SEC_I_COMPLETE_AND_CONTINUE) + { + sc = g_pSSPI->CompleteAuthToken(&hNtlm->hClientContext, &outputBufferDescriptor); + } + + if (sc != SEC_E_OK && sc != SEC_I_CONTINUE_NEEDED) + { + ReportSecError(sc, __LINE__); + return NULL; + } + + nlb64.cbDecoded = outputSecurityToken.cbBuffer; + nlb64.pbDecoded = (PBYTE)outputSecurityToken.pvBuffer; + } + else + { + if (!login || !psw) return NULL; + + char *szLogin = mir_t2a(login); + char *szPassw = mir_t2a(psw); + + size_t authLen = strlen(szLogin) + strlen(szPassw) + 5; + char *szAuth = (char*)alloca(authLen); + + nlb64.cbDecoded = mir_snprintf(szAuth, authLen,"%s:%s", szLogin, szPassw); + nlb64.pbDecoded=(PBYTE)szAuth; + complete = true; + + mir_free(szPassw); + mir_free(szLogin); + } + + nlb64.cchEncoded = Netlib_GetBase64EncodedBufferSize(nlb64.cbDecoded); + nlb64.pszEncoded = (char*)alloca(nlb64.cchEncoded); + if (!NetlibBase64Encode(0,(LPARAM)&nlb64)) return NULL; + + char* result; + if (http) + { + char* szProvider = mir_t2a(hNtlm->szProvider); + nlb64.cchEncoded += (int)strlen(szProvider) + 10; + result = (char*)mir_alloc(nlb64.cchEncoded); + mir_snprintf(result, nlb64.cchEncoded, "%s %s", szProvider, nlb64.pszEncoded); + mir_free(szProvider); + } + else + result = mir_strdup(nlb64.pszEncoded); + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// + +static INT_PTR InitSecurityProviderService(WPARAM, LPARAM lParam) +{ + HANDLE hSecurity = NetlibInitSecurityProvider((char*)lParam, NULL); + return (INT_PTR)hSecurity; +} + +static INT_PTR InitSecurityProviderService2(WPARAM, LPARAM lParam) +{ + NETLIBNTLMINIT2 *req = ( NETLIBNTLMINIT2* )lParam; + if (req->cbSize < sizeof(*req)) return 0; + + HANDLE hSecurity; + +#ifdef UNICODE + if (req->flags & NNR_UNICODE) + hSecurity = NetlibInitSecurityProvider(req->szProviderName, req->szPrincipal); + else +#endif + hSecurity = NetlibInitSecurityProvider((char*)req->szProviderName, (char*)req->szPrincipal); + + return (INT_PTR)hSecurity; +} + +static INT_PTR DestroySecurityProviderService( WPARAM, LPARAM lParam ) +{ + NetlibDestroySecurityProvider(( HANDLE )lParam ); + return 0; +} + +static INT_PTR NtlmCreateResponseService( WPARAM wParam, LPARAM lParam ) +{ + NETLIBNTLMREQUEST* req = ( NETLIBNTLMREQUEST* )lParam; + unsigned complete; + + char* response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + StrConvT(req->userName), StrConvT(req->password), false, complete ); + + return (INT_PTR)response; +} + +static INT_PTR NtlmCreateResponseService2( WPARAM wParam, LPARAM lParam ) +{ + NETLIBNTLMREQUEST2* req = ( NETLIBNTLMREQUEST2* )lParam; + if (req->cbSize < sizeof(*req)) return 0; + + char* response; + +#ifdef UNICODE + if (req->flags & NNR_UNICODE) + { + response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + req->szUserName, req->szPassword, false, req->complete ); + } + else + { + TCHAR *szLogin = mir_a2t((char*)req->szUserName); + TCHAR *szPassw = mir_a2t((char*)req->szPassword); + response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + szLogin, szPassw, false, req->complete ); + mir_free(szLogin); + mir_free(szPassw); + } +#else + response = NtlmCreateResponseFromChallenge(( HANDLE )wParam, req->szChallenge, + req->szUserName, req->szPassword, false, req->complete ); +#endif + + return (INT_PTR)response; +} + +void NetlibSecurityInit(void) +{ + hSecMutex = CreateMutex(NULL, FALSE, NULL); + + CreateServiceFunction( MS_NETLIB_INITSECURITYPROVIDER, InitSecurityProviderService ); + CreateServiceFunction( MS_NETLIB_INITSECURITYPROVIDER2, InitSecurityProviderService2 ); + CreateServiceFunction( MS_NETLIB_DESTROYSECURITYPROVIDER, DestroySecurityProviderService ); + CreateServiceFunction( MS_NETLIB_NTLMCREATERESPONSE, NtlmCreateResponseService ); + CreateServiceFunction( MS_NETLIB_NTLMCREATERESPONSE2, NtlmCreateResponseService2 ); +} + +void NetlibSecurityDestroy(void) +{ + CloseHandle(hSecMutex); +} \ No newline at end of file diff --git a/src/modules/netlib/netlibsock.cpp b/src/modules/netlib/netlibsock.cpp new file mode 100644 index 0000000000..875c47b0a1 --- /dev/null +++ b/src/modules/netlib/netlibsock.cpp @@ -0,0 +1,203 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +extern HANDLE hConnectionHeaderMutex,hSendEvent,hRecvEvent; + +INT_PTR NetlibSend(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc=(struct NetlibConnection*)wParam; + NETLIBBUFFER *nlb=(NETLIBBUFFER*)lParam; + INT_PTR result; + + if ( nlb == NULL ) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + if ( !NetlibEnterNestedCS( nlc, NLNCS_SEND )) + return SOCKET_ERROR; + + if ( nlc->usingHttpGateway && !( nlb->flags & MSG_RAW )) { + if ( !( nlb->flags & MSG_NOHTTPGATEWAYWRAP ) && nlc->nlu->user.pfnHttpGatewayWrapSend ) { + NetlibDumpData( nlc, ( PBYTE )nlb->buf, nlb->len, 1, nlb->flags ); + result = nlc->nlu->user.pfnHttpGatewayWrapSend(( HANDLE )nlc, ( PBYTE )nlb->buf, nlb->len, nlb->flags | MSG_NOHTTPGATEWAYWRAP, NetlibSend ); + } + else result = NetlibHttpGatewayPost( nlc, nlb->buf, nlb->len, nlb->flags ); + } + else { + NetlibDumpData( nlc, ( PBYTE )nlb->buf, nlb->len, 1, nlb->flags ); + if (nlc->hSsl) + result = si.write( nlc->hSsl, nlb->buf, nlb->len ); + else + result = send( nlc->s, nlb->buf, nlb->len, nlb->flags & 0xFFFF ); + } + NetlibLeaveNestedCS( &nlc->ncsSend ); + + if ((( THook* )hSendEvent)->subscriberCount ) { + NETLIBNOTIFY nln = { nlb, result }; + CallHookSubscribers( hSendEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user ); + } + return result; +} + +INT_PTR NetlibRecv(WPARAM wParam,LPARAM lParam) +{ + struct NetlibConnection *nlc = (struct NetlibConnection*)wParam; + NETLIBBUFFER* nlb = ( NETLIBBUFFER* )lParam; + int recvResult; + + if ( nlb == NULL ) { + SetLastError( ERROR_INVALID_PARAMETER ); + return SOCKET_ERROR; + } + + if ( !NetlibEnterNestedCS( nlc, NLNCS_RECV )) + return SOCKET_ERROR; + + if ( nlc->usingHttpGateway && !( nlb->flags & MSG_RAW )) + recvResult = NetlibHttpGatewayRecv( nlc, nlb->buf, nlb->len, nlb->flags ); + else + { + if (nlc->hSsl) + recvResult = si.read( nlc->hSsl, nlb->buf, nlb->len, (nlb->flags & MSG_PEEK) != 0 ); + else + recvResult = recv( nlc->s, nlb->buf, nlb->len, nlb->flags & 0xFFFF ); + } + NetlibLeaveNestedCS( &nlc->ncsRecv ); + if (recvResult <= 0) + return recvResult; + + NetlibDumpData(nlc, (PBYTE)nlb->buf, recvResult, 0, nlb->flags); + + if ((nlb->flags & MSG_PEEK) == 0 && ((THook*)hRecvEvent)->subscriberCount) + { + NETLIBNOTIFY nln = { nlb, recvResult }; + CallHookSubscribers(hRecvEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user); + } + return recvResult; +} + +static int ConnectionListToSocketList(HANDLE *hConns, fd_set *fd, int& pending) +{ + struct NetlibConnection *nlcCheck; + int i; + + FD_ZERO(fd); + for(i=0;hConns[i] && hConns[i]!=INVALID_HANDLE_VALUE && ihandleType!=NLH_CONNECTION && nlcCheck->handleType!=NLH_BOUNDPORT) { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + FD_SET(nlcCheck->s,fd); + if ( si.pending( nlcCheck->hSsl )) + pending++; + } + return 1; +} + +INT_PTR NetlibSelect(WPARAM,LPARAM lParam) +{ + NETLIBSELECT *nls=(NETLIBSELECT*)lParam; + if (nls==NULL || nls->cbSize!=sizeof(NETLIBSELECT)) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + TIMEVAL tv; + tv.tv_sec=nls->dwTimeout/1000; + tv.tv_usec=(nls->dwTimeout%1000)*1000; + + int pending = 0; + fd_set readfd, writefd, exceptfd; + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + if (!ConnectionListToSocketList(nls->hReadConns,&readfd,pending) + || !ConnectionListToSocketList(nls->hWriteConns,&writefd,pending) + || !ConnectionListToSocketList(nls->hExceptConns,&exceptfd,pending)) { + ReleaseMutex(hConnectionHeaderMutex); + return SOCKET_ERROR; + } + ReleaseMutex(hConnectionHeaderMutex); + if (pending) + return 1; + + return select(0,&readfd,&writefd,&exceptfd,nls->dwTimeout==INFINITE?NULL:&tv); +} + +INT_PTR NetlibSelectEx(WPARAM, LPARAM lParam) +{ + NETLIBSELECTEX *nls=(NETLIBSELECTEX*)lParam; + if (nls==NULL || nls->cbSize!=sizeof(NETLIBSELECTEX)) { + SetLastError(ERROR_INVALID_PARAMETER); + return SOCKET_ERROR; + } + + TIMEVAL tv; + tv.tv_sec=nls->dwTimeout/1000; + tv.tv_usec=(nls->dwTimeout%1000)*1000; + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + + int pending = 0; + fd_set readfd,writefd,exceptfd; + if (!ConnectionListToSocketList(nls->hReadConns,&readfd,pending) + || !ConnectionListToSocketList(nls->hWriteConns,&writefd,pending) + || !ConnectionListToSocketList(nls->hExceptConns,&exceptfd,pending)) { + ReleaseMutex(hConnectionHeaderMutex); + return SOCKET_ERROR; + } + ReleaseMutex(hConnectionHeaderMutex); + + int rc = (pending) ? pending : select(0,&readfd,&writefd,&exceptfd,nls->dwTimeout==INFINITE?NULL:&tv); + + WaitForSingleObject(hConnectionHeaderMutex,INFINITE); + /* go thru each passed HCONN array and grab its socket handle, then give it to FD_ISSET() + to see if an event happened for that socket, if it has it will be returned as TRUE (otherwise not) + This happens for read/write/except */ + struct NetlibConnection *conn=NULL; + int j; + for (j=0; jhReadConns[j]; + if (conn==NULL || conn==INVALID_HANDLE_VALUE) break; + + if (si.pending(conn->hSsl)) + nls->hReadStatus[j] = TRUE; + if (conn->usingHttpGateway && conn->nlhpi.szHttpGetUrl == NULL && conn->dataBuffer == NULL) + nls->hReadStatus[j] = (conn->pHttpProxyPacketQueue != NULL); + else + nls->hReadStatus[j] = FD_ISSET(conn->s,&readfd); + } + for (j=0; jhWriteConns[j]; + if (conn==NULL || conn==INVALID_HANDLE_VALUE) break; + nls->hWriteStatus[j] = FD_ISSET(conn->s,&writefd); + } + for (j=0; jhExceptConns[j]; + if (conn==NULL || conn==INVALID_HANDLE_VALUE) break; + nls->hExceptStatus[j] = FD_ISSET(conn->s,&exceptfd); + } + ReleaseMutex(hConnectionHeaderMutex); + return rc; +} diff --git a/src/modules/netlib/netlibssl.cpp b/src/modules/netlib/netlibssl.cpp new file mode 100644 index 0000000000..2db769a3da --- /dev/null +++ b/src/modules/netlib/netlibssl.cpp @@ -0,0 +1,981 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include "netlib.h" + +#define SECURITY_WIN32 +#include +#include + +//#include + +typedef BOOL (* SSL_EMPTY_CACHE_FN_M)(VOID); + +static HMODULE g_hSchannel; +static PSecurityFunctionTableA g_pSSPI; +static HANDLE g_hSslMutex; +static SSL_EMPTY_CACHE_FN_M MySslEmptyCache; +static CredHandle hCreds; +static bool bSslInitDone; + +typedef BOOL (WINAPI *pfnCertGetCertificateChain)(HCERTCHAINENGINE, PCCERT_CONTEXT, LPFILETIME, HCERTSTORE, PCERT_CHAIN_PARA, DWORD, LPVOID, PCCERT_CHAIN_CONTEXT*); +static pfnCertGetCertificateChain fnCertGetCertificateChain; + +typedef VOID (WINAPI *pfnCertFreeCertificateChain)(PCCERT_CHAIN_CONTEXT); +static pfnCertFreeCertificateChain fnCertFreeCertificateChain; + +typedef BOOL (WINAPI *pfnCertFreeCertificateContext)(PCCERT_CONTEXT); +static pfnCertFreeCertificateContext fnCertFreeCertificateContext; + +typedef BOOL (WINAPI *pfnCertVerifyCertificateChainPolicy)(LPCSTR, PCCERT_CHAIN_CONTEXT, PCERT_CHAIN_POLICY_PARA, PCERT_CHAIN_POLICY_STATUS); +static pfnCertVerifyCertificateChainPolicy fnCertVerifyCertificateChainPolicy; + +typedef enum +{ + sockOpen, + sockClosed, + sockError +} SocketState; + + +struct SslHandle +{ + SOCKET s; + + CtxtHandle hContext; + + BYTE *pbRecDataBuf; + int cbRecDataBuf; + int sbRecDataBuf; + + BYTE *pbIoBuffer; + int cbIoBuffer; + int sbIoBuffer; + + SocketState state; +}; + +static void ReportSslError(SECURITY_STATUS scRet, int line, bool showPopup = false) +{ + TCHAR szMsgBuf[256]; + switch (scRet) + { + case 0: + case ERROR_NOT_READY: + return; + + case SEC_E_INVALID_TOKEN: + _tcscpy(szMsgBuf, TranslateT("Client cannot decode host message. Possible causes: Host does not support SSL or requires not existing security package")); + break; + + case CERT_E_CN_NO_MATCH: + case SEC_E_WRONG_PRINCIPAL: + _tcscpy(szMsgBuf, TranslateT("Host we are connecting to is not the one certificate was issued for")); + break; + + default: + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, scRet, LANG_USER_DEFAULT, szMsgBuf, SIZEOF(szMsgBuf), NULL); + } + + TCHAR szMsgBuf2[512]; + mir_sntprintf(szMsgBuf2, SIZEOF(szMsgBuf2), _T("SSL connection failure (%x %u): %s"), scRet, line, szMsgBuf); + + char* szMsg = Utf8EncodeT(szMsgBuf2); + NetlibLogf(NULL, szMsg); + mir_free(szMsg); + + SetLastError(scRet); + PUShowMessageT(szMsgBuf2, SM_WARNING); +} + +static bool AcquireCredentials(void) +{ + SCHANNEL_CRED SchannelCred; + TimeStamp tsExpiry; + SECURITY_STATUS scRet; + + ZeroMemory(&SchannelCred, sizeof(SchannelCred)); + + SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; + SchannelCred.grbitEnabledProtocols = SP_PROT_SSL3TLS1_CLIENTS /*| 0xA00 TLS1.1 & 1.2*/; + + SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION; + + // Create an SSPI credential. + scRet = g_pSSPI->AcquireCredentialsHandleA( + NULL, // Name of principal + UNISP_NAME_A, // Name of package + SECPKG_CRED_OUTBOUND, // Flags indicating use + NULL, // Pointer to logon ID + &SchannelCred, // Package specific data + NULL, // Pointer to GetKey() func + NULL, // Value to pass to GetKey() + &hCreds, // (out) Cred Handle + &tsExpiry); // (out) Lifetime (optional) + + ReportSslError(scRet, __LINE__); + return scRet == SEC_E_OK; +} + +static bool SSL_library_init(void) +{ + if (bSslInitDone) return true; + + WaitForSingleObject(g_hSslMutex, INFINITE); + + if (!bSslInitDone) + { + g_hSchannel = LoadLibraryA("schannel.dll"); + if (g_hSchannel) + { + INIT_SECURITY_INTERFACE_A pInitSecurityInterface; + pInitSecurityInterface = (INIT_SECURITY_INTERFACE_A)GetProcAddress(g_hSchannel, SECURITY_ENTRYPOINT_ANSIA); + if (pInitSecurityInterface != NULL) + g_pSSPI = pInitSecurityInterface(); + + if (g_pSSPI) + { + HINSTANCE hCrypt = LoadLibraryA("crypt32.dll"); + if (hCrypt) + { + fnCertGetCertificateChain = (pfnCertGetCertificateChain)GetProcAddress(hCrypt, "CertGetCertificateChain"); + fnCertFreeCertificateChain = (pfnCertFreeCertificateChain)GetProcAddress(hCrypt, "CertFreeCertificateChain"); + fnCertFreeCertificateContext = (pfnCertFreeCertificateContext)GetProcAddress(hCrypt, "CertFreeCertificateContext"); + fnCertVerifyCertificateChainPolicy = (pfnCertVerifyCertificateChainPolicy)GetProcAddress(hCrypt, "CertVerifyCertificateChainPolicy"); + } + + MySslEmptyCache = (SSL_EMPTY_CACHE_FN_M)GetProcAddress(g_hSchannel, "SslEmptyCache"); + AcquireCredentials(); + bSslInitDone = true; + } + else + { + FreeLibrary(g_hSchannel); + g_hSchannel = NULL; + } + } + } + + ReleaseMutex(g_hSslMutex); + return bSslInitDone; +} + +void NetlibSslFree(SslHandle *ssl) +{ + if (ssl == NULL) return; + + g_pSSPI->DeleteSecurityContext(&ssl->hContext); + + mir_free(ssl->pbRecDataBuf); + mir_free(ssl->pbIoBuffer); + memset(ssl, 0, sizeof(SslHandle)); + mir_free(ssl); +} + +BOOL NetlibSslPending(SslHandle *ssl) +{ + return ssl != NULL && ( ssl->cbRecDataBuf != 0 || ssl->cbIoBuffer != 0 ); +} + +static bool VerifyCertificate(SslHandle *ssl, PCSTR pszServerName, DWORD dwCertFlags) +{ + if (!fnCertGetCertificateChain) + return true; + + static LPSTR rgszUsages[] = + { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + + CERT_CHAIN_PARA ChainPara = {0}; + HTTPSPolicyCallbackData polHttps = {0}; + CERT_CHAIN_POLICY_PARA PolicyPara = {0}; + CERT_CHAIN_POLICY_STATUS PolicyStatus = {0}; + PCCERT_CHAIN_CONTEXT pChainContext = NULL; + PCCERT_CONTEXT pServerCert = NULL; + DWORD scRet; + + PWSTR pwszServerName = mir_a2u(pszServerName); + + scRet = g_pSSPI->QueryContextAttributesA(&ssl->hContext, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pServerCert); + if (scRet != SEC_E_OK) + goto cleanup; + + if (pServerCert == NULL) + { + scRet = SEC_E_WRONG_PRINCIPAL; + goto cleanup; + } + + ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + ChainPara.RequestedUsage.Usage.cUsageIdentifier = SIZEOF(rgszUsages); + ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; + + if (!fnCertGetCertificateChain(NULL, pServerCert, NULL, pServerCert->hCertStore, + &ChainPara, 0, NULL, &pChainContext)) + { + scRet = GetLastError(); + goto cleanup; + } + + polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); + polHttps.dwAuthType = AUTHTYPE_SERVER; + polHttps.fdwChecks = dwCertFlags; + polHttps.pwszServerName = pwszServerName; + + PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.pvExtraPolicyPara = &polHttps; + + PolicyStatus.cbSize = sizeof(PolicyStatus); + + if (!fnCertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, + &PolicyPara, &PolicyStatus)) + { + scRet = GetLastError(); + goto cleanup; + } + + if (PolicyStatus.dwError) + { + scRet = PolicyStatus.dwError; + goto cleanup; + } + + scRet = SEC_E_OK; + +cleanup: + if (pChainContext) + fnCertFreeCertificateChain(pChainContext); + if (pServerCert) + fnCertFreeCertificateContext(pServerCert); + mir_free(pwszServerName); + + ReportSslError(scRet, __LINE__, true); + return scRet == SEC_E_OK; +} + +static SECURITY_STATUS ClientHandshakeLoop(SslHandle *ssl, BOOL fDoInitialRead) +{ + SecBufferDesc InBuffer; + SecBuffer InBuffers[2]; + SecBufferDesc OutBuffer; + SecBuffer OutBuffers[1]; + DWORD dwSSPIFlags; + DWORD dwSSPIOutFlags; + TimeStamp tsExpiry; + SECURITY_STATUS scRet; + DWORD cbData; + + BOOL fDoRead; + + dwSSPIFlags = + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + ssl->cbIoBuffer = 0; + + fDoRead = fDoInitialRead; + + scRet = SEC_I_CONTINUE_NEEDED; + + // Loop until the handshake is finished or an error occurs. + while (scRet == SEC_I_CONTINUE_NEEDED || scRet == SEC_E_INCOMPLETE_MESSAGE || scRet == SEC_I_INCOMPLETE_CREDENTIALS) + { + // Read server data + if (0 == ssl->cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) + { + if (fDoRead) + { + static const TIMEVAL tv = {6, 0}; + fd_set fd; + + // If buffer not large enough reallocate buffer + if (ssl->sbIoBuffer <= ssl->cbIoBuffer) + { + ssl->sbIoBuffer += 4096; + ssl->pbIoBuffer = (PUCHAR)mir_realloc(ssl->pbIoBuffer, ssl->sbIoBuffer); + } + + FD_ZERO(&fd); + FD_SET(ssl->s, &fd); + if (select(1, &fd, NULL, NULL, &tv) != 1) + { + NetlibLogf(NULL, "SSL Negotiation failure recieving data (timeout) (bytes %u)", ssl->cbIoBuffer); + scRet = ERROR_NOT_READY; + break; + } + + cbData = recv(ssl->s, (char*)ssl->pbIoBuffer + ssl->cbIoBuffer, ssl->sbIoBuffer - ssl->cbIoBuffer, 0); + if (cbData == SOCKET_ERROR) + { + NetlibLogf(NULL, "SSL Negotiation failure recieving data (%d)", WSAGetLastError()); + scRet = ERROR_NOT_READY; + break; + } + if (cbData == 0) + { + NetlibLogf(NULL, "SSL Negotiation connection gracefully closed"); + scRet = ERROR_NOT_READY; + break; + } + + NetlibDumpData(NULL, ssl->pbIoBuffer + ssl->cbIoBuffer, cbData, 0, MSG_DUMPSSL); + ssl->cbIoBuffer += cbData; + } + else fDoRead = TRUE; + } + + // Set up the input buffers. Buffer 0 is used to pass in data + // received from the server. Schannel will consume some or all + // of this. Leftover data (if any) will be placed in buffer 1 and + // given a buffer type of SECBUFFER_EXTRA. + + InBuffers[0].pvBuffer = ssl->pbIoBuffer; + InBuffers[0].cbBuffer = ssl->cbIoBuffer; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + + InBuffers[1].pvBuffer = NULL; + InBuffers[1].cbBuffer = 0; + InBuffers[1].BufferType = SECBUFFER_EMPTY; + + InBuffer.cBuffers = 2; + InBuffer.pBuffers = InBuffers; + InBuffer.ulVersion = SECBUFFER_VERSION; + + // Set up the output buffers. These are initialized to NULL + // so as to make it less likely we'll attempt to free random + // garbage later. + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType= SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->InitializeSecurityContextA( + &hCreds, + &ssl->hContext, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + &InBuffer, + 0, + NULL, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry); + + // If success (or if the error was one of the special extended ones), + // send the contents of the output buffer to the server. + if (scRet == SEC_E_OK || + scRet == SEC_I_CONTINUE_NEEDED || + (FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))) + { + if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) + { + NetlibDumpData(NULL, (unsigned char*)(OutBuffers[0].pvBuffer), OutBuffers[0].cbBuffer, 1, MSG_DUMPSSL); + cbData = send(ssl->s, (char*)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + NetlibLogf(NULL, "SSL Negotiation failure sending data (%d)", WSAGetLastError()); + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + return SEC_E_INTERNAL_ERROR; + } + + // Free output buffer. + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + OutBuffers[0].pvBuffer = NULL; + } + } + + // we need to read more data from the server and try again. + if (scRet == SEC_E_INCOMPLETE_MESSAGE) continue; + + // handshake completed successfully. + if (scRet == SEC_E_OK) + { + // Store remaining data for further use + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + memmove(ssl->pbIoBuffer, + ssl->pbIoBuffer + (ssl->cbIoBuffer - InBuffers[1].cbBuffer), + InBuffers[1].cbBuffer); + ssl->cbIoBuffer = InBuffers[1].cbBuffer; + } + else + ssl->cbIoBuffer = 0; + break; + } + + // Check for fatal error. + if (FAILED(scRet)) break; + + // server just requested client authentication. + if (scRet == SEC_I_INCOMPLETE_CREDENTIALS) + { + // Server has requested client authentication and + // GetNewClientCredentials(ssl); + + // Go around again. + fDoRead = FALSE; + scRet = SEC_I_CONTINUE_NEEDED; + continue; + } + + + // Copy any leftover data from the buffer, and go around again. + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + memmove(ssl->pbIoBuffer, + ssl->pbIoBuffer + (ssl->cbIoBuffer - InBuffers[1].cbBuffer), + InBuffers[1].cbBuffer); + + ssl->cbIoBuffer = InBuffers[1].cbBuffer; + } + else ssl->cbIoBuffer = 0; + } + + // Delete the security context in the case of a fatal error. + ReportSslError(scRet, __LINE__); + + if (ssl->cbIoBuffer == 0) + { + mir_free(ssl->pbIoBuffer); + ssl->pbIoBuffer = NULL; + ssl->sbIoBuffer = 0; + } + + return scRet; +} + +static bool ClientConnect(SslHandle *ssl, const char *host) +{ + SecBufferDesc OutBuffer; + SecBuffer OutBuffers[1]; + DWORD dwSSPIFlags; + DWORD dwSSPIOutFlags; + TimeStamp tsExpiry; + SECURITY_STATUS scRet; + DWORD cbData; + + if (SecIsValidHandle(&ssl->hContext)) + { + g_pSSPI->DeleteSecurityContext(&ssl->hContext); + SecInvalidateHandle(&ssl->hContext); + } + + if (MySslEmptyCache) MySslEmptyCache(); + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + // Initiate a ClientHello message and generate a token. + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->InitializeSecurityContextA( + &hCreds, + NULL, + (SEC_CHAR*)host, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &ssl->hContext, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry); + + if (scRet != SEC_I_CONTINUE_NEEDED) + { + ReportSslError(scRet, __LINE__); + return 0; + } + + // Send response to server if there is one. + if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) + { + NetlibDumpData(NULL, (unsigned char*)(OutBuffers[0].pvBuffer), OutBuffers[0].cbBuffer, 1, MSG_DUMPSSL); + cbData = send(ssl->s, (char*)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + NetlibLogf(NULL, "SSL failure sending connection data (%d %d)", ssl->s, WSAGetLastError()); + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + return 0; + } + + // Free output buffer. + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + OutBuffers[0].pvBuffer = NULL; + } + + return ClientHandshakeLoop(ssl, TRUE) == SEC_E_OK; +} + + +SslHandle *NetlibSslConnect(SOCKET s, const char* host, int verify) +{ + SslHandle *ssl = (SslHandle*)mir_calloc(sizeof(SslHandle)); + ssl->s = s; + + SecInvalidateHandle(&ssl->hContext); + + DWORD dwFlags = 0; + + if (!host || inet_addr(host) != INADDR_NONE) + dwFlags |= 0x00001000; + + bool res = SSL_library_init(); + + if (res) res = ClientConnect(ssl, host); + if (res && verify) res = VerifyCertificate(ssl, host, dwFlags); + + if (!res) + { + NetlibSslFree(ssl); + ssl = NULL; + } + return ssl; +} + + +void NetlibSslShutdown(SslHandle *ssl) +{ + DWORD dwType; + + SecBufferDesc OutBuffer; + SecBuffer OutBuffers[1]; + DWORD dwSSPIFlags; + DWORD dwSSPIOutFlags; + TimeStamp tsExpiry; + DWORD scRet; + + if (ssl == NULL || !SecIsValidHandle(&ssl->hContext)) + return; + + dwType = SCHANNEL_SHUTDOWN; + + OutBuffers[0].pvBuffer = &dwType; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = sizeof(dwType); + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->ApplyControlToken(&ssl->hContext, &OutBuffer); + if (FAILED(scRet)) return; + + // + // Build an SSL close notify message. + // + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_RET_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + scRet = g_pSSPI->InitializeSecurityContextA( + &hCreds, + &ssl->hContext, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &ssl->hContext, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry); + + if (FAILED(scRet)) return; + + // Send the close notify message to the server. + if (OutBuffers[0].pvBuffer != NULL && OutBuffers[0].cbBuffer != 0) + { + NetlibDumpData(NULL, (unsigned char*)(OutBuffers[0].pvBuffer), OutBuffers[0].cbBuffer, 1, MSG_DUMPSSL); + send(ssl->s, (char*)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); + g_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); + } +} + +static int NetlibSslReadSetResult(SslHandle *ssl, char *buf, int num, int peek) +{ + if (ssl->cbRecDataBuf == 0) + { + return (ssl->state == sockClosed ? 0: SOCKET_ERROR); + } + + int bytes = min(num, ssl->cbRecDataBuf); + int rbytes = ssl->cbRecDataBuf - bytes; + + memcpy(buf, ssl->pbRecDataBuf, bytes); + if (!peek) + { + memmove(ssl->pbRecDataBuf, ssl->pbRecDataBuf + bytes, rbytes); + ssl->cbRecDataBuf = rbytes; + } + + return bytes; +} + +int NetlibSslRead(SslHandle *ssl, char *buf, int num, int peek) +{ + SECURITY_STATUS scRet; + DWORD cbData; + DWORD resNum = 0; + int i; + + SecBufferDesc Message; + SecBuffer Buffers[4]; + SecBuffer * pDataBuffer; + SecBuffer * pExtraBuffer; + + if (ssl == NULL) return SOCKET_ERROR; + + if (num <= 0) return 0; + + if (ssl->state != sockOpen || (ssl->cbRecDataBuf != 0 && (!peek || ssl->cbRecDataBuf >= num))) + { + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + scRet = SEC_E_OK; + + for (;;) + { + if (0 == ssl->cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) + { + if (ssl->sbIoBuffer <= ssl->cbIoBuffer) + { + ssl->sbIoBuffer += 2048; + ssl->pbIoBuffer = (PUCHAR)mir_realloc(ssl->pbIoBuffer, ssl->sbIoBuffer); + } + + if (peek) + { + static const TIMEVAL tv = {0}; + fd_set fd; + FD_ZERO(&fd); + FD_SET(ssl->s, &fd); + + cbData = select(1, &fd, NULL, NULL, &tv); + if (cbData == SOCKET_ERROR) + { + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + if (cbData == 0 && ssl->cbRecDataBuf) + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + cbData = recv(ssl->s, (char*)ssl->pbIoBuffer + ssl->cbIoBuffer, ssl->sbIoBuffer - ssl->cbIoBuffer, 0); + if (cbData == SOCKET_ERROR) + { + NetlibLogf(NULL, "SSL failure recieving data (%d)", WSAGetLastError()); + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + if (cbData == 0) + { + NetlibLogf(NULL, "SSL connection gracefully closed"); + if (peek && ssl->cbRecDataBuf) + { + ssl->state = sockClosed; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + // Server disconnected. + if (ssl->cbIoBuffer) + { + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + return 0; + } + else + { + NetlibDumpData(NULL, ssl->pbIoBuffer + ssl->cbIoBuffer, cbData, 0, MSG_DUMPSSL); + ssl->cbIoBuffer += cbData; + } + } + + // Attempt to decrypt the received data. + Buffers[0].pvBuffer = ssl->pbIoBuffer; + Buffers[0].cbBuffer = ssl->cbIoBuffer; + Buffers[0].BufferType = SECBUFFER_DATA; + + Buffers[1].BufferType = SECBUFFER_EMPTY; + Buffers[2].BufferType = SECBUFFER_EMPTY; + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + + if (g_pSSPI->DecryptMessage != NULL && g_pSSPI->DecryptMessage != PVOID(0x80000000)) + scRet = g_pSSPI->DecryptMessage(&ssl->hContext, &Message, 0, NULL); + else + scRet = ((DECRYPT_MESSAGE_FN)g_pSSPI->Reserved4)(&ssl->hContext, &Message, 0, NULL); + + // The input buffer contains only a fragment of an + // encrypted record. Loop around and read some more + // data. + if (scRet == SEC_E_INCOMPLETE_MESSAGE) + continue; + + if ( scRet != SEC_E_OK && scRet != SEC_I_RENEGOTIATE && scRet != SEC_I_CONTEXT_EXPIRED) + { + ReportSslError(scRet, __LINE__); + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + // Locate data and (optional) extra buffers. + pDataBuffer = NULL; + pExtraBuffer = NULL; + for(i = 1; i < 4; i++) + { + if (pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA) + pDataBuffer = &Buffers[i]; + + if (pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA) + pExtraBuffer = &Buffers[i]; + } + + // Return decrypted data. + if (pDataBuffer) + { + DWORD bytes, rbytes; + + bytes = peek ? 0 : min((DWORD)num, pDataBuffer->cbBuffer); + rbytes = pDataBuffer->cbBuffer - bytes; + + NetlibDumpData(NULL, (PBYTE)pDataBuffer->pvBuffer, pDataBuffer->cbBuffer, 0, MSG_DUMPSSL); + + if (rbytes > 0) + { + int nbytes = ssl->cbRecDataBuf + rbytes; + if (ssl->sbRecDataBuf < nbytes) + { + ssl->sbRecDataBuf = nbytes; + ssl->pbRecDataBuf = (PUCHAR)mir_realloc(ssl->pbRecDataBuf, nbytes); + } + memcpy(ssl->pbRecDataBuf + ssl->cbRecDataBuf, (char*)pDataBuffer->pvBuffer + bytes, rbytes); + ssl->cbRecDataBuf = nbytes; + } + + if (peek) + { + resNum = bytes = min(num, ssl->cbRecDataBuf); + memcpy(buf, ssl->pbRecDataBuf, bytes); + } + else + { + resNum = bytes; + memcpy(buf, pDataBuffer->pvBuffer, bytes); + } + } + + // Move any "extra" data to the input buffer. + if (pExtraBuffer) + { + memmove(ssl->pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer); + ssl->cbIoBuffer = pExtraBuffer->cbBuffer; + } + else ssl->cbIoBuffer = 0; + + if (pDataBuffer && resNum) + return resNum; + + // Server signaled end of session + if (scRet == SEC_I_CONTEXT_EXPIRED) + { + NetlibLogf(NULL, "SSL Server signaled SSL Shutdown"); + ssl->state = sockClosed; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + + if (scRet == SEC_I_RENEGOTIATE) + { + // The server wants to perform another handshake + // sequence. + + scRet = ClientHandshakeLoop(ssl, FALSE); + if (scRet != SEC_E_OK) + { + ssl->state = sockError; + return NetlibSslReadSetResult(ssl, buf, num, peek); + } + } + } +} + +int NetlibSslWrite(SslHandle *ssl, const char *buf, int num) +{ + SecPkgContext_StreamSizes Sizes; + SECURITY_STATUS scRet; + DWORD cbData; + + SecBufferDesc Message; + SecBuffer Buffers[4] = {0}; + + PUCHAR pbDataBuffer; + + PUCHAR pbMessage; + DWORD cbMessage; + + DWORD sendOff = 0; + + if (ssl == NULL) return SOCKET_ERROR; + + scRet = g_pSSPI->QueryContextAttributesA(&ssl->hContext, SECPKG_ATTR_STREAM_SIZES, &Sizes); + if (scRet != SEC_E_OK) return scRet; + + pbDataBuffer = (PUCHAR)mir_calloc(Sizes.cbMaximumMessage + Sizes.cbHeader + Sizes.cbTrailer); + + pbMessage = pbDataBuffer + Sizes.cbHeader; + + while (sendOff < (DWORD)num) + { + cbMessage = min(Sizes.cbMaximumMessage, (DWORD)num - sendOff); + CopyMemory(pbMessage, buf+sendOff, cbMessage); + + Buffers[0].pvBuffer = pbDataBuffer; + Buffers[0].cbBuffer = Sizes.cbHeader; + Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; + + Buffers[1].pvBuffer = pbMessage; + Buffers[1].cbBuffer = cbMessage; + Buffers[1].BufferType = SECBUFFER_DATA; + + Buffers[2].pvBuffer = pbMessage + cbMessage; + Buffers[2].cbBuffer = Sizes.cbTrailer; + Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + + if (g_pSSPI->EncryptMessage != NULL) + scRet = g_pSSPI->EncryptMessage(&ssl->hContext, 0, &Message, 0); + else + scRet = ((ENCRYPT_MESSAGE_FN)g_pSSPI->Reserved3)(&ssl->hContext, 0, &Message, 0); + + if (FAILED(scRet)) break; + + // Calculate encrypted packet size + cbData = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer; + + // Send the encrypted data to the server. + NetlibDumpData(NULL, pbDataBuffer, cbData, 1, MSG_DUMPSSL); + cbData = send(ssl->s, (char*)pbDataBuffer, cbData, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + NetlibLogf(NULL, "SSL failure sending data (%d)", WSAGetLastError()); + scRet = SEC_E_INTERNAL_ERROR; + break; + } + + sendOff += cbMessage; + } + + mir_free(pbDataBuffer); + return scRet == SEC_E_OK ? num : SOCKET_ERROR; +} + +static INT_PTR GetSslApi(WPARAM, LPARAM lParam) +{ + SSL_API* si = (SSL_API*)lParam; + if (si == NULL) return FALSE; + + if (si->cbSize != sizeof(SSL_API)) + return FALSE; + + si->connect = (HSSL (__cdecl *)(SOCKET,const char *,int))NetlibSslConnect; + si->pending = (BOOL (__cdecl *)(HSSL))NetlibSslPending; + si->read = (int (__cdecl *)(HSSL,char *,int,int))NetlibSslRead; + si->write = (int (__cdecl *)(HSSL,const char *,int))NetlibSslWrite; + si->shutdown = (void (__cdecl *)(HSSL))NetlibSslShutdown; + si->sfree = (void (__cdecl *)(HSSL))NetlibSslFree; + + return TRUE; +} + +int LoadSslModule(void) +{ + CreateServiceFunction(MS_SYSTEM_GET_SI, GetSslApi); + g_hSslMutex = CreateMutex(NULL, FALSE, NULL); + SecInvalidateHandle(&hCreds); + + return 0; +} + +void UnloadSslModule(void) +{ + if (g_pSSPI && SecIsValidHandle(&hCreds)) + g_pSSPI->FreeCredentialsHandle(&hCreds); + CloseHandle(g_hSslMutex); + if (g_hSchannel) FreeLibrary(g_hSchannel); +} diff --git a/src/modules/netlib/netlibupnp.cpp b/src/modules/netlib/netlibupnp.cpp new file mode 100644 index 0000000000..935801d330 --- /dev/null +++ b/src/modules/netlib/netlibupnp.cpp @@ -0,0 +1,885 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2012 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "netlib.h" + +static const char search_request_msg[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: 1\r\n" + "ST: urn:schemas-upnp-org:service:%s\r\n" + "\r\n"; + +static const char xml_get_hdr[] = + "GET %s HTTP/1.1\r\n" + "HOST: %s:%u\r\n" + "ACCEPT-LANGUAGE: *\r\n\r\n"; + +static const char soap_post_hdr[] = + "POST %s HTTP/1.1\r\n" + "HOST: %s:%u\r\n" + "CONTENT-LENGTH: %u\r\n" + "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" + "SOAPACTION: \"%s#%s\"\r\n\r\n" + "%s"; + +static const char soap_post_hdr_m[] = + "M-POST %s URL HTTP/1.1\r\n" + "HOST: %s:%u\r\n" + "CONTENT-LENGTH: %u\r\n" + "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" + "MAN: \"http://schemas.xmlsoap.org/soap/envelope/\"; ns=01\r\n" + "01-SOAPACTION: \"%s#%s\"\r\n\r\n" + "%s"; + +static const char search_device[] = + "%s"; + +static const char soap_action[] = + "\r\n" + "\r\n" + " \r\n" + " \r\n" + "%s" + " \r\n" + " \r\n" + "\r\n"; + +static const char soap_query[] = + "\r\n" + " \r\n" + " \r\n" + " %s\r\n" + " \r\n" + " \r\n" + "\r\n"; + +static const char add_port_mapping[] = + " \r\n" + " %i\r\n" + " %s\r\n" + " %i\r\n" + " %s\r\n" + " 1\r\n" + " Miranda\r\n" + " 0\r\n"; + +static const char delete_port_mapping[] = + " \r\n" + " %i\r\n" + " %s\r\n"; + +static const char get_port_mapping[] = + " %i\r\n"; + +static bool gatewayFound; +static SOCKADDR_IN locIP; +static time_t lastDiscTime; +static int expireTime = 120; + +static int retryCount; +static SOCKET sock = INVALID_SOCKET; +static char szConnHost[256]; +static unsigned short sConnPort; + +static WORD *portList; +static unsigned numports, numportsAlloc; +static HANDLE portListMutex; + +static char szCtlUrl[256], szDev[256]; + +typedef enum +{ + DeviceGetReq, + ControlAction, + ControlQuery +} ReqType; + +static bool txtParseParam(char* szData, char* presearch, + char* start, char* finish, char* param, size_t size) +{ + char *cp, *cp1; + size_t len; + + *param = 0; + + if (presearch != NULL) { + cp1 = strstr(szData, presearch); + if (cp1 == NULL) return false; + } + else + cp1 = szData; + + cp = strstr(cp1, start); + if (cp == NULL) return false; + cp += strlen(start); + while (*cp == ' ') ++cp; + + cp1 = strstr(cp, finish); + if (cp1 == NULL) return false; + while (*(cp1-1) == ' ' && cp1 > cp) --cp1; + + len = min((size_t)(cp1 - cp), size-1); + strncpy(param, cp, len); + param[len] = 0; + + return true; +} + +void parseURL(char* szUrl, char* szHost, unsigned short* sPort, char* szPath) +{ + char *ppath, *phost, *pport; + int sz; + + phost = strstr(szUrl,"://"); + if (phost == NULL) phost = szUrl; + else phost += 3; + + ppath = strchr(phost,'/'); + if (ppath == NULL) ppath = phost + strlen(phost); + + pport = strchr(phost,':'); + if (pport == NULL) pport = ppath; + + if (szHost != NULL) + { + sz = pport - phost + 1; + if (sz>256) sz = 256; + strncpy(szHost, phost, sz); + szHost[sz-1] = 0; + } + + if (sPort != NULL) + { + if (pport < ppath) + { + long prt = atol(pport+1); + *sPort = prt != 0 ? (unsigned short)prt : 80; + } + else + *sPort = 80; + } + + if (szPath != NULL) + { + strncpy(szPath, ppath, 256); + szPath[255] = 0; + } +} + + +static void LongLog(char* szData) +{ + CallService(MS_NETLIB_LOG, 0, (LPARAM)szData); +} + +static void closeRouterConnection(void) +{ + if (sock != INVALID_SOCKET) + { + closesocket(sock); + sock = INVALID_SOCKET; + } +} + +static void validateSocket(void) +{ + static const TIMEVAL tv = { 0, 0 }; + fd_set rfd; + char buf[4]; + bool opened; + + if (sock == INVALID_SOCKET) + return; + + FD_ZERO(&rfd); + FD_SET(sock, &rfd); + + switch (select(1, &rfd, NULL, NULL, &tv)) + { + case SOCKET_ERROR: + opened = false; + break; + + case 0: + opened = true; + break; + + case 1: + opened = recv(sock, buf, 1, MSG_PEEK) > 0; + break; + } + + if (!opened) + closeRouterConnection(); +} + +static int httpTransact(char* szUrl, char* szResult, int resSize, char* szActionName, ReqType reqtype) +{ + // Parse URL + char szHost[256], szPath[256], szRes[16]; + int sz = 0, res = 0; + unsigned short sPort; + bool needClose; + + const char* szPostHdr = soap_post_hdr; + char* szData = ( char* )mir_alloc(4096); + char* szReq = NULL; + + parseURL(szUrl, szHost, &sPort, szPath); + + if (sPort != sConnPort || _stricmp(szHost, szConnHost)) + closeRouterConnection(); + else + validateSocket(); + + for (;;) + { + retryCount = 0; + switch(reqtype) + { + case DeviceGetReq: + sz = mir_snprintf (szData, 4096, xml_get_hdr, szPath, szHost, sPort); + break; + + case ControlAction: + { + char szData1[1024]; + + szReq = mir_strdup(szResult); + sz = mir_snprintf (szData1, sizeof(szData1), + soap_action, szActionName, szDev, szReq, szActionName); + + sz = mir_snprintf (szData, 4096, + szPostHdr, szPath, szHost, sPort, + sz, szDev, szActionName, szData1); + } + break; + + case ControlQuery: + { + char szData1[1024]; + + sz = mir_snprintf (szData1, sizeof(szData1), + soap_query, szActionName); + + sz = mir_snprintf (szData, 4096, + szPostHdr, szPath, szHost, sPort, + sz, "urn:schemas-upnp-org:control-1-0", "QueryStateVariable", szData1); + } + break; + } + szResult[0] = 0; + { + static const TIMEVAL tv = { 6, 0 }; + static unsigned ttl = 4; + static u_long mode = 1; + fd_set rfd, wfd, efd; + SOCKADDR_IN enetaddr; + +retrycon: + if (sock == INVALID_SOCKET) + { + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + enetaddr.sin_family = AF_INET; + enetaddr.sin_port = htons(sPort); + enetaddr.sin_addr.s_addr = inet_addr(szHost); + + // Resolve host name if needed + if (enetaddr.sin_addr.s_addr == INADDR_NONE) + { + PHOSTENT he = gethostbyname(szHost); + if (he) + enetaddr.sin_addr.s_addr = *(unsigned*)he->h_addr_list[0]; + } + + NetlibLogf(NULL, "UPnP HTTP connection Host: %s Port: %u", szHost, sPort); + + FD_ZERO(&rfd); FD_ZERO(&wfd); FD_ZERO(&efd); + FD_SET(sock, &rfd); FD_SET(sock, &wfd); FD_SET(sock, &efd); + + // Limit the scope of the connection (does not work for + setsockopt(sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(unsigned)); + + // Put socket into non-blocking mode for timeout on connect + ioctlsocket(sock, FIONBIO, &mode); + + // Connect to the remote host + if (connect(sock, (SOCKADDR*)&enetaddr, sizeof(enetaddr)) == SOCKET_ERROR) + { + int err = WSAGetLastError(); + + // Socket connection failed + if (err != WSAEWOULDBLOCK) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP connect failed %d", err); + break; + } + // Wait for socket to connect + else if (select(1, &rfd, &wfd, &efd, &tv) != 1) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP connect timeout"); + break; + } + else if (!FD_ISSET(sock, &wfd)) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP connect failed"); + break; + } + } + strcpy(szConnHost, szHost); sConnPort = sPort; + } + + if (send(sock, szData, sz, 0) != SOCKET_ERROR) + { + char *hdrend = NULL; + int acksz = 0, pktsz = 0; + + if (szActionName == NULL) + { + int len = sizeof(locIP); + getsockname(sock, (SOCKADDR*)&locIP, &len); + if (locIP.sin_addr.S_un.S_addr == 0x0100007f) + { + struct hostent *he; + + gethostname(szPath, sizeof(szPath)); + he = gethostbyname(szPath); + if (he != NULL) + locIP.sin_addr.S_un.S_addr = *(PDWORD)he->h_addr_list[0]; + } + } + + LongLog(szData); + sz = 0; + for(;;) + { + int bytesRecv; + + FD_ZERO(&rfd); + FD_SET(sock, &rfd); + + // Wait for the next packet + if (select(1, &rfd, NULL, NULL, &tv) != 1) + { + closeRouterConnection(); + NetlibLogf(NULL, "UPnP recieve timeout"); + break; + } + + // + bytesRecv = recv(sock, &szResult[sz], resSize-sz, 0); + + // Connection closed or aborted, all data received + if (bytesRecv == 0 || bytesRecv == SOCKET_ERROR) + { + closeRouterConnection(); + if ((bytesRecv == SOCKET_ERROR || sz == 0) && retryCount < 2) + { + ++retryCount; + goto retrycon; + } + break; + } + + sz += bytesRecv; + + // Insert null terminator to use string functions + if (sz >= (resSize-1)) + { + szResult[resSize-1] = 0; + break; + } + else + szResult[sz] = 0; + + // HTTP header found? + if (hdrend == NULL) + { + // Find HTTP header end + hdrend = strstr(szResult, "\r\n\r\n"); + if (hdrend == NULL) + { + hdrend = strstr(szResult, "\n\n"); + if (hdrend) hdrend += 2; + } + + else + hdrend += 4; + + if (hdrend != NULL) + { + // Get packet size if provided + if (txtParseParam(szResult, NULL, "Content-Length:", "\n", szRes, sizeof(szRes)) || + txtParseParam(szResult, NULL, "CONTENT-LENGTH:", "\n", szRes, sizeof(szRes))) + { + // Add size of HTTP header to the packet size to compute full transmission size + pktsz = atol(ltrimp(szRes)) + (hdrend - szResult); + } + // Get encoding type if provided + else if (txtParseParam(szResult, NULL, "Transfer-Encoding:", "\n", szRes, sizeof(szRes))) + { + if (_stricmp(lrtrimp(szRes), "Chunked") == 0) + acksz = hdrend - szResult; + } + if (txtParseParam(szResult, NULL, "Connection:", "\n", szRes, sizeof(szRes))) + { + needClose = (_stricmp(lrtrimp(szRes), "close") == 0); + } + } + } + + // Content-Length bytes reached, all data received + if (sz >= pktsz && pktsz != 0) + { + szResult[pktsz] = 0; + break; + } + + // Chunked encoding processing + if (sz > acksz && acksz != 0) + { +retry: + // Parse out chunk size + char* data = szResult + acksz; + char* peol1 = data == hdrend ? data - 1 : strchr(data, '\n'); + if (peol1 != NULL) + { + char *peol2 = strchr(++peol1, '\n'); + if (peol2 != NULL) + { + // Get chunk size + int chunkBytes = strtol(peol1, NULL, 16); + acksz += chunkBytes; + peol2++; + + memmove(data, peol2, strlen(peol2) + 1); + sz -= peol2 - data; + + // Last chunk, all data received + if (chunkBytes == 0) break; + if (sz > acksz) goto retry; + } + } + } + } + LongLog(szResult); + } + else + { + if (retryCount < 2) + { + closeRouterConnection(); + ++retryCount; + goto retrycon; + } + else + NetlibLogf(NULL, "UPnP send failed %d", WSAGetLastError()); + } + } + txtParseParam(szResult, "HTTP", " ", " ", szRes, sizeof(szRes)); + res = atol(szRes); + if (szActionName != NULL && res == 405 && szPostHdr == soap_post_hdr) + szPostHdr = soap_post_hdr_m; + else + break; + } + + if (needClose) + closeRouterConnection(); + + mir_free(szData); + mir_free(szReq); + return res; +} + +static unsigned getExtIP(void) +{ + char szExtIP[30]; + char* szData = (char*)mir_alloc(4096); szData[0] = 0; + + unsigned extip = 0; + int res = httpTransact(szCtlUrl, szData, 4096, "GetExternalIPAddress", ControlAction); + if (res == 200 && txtParseParam(szData, "", "<", szExtIP, sizeof(szExtIP))) + extip = ntohl(inet_addr(szExtIP)); + + mir_free(szData); + return extip; +} + +static bool getUPnPURLs(char* szUrl, size_t sizeUrl) +{ + char* szData = (char*)mir_alloc(8192); + + gatewayFound = httpTransact(szUrl, szData, 8192, NULL, DeviceGetReq) == 200; + if (gatewayFound) + { + char szTemp[256], *rpth; + size_t ctlLen; + + txtParseParam(szData, NULL, "", "", szTemp, sizeof(szTemp)); + strncpy(szCtlUrl, szTemp[0] ? szTemp : szUrl, sizeof(szCtlUrl)); + szCtlUrl[sizeof(szCtlUrl)-1] = 0; + + mir_snprintf(szTemp, sizeof(szTemp), search_device, szDev); + txtParseParam(szData, szTemp, "", "", szUrl, sizeUrl); + + // URL combining per RFC 2396 + if ( szUrl[0] != 0 ) + { + if (strstr(szUrl, "://") != NULL) // absolute URI + rpth = szCtlUrl; + else if (strncmp(szUrl, "//", 2) == 0) // relative URI net_path + { + rpth = strstr(szCtlUrl, "//"); + if (rpth == NULL) rpth = szCtlUrl; + } + else if (szUrl[0] == '/') // relative URI abs_path + { + rpth = strstr(szCtlUrl, "//"); + rpth = rpth ? rpth + 2 : szCtlUrl; + + rpth = strchr(rpth, '/'); + if (rpth == NULL) rpth = szCtlUrl + strlen(szCtlUrl); + } + else + { // relative URI rel_path + size_t ctlCLen = strlen(szCtlUrl); + rpth = szCtlUrl + ctlCLen; + if (ctlCLen != 0 && *(rpth-1) != '/') + strncpy(rpth++, "/", sizeof(szCtlUrl) - ctlCLen); + } + + ctlLen = sizeof(szCtlUrl) - (rpth - szCtlUrl); + strncpy(rpth, szUrl, ctlLen); + szCtlUrl[sizeof(szCtlUrl)-1] = 0; + } + else + { + szCtlUrl[0] = 0; + gatewayFound = false; + } + } + mir_free(szData); + + return gatewayFound; +} + + +static void discoverUPnP(void) +{ + char* buf; + int buflen; + unsigned i, j, nip = 0; + unsigned* ips = NULL; + + static const unsigned any = INADDR_ANY; + static const TIMEVAL tv = { 1, 600000 }; + + char szUrl[256] = ""; + char hostname[256]; + PHOSTENT he; + fd_set readfd; + + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + SOCKADDR_IN enetaddr; + enetaddr.sin_family = AF_INET; + enetaddr.sin_port = htons(1900); + enetaddr.sin_addr.s_addr = inet_addr("239.255.255.250"); + + gethostname(hostname, sizeof(hostname)); + he = gethostbyname(hostname); + + if (he) + { + while(he->h_addr_list[nip]) ++nip; + + ips = ( unsigned* )mir_alloc(nip * sizeof(unsigned)); + + for (j = 0; j < nip; ++j) + ips[j] = *(unsigned*)he->h_addr_list[j]; + } + + buf = (char*)mir_alloc(1500); + + for(i = 3; --i && szUrl[0] == 0;) + { + for (j = 0; j < nip; ++j) + { + if (ips) + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&ips[j], sizeof(unsigned)); + + buflen = mir_snprintf(buf, 1500, search_request_msg, "WANIPConnection:1"); + sendto(sock, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); + LongLog(buf); + + buflen = mir_snprintf(buf, 1500, search_request_msg, "WANPPPConnection:1"); + sendto(sock, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); + LongLog(buf); + } + + if (Miranda_Terminated()) break; + + FD_ZERO(&readfd); + FD_SET(sock, &readfd); + + while (select(1, &readfd, NULL, NULL, &tv) >= 1) + { + buflen = recv(sock, buf, 1500, 0); + if (buflen != SOCKET_ERROR) + { + buf[buflen] = 0; + LongLog(buf); + + if (txtParseParam(buf, NULL, "LOCATION:", "\n", szUrl, sizeof(szUrl)) || + txtParseParam(buf, NULL, "Location:", "\n", szUrl, sizeof(szUrl))) + { + char age[30]; + char szHostNew[256], szHostExist[256]; + + lrtrim(szUrl); + + parseURL(szUrl, szHostNew, NULL, NULL); + parseURL(szCtlUrl, szHostExist, NULL, NULL); + if (strcmp(szHostNew, szHostExist) == 0) + { + gatewayFound = true; + break; + } + + txtParseParam(buf, NULL, "ST:", "\n", szDev, sizeof(szDev)); + txtParseParam(buf, "max-age", "=", "\n", age, sizeof(age)); + expireTime = atoi(lrtrimp(age)); + lrtrim(szDev); + + if (getUPnPURLs(szUrl, sizeof(szUrl))) + { + gatewayFound = getExtIP() != 0; + if (gatewayFound) break; + } + } + } + FD_ZERO(&readfd); + FD_SET(sock, &readfd); + } + } + + mir_free(buf); + mir_free(ips); + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&any, sizeof(unsigned)); + closesocket(sock); +} + + +static bool findUPnPGateway(void) +{ + if ((time(NULL) - lastDiscTime) >= expireTime) + { + WaitForSingleObject(portListMutex, INFINITE); + + time_t curTime = time(NULL); + + if ((curTime - lastDiscTime) >= expireTime) + { + gatewayFound = false; + + discoverUPnP(); + lastDiscTime = curTime; + + NetlibLogf(NULL, "UPnP Gateway detected %d, Control URL: %s", gatewayFound, szCtlUrl); + } + + ReleaseMutex(portListMutex); + } + + return gatewayFound; +} + +bool NetlibUPnPAddPortMapping(WORD intport, char *proto, WORD *extport, DWORD *extip, bool search) +{ + int res = 0, i = 5; + + if (findUPnPGateway()) + { + char* szData = (char*)mir_alloc(4096); + char szExtIP[30]; + + *extport = intport - 1; + *extip = ntohl(locIP.sin_addr.S_un.S_addr); + + WaitForSingleObject(portListMutex, INFINITE); + + do + { + ++*extport; + mir_snprintf(szData, 4096, add_port_mapping, + *extport, proto, intport, inet_ntoa(locIP.sin_addr)); + res = httpTransact(szCtlUrl, szData, 4096, "AddPortMapping", ControlAction); + txtParseParam(szData, NULL, "", "", szExtIP, sizeof(szExtIP)); + + } + while (search && res == 500 && atol(szExtIP) == 718 && --i); + + mir_free(szData); + + if (res == 200) + { + unsigned ip = getExtIP(); + if (ip) *extip = ip; + + if (numports >= numportsAlloc) + mir_realloc(portList, sizeof(WORD)*(numportsAlloc += 10)); + portList[numports++] = *extport; + } + + ReleaseMutex(portListMutex); + } + + return res == 200; +} + +void NetlibUPnPDeletePortMapping(WORD extport, char* proto) +{ + if (extport == 0) + return; + + // findUPnPGateway(); + + if (gatewayFound) + { + unsigned i; + char* szData = (char*)mir_alloc(4096); + + WaitForSingleObject(portListMutex, INFINITE); + mir_snprintf(szData, 4096, delete_port_mapping, extport, proto); + httpTransact(szCtlUrl, szData, 4096, "DeletePortMapping", ControlAction); + + for (i = 0; i < numports; ++i) + if (portList[i] == extport && --numports > 0) + memmove(&portList[i], &portList[i+1], (numports - i) * sizeof(WORD)); + + mir_free(szData); + ReleaseMutex(portListMutex); + } +} + +void NetlibUPnPCleanup(void*) +{ + if (DBGetContactSettingByte(NULL,"Netlib","NLEnableUPnP",1)==0) + // upnp is disabled globally, no need for a cleanup + return; + + { + int i, incoming = 0; + EnterCriticalSection(&csNetlibUser); + for (i = 0; i < netlibUser.getCount(); ++i) + { + if (netlibUser[i]->user.flags & NUF_INCOMING) + { + incoming = 1; + break; + } + } + LeaveCriticalSection(&csNetlibUser); + if (!incoming) return; + } + + if (findUPnPGateway()) + { + char* szData = (char*)alloca(4096); + char buf[50], lip[50]; + unsigned i, j = 0, k, num = 100; + + WORD ports[30]; + + strcpy(lip, inet_ntoa(locIP.sin_addr)); + + WaitForSingleObject(portListMutex, INFINITE); + + if (httpTransact(szCtlUrl, szData, 4096, "PortMappingNumberOfEntries", ControlQuery) == 200 && + txtParseParam(szData, "QueryStateVariableResponse", "", "<", buf, sizeof(buf))) + num = atol(buf); + + for (i=0; i", "<", buf, sizeof(buf)) || strcmp(buf, "Miranda") != 0) + continue; + + if (!txtParseParam(szData, "", "<", buf, sizeof(buf)) || strcmp(buf, lip) != 0) + continue; + + if (txtParseParam(szData, "", "<", buf, sizeof(buf))) + { + WORD mport = (WORD)atol(buf); + + if (j >= SIZEOF(ports)) + break; + + for (k=0; k= numports) + ports[j++] = mport; + } + } + + ReleaseMutex(portListMutex); + + for (i=0; irgbBkgBottom.rgbRed = (dat->rgbBkgTop.rgbRed = GetRValue(cl)) * .95; + dat->rgbBkgBottom.rgbGreen = (dat->rgbBkgTop.rgbGreen = GetGValue(cl)) * .95; + dat->rgbBkgBottom.rgbBlue = (dat->rgbBkgTop.rgbBlue = GetBValue(cl)) * .95; + + cl = GetSysColor(COLOR_HIGHLIGHT); + dat->rgbSelTop.rgbRed = (dat->rgbSelBottom.rgbRed = GetRValue(cl)) * .75; + dat->rgbSelTop.rgbGreen = (dat->rgbSelBottom.rgbGreen = GetGValue(cl)) * .75; + dat->rgbSelTop.rgbBlue = (dat->rgbSelBottom.rgbBlue = GetBValue(cl)) * .75; + + dat->rgbHotTop.rgbRed = (dat->rgbSelTop.rgbRed + 255) / 2; + dat->rgbHotTop.rgbGreen = (dat->rgbSelTop.rgbGreen + 255) / 2; + dat->rgbHotTop.rgbBlue = (dat->rgbSelTop.rgbBlue + 255) / 2; + + dat->rgbHotBottom.rgbRed = (dat->rgbSelBottom.rgbRed + 255) / 2; + dat->rgbHotBottom.rgbGreen = (dat->rgbSelBottom.rgbGreen + 255) / 2; + dat->rgbHotBottom.rgbBlue = (dat->rgbSelBottom.rgbBlue + 255) / 2; + + dat->clBackground = GetSysColor(COLOR_3DFACE); + dat->clText = GetSysColor(COLOR_WINDOWTEXT); + dat->clSelText = GetSysColor(COLOR_HIGHLIGHTTEXT); + dat->clSelBorder = RGB(dat->rgbSelTop.rgbRed, dat->rgbSelTop.rgbGreen, dat->rgbSelTop.rgbBlue); + dat->clHotBorder = RGB(dat->rgbHotTop.rgbRed, dat->rgbHotTop.rgbGreen, dat->rgbHotTop.rgbBlue); + + if (!dat->hFont) dat->hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); +} + +static void MDescButton_FillRect(HDC hdc, int x, int y, int width, int height, COLORREF cl) +{ + int oldMode = SetBkMode(hdc, OPAQUE); + COLORREF oldColor = SetBkColor(hdc, cl); + + RECT rc; SetRect(&rc, x, y, x+width, y+height); + ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0); + + SetBkMode(hdc, oldMode); + SetBkColor(hdc, oldColor); +} + +static void MDescButton_DrawGradient(HDC hdc, int x, int y, int width, int height, RGBQUAD *rgb0, RGBQUAD *rgb1) +{ + int oldMode = SetBkMode(hdc, OPAQUE); + COLORREF oldColor = SetBkColor(hdc, 0); + + RECT rc; SetRect(&rc, x, 0, x+width, 0); + for (int i=y+height; --i >= y; ) { + COLORREF color = RGB( + ((height-i-1)*rgb0->rgbRed + i*rgb1->rgbRed) / height, + ((height-i-1)*rgb0->rgbGreen + i*rgb1->rgbGreen) / height, + ((height-i-1)*rgb0->rgbBlue + i*rgb1->rgbBlue) / height); + rc.top = rc.bottom = i; + ++rc.bottom; + SetBkColor(hdc, color); + ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0); + } + + SetBkMode(hdc, oldMode); + SetBkColor(hdc, oldColor); +} + +static LRESULT MDescButton_OnPaint(HWND hwndDlg, MDescButtonCtrl *dat, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + HBITMAP hBmp, hOldBmp; + RECT temprc; + HFONT hfntSave; + + HDC hdc=BeginPaint(hwndDlg,&ps); + HDC tempDC=CreateCompatibleDC(hdc); + + SIZE titleSize = {0}; + + hBmp=CreateCompatibleBitmap(hdc,dat->width, dat->height); + hOldBmp=(HBITMAP)SelectObject(tempDC,hBmp); + + temprc.left=0; + temprc.right=dat->width; + temprc.top=0; + + //Draw background + if (dat->bMouseInside || (GetFocus() == hwndDlg)) { + MDescButton_FillRect(tempDC, 0, 0, dat->width, dat->height, dat->clSelBorder); + MDescButton_DrawGradient(tempDC, 1, 1, dat->width-2, dat->height-2, &dat->rgbSelTop, &dat->rgbSelBottom); + SetTextColor(tempDC, dat->clSelText); + } + else { + MDescButton_FillRect(tempDC, 0, 0, dat->width, dat->height, dat->clBackground); + SetTextColor(tempDC, dat->clText); + } + + if (dat->hIcon) + DrawIcon(tempDC, DBC_BORDER_SIZE, DBC_BORDER_SIZE, dat->hIcon); + + hfntSave = (HFONT)SelectObject(tempDC, dat->hFont); + SetBkMode(tempDC, TRANSPARENT); + + if (dat->lpzTitle) { + LOGFONT lf; + RECT textRect; + HFONT hfntSave; + + GetObject(dat->hFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + lf.lfHeight *= 1.5; + hfntSave = (HFONT)SelectObject(tempDC, CreateFontIndirect(&lf)); + + textRect.left = DBC_BORDER_SIZE + dat->hIcon ? 32 + DBC_VSPACING : 0; + textRect.right = dat->width - DBC_BORDER_SIZE; + textRect.top = DBC_BORDER_SIZE; + textRect.bottom = dat->height - DBC_BORDER_SIZE; + DrawText(tempDC, dat->lpzTitle, -1, &textRect, DT_TOP|DT_LEFT|DT_END_ELLIPSIS); + GetTextExtentPoint32(tempDC, dat->lpzTitle, lstrlen(dat->lpzTitle), &titleSize); + + DeleteObject(SelectObject(tempDC, hfntSave)); + } + + if (dat->lpzDescription) { + RECT textRect; + textRect.left = DBC_BORDER_SIZE + dat->hIcon ? 32 + DBC_VSPACING : 0; + textRect.right = dat->width - DBC_BORDER_SIZE; + textRect.top = DBC_BORDER_SIZE + titleSize.cy ? titleSize.cy + DBC_HSPACING : 0; + textRect.bottom = dat->height - DBC_BORDER_SIZE; + DrawText(tempDC, dat->lpzDescription, -1, &textRect, DT_TOP|DT_LEFT|DT_WORDBREAK|DT_END_ELLIPSIS); + GetTextExtentPoint32(tempDC, dat->lpzTitle, lstrlen(dat->lpzTitle), &titleSize); + } + + SelectObject(tempDC, hfntSave); + + //Copy to output + BitBlt(hdc,dat->rc.left,dat->rc.top,dat->width,dat->height,tempDC,0,0,SRCCOPY); + SelectObject(tempDC,hOldBmp); + DeleteObject(hBmp); + DeleteDC(tempDC); + EndPaint(hwndDlg,&ps); + + return TRUE; +} + +static LRESULT CALLBACK MDescButtonWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + MDescButtonCtrl *dat = (MDescButtonCtrl *)GetWindowLongPtr(hwndDlg, 0); + switch(msg) { + case WM_NCCREATE: + dat = (MDescButtonCtrl*)mir_alloc(sizeof(MDescButtonCtrl)); + if (dat==NULL) + return FALSE; + + memset(dat, 0, sizeof(MDescButtonCtrl)); + SetWindowLongPtr(hwndDlg, 0, (LONG_PTR)dat); + MDescButton_SetupColors(dat); + return TRUE; + + case WM_SETFONT: + dat->hFont = (HFONT)wParam; + break; + + case WM_SIZE: + GetClientRect(hwndDlg,&dat->rc); + dat->width=dat->rc.right-dat->rc.left; + dat->height=dat->rc.bottom-dat->rc.top; + return TRUE; + + case WM_THEMECHANGED: + case WM_STYLECHANGED: + MDescButton_SetupColors(dat); + return TRUE; + + case WM_MOUSEMOVE: + if (!dat->bMouseInside) { + TRACKMOUSEEVENT tme = {0}; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hwndDlg; + _TrackMouseEvent(&tme); + dat->bMouseInside = TRUE; + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + } + return 0; + + case WM_MOUSELEAVE: + dat->bMouseInside = FALSE; + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return 0; + + case WM_LBUTTONUP: + SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(GetWindowLongPtr(hwndDlg, GWL_ID), 0), 0); + return 0; + + case WM_ERASEBKGND: + return 1; + + case WM_NCPAINT: + InvalidateRect(hwndDlg, NULL, FALSE); + break; + + case WM_PAINT: + MDescButton_OnPaint(hwndDlg, dat, msg, wParam, lParam); + break; + + case DBCM_SETTITLE: + if (dat->lpzTitle) + mir_free(dat->lpzTitle); + if (wParam & MDBCF_UNICODE) + dat->lpzTitle = mir_u2t((WCHAR *)lParam); + else + dat->lpzTitle = mir_a2t((char *)lParam); + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return TRUE; + + case DBCM_SETDESCRIPTION: + if (dat->lpzDescription) + mir_free(dat->lpzDescription); + if (wParam & MDBCF_UNICODE) + dat->lpzDescription = mir_u2t((WCHAR *)lParam); + else + dat->lpzDescription = mir_a2t((char *)lParam); + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return TRUE; + + case DBCM_SETICON: + if (dat->hIcon && !dat->bSharedIcon) + DestroyIcon(dat->hIcon); + + if (wParam & MDBCF_SHAREDICON) { + dat->bSharedIcon = TRUE; + dat->hIcon = (HICON)lParam; + } + else { + dat->bSharedIcon = FALSE; + dat->hIcon = CopyIcon((HICON)lParam); + } + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return TRUE; + + case WM_DESTROY: + if (dat->lpzTitle) + mir_free(dat->lpzTitle); + if (dat->lpzDescription) + mir_free(dat->lpzDescription); + if (dat->hIcon && !dat->bSharedIcon) + DestroyIcon(dat->hIcon); + mir_free(dat); + return TRUE; + } + + return DefWindowProc(hwndDlg, msg, wParam, lParam); +} diff --git a/src/modules/options/filter.cpp b/src/modules/options/filter.cpp new file mode 100644 index 0000000000..116899d44e --- /dev/null +++ b/src/modules/options/filter.cpp @@ -0,0 +1,217 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "filter.h" + +HANDLE hOptionsInitialize; + +int HookFilterEvents() +{ + hOptionsInitialize = HookEvent(ME_OPT_INITIALISE, OnOptionsInitialise); + return 0; +} + +int UnhookFilterEvents() +{ + UnhookEvent(hOptionsInitialize); + return 0; +} + +INT_PTR CALLBACK DlgProcOptSearch(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hWnd); + + CheckDlgButton(hWnd, IDC_ENABLE_KEYWORDFILTERING, DBGetContactSettingWord(NULL, "Options", "EnableKeywordFiltering", TRUE) ? BST_CHECKED : BST_UNCHECKED); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_ENABLE_KEYWORDFILTERING: + SendMessage(GetParent(hWnd), PSM_CHANGED,0,0); + break; + } + break; + + case WM_SETFOCUS: + SetFocus(GetDlgItem(hWnd, IDC_ENABLE_KEYWORDFILTERING)); + break; + + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + DBWriteContactSettingWord(NULL, "Options", "EnableKeywordFiltering", IsDlgButtonChecked(hWnd, IDC_ENABLE_KEYWORDFILTERING)); + break; + } + break; + } + break; + } + + return 0; +} + +int OnOptionsInitialise(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {0}; + + odp.cbSize = sizeof(odp); + odp.position = -190000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_KEYWORDFILTER); + odp.ptszTitle = TranslateT("Options search"); + odp.ptszGroup = TranslateT("Customize"); + odp.groupPosition = 810000000; + odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR; + odp.pfnDlgProc = DlgProcOptSearch; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + + return 0; +} + +CPageList filterStrings(1); + +void AddFilterString(const PageHash key, TCHAR *data) +{ + if (ContainsFilterString(key, data)) return; + + CPageKeywords * values = filterStrings[key]; + if ( values == NULL ) { + values = new CPageKeywords( key ); + filterStrings.insert( values ); + } + values->AddKeyWord( data ); +} + +void ClearFilterStrings() +{ + filterStrings.destroy(); +} + +BOOL ContainsFilterString(const PageHash key, TCHAR *data) +{ + CPageKeywords* values = filterStrings[key]; + return (values) ? values->ContainsString( data ) : FALSE; +} + +void AddTreeViewNodes(HWND hWndDlg, PageHash key, HTREEITEM root) +{ + if (root) { + TCHAR title[2048] = {0}; + + TVITEM item = {0}; + item.mask = TVIF_TEXT; + item.hItem = root; + item.pszText = title; + item.cchTextMax = SIZEOF(title); + + if (TreeView_GetItem(hWndDlg, &item)) + if (_tcslen(title) > 0) + AddFilterString(key, title); + + HTREEITEM child = root; + while (child) { + child = TreeView_GetNextItem(hWndDlg, child, TVGN_CHILD); + AddTreeViewNodes(hWndDlg, key, child); + } + + AddTreeViewNodes(hWndDlg, key, TreeView_GetNextSibling(hWndDlg, root)); + } +} + +void AddDialogString(HWND hWndDlg, const PageHash key) +{ + TCHAR title[2048]; + GetWindowText(hWndDlg, title, SIZEOF( title )); + if (_tcslen(title) > 0) + AddFilterString(key, title); + + TCHAR szClass[64]; + GetClassName(hWndDlg,szClass, SIZEOF(szClass)); + + if (lstrcmpi(szClass, _T("SysTreeView32")) == 0) { + HTREEITEM hItem = TreeView_GetRoot(hWndDlg); + AddTreeViewNodes(hWndDlg, key, hItem); + } + else { + if (lstrcmpi(szClass, _T("listbox")) == 0) { + if (GetWindowStyle(hWndDlg) & LBS_HASSTRINGS) { + int count = ListBox_GetCount(hWndDlg); + for (int i = 0; i < count; i++) { + title[0] = 0; //safety + int res = ListBox_GetText(hWndDlg, i, title); + if (res != LB_ERR) { + title[SIZEOF(title) - 1] = 0; + if (_tcslen(title) > 0) + AddFilterString(key, title); + } } } + } + else { + if (lstrcmpi(szClass, _T("SysListView32")) == 0) { + int count = ListView_GetItemCount(hWndDlg); + for (int i = 0; i < count; i++) { + title[0] = 0; //safety + ListView_GetItemText(hWndDlg, i, 0, title, SIZEOF(title)); + + if (_tcslen(title) > 0) + AddFilterString(key, title); + } } + + if (lstrcmpi(szClass, _T("combobox")) == 0) { + if (GetWindowStyle(hWndDlg) & CBS_HASSTRINGS) { + int count = ComboBox_GetCount(hWndDlg); + for (int i = 0; i < count; i++) { + title[0] = 0; //safety + int res = ComboBox_GetLBText(hWndDlg, i, title); + if (res != CB_ERR) { + title[SIZEOF(title) - 1] = 0; + + if (_tcslen(title) > 0) + AddFilterString(key, title); +} } } } } } } + +static BOOL CALLBACK GetDialogStringsCallback(HWND hWnd,LPARAM lParam) +{ + AddDialogString(hWnd, lParam); + + return TRUE; +} + +void GetDialogStrings(int enableKeywordFiltering, const PageHash key, TCHAR *pluginName, HWND hWnd, TCHAR * group, TCHAR * title, TCHAR * tab, TCHAR * name ) +{ + AddFilterString(key, pluginName); //add the plugin name as keyword + if ( group ) AddFilterString(key, group); + if ( title ) AddFilterString(key, title); + if ( tab ) AddFilterString(key, tab); + if ( name ) AddFilterString(key, name); + + if ((enableKeywordFiltering) && (hWnd != 0)) { + AddDialogString(hWnd, key); + + EnumChildWindows(hWnd, GetDialogStringsCallback, (LPARAM) key); + } +} diff --git a/src/modules/options/filter.h b/src/modules/options/filter.h new file mode 100644 index 0000000000..3db823fcdf --- /dev/null +++ b/src/modules/options/filter.h @@ -0,0 +1,108 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +#ifndef M_OPTIONS_FILTERING_H +#define M_OPTIONS_FILTERING_H + +extern HANDLE hOptionsInitialize; + +int HookFilterEvents(); +int UnhookFilterEvents(); +int OnOptionsInitialise(WPARAM wParam, LPARAM lParam); + +typedef DWORD PageHash; + +void AddFilterString(const PageHash key, const TCHAR *data); +BOOL ContainsFilterString(const PageHash key, TCHAR *data); +void ClearFilterStrings(); +void GetDialogStrings(int enableKeywordFiltering, const PageHash key, TCHAR *pluginName, HWND hWnd, TCHAR * group, TCHAR * title, TCHAR * tab, TCHAR * name ); + +_inline TCHAR * _tcslwr_locale( TCHAR * buf ) +{ + LCMapString( LangPackGetDefaultLocale() , LCMAP_LOWERCASE, buf, (int)_tcslen( buf ), buf, (int)_tcslen( buf ) ); + return buf; +} + +typedef LIST KeywordList; +class CPageKeywords +{ + PageHash _pageHashKey; + KeywordList _pageKeyWords; + static int _KeyWordsSortFunc( const TCHAR* p1, const TCHAR* p2 ) { return _tcscmp( p1, p2 ); }; + +public: + CPageKeywords( PageHash pageHashKey ) : _pageHashKey( pageHashKey ), _pageKeyWords( 1, _KeyWordsSortFunc ) {}; + ~CPageKeywords() + { + for ( int j = 0; j < _pageKeyWords.getCount(); j++ ) + { + TCHAR * data = _pageKeyWords[j]; + mir_free( data ); + } + _pageKeyWords.destroy(); + }; + + void AddKeyWord( TCHAR * ptKeyWord ) + { + TCHAR * plwrWord = _tcslwr_locale( mir_tstrdup( ptKeyWord ) ); + if ( _pageKeyWords.getIndex( plwrWord ) == -1 ) + _pageKeyWords.insert( plwrWord ) ; + else + mir_free( plwrWord ); + }; + + BOOL ContainsString( TCHAR * data ) + { + for ( int i = 0; i < _pageKeyWords.getCount(); i++) + if (_tcsstr(_pageKeyWords[i], data)) + return TRUE; + return FALSE; + } + static int PageSortFunc( const CPageKeywords* p1, const CPageKeywords* p2 ) + { + if (p1->_pageHashKey < p2->_pageHashKey) { return -1; } + else if (p1->_pageHashKey > p2->_pageHashKey) { return 1; } + return 0; + } +}; + +class CPageList : public OBJLIST +{ + CPageList(); +public: + CPageList( int aincr, FTSortFunc afunc = CPageKeywords::PageSortFunc ) : OBJLIST( aincr, afunc ) {}; + CPageKeywords * operator[]( PageHash key ) + { + CPageKeywords keyToSearch( key ); + return this->find( &keyToSearch ); + } + ~CPageList() {}; +}; + + + +int LangPackGetDefaultLocale(); + + +#endif //M_OPTIONS_FILTERING_H + diff --git a/src/modules/options/headerbar.cpp b/src/modules/options/headerbar.cpp new file mode 100644 index 0000000000..a2407d2aab --- /dev/null +++ b/src/modules/options/headerbar.cpp @@ -0,0 +1,372 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2007 Artem Shpynov +Copyright 2000-2007 Miranda ICQ/IM project, + +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "m_iconheader.h" + +extern HINSTANCE hMirandaInst; + + +static BOOL IsAeroMode() +{ + BOOL result; + return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result; +} + +static BOOL IsVSMode() +{ + return isThemeActive && IsWinVerVistaPlus() && isThemeActive(); +} + +//////////////////////////////////////////////////////////////////////////////////// +// Internals + +static LRESULT CALLBACK MHeaderbarWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// structure is used for storing list of tab info +struct MHeaderbarCtrl +{ + __inline void* operator new( size_t size ) + { return mir_calloc( size ); + } + __inline void operator delete( void* p ) + { mir_free( p ); + } + + MHeaderbarCtrl() {} + ~MHeaderbarCtrl() { mir_free( controlsToRedraw ); } + + HWND hwnd; + + // UI info + RECT rc; + int width, height; + HICON hIcon; + + // control colors + RGBQUAD rgbBkgTop, rgbBkgBottom; + COLORREF clText; + + int nControlsToRedraw; + HWND *controlsToRedraw; + + // fonts + HFONT hFont; +}; + +int LoadHeaderbarModule() +{ + WNDCLASSEX wc; + + ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(wc); + wc.lpszClassName = _T("MHeaderbarCtrl"); //MIRANDAHEADERBARCLASS; + wc.lpfnWndProc = MHeaderbarWndProc; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.cbWndExtra = sizeof(MHeaderbarCtrl*); + wc.hbrBackground = 0; //GetStockObject(WHITE_BRUSH); + wc.style = CS_GLOBALCLASS|CS_SAVEBITS; + RegisterClassEx(&wc); + return 0; +} + +static void MHeaderbar_SetupColors(MHeaderbarCtrl *dat) +{ + COLORREF cl; + + cl = GetSysColor(COLOR_WINDOW); + dat->rgbBkgBottom.rgbRed = (dat->rgbBkgTop.rgbRed = GetRValue(cl)) * .95; + dat->rgbBkgBottom.rgbGreen = (dat->rgbBkgTop.rgbGreen = GetGValue(cl)) * .95; + dat->rgbBkgBottom.rgbBlue = (dat->rgbBkgTop.rgbBlue = GetBValue(cl)) * .95; + + dat->clText = GetSysColor(COLOR_WINDOWTEXT); + + if (!dat->hFont) dat->hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); +} + +static void MHeaderbar_FillRect(HDC hdc, int x, int y, int width, int height, COLORREF cl) +{ + int oldMode = SetBkMode(hdc, OPAQUE); + COLORREF oldColor = SetBkColor(hdc, cl); + + RECT rc; SetRect(&rc, x, y, x+width, y+height); + ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0); + + SetBkMode(hdc, oldMode); + SetBkColor(hdc, oldColor); +} + +static void MHeaderbar_DrawGradient(HDC hdc, int x, int y, int width, int height, RGBQUAD *rgb0, RGBQUAD *rgb1) +{ + int i; + + int oldMode = SetBkMode(hdc, OPAQUE); + COLORREF oldColor = SetBkColor(hdc, 0); + + RECT rc; SetRect(&rc, x, 0, x+width, 0); + for (i=y+height; --i >= y; ) + { + COLORREF color = RGB( + ((height-i-1)*rgb0->rgbRed + i*rgb1->rgbRed) / height, + ((height-i-1)*rgb0->rgbGreen + i*rgb1->rgbGreen) / height, + ((height-i-1)*rgb0->rgbBlue + i*rgb1->rgbBlue) / height); + rc.top = rc.bottom = i; + ++rc.bottom; + SetBkColor(hdc, color); + ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0); + } + + SetBkMode(hdc, oldMode); + SetBkColor(hdc, oldColor); +} + +static LRESULT MHeaderbar_OnPaint(HWND hwndDlg, MHeaderbarCtrl *mit, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int iTopSpace = IsAeroMode() ? 0 : 3; + PAINTSTRUCT ps; + HBITMAP hBmp, hOldBmp; + + int titleLength = GetWindowTextLength(hwndDlg) + 1; + TCHAR *szTitle = (TCHAR *)mir_alloc(sizeof(TCHAR) * titleLength); + GetWindowText(hwndDlg, szTitle, titleLength); + + TCHAR *szSubTitle = _tcschr(szTitle, _T('\n')); + if (szSubTitle) *szSubTitle++ = 0; + + HDC hdc=BeginPaint(hwndDlg,&ps); + HDC tempDC=CreateCompatibleDC(hdc); + + BITMAPINFO bmi; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = mit->width; + bmi.bmiHeader.biHeight = -mit->height; // we need this for DrawThemeTextEx + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + hBmp = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); + + hOldBmp=(HBITMAP)SelectObject(tempDC,hBmp); + + if (IsAeroMode()) { + RECT temprc; + temprc.left=0; + temprc.right=mit->width; + temprc.top=0; + temprc.bottom=mit->width; + FillRect(tempDC, &temprc, (HBRUSH)GetStockObject(BLACK_BRUSH)); + + MARGINS margins = {0,0,mit->height,0}; + dwmExtendFrameIntoClientArea(GetParent(hwndDlg), &margins); + + WTA_OPTIONS opts; + opts.dwFlags = opts.dwMask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; + setWindowThemeAttribute(GetParent(hwndDlg), WTA_NONCLIENT, &opts, sizeof(opts)); + } + else { + if (IsVSMode()) + MHeaderbar_FillRect(tempDC, 0, 0, mit->width, mit->height, GetSysColor(COLOR_WINDOW)); + else + MHeaderbar_DrawGradient(tempDC, 0, 0, mit->width, mit->height, &mit->rgbBkgTop, &mit->rgbBkgBottom); + + MHeaderbar_FillRect(tempDC, 0, mit->height-2, mit->width, 1, GetSysColor(COLOR_BTNSHADOW)); + MHeaderbar_FillRect(tempDC, 0, mit->height-1, mit->width, 1, GetSysColor(COLOR_BTNHIGHLIGHT)); + } + + HFONT hFont = mit->hFont; + SetBkMode(tempDC, TRANSPARENT); + SetTextColor(tempDC, mit->clText); + + LOGFONT lf; + GetObject(hFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + HFONT hFntBold = CreateFontIndirect(&lf); + + if (mit->hIcon) + DrawIcon(tempDC, 10, iTopSpace, mit->hIcon); + else { + HICON hIcon = (HICON)SendMessage(GetParent(hwndDlg), WM_GETICON, ICON_BIG, 0); + if (hIcon == NULL) + hIcon = (HICON)SendMessage(GetParent(hwndDlg), WM_GETICON, ICON_SMALL, 0); + DrawIcon(tempDC, 10, iTopSpace, hIcon); + } + + RECT textRect; + textRect.left=50; + textRect.right=mit->width; + textRect.top=2 + iTopSpace; + textRect.bottom=GetSystemMetrics(SM_CYICON)-2 + iTopSpace; + + if (IsAeroMode()) { + DTTOPTS dto = {0}; + dto.dwSize = sizeof(dto); + dto.dwFlags = DTT_COMPOSITED|DTT_GLOWSIZE; + dto.iGlowSize = 10; + + HANDLE hTheme = openThemeData(hwndDlg, L"Window"); + textRect.left=50; + SelectObject(tempDC, hFntBold); + + wchar_t *szTitleW = mir_t2u(szTitle); + drawThemeTextEx(hTheme, tempDC, WP_CAPTION, CS_ACTIVE, szTitleW, -1, DT_TOP|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX|DT_NOCLIP|DT_END_ELLIPSIS, &textRect, &dto); + mir_free(szTitleW); + + if (szSubTitle) { + textRect.left=66; + SelectObject(tempDC, hFont); + + wchar_t *szSubTitleW = mir_t2u(szSubTitle); + drawThemeTextEx(hTheme, tempDC, WP_CAPTION, CS_ACTIVE, szSubTitleW, -1, DT_BOTTOM|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX|DT_NOCLIP|DT_END_ELLIPSIS, &textRect, &dto); + mir_free(szSubTitleW); + } + closeThemeData(hTheme); + } + else { + textRect.left=50; + SelectObject(tempDC, hFntBold); + DrawText(tempDC, szTitle, -1, &textRect, DT_TOP|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX|DT_NOCLIP|DT_END_ELLIPSIS); + + if (szSubTitle) { + textRect.left=66; + SelectObject(tempDC, hFont); + DrawText(tempDC, szSubTitle, -1, &textRect, DT_BOTTOM|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX|DT_NOCLIP|DT_END_ELLIPSIS); + } } + + DeleteObject(hFntBold); + + mir_free(szTitle); + + //Copy to output + if (mit->nControlsToRedraw) + { + RECT temprc; + temprc.left=0; + temprc.right=mit->width; + temprc.top=0; + temprc.bottom=mit->width; + HRGN hRgn = CreateRectRgnIndirect(&temprc); + + for (int i = 0; i < mit->nControlsToRedraw; ++i) + { + GetWindowRect(mit->controlsToRedraw[i], &temprc); + MapWindowPoints(NULL, hwndDlg, (LPPOINT)&temprc, 2); + HRGN hRgnTmp = CreateRectRgnIndirect(&temprc); + CombineRgn(hRgn, hRgn, hRgnTmp, RGN_DIFF); + DeleteObject(hRgnTmp); + } + SelectClipRgn(hdc,hRgn); + DeleteObject(hRgn); + } + + BitBlt(hdc,mit->rc.left,mit->rc.top,mit->width,mit->height,tempDC,0,0,SRCCOPY); + + SelectClipRgn(hdc,NULL); + + SelectObject(tempDC,hOldBmp); + DeleteObject(hBmp); + DeleteDC(tempDC); + + EndPaint(hwndDlg,&ps); + + return TRUE; +} + +static LRESULT CALLBACK MHeaderbarWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + MHeaderbarCtrl* itc = (MHeaderbarCtrl *)GetWindowLongPtr(hwndDlg, 0); + switch(msg) { + case WM_NCCREATE: + itc = new MHeaderbarCtrl; //(MHeaderbarCtrl*)mir_alloc(sizeof(MHeaderbarCtrl)); + if (itc==NULL) + return FALSE; + + SetWindowLongPtr(hwndDlg, 0, (LONG_PTR)itc); + MHeaderbar_SetupColors(itc); + + { HWND hParent = GetParent(hwndDlg); + RECT rcWnd; GetWindowRect(hwndDlg, &rcWnd); + itc->controlsToRedraw = 0; + itc->nControlsToRedraw = 0; + for (HWND hChild = FindWindowEx(hParent, NULL, NULL, NULL); hChild; hChild = FindWindowEx(hParent, hChild, NULL, NULL)) + { + if (hChild != hwndDlg) + { + RECT rcChild; GetWindowRect(hChild, &rcChild); + RECT rc; + IntersectRect(&rc, &rcChild, &rcWnd); + if (!IsRectEmpty(&rc)) + { + ++itc->nControlsToRedraw; + itc->controlsToRedraw = (HWND *)mir_realloc(itc->controlsToRedraw, sizeof(HWND) * itc->nControlsToRedraw); + itc->controlsToRedraw[itc->nControlsToRedraw - 1] = hChild; + } + } + } + } + + break; + + case WM_SETFONT: + itc->hFont = (HFONT)wParam; + break; + + case WM_SIZE: + GetClientRect(hwndDlg,&itc->rc); + itc->width=itc->rc.right-itc->rc.left; + itc->height=itc->rc.bottom-itc->rc.top; + return TRUE; + + case WM_THEMECHANGED: + case WM_STYLECHANGED: + MHeaderbar_SetupColors(itc); + return TRUE; + + case WM_LBUTTONDOWN: + SendMessage(GetParent(hwndDlg), WM_SYSCOMMAND, 0xF012, 0); + return 0; + + case WM_SETICON: + if (wParam < 3) { + itc->hIcon = (HICON)lParam; + InvalidateRect(hwndDlg, NULL, FALSE); + } + break; + + case WM_ERASEBKGND: + return 1; + + case WM_NCPAINT: + InvalidateRect(hwndDlg, NULL, FALSE); + break; + + case WM_PAINT: + MHeaderbar_OnPaint(hwndDlg, itc, msg, wParam, lParam); + break; + + case WM_DESTROY: + delete itc; + break; + } + return DefWindowProc(hwndDlg, msg, wParam, lParam); +} diff --git a/src/modules/options/iconheader.cpp b/src/modules/options/iconheader.cpp new file mode 100644 index 0000000000..9ab44b0852 --- /dev/null +++ b/src/modules/options/iconheader.cpp @@ -0,0 +1,545 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2007 Artem Shpynov +Copyright 2000-2007 Miranda ICQ/IM project, + +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "m_iconheader.h" + + +extern HINSTANCE hMirandaInst; + +static BOOL IsAeroMode() +{ + BOOL result; + return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result; +} + +static BOOL IsVSMode() +{ + return isThemeActive && IsWinVerVistaPlus() && isThemeActive(); +} + +//////////////////////////////////////////////////////////////////////////////////// +// Internals + +#define ITC_BORDER_SIZE 3 + +static LRESULT CALLBACK MIcoTabWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// structure is used for storing list of tab info +struct MIcoTabCtrl +{ + __inline void* operator new( size_t size ) + { return mir_calloc( size ); + } + __inline void operator delete( void* p ) + { mir_free( p ); + } + + MIcoTabCtrl(): pList(1) {} + + HWND hwnd; + int nSelectedIdx, nHotIdx; + LIST pList; + + // UI info + BOOL bMouseInside; + RECT rc; + int width, height; + int itemWidth, itemHeight; + + //background bitmap + HBITMAP hBkgBmp; + HBITMAP hBkgOldBmp; + HDC hBkgDC; + SIZE BkgSize; + + // control colors + RGBQUAD rgbBkgTop, rgbBkgBottom; + RGBQUAD rgbSelTop, rgbSelBottom; + RGBQUAD rgbHotTop, rgbHotBottom; + COLORREF clText; + COLORREF clSelText, clSelBorder; + COLORREF clHotText, clHotBorder; + + // fonts + HFONT hFont; +}; + +typedef void (*ItemDestuctor)(void*); + +static void MITListDestructor(void * adr) +{ + MIcoTab * mit=(MIcoTab *)adr; + mir_free(mit->tcsName); + if (mit->hIcon && !(mit->flag&MITCF_SHAREDICON)) + DestroyIcon(mit->hIcon); + mir_free(adr); +} + +void li_ListDestruct(LIST &pList, ItemDestuctor pItemDestructor) +{ + for (int i=0; irgbBkgBottom.rgbRed = (dat->rgbBkgTop.rgbRed = GetRValue(cl)) * .95; + dat->rgbBkgBottom.rgbGreen = (dat->rgbBkgTop.rgbGreen = GetGValue(cl)) * .95; + dat->rgbBkgBottom.rgbBlue = (dat->rgbBkgTop.rgbBlue = GetBValue(cl)) * .95; + + cl = GetSysColor(COLOR_HIGHLIGHT); + dat->rgbSelTop.rgbRed = (dat->rgbSelBottom.rgbRed = GetRValue(cl)) * .75; + dat->rgbSelTop.rgbGreen = (dat->rgbSelBottom.rgbGreen = GetGValue(cl)) * .75; + dat->rgbSelTop.rgbBlue = (dat->rgbSelBottom.rgbBlue = GetBValue(cl)) * .75; + + dat->rgbHotTop.rgbRed = (dat->rgbSelTop.rgbRed + 255) / 2; + dat->rgbHotTop.rgbGreen = (dat->rgbSelTop.rgbGreen + 255) / 2; + dat->rgbHotTop.rgbBlue = (dat->rgbSelTop.rgbBlue + 255) / 2; + + dat->rgbHotBottom.rgbRed = (dat->rgbSelBottom.rgbRed + 255) / 2; + dat->rgbHotBottom.rgbGreen = (dat->rgbSelBottom.rgbGreen + 255) / 2; + dat->rgbHotBottom.rgbBlue = (dat->rgbSelBottom.rgbBlue + 255) / 2; + + dat->clText = GetSysColor(COLOR_WINDOWTEXT); + dat->clSelText = GetSysColor(COLOR_HIGHLIGHTTEXT); + dat->clSelBorder = RGB(dat->rgbSelTop.rgbRed, dat->rgbSelTop.rgbGreen, dat->rgbSelTop.rgbBlue); + dat->clHotBorder = RGB(dat->rgbHotTop.rgbRed, dat->rgbHotTop.rgbGreen, dat->rgbHotTop.rgbBlue); + + if (!dat->hFont) dat->hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); +} + +static void MIcoTab_FillRect(HDC hdc, int x, int y, int width, int height, COLORREF cl) +{ + int oldMode = SetBkMode(hdc, OPAQUE); + COLORREF oldColor = SetBkColor(hdc, cl); + + RECT rc; SetRect(&rc, x, y, x+width, y+height); + ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0); + + SetBkMode(hdc, oldMode); + SetBkColor(hdc, oldColor); +} + +static void MIcoTab_DrawGradient(HDC hdc, int x, int y, int width, int height, RGBQUAD *rgb0, RGBQUAD *rgb1) +{ + int oldMode = SetBkMode(hdc, OPAQUE); + COLORREF oldColor = SetBkColor(hdc, 0); + + RECT rc; SetRect(&rc, x, 0, x+width, 0); + for ( int i=y+height; --i >= y; ) { + COLORREF color = RGB( + ((height-i-1)*rgb0->rgbRed + i*rgb1->rgbRed) / height, + ((height-i-1)*rgb0->rgbGreen + i*rgb1->rgbGreen) / height, + ((height-i-1)*rgb0->rgbBlue + i*rgb1->rgbBlue) / height); + rc.top = rc.bottom = i; + ++rc.bottom; + SetBkColor(hdc, color); + ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0); + } + + SetBkMode(hdc, oldMode); + SetBkColor(hdc, oldColor); +} + +static void MIcoTab_DrawItem(HWND hwnd, HDC hdc, MIcoTabCtrl *dat, MIcoTab *tab, int i) +{ + int iTopSpace = IsAeroMode() ? 0 : ITC_BORDER_SIZE; + int itemX = ITC_BORDER_SIZE + dat->itemWidth * i; + int iconTop = iTopSpace + 5; + int textTop = iconTop + 32 + 3; + + HFONT hFntSave = NULL; + + if (dat->nSelectedIdx == i) { + LOGFONT lf; + GetObject(GetCurrentObject(hdc, OBJ_FONT), sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + hFntSave = (HFONT)SelectObject(hdc, CreateFontIndirect(&lf)); + + if (IsVSMode()) { + RECT rc; + rc.left = itemX; + rc.top = iTopSpace; + rc.right = itemX + dat->itemWidth; + rc.bottom = iTopSpace + dat->itemHeight; + HANDLE hTheme = openThemeData(hwnd, L"ListView"); + if (dat->nHotIdx == i || GetFocus() == hwnd) + drawThemeBackground(hTheme, hdc, LVP_LISTITEM, LISS_HOTSELECTED, &rc, NULL); + else + drawThemeBackground(hTheme, hdc, LVP_LISTITEM, LISS_SELECTED, &rc, NULL); + + closeThemeData(hTheme); + } + else { + MIcoTab_FillRect(hdc, itemX, ITC_BORDER_SIZE, dat->itemWidth, dat->itemHeight, dat->clSelBorder); + MIcoTab_DrawGradient(hdc, itemX+1, ITC_BORDER_SIZE+1, dat->itemWidth-2, dat->itemHeight-2, &dat->rgbSelTop, &dat->rgbSelBottom); + } + SetTextColor(hdc, dat->clSelText); + } + else if (dat->nHotIdx == i) { + if (IsVSMode()) { + RECT rc; + rc.left = itemX; + rc.top = iTopSpace; + rc.right = itemX + dat->itemWidth; + rc.bottom = iTopSpace + dat->itemHeight; + setWindowTheme(hwnd, L"explorer", NULL); + HANDLE hTheme = openThemeData(hwnd, L"ListView"); + drawThemeBackground(hTheme, hdc, LVP_LISTITEM, LISS_HOT, &rc, NULL); + closeThemeData(hTheme); + } + else { + MIcoTab_FillRect(hdc, itemX, ITC_BORDER_SIZE, dat->itemWidth, dat->itemHeight, dat->clHotBorder); + MIcoTab_DrawGradient(hdc, itemX+1, ITC_BORDER_SIZE+1, dat->itemWidth-2, dat->itemHeight-2, &dat->rgbHotTop, &dat->rgbHotBottom); + } + SetTextColor(hdc, dat->clHotText); + } + else SetTextColor(hdc, dat->clText); + + RECT textRect; + textRect.left=itemX; + textRect.right=itemX+dat->itemWidth; + textRect.top=textTop; + textRect.bottom=iconTop+dat->itemHeight; + DrawIcon(hdc,itemX+dat->itemWidth/2-16, iconTop, tab->hIcon); + + if (IsVSMode()) { + DTTOPTS dto = {0}; + dto.dwSize = sizeof(dto); + dto.dwFlags = DTT_COMPOSITED|DTT_GLOWSIZE; + dto.iGlowSize = 10; + HANDLE hTheme = openThemeData(hwnd, L"Window"); + wchar_t *tcsNameW = mir_t2u(tab->tcsName); + drawThemeTextEx(hTheme, hdc, WP_CAPTION, CS_ACTIVE, tcsNameW, -1, DT_VCENTER|DT_CENTER|DT_END_ELLIPSIS, &textRect, &dto); + mir_free(tcsNameW); + closeThemeData(hTheme); + } + else DrawText(hdc,tab->tcsName,-1,&textRect, DT_VCENTER|DT_CENTER|DT_END_ELLIPSIS); + + if (hFntSave) + DeleteObject(SelectObject(hdc, hFntSave)); +} + +static LRESULT MIcoTab_OnPaint(HWND hwndDlg, MIcoTabCtrl *mit, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + HBITMAP hBmp, hOldBmp; + RECT temprc; + int i; + + HDC hdc=BeginPaint(hwndDlg,&ps); + HDC tempDC=CreateCompatibleDC(hdc); + + HFONT hFont = 0; + + BITMAPINFO bmi; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = mit->width; + bmi.bmiHeader.biHeight = -mit->height; // we need this for DrawThemeTextEx + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + hBmp = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); + + hOldBmp=(HBITMAP)SelectObject(tempDC,hBmp); + + if (IsAeroMode()) { + temprc.left=0; + temprc.right=mit->width; + temprc.top=0; + temprc.bottom=mit->width; + FillRect(tempDC, &temprc, (HBRUSH)GetStockObject(BLACK_BRUSH)); + } + else { + if (mit->hBkgBmp) + StretchBlt(tempDC,0,0,mit->width,mit->height,mit->hBkgDC,0,0,mit->BkgSize.cx,mit->BkgSize.cy,SRCCOPY); + else { + if (IsVSMode()) + MIcoTab_FillRect(tempDC, 0, 0, mit->width, mit->height, GetSysColor(COLOR_WINDOW)); + else + MIcoTab_DrawGradient(tempDC, 0, 0, mit->width, mit->height, &mit->rgbBkgTop, &mit->rgbBkgBottom); + + MIcoTab_FillRect(tempDC, 0, mit->height-2, mit->width, 1, GetSysColor(COLOR_BTNSHADOW)); + MIcoTab_FillRect(tempDC, 0, mit->height-1, mit->width, 1, GetSysColor(COLOR_BTNHIGHLIGHT)); + } } + + //Draw Items + hFont = mit->hFont; + SelectObject(tempDC,hFont); + SetBkMode(tempDC,TRANSPARENT); + + for (i=0; ipList.getCount(); i++) { + MIcoTab *tab = (MIcoTab *)mit->pList[i]; + MIcoTab_DrawItem(hwndDlg, tempDC, mit, tab, i); + } + + //Copy to output + BitBlt(hdc,mit->rc.left,mit->rc.top,mit->width,mit->height,tempDC,0,0,SRCCOPY); + SelectObject(tempDC,hOldBmp); + DeleteObject(hBmp); + DeleteDC(tempDC); + + EndPaint(hwndDlg,&ps); + + return TRUE; +} + +static LRESULT CALLBACK MIcoTabWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + MIcoTabCtrl* itc = (MIcoTabCtrl *)GetWindowLongPtr(hwndDlg, 0); + switch(msg) { + case WM_NCCREATE: + itc = new MIcoTabCtrl; //(MIcoTabCtrl*)mir_alloc(sizeof(MIcoTabCtrl)); + if (itc==NULL) return FALSE; + itc->nSelectedIdx=-1; + itc->nHotIdx=-1; + itc->bMouseInside = FALSE; + SetWindowLongPtr(hwndDlg, 0, (LONG_PTR)itc); + MIcoTab_SetupColors(itc); + + if (IsAeroMode()) { + RECT rc; GetWindowRect(hwndDlg, &rc); + MARGINS margins = {0,0,rc.bottom-rc.top,0}; + dwmExtendFrameIntoClientArea(GetParent(hwndDlg), &margins); + } + + return TRUE; + + case WM_SETFONT: + itc->hFont = (HFONT)wParam; + break; + + case WM_SIZE: + GetClientRect(hwndDlg,&itc->rc); + itc->width=itc->rc.right-itc->rc.left; + itc->height=itc->rc.bottom-itc->rc.top; + + if (itc->pList.getCount()) { + itc->itemWidth=(itc->width-2*ITC_BORDER_SIZE)/itc->pList.getCount(); + itc->itemHeight=itc->height-2*ITC_BORDER_SIZE-2; + } + else itc->itemWidth = itc->itemHeight = 0; + return TRUE; + + case WM_THEMECHANGED: + case WM_STYLECHANGED: + MIcoTab_SetupColors(itc); + return TRUE; + + case WM_MOUSEMOVE: + if (!itc->bMouseInside) { + TRACKMOUSEEVENT tme = {0}; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hwndDlg; + _TrackMouseEvent(&tme); + itc->bMouseInside = TRUE; + } + + itc->nHotIdx = (LOWORD(lParam) - ITC_BORDER_SIZE) / itc->itemWidth; + if (itc->nHotIdx >= itc->pList.getCount()) + itc->nHotIdx = -1; + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return 0; + + case WM_MOUSELEAVE: + itc->bMouseInside = FALSE; + itc->nHotIdx = -1; + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return 0; + + case WM_LBUTTONUP: + if ((itc->nHotIdx >= 0) && (itc->nHotIdx != itc->nSelectedIdx)) + { + itc->nSelectedIdx = itc->nHotIdx; + SetWindowText(hwndDlg, itc->pList[itc->nSelectedIdx]->tcsName); + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + SendMessage(GetParent(hwndDlg), WM_COMMAND, + MAKEWPARAM(GetWindowLongPtr(hwndDlg, GWL_ID), ITCN_SELCHANGED), + itc->nSelectedIdx); + } + return 0; + + case WM_SETFOCUS: + case WM_KILLFOCUS: + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + break; + + case WM_MOUSEACTIVATE: + SetFocus(hwndDlg); + return MA_ACTIVATE; + + case WM_GETDLGCODE: + { + if (lParam) + { + MSG *msg = (MSG *) lParam; + if (msg->message == WM_KEYDOWN) + { + if (msg->wParam == VK_TAB) + return 0; + if (msg->wParam == VK_ESCAPE) + return 0; + } else + if (msg->message == WM_CHAR) + { + if (msg->wParam == '\t') + return 0; + if (msg->wParam == 27) + return 0; + } + } + return DLGC_WANTMESSAGE; + } + + case WM_KEYDOWN: + { + int newIdx = itc->nSelectedIdx; + switch (wParam) + { + case VK_NEXT: + case VK_RIGHT: + newIdx++; + break; + case VK_PRIOR: + case VK_LEFT: + newIdx--; + break; + } + if ((newIdx >= 0) && (newIdx < itc->pList.getCount()) && (newIdx != itc->nSelectedIdx)) + { + itc->nSelectedIdx = newIdx; + SetWindowText(hwndDlg, itc->pList[itc->nSelectedIdx]->tcsName); + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + SendMessage(GetParent(hwndDlg), WM_COMMAND, + MAKEWPARAM(GetWindowLongPtr(hwndDlg, GWL_ID), ITCN_SELCHANGEDKBD), + itc->nSelectedIdx); + } + return 0; + } + + case WM_ERASEBKGND: + return 1; + + case WM_NCPAINT: + InvalidateRect(hwndDlg, NULL, FALSE); + break; + + case WM_PAINT: + MIcoTab_OnPaint(hwndDlg, itc, msg, wParam, lParam); + break; + + case ITCM_SETBACKGROUND: + itc->hBkgBmp=(HBITMAP)lParam; + if (!itc->hBkgDC) + itc->hBkgDC = CreateCompatibleDC(NULL); + itc->hBkgOldBmp = (HBITMAP)SelectObject(itc->hBkgDC, itc->hBkgBmp); + { + BITMAPINFO bmp; + GetObject(itc->hBkgBmp, sizeof(bmp), &bmp); + itc->BkgSize.cx=bmp.bmiHeader.biWidth; + itc->BkgSize.cy=bmp.bmiHeader.biHeight; + } + return TRUE; + + case ITCM_ADDITEM: + { + MIcoTab* pMit=(MIcoTab *)wParam; + if (!pMit) + return FALSE; + + MIcoTab* pListMit=(MIcoTab *)mir_calloc(sizeof(MIcoTab)); + pListMit->flag=pMit->flag; + pListMit->data=pMit->data; + if (pMit->flag & MITCF_UNICODE) + pListMit->tcsName=mir_u2t(pMit->lpwzName); + else + pListMit->tcsName=mir_a2t(pMit->lpzName); + if (pMit->hIcon) { + if (pListMit->flag&MITCF_SHAREDICON) + pListMit->hIcon=pMit->hIcon; + else + pListMit->hIcon=CopyIcon(pMit->hIcon); + } + itc->pList.insert(pListMit); + + itc->itemWidth=(itc->width-2*ITC_BORDER_SIZE)/itc->pList.getCount(); + itc->itemHeight=itc->height-2*ITC_BORDER_SIZE-2; + + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + return TRUE; + } + + case ITCM_SETSEL: + if ( wParam >= 0 && (int)wParam < itc->pList.getCount()) { + itc->nSelectedIdx = wParam; + SetWindowText(hwndDlg, itc->pList[itc->nSelectedIdx]->tcsName); + RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE); + SendMessage(GetParent(hwndDlg), WM_COMMAND, + MAKEWPARAM(GetWindowLongPtr(hwndDlg, GWL_ID), ITCN_SELCHANGED), + itc->nSelectedIdx); + } + return TRUE; + + case ITCM_GETSEL: + return itc->nSelectedIdx; + + case ITCM_GETITEMDATA: + if ( wParam >= 0 && (int)wParam < itc->pList.getCount()) + return ((MIcoTab *)itc->pList[wParam])->data; + return 0; + + case WM_DESTROY: + if (itc->hBkgDC) { + SelectObject(itc->hBkgDC, itc->hBkgOldBmp); + DeleteDC(itc->hBkgDC); + } + li_ListDestruct(itc->pList,MITListDestructor); + delete itc; + return TRUE; + } + return DefWindowProc(hwndDlg, msg, wParam, lParam); +} diff --git a/src/modules/options/options.cpp b/src/modules/options/options.cpp new file mode 100644 index 0000000000..0366d99062 --- /dev/null +++ b/src/modules/options/options.cpp @@ -0,0 +1,1521 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "filter.h" + +#define OPENOPTIONSDIALOG_OLD_SIZE 12 + +#define FILTER_TIMEOUT_TIMER 10012 + +#define ALL_MODULES_FILTER _T("") +#define CORE_MODULES_FILTER _T("") + +static HANDLE hOptionsInitEvent; +static HWND hwndOptions=NULL; +static HWND hFilterSearchWnd = NULL; + +// Thread for search keywords in dialogs +static BYTE bSearchState = 0; // 0 - not executed; 1 - in progress; 2 - completed; +static int FilterPage = 0; +static int FilterLoadProgress = 100; +static int FilterTimerId = 0; + +char * GetPluginNameByInstance( HINSTANCE hInstance ); + +struct OptionsPageInit +{ + int pageCount; + OPTIONSDIALOGPAGE *odp; +}; + +struct DlgTemplateExBegin +{ + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; +}; + +struct OptionsPageData +{ + DLGTEMPLATE *pTemplate; + DLGPROC dlgProc; + HINSTANCE hInst; + HTREEITEM hTreeItem; + HWND hwnd; + int changed; + int simpleHeight,expertHeight; + int simpleWidth,expertWidth; + int simpleBottomControlId,simpleRightControlId; + int nExpertOnlyControls; + UINT *expertOnlyControls; + DWORD flags; + TCHAR *pszTitle, *pszGroup, *pszTab; + BOOL insideTab; + LPARAM dwInitParam; + + int offsetX; + int offsetY; +}; + +struct OptionsDlgData +{ + int pageCount; + int currentPage; + HTREEITEM hCurrentPage; + struct OptionsPageData *opd; + RECT rcDisplay; + RECT rcTab; + HFONT hBoldFont; + TCHAR szFilterString[1024]; +}; + +static HTREEITEM FindNamedTreeItemAtRoot(HWND hwndTree, const TCHAR* name) +{ + TVITEM tvi; + TCHAR str[128]; + + tvi.mask = TVIF_TEXT; + tvi.pszText = str; + tvi.cchTextMax = SIZEOF( str ); + tvi.hItem = TreeView_GetRoot( hwndTree ); + while( tvi.hItem != NULL ) { + SendMessage( hwndTree, TVM_GETITEM, 0, (LPARAM)&tvi ); + if( !_tcsicmp( str,name )) + return tvi.hItem; + + tvi.hItem = TreeView_GetNextSibling( hwndTree, tvi.hItem ); + } + return NULL; +} + +static HTREEITEM FindNamedTreeItemAtChildren(HWND hwndTree, HTREEITEM hItem, const TCHAR* name) +{ + TVITEM tvi; + TCHAR str[128]; + + tvi.mask = TVIF_TEXT; + tvi.pszText = str; + tvi.cchTextMax = SIZEOF( str ); + tvi.hItem = TreeView_GetChild( hwndTree, hItem ); + while( tvi.hItem != NULL ) { + SendMessage( hwndTree, TVM_GETITEM, 0, (LPARAM)&tvi ); + if( !_tcsicmp( str,name )) + return tvi.hItem; + + tvi.hItem = TreeView_GetNextSibling( hwndTree, tvi.hItem ); + } + return NULL; +} + +static BOOL CALLBACK BoldGroupTitlesEnumChildren(HWND hwnd,LPARAM lParam) +{ + TCHAR szClass[64]; + + GetClassName(hwnd,szClass,SIZEOF(szClass)); + if(!lstrcmp(szClass,_T("Button")) && (GetWindowLongPtr(hwnd,GWL_STYLE)&0x0F)==BS_GROUPBOX) + SendMessage(hwnd,WM_SETFONT,lParam,0); + return TRUE; +} + +struct MoveChildParam +{ + HWND hDlg; + POINT offset; +}; +static BOOL CALLBACK MoveEnumChildren(HWND hwnd,LPARAM lParam) +{ + struct MoveChildParam * param = ( struct MoveChildParam *) lParam; + + RECT rcWnd; + GetWindowRect( hwnd, &rcWnd); + + HWND hwndParent = GetParent( hwnd ); + if ( hwndParent != param->hDlg ) + return TRUE; // Do not move subchilds + + POINT pt; pt.x = 0; pt.y = 0; + + ClientToScreen( hwndParent, &pt ); + OffsetRect( &rcWnd, -pt.x, -pt.y ); + + SetWindowPos( hwnd, NULL, rcWnd.left + param->offset.x, rcWnd.top + param->offset.y, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE ); + + return TRUE; +} + +#define OPTSTATE_PREFIX "s_" + +static void SaveOptionsTreeState(HWND hdlg) +{ + TVITEMA tvi; + char buf[130],str[128]; + tvi.mask = TVIF_TEXT | TVIF_STATE; + tvi.pszText = str; + tvi.cchTextMax = SIZEOF(str); + tvi.hItem = TreeView_GetRoot( GetDlgItem( hdlg, IDC_PAGETREE )); + while ( tvi.hItem != NULL ) { + if ( SendMessageA( GetDlgItem(hdlg,IDC_PAGETREE), TVM_GETITEMA, 0, (LPARAM)&tvi )) { + mir_snprintf(buf, SIZEOF(buf), "%s%s",OPTSTATE_PREFIX,str); + DBWriteContactSettingByte(NULL,"Options",buf,(BYTE)((tvi.state&TVIS_EXPANDED)?1:0)); + } + tvi.hItem = TreeView_GetNextSibling( GetDlgItem( hdlg, IDC_PAGETREE ), tvi.hItem ); +} } + +#define DM_FOCUSPAGE (WM_USER+10) +#define DM_REBUILDPAGETREE (WM_USER+11) + +static void ThemeDialogBackground(HWND hwnd, BOOL tabbed) +{ + if (enableThemeDialogTexture) + enableThemeDialogTexture(hwnd, (tabbed ? ETDT_ENABLE : ETDT_DISABLE) | ETDT_USETABTEXTURE); +} + +static int lstrcmpnull(TCHAR *str1, TCHAR *str2) +{ + if ( str1 == NULL && str2 == NULL ) + return 0; + if ( str1 != NULL && str2 == NULL ) + return 1; + if ( str1 == NULL && str2 != NULL ) + return -1; + + return lstrcmp(str1, str2); +} + +static TCHAR *GetPluginName(HINSTANCE hInstance, TCHAR *buffer, int size) +{ + TCHAR tszModuleName[MAX_PATH]; + GetModuleFileName(hInstance, tszModuleName, SIZEOF(tszModuleName)); + TCHAR *dllName = _tcsrchr(tszModuleName,'\\'); + if (!dllName) + { + dllName = tszModuleName; + } + else { + dllName++; + } + + _tcsncpy(buffer, dllName, size); + + return buffer; +} + +PageHash GetPluginPageHash(const OptionsPageData *page) +{ + return hashstr(page->pszGroup) + hashstr(page->pszTitle) + hashstr(page->pszTab); +} + +static void FindFilterStrings(int enableKeywordFiltering, int current, HWND hWndParent, const OptionsPageData *page) +{ + TCHAR pluginName[MAX_PATH]; + HWND hWnd = 0; + if (enableKeywordFiltering) { + if (current) + hWnd = page->hwnd; + else + { + hWnd = CreateDialogIndirectParamA(page->hInst, page->pTemplate, hWndParent, page->dlgProc, page->dwInitParam); //create the options dialog page so we can parse it + ShowWindow(hWnd, SW_HIDE); //make sure it's hidden + } } + + DWORD key = GetPluginPageHash(page); //get the plugin page hash + + TCHAR * PluginFullName = NULL; + char * temp = GetPluginNameByInstance( page->hInst ); + if ( temp ) PluginFullName = mir_a2t( temp ); + GetDialogStrings(enableKeywordFiltering, key, GetPluginName(page->hInst, pluginName, SIZEOF(pluginName)), hWnd, page->pszGroup, page->pszTitle, page->pszTab, PluginFullName ); + if ( PluginFullName ) mir_free( PluginFullName ) ; + + if (enableKeywordFiltering && !current) + DestroyWindow(hWnd); //destroy the page, we're done with it +} + +static int MatchesFilter(const OptionsPageData *page, TCHAR *szFilterString) +{ + DWORD key = GetPluginPageHash(page); + + return ContainsFilterString(key, szFilterString); +} + +static WNDPROC OptionsFilterDefaultProc = NULL; + +static LRESULT CALLBACK OptionsFilterSubclassProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message != WM_PAINT && message != WM_PRINT) + return CallWindowProc(OptionsFilterDefaultProc, hWnd, message, wParam, lParam ); + + if ( GetFocus() == hWnd || GetWindowTextLength( hWnd ) ) + return CallWindowProc(OptionsFilterDefaultProc, hWnd, message, wParam, lParam ); + + RECT rc; + GetClientRect( hWnd, &rc); + HDC hdc; + PAINTSTRUCT paint; + + if (message == WM_PAINT) + hdc = BeginPaint( hWnd, &paint); + else + hdc = (HDC)wParam; + + TCHAR buf[255]; + if ( bSearchState==1 && FilterLoadProgress < 100 && FilterLoadProgress > 0 ) + mir_sntprintf( buf, SIZEOF(buf), TranslateT("Loading... %d%%"), FilterLoadProgress ); + else + mir_sntprintf( buf, SIZEOF(buf), TranslateT( "Search" ) ); + + BOOL bDrawnByTheme = FALSE; + + int oldMode = SetBkMode( hdc, TRANSPARENT ); + + if ( openThemeData ) { + HTHEME hTheme = openThemeData( hWnd, L"EDIT"); + if ( hTheme ) { + if ( isThemeBackgroundPartiallyTransparent( hTheme, EP_EDITTEXT, ETS_NORMAL )) + drawThemeParentBackground( hWnd, hdc, &rc ); + + RECT rc2; + getThemeBackgroundContentRect( hTheme, hdc, EP_EDITTEXT, ETS_NORMAL, &rc, &rc2 ); + rc2.top = 2 * rc.top - rc2.top; + rc2.left = 2 * rc.left - rc2.left; + rc2.bottom = 2 * rc.bottom - rc2.bottom; + rc2.right = 2 * rc.right - rc2.right; + + drawThemeBackground( hTheme, hdc, EP_EDITTEXT, ETS_NORMAL, &rc2, &rc ); + HFONT hFont = (HFONT) SendMessage(hWnd, WM_GETFONT, 0, 0); + HFONT oldFont = (HFONT) SelectObject( hdc, hFont ); + + wchar_t *bufW = mir_t2u(buf); + drawThemeText( hTheme, hdc, EP_EDITTEXT, ETS_DISABLED, bufW, -1, 0, 0, &rc ); + mir_free(bufW); + + SelectObject( hdc, oldFont ); + closeThemeData( hTheme ); + bDrawnByTheme = TRUE; + } + } + + SetBkMode( hdc, oldMode ); + + if ( !bDrawnByTheme ) { + HFONT hFont = (HFONT) SendMessage(hWnd, WM_GETFONT, 0, 0); + HFONT oldFont = (HFONT) SelectObject( hdc, hFont ); + SetTextColor( hdc, GetSysColor(COLOR_GRAYTEXT) ); + FillRect( hdc, &rc, GetSysColorBrush( COLOR_WINDOW ) ); + int oldMode = SetBkMode( hdc, TRANSPARENT ); + DrawText( hdc, buf, -1, &rc, 0 ); + SetBkMode( hdc, oldMode ); + SelectObject( hdc, oldFont ); + } + + if (message == WM_PAINT) + EndPaint( hWnd, &paint); + + return 0; +} + +static BOOL CheckPageShow( HWND hdlg, OptionsDlgData * dat, int i ) +{ + if (dat->szFilterString && dat->szFilterString[0] && !MatchesFilter(&dat->opd[i], dat->szFilterString)) return FALSE; + if ((dat->opd[i].flags & ODPF_SIMPLEONLY) && IsDlgButtonChecked( hdlg, IDC_EXPERT)) return FALSE; + if ((dat->opd[i].flags & ODPF_EXPERTONLY) && !IsDlgButtonChecked( hdlg, IDC_EXPERT)) return FALSE; + return TRUE; +} + +static BOOL IsAeroMode() +{ + BOOL result; + return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result; +} + +static void AeroPaintControl(HWND hwnd, HDC hdc, WNDPROC OldWndProc, UINT msg = WM_PRINT, LPARAM lpFlags = PRF_CLIENT|PRF_NONCLIENT) +{ + HBITMAP hBmp, hOldBmp; + RECT rc; GetClientRect(hwnd, &rc); + BYTE *pBits; + + HDC tempDC = CreateCompatibleDC(hdc); + + BITMAPINFO bmi; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = rc.right; + bmi.bmiHeader.biHeight = -rc.bottom; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + hBmp = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, (void **)&pBits, NULL, 0); + + hOldBmp = (HBITMAP)SelectObject(tempDC,hBmp); + + //paint + SetPropA(hwnd, "Miranda.AeroRender.Active", (HANDLE)TRUE); + CallWindowProc(OldWndProc, hwnd, msg, (WPARAM)tempDC, lpFlags); + SetPropA(hwnd, "Miranda.AeroRender.Active", (HANDLE)FALSE); + + // Fix alpha channel + GdiFlush(); + for (int i = 0; i < rc.right*rc.bottom; ++i, pBits += 4) + if (!pBits[3]) pBits[3] = 255; + + //Copy to output + BitBlt(hdc,0,0,rc.right,rc.bottom,tempDC,0,0,SRCCOPY); + SelectObject(tempDC,hOldBmp); + DeleteObject(hBmp); + DeleteDC(tempDC); +} + +static LRESULT CALLBACK AeroPaintSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + WNDPROC OldWndProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) + { + case WM_CTLCOLOREDIT: + if (!GetPropA((HWND)lParam, "Miranda.AeroRender.Active")) + RedrawWindow((HWND)lParam, NULL, NULL, RDW_INVALIDATE); + break; + + case WM_ERASEBKGND: + return TRUE; + + case WM_PRINT: + case WM_PRINTCLIENT: + AeroPaintControl(hwnd, (HDC)wParam, OldWndProc, msg, lParam); + return TRUE; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + AeroPaintControl(hwnd, hdc, OldWndProc); + EndPaint(hwnd, &ps); + return TRUE; + } + + case WM_DESTROY: + RemovePropA(hwnd, "Miranda.AeroRender.Active"); + break; + } + return CallWindowProc(OldWndProc, hwnd, msg, wParam, lParam); +} + +static void CALLBACK FilterSearchTimerFunc( HWND hwnd, UINT, UINT_PTR, DWORD ) +{ + struct OptionsDlgData* dat = (struct OptionsDlgData* )GetWindowLongPtr( hwnd, GWLP_USERDATA ); + if ( !dat ) + return; + + if ( hFilterSearchWnd == NULL) + hFilterSearchWnd = CreateWindowA( "STATIC", "Test", WS_OVERLAPPED|WS_DISABLED, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), 0 ); // Fake window to keep option page focused + + if ( FilterPage < dat->pageCount ) + FindFilterStrings( TRUE, dat->currentPage == FilterPage, hFilterSearchWnd, &( dat->opd[FilterPage]) ); + + FilterPage++; + FilterLoadProgress = FilterPage*100/( (dat->pageCount) ? dat->pageCount : FilterPage ); + if ( FilterPage >= dat->pageCount ) + { + KillTimer( hwnd, FilterTimerId ); + FilterTimerId = 0; + bSearchState = 2; + FilterLoadProgress = 100; + DestroyWindow( hFilterSearchWnd ); + hFilterSearchWnd = NULL; + } + RedrawWindow( GetDlgItem(hwnd, IDC_KEYWORD_FILTER ), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE ); +} + +static void ExecuteFindFilterStringsTimer( HWND hdlg ) +{ + bSearchState = 1; + FilterPage = 0; + if (FilterTimerId) KillTimer( hdlg, FilterTimerId ); + FilterTimerId = SetTimer( hdlg, NULL, 1, FilterSearchTimerFunc ); +} + +static void FillFilterCombo(int enableKeywordFiltering, HWND hDlg, struct OptionsPageData * opd, int PageCount) +{ + int i; + int index; + HINSTANCE* KnownInstances = ( HINSTANCE* )alloca(sizeof(HINSTANCE)*PageCount); + int countKnownInst = 0; + SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER,(UINT) CB_RESETCONTENT, 0,0); + index=SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER,(UINT) CB_ADDSTRING,(WPARAM)0, (LPARAM)TranslateTS(ALL_MODULES_FILTER)); + SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER,(UINT) CB_SETITEMDATA,(WPARAM)index, (LPARAM)NULL); + index=SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER,(UINT) CB_ADDSTRING,(WPARAM)0, (LPARAM)TranslateTS(CORE_MODULES_FILTER)); + SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER,(UINT) CB_SETITEMDATA,(WPARAM)index, (LPARAM)hMirandaInst); + TCHAR* tszModuleName = ( TCHAR* )alloca(MAX_PATH*sizeof(TCHAR)); + for (i=0; iopd[i].pszTab != NULL) + { + // Count tabs to calc position + for (int j=0; j < dat->pageCount && pages < 2; j++ ) + { + if (!CheckPageShow( hdlg, dat, j ) ) continue; + //if (( dat->opd[j].flags & ODPF_SIMPLEONLY ) && IsDlgButtonChecked( hdlg, IDC_EXPERT )) continue; + //if (( dat->opd[j].flags & ODPF_EXPERTONLY ) && !IsDlgButtonChecked( hdlg, IDC_EXPERT )) continue; + if ( lstrcmp(dat->opd[j].pszTitle, dat->opd[i].pszTitle) || lstrcmpnull(dat->opd[j].pszGroup, dat->opd[i].pszGroup) ) continue; + pages++; + } + } + return (pages > 1); +} + +static INT_PTR CALLBACK OptionsDlgProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + struct OptionsDlgData* dat = (struct OptionsDlgData* )GetWindowLongPtr( hdlg, GWLP_USERDATA ); + HWND hwndTree = GetDlgItem(hdlg, IDC_PAGETREE); + + switch ( message ) { + case WM_CTLCOLORSTATIC: + switch ( GetDlgCtrlID(( HWND )lParam )) + { + case IDC_WHITERECT: + case IDC_KEYWORD_FILTER: + SetBkColor(( HDC )wParam, GetSysColor( COLOR_WINDOW )); + return ( INT_PTR )GetSysColorBrush( COLOR_WINDOW ); + } + break; + + case WM_INITDIALOG: + { + PROPSHEETHEADER *psh=(PROPSHEETHEADER*)lParam; + OPENOPTIONSDIALOG *ood=(OPENOPTIONSDIALOG*)psh->pStartPage; + OPTIONSDIALOGPAGE *odp; + int i; + struct DlgTemplateExBegin *dte; + TCHAR *lastPage = NULL, *lastGroup = NULL, *lastTab = NULL; + DBVARIANT dbv; + TCITEM tie; + LOGFONT lf; + + typedef BOOL (STDAPICALLTYPE *pfnGetComboBoxInfo)(HWND, PCOMBOBOXINFO); + pfnGetComboBoxInfo getComboBoxInfo = (pfnGetComboBoxInfo)GetProcAddress(GetModuleHandleA("user32"), "GetComboBoxInfo"); + if (getComboBoxInfo) { + COMBOBOXINFO cbi; + cbi.cbSize = sizeof(COMBOBOXINFO); + getComboBoxInfo(GetDlgItem( hdlg, IDC_KEYWORD_FILTER), &cbi); + OptionsFilterDefaultProc = (WNDPROC)SetWindowLongPtr( cbi.hwndItem, GWLP_WNDPROC, (LONG_PTR) OptionsFilterSubclassProc ); + + if (IsAeroMode()) { + SetWindowLongPtr(cbi.hwndCombo, GWLP_USERDATA, GetWindowLongPtr(cbi.hwndCombo, GWLP_WNDPROC)); + SetWindowLongPtr(cbi.hwndCombo, GWLP_WNDPROC, (LONG_PTR)AeroPaintSubclassProc); + SetWindowLongPtr(cbi.hwndItem, GWLP_USERDATA, GetWindowLongPtr(cbi.hwndItem, GWLP_WNDPROC)); + SetWindowLongPtr(cbi.hwndItem, GWLP_WNDPROC, (LONG_PTR)AeroPaintSubclassProc); + } } + + Utils_RestoreWindowPositionNoSize(hdlg, NULL, "Options", ""); + TranslateDialogDefault(hdlg); + Window_SetIcon_IcoLib(hdlg, SKINICON_OTHER_OPTIONS); + CheckDlgButton(hdlg,IDC_EXPERT,DBGetContactSettingByte(NULL,"Options","Expert",SETTING_SHOWEXPERT_DEFAULT)?BST_CHECKED:BST_UNCHECKED); + EnableWindow(GetDlgItem(hdlg,IDC_APPLY),FALSE); + dat=(struct OptionsDlgData*)mir_alloc(sizeof(struct OptionsDlgData)); + SetWindowLongPtr(hdlg,GWLP_USERDATA,(LONG_PTR)dat); + SetWindowText(hdlg,psh->pszCaption); + + dat->hBoldFont=(HFONT)SendDlgItemMessage(hdlg,IDC_EXPERT,WM_GETFONT,0,0); + GetObject(dat->hBoldFont,sizeof(lf),&lf); + lf.lfWeight=FW_BOLD; + dat->hBoldFont=CreateFontIndirect(&lf); + + dat->pageCount = psh->nPages; + dat->opd = ( struct OptionsPageData* )mir_alloc( sizeof(struct OptionsPageData) * dat->pageCount ); + odp = ( OPTIONSDIALOGPAGE* )psh->ppsp; + + dat->currentPage = -1; + if ( ood->pszPage == NULL ) { + if ( !DBGetContactSettingTString( NULL, "Options", "LastPage", &dbv )) { + lastPage = mir_tstrdup( dbv.ptszVal ); + DBFreeVariant( &dbv ); + } + + if ( ood->pszGroup == NULL ) { + if ( !DBGetContactSettingTString( NULL, "Options", "LastGroup", &dbv )) { + lastGroup = mir_tstrdup( dbv.ptszVal ); + DBFreeVariant( &dbv ); + } + } + else lastGroup = LangPackPcharToTchar( ood->pszGroup ); + } + else + { + lastPage = LangPackPcharToTchar( ood->pszPage ); + lastGroup = ( ood->pszGroup == NULL ) ? NULL : LangPackPcharToTchar( ood->pszGroup ); + } + + if ( ood->pszTab == NULL ) { + if ( !DBGetContactSettingTString( NULL, "Options", "LastTab", &dbv )) { + lastTab = mir_tstrdup( dbv.ptszVal ); + DBFreeVariant( &dbv ); + } + } + else lastTab = LangPackPcharToTchar( ood->pszTab ); + + for ( i=0; i < dat->pageCount; i++, odp++ ) { + struct OptionsPageData* opd = &dat->opd[i]; + HRSRC hrsrc=FindResourceA(odp->hInstance,odp->pszTemplate,MAKEINTRESOURCEA(5)); + HGLOBAL hglb=LoadResource(odp->hInstance,hrsrc); + DWORD resSize=SizeofResource(odp->hInstance,hrsrc); + opd->pTemplate = ( DLGTEMPLATE* )mir_alloc(resSize); + memcpy(opd->pTemplate,LockResource(hglb),resSize); + dte=(struct DlgTemplateExBegin*)opd->pTemplate; + if ( dte->signature == 0xFFFF ) { + //this feels like an access violation, and is according to boundschecker + //...but it works - for now + //may well have to remove and sort out the original dialogs + dte->style&=~(WS_VISIBLE|WS_CHILD|WS_POPUP|WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|DS_MODALFRAME|DS_CENTER); + dte->style|=WS_CHILD; + } + else { + opd->pTemplate->style&=~(WS_VISIBLE|WS_CHILD|WS_POPUP|WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|DS_MODALFRAME|DS_CENTER); + opd->pTemplate->style|=WS_CHILD; + } + opd->dlgProc=odp->pfnDlgProc; + opd->hInst=odp->hInstance; + opd->hwnd=NULL; + opd->changed=0; + opd->simpleHeight=opd->expertHeight=0; + opd->simpleBottomControlId=odp->nIDBottomSimpleControl; + opd->simpleWidth=opd->expertWidth=0; + opd->simpleRightControlId=odp->nIDRightSimpleControl; + opd->nExpertOnlyControls=odp->nExpertOnlyControls; + opd->expertOnlyControls=odp->expertOnlyControls; + opd->flags=odp->flags; + opd->dwInitParam=odp->dwInitParam; + if ( odp->pszTitle == NULL ) + opd->pszTitle = NULL; + else if ( odp->flags & ODPF_UNICODE ) { + #if defined ( _UNICODE ) + opd->pszTitle = ( TCHAR* )mir_wstrdup( odp->ptszTitle ); + #else + opd->pszTitle = mir_u2a(( WCHAR* )odp->ptszTitle ); + #endif + } + else opd->pszTitle = ( TCHAR* )mir_strdup( odp->pszTitle ); + + if ( odp->pszGroup == NULL ) + opd->pszGroup = NULL; + else if ( odp->flags & ODPF_UNICODE ) { + #if defined ( _UNICODE ) + opd->pszGroup = ( TCHAR* )mir_wstrdup( odp->ptszGroup ); + #else + opd->pszGroup = mir_u2a(( WCHAR* )odp->ptszGroup ); + #endif + } + else opd->pszGroup = ( TCHAR* )mir_strdup( odp->pszGroup ); + + if ( odp->pszTab == NULL ) + opd->pszTab = NULL; + else if ( odp->flags & ODPF_UNICODE ) { + #if defined ( _UNICODE ) + opd->pszTab = ( TCHAR* )mir_wstrdup( odp->ptszTab ); + #else + opd->pszTab = mir_u2a(( WCHAR* )odp->ptszTab ); + #endif + } + else opd->pszTab = ( TCHAR* )mir_strdup( odp->pszTab ); + + if ( !lstrcmp( lastPage, odp->ptszTitle ) && + !lstrcmpnull( lastGroup, odp->ptszGroup ) && + (( ood->pszTab == NULL && dat->currentPage == -1 ) || !lstrcmpnull( lastTab, odp->ptszTab ))) + dat->currentPage = i; + } + mir_free( lastGroup ); + mir_free( lastPage ); + mir_free( lastTab ); + + GetWindowRect(GetDlgItem(hdlg,IDC_STNOPAGE),&dat->rcDisplay); + MapWindowPoints(NULL, hdlg, (LPPOINT)&dat->rcDisplay, 2); + + // Add an item to count in height + tie.mask = TCIF_TEXT | TCIF_IMAGE; + tie.iImage = -1; + tie.pszText = _T("X"); + TabCtrl_InsertItem(GetDlgItem(hdlg,IDC_TAB), 0, &tie); + + GetWindowRect(GetDlgItem(hdlg,IDC_TAB), &dat->rcTab); + MapWindowPoints(NULL, hdlg, (LPPOINT)&dat->rcTab, 2); + TabCtrl_AdjustRect(GetDlgItem(hdlg,IDC_TAB), FALSE, &dat->rcTab); + + //!!!!!!!!!! int enableKeywordFiltering = DBGetContactSettingWord(NULL, "Options", "EnableKeywordFiltering", TRUE); + FillFilterCombo( 0, //!!!!!!!!!! enableKeywordFiltering, + hdlg, dat->opd, dat->pageCount); + SendMessage(hdlg,DM_REBUILDPAGETREE,0,0); + + return TRUE; + } + case DM_REBUILDPAGETREE: + { + int i; + TVINSERTSTRUCT tvis; + TVITEMA tvi; + BOOL bRemoveFocusFromFilter = FALSE; + char str[128],buf[130]; + + HINSTANCE FilterInst=NULL; + + LPARAM oldSel = SendDlgItemMessage(hdlg, IDC_KEYWORD_FILTER, CB_GETEDITSEL, 0, 0); + + GetDlgItemText(hdlg, IDC_KEYWORD_FILTER, dat->szFilterString, SIZEOF(dat->szFilterString)); + + //if filter string is set to all modules then make the filter string empty (this will return all modules) + if (_tcscmp(dat->szFilterString, TranslateTS( ALL_MODULES_FILTER ) ) == 0) { + dat->szFilterString[0] = 0; + bRemoveFocusFromFilter = TRUE; + } + //if filter string is set to core modules replace it with the name of the executable (this will return all core modules) + else if (_tcscmp(dat->szFilterString, TranslateTS( CORE_MODULES_FILTER) ) == 0) { + //replace string with process name - that will show core settings + TCHAR szFileName[300]; + GetModuleFileName(NULL, szFileName, SIZEOF(szFileName)); + TCHAR *pos = _tcsrchr(szFileName, _T('\\')); + if (pos) + pos++; + else + pos = szFileName; + + _tcsncpy(dat->szFilterString, pos, SIZEOF(dat->szFilterString)); + } + else { + int sel = SendMessage( GetDlgItem(hdlg, IDC_KEYWORD_FILTER ), (UINT) CB_GETCURSEL, 0,0 ); + if (sel != -1) { + HINSTANCE hinst = (HINSTANCE)SendMessage( GetDlgItem(hdlg, IDC_KEYWORD_FILTER ), (UINT) CB_GETITEMDATA, sel ,0 ); + TCHAR szFileName[300]; + GetModuleFileName(hinst, szFileName, SIZEOF(szFileName)); + TCHAR *pos = _tcsrchr(szFileName, _T('\\')); + if (pos) pos++; + else pos = szFileName; + _tcsncpy(dat->szFilterString, pos, SIZEOF(dat->szFilterString)); + } } + + _tcslwr_locale(dat->szFilterString); //all strings are stored as lowercase ... make sure filter string is lowercase too + + ShowWindow(hwndTree, SW_HIDE); //deleteall is annoyingly visible + + HWND oldWnd = NULL; + HWND oldTab = NULL; + if ( dat->currentPage != (-1)) { + oldWnd = dat->opd[dat->currentPage].hwnd; + if ( dat->opd[dat->currentPage].insideTab ) + oldTab = GetDlgItem( hdlg,IDC_TAB ); + } + + dat->hCurrentPage = NULL; + + TreeView_SelectItem(hwndTree, NULL); + + TreeView_DeleteAllItems(hwndTree); + + tvis.hParent = NULL; + tvis.hInsertAfter = TVI_SORT; + tvis.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM; + tvis.item.state = tvis.item.stateMask = TVIS_EXPANDED; + for ( i=0; i < dat->pageCount; i++ ) { + static TCHAR *fullTitle=NULL; + TCHAR * useTitle; + if (fullTitle) mir_free(fullTitle); + fullTitle=NULL; + if (! CheckPageShow( hdlg, dat, i ) ) continue; + tvis.hParent = NULL; + if ( FilterInst!=NULL ) { + size_t sz=dat->opd[i].pszGroup?_tcslen(dat->opd[i].pszGroup)+1:0; + if (sz) sz+=3; + sz+=dat->opd[i].pszTitle?_tcslen(dat->opd[i].pszTitle)+1:0; + fullTitle = ( TCHAR* )mir_alloc(sz*sizeof(TCHAR)); + mir_sntprintf(fullTitle,sz,(dat->opd[i].pszGroup && dat->opd[i].pszTitle)?_T("%s - %s"):_T("%s%s"),dat->opd[i].pszGroup?dat->opd[i].pszGroup:_T(""),dat->opd[i].pszTitle?dat->opd[i].pszTitle:_T("") ); + } + useTitle=fullTitle?fullTitle:dat->opd[i].pszTitle; + if(dat->opd[i].pszGroup != NULL && FilterInst==NULL) { + tvis.hParent = FindNamedTreeItemAtRoot(hwndTree, dat->opd[i].pszGroup); + if(tvis.hParent == NULL) { + tvis.item.lParam = -1; + tvis.item.pszText = dat->opd[i].pszGroup; + tvis.hParent = TreeView_InsertItem(hwndTree, &tvis); + } + } + else { + TVITEM tvi; + tvi.hItem = FindNamedTreeItemAtRoot(hwndTree,useTitle); + if( tvi.hItem != NULL ) { + if ( i == dat->currentPage ) dat->hCurrentPage=tvi.hItem; + tvi.mask = TVIF_PARAM; + TreeView_GetItem(hwndTree,&tvi); + if ( tvi.lParam == -1 ) { + tvi.lParam = i; + TreeView_SetItem(hwndTree,&tvi); + continue; + } } } + + if ( dat->opd[i].pszTab != NULL ) { + HTREEITEM hItem; + if (tvis.hParent == NULL) + hItem = FindNamedTreeItemAtRoot(hwndTree,useTitle); + else + hItem = FindNamedTreeItemAtChildren(hwndTree,tvis.hParent,useTitle); + if( hItem != NULL ) { + if ( i == dat->currentPage ) { + TVITEM tvi; + tvi.hItem=hItem; + tvi.mask=TVIF_PARAM; + tvi.lParam=dat->currentPage; + TreeView_SetItem(hwndTree,&tvi); + dat->hCurrentPage=hItem; + } + continue; + } + } + + tvis.item.pszText = useTitle; + tvis.item.lParam = i; + dat->opd[i].hTreeItem = TreeView_InsertItem(hwndTree, &tvis); + if ( i == dat->currentPage ) + dat->hCurrentPage = dat->opd[i].hTreeItem; + + if (fullTitle) mir_free(fullTitle); + fullTitle=NULL; + } + tvi.mask = TVIF_TEXT | TVIF_STATE; + tvi.pszText = str; + tvi.cchTextMax = SIZEOF(str); + tvi.hItem = TreeView_GetRoot(hwndTree); + while ( tvi.hItem != NULL ) { + if ( SendMessageA( hwndTree, TVM_GETITEMA, 0, (LPARAM)&tvi )) { + mir_snprintf(buf, SIZEOF(buf), "%s%s",OPTSTATE_PREFIX,str); + if ( !DBGetContactSettingByte( NULL, "Options", buf, 1 )) + TreeView_Expand( hwndTree, tvi.hItem, TVE_COLLAPSE ); + } + tvi.hItem = TreeView_GetNextSibling( hwndTree, tvi.hItem ); + } + if(dat->hCurrentPage==NULL) { + dat->hCurrentPage=TreeView_GetRoot(hwndTree); + dat->currentPage=-1; + } + TreeView_SelectItem(hwndTree,dat->hCurrentPage); + + if ( oldWnd ) { + if ( dat->currentPage == (-1) || oldWnd != dat->opd[dat->currentPage].hwnd ) { + ShowWindow( oldWnd, SW_HIDE); + if ( oldTab && ( dat->currentPage ==-1 || !dat->opd[dat->currentPage].insideTab ) ) + ShowWindow( oldTab, SW_HIDE ); + } + } + + if ( dat->szFilterString[0] == 0 ) // Clear the keyword combo box + SetWindowText( GetDlgItem(hdlg, IDC_KEYWORD_FILTER), _T("") ); + if ( !bRemoveFocusFromFilter ) + SetFocus(GetDlgItem(hdlg, IDC_KEYWORD_FILTER)); //set the focus back to the combo box + + SendDlgItemMessage(hdlg, IDC_KEYWORD_FILTER, CB_SETEDITSEL, 0, oldSel ); //but don't select any of the text + + ShowWindow(hwndTree,SW_SHOW); + } + break; + + case PSM_CHANGED: + EnableWindow(GetDlgItem(hdlg,IDC_APPLY),TRUE); + if(dat->currentPage != (-1)) dat->opd[dat->currentPage].changed=1; + return TRUE; + + case PSM_ISEXPERT: + SetWindowLongPtr(hdlg,DWLP_MSGRESULT,IsDlgButtonChecked(hdlg,IDC_EXPERT)); + return TRUE; + + case PSM_GETBOLDFONT: + SetWindowLongPtr(hdlg,DWLP_MSGRESULT,(LONG_PTR)dat->hBoldFont); + return TRUE; + + case WM_NOTIFY: + switch(wParam) { + case IDC_TAB: + case IDC_PAGETREE: + switch(((LPNMHDR)lParam)->code) { + case TVN_ITEMEXPANDING: + SetWindowLongPtr(hdlg,DWLP_MSGRESULT,FALSE); + return TRUE; + case TCN_SELCHANGING: + case TVN_SELCHANGING: + { PSHNOTIFY pshn; + if(dat->currentPage==-1 || dat->opd[dat->currentPage].hwnd==NULL) break; + pshn.hdr.code=PSN_KILLACTIVE; + pshn.hdr.hwndFrom=dat->opd[dat->currentPage].hwnd; + pshn.hdr.idFrom=0; + pshn.lParam=0; + if(SendMessage(dat->opd[dat->currentPage].hwnd,WM_NOTIFY,0,(LPARAM)&pshn)) { + SetWindowLongPtr(hdlg,DWLP_MSGRESULT,TRUE); + return TRUE; + } + break; + } + case TCN_SELCHANGE: + case TVN_SELCHANGED: + { BOOL tabChange = (wParam == IDC_TAB); + ShowWindow(GetDlgItem(hdlg,IDC_STNOPAGE),SW_HIDE); + if(dat->currentPage!=-1 && dat->opd[dat->currentPage].hwnd!=NULL) ShowWindow(dat->opd[dat->currentPage].hwnd,SW_HIDE); + if (!tabChange) { + TVITEM tvi; + tvi.hItem=dat->hCurrentPage=TreeView_GetSelection(hwndTree); + if(tvi.hItem==NULL) break; + tvi.mask=TVIF_HANDLE|TVIF_PARAM; + TreeView_GetItem(hwndTree,&tvi); + dat->currentPage=tvi.lParam; + ShowWindow(GetDlgItem(hdlg,IDC_TAB),SW_HIDE); + } + else { + TCITEM tie; + TVITEM tvi; + + tie.mask = TCIF_PARAM; + TabCtrl_GetItem(GetDlgItem(hdlg,IDC_TAB),TabCtrl_GetCurSel(GetDlgItem(hdlg,IDC_TAB)),&tie); + dat->currentPage=tie.lParam; + + tvi.hItem=dat->hCurrentPage; + tvi.mask=TVIF_PARAM; + tvi.lParam=dat->currentPage; + TreeView_SetItem(hwndTree,&tvi); + } + if ( dat->currentPage != -1 ) { + if ( dat->opd[dat->currentPage].hwnd == NULL ) { + RECT rcPage; + RECT rcControl,rc; + int w,h; + + dat->opd[dat->currentPage].hwnd=CreateDialogIndirectParamA(dat->opd[dat->currentPage].hInst,dat->opd[dat->currentPage].pTemplate,hdlg,dat->opd[dat->currentPage].dlgProc,dat->opd[dat->currentPage].dwInitParam); + if(dat->opd[dat->currentPage].flags&ODPF_BOLDGROUPS) + EnumChildWindows(dat->opd[dat->currentPage].hwnd,BoldGroupTitlesEnumChildren,(LPARAM)dat->hBoldFont); + GetClientRect(dat->opd[dat->currentPage].hwnd,&rcPage); + dat->opd[dat->currentPage].expertWidth=rcPage.right; + dat->opd[dat->currentPage].expertHeight=rcPage.bottom; + GetWindowRect(dat->opd[dat->currentPage].hwnd,&rc); + + if(dat->opd[dat->currentPage].simpleBottomControlId) { + GetWindowRect(GetDlgItem(dat->opd[dat->currentPage].hwnd,dat->opd[dat->currentPage].simpleBottomControlId),&rcControl); + dat->opd[dat->currentPage].simpleHeight=rcControl.bottom-rc.top; + } + else dat->opd[dat->currentPage].simpleHeight=dat->opd[dat->currentPage].expertHeight; + + if(dat->opd[dat->currentPage].simpleRightControlId) { + GetWindowRect(GetDlgItem(dat->opd[dat->currentPage].hwnd,dat->opd[dat->currentPage].simpleRightControlId),&rcControl); + dat->opd[dat->currentPage].simpleWidth=rcControl.right-rc.left; + } + else dat->opd[dat->currentPage].simpleWidth=dat->opd[dat->currentPage].expertWidth; + + if(IsDlgButtonChecked(hdlg,IDC_EXPERT)) { + w=dat->opd[dat->currentPage].expertWidth; + h=dat->opd[dat->currentPage].expertHeight; + } + else { + int i; + for(i=0;iopd[dat->currentPage].nExpertOnlyControls;i++) + ShowWindow(GetDlgItem(dat->opd[dat->currentPage].hwnd,dat->opd[dat->currentPage].expertOnlyControls[i]),SW_HIDE); + w=dat->opd[dat->currentPage].simpleWidth; + h=dat->opd[dat->currentPage].simpleHeight; + } + + dat->opd[dat->currentPage].offsetX = 0; + dat->opd[dat->currentPage].offsetY = 0; + + dat->opd[dat->currentPage].insideTab = IsInsideTab( hdlg, dat, dat->currentPage ); + if (dat->opd[dat->currentPage].insideTab) { + SetWindowPos(dat->opd[dat->currentPage].hwnd,HWND_TOP,(dat->rcTab.left+dat->rcTab.right-w)>>1,dat->rcTab.top,w,h,0); + ThemeDialogBackground(dat->opd[dat->currentPage].hwnd,TRUE); + } else { + SetWindowPos(dat->opd[dat->currentPage].hwnd,HWND_TOP,(dat->rcDisplay.left+dat->rcDisplay.right-w)>>1,(dat->rcDisplay.top+dat->rcDisplay.bottom-h)>>1,w,h,0); + ThemeDialogBackground(dat->opd[dat->currentPage].hwnd,FALSE); + } + } + if ( !tabChange ) + { + dat->opd[ dat->currentPage].insideTab = IsInsideTab( hdlg, dat, dat->currentPage ); + if ( dat->opd[dat->currentPage].insideTab ) { + // Make tabbed pane + int i,pages=0,sel=0; + TCITEM tie; + HWND hwndTab = GetDlgItem(hdlg,IDC_TAB); + + TabCtrl_DeleteAllItems(hwndTab); + tie.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM; + tie.iImage = -1; + for ( i=0; i < dat->pageCount; i++ ) { + if (!CheckPageShow( hdlg, dat, i ) ) continue; + //if (( dat->opd[i].flags & ODPF_SIMPLEONLY ) && IsDlgButtonChecked( hdlg, IDC_EXPERT )) continue; + //if (( dat->opd[i].flags & ODPF_EXPERTONLY ) && !IsDlgButtonChecked( hdlg, IDC_EXPERT )) continue; + if ( lstrcmp(dat->opd[i].pszTitle, dat->opd[dat->currentPage].pszTitle) || lstrcmpnull(dat->opd[i].pszGroup, dat->opd[dat->currentPage].pszGroup) ) continue; + + tie.pszText = dat->opd[i].pszTab; + tie.lParam = i; + TabCtrl_InsertItem(hwndTab, pages, &tie); + if ( !lstrcmp(dat->opd[i].pszTab,dat->opd[dat->currentPage].pszTab) ) + sel = pages; + pages++; + } + TabCtrl_SetCurSel(hwndTab,sel); + ShowWindow(hwndTab, dat->opd[dat->currentPage].insideTab ? SW_SHOW : SW_HIDE ); + } + + if (dat->opd[dat->currentPage].insideTab) + ThemeDialogBackground(dat->opd[dat->currentPage].hwnd,TRUE); + else + ThemeDialogBackground(dat->opd[dat->currentPage].hwnd,FALSE); + } + + // Resizing + if (!dat->opd[dat->currentPage].simpleBottomControlId) + { + int pageWidth, pageHeight; + + if(IsDlgButtonChecked(hdlg,IDC_EXPERT)) { + pageWidth=dat->opd[dat->currentPage].expertWidth; + pageHeight=dat->opd[dat->currentPage].expertHeight; + } + else { + pageWidth=dat->opd[dat->currentPage].simpleWidth; + pageHeight=dat->opd[dat->currentPage].simpleHeight; + } + + RECT * parentPageRect = &dat->rcDisplay; + + if ( dat->opd[dat->currentPage].insideTab ) + parentPageRect = &dat->rcTab; + + pageHeight = min( pageHeight, parentPageRect->bottom - parentPageRect->top ); + pageWidth = min( pageWidth, parentPageRect->right - parentPageRect->left ); + + int newOffsetX = ( parentPageRect->right - parentPageRect->left - pageWidth ) >> 1; + int newOffsetY = dat->opd[dat->currentPage].insideTab ? 0 : ( parentPageRect->bottom - parentPageRect->top - pageHeight ) >> 1; + + struct MoveChildParam mcp; + mcp.hDlg = dat->opd[dat->currentPage].hwnd; + mcp.offset.x = newOffsetX - dat->opd[dat->currentPage].offsetX; + mcp.offset.y = newOffsetY - dat->opd[dat->currentPage].offsetY; + + + if ( mcp.offset.x || mcp.offset.y ) + { + EnumChildWindows(dat->opd[dat->currentPage].hwnd,MoveEnumChildren,(LPARAM)(&mcp)); + + SetWindowPos( dat->opd[dat->currentPage].hwnd, NULL, + parentPageRect->left, parentPageRect->top, + parentPageRect->right - parentPageRect->left, + parentPageRect->bottom - parentPageRect->top, + SWP_NOZORDER | SWP_NOACTIVATE ); + dat->opd[dat->currentPage].offsetX = newOffsetX; + dat->opd[dat->currentPage].offsetY = newOffsetY; + } + + } + + ShowWindow(dat->opd[dat->currentPage].hwnd,SW_SHOW); + if(((LPNMTREEVIEW)lParam)->action==TVC_BYMOUSE) PostMessage(hdlg,DM_FOCUSPAGE,0,0); + else SetFocus(hwndTree); + } + else ShowWindow(GetDlgItem(hdlg,IDC_STNOPAGE),SW_SHOW); + break; + } } } + break; + + case DM_FOCUSPAGE: + if (dat->currentPage != -1) + SetFocus(dat->opd[dat->currentPage].hwnd); + break; + + case WM_TIMER: + if (wParam == FILTER_TIMEOUT_TIMER) { + SaveOptionsTreeState(hdlg); + SendMessage(hdlg,DM_REBUILDPAGETREE,0,0); + + KillTimer(hdlg, FILTER_TIMEOUT_TIMER); + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_KEYWORD_FILTER: + //add a timer - when the timer elapses filter the option pages + if ( (HIWORD(wParam)==CBN_SELCHANGE) || (HIWORD(wParam) == CBN_EDITCHANGE)) + if (!SetTimer(hdlg, FILTER_TIMEOUT_TIMER, 400, NULL)) + MessageBeep(MB_ICONSTOP); + + break; + + case IDC_EXPERT: + { + int expert=IsDlgButtonChecked(hdlg,IDC_EXPERT); + int i,j; + PSHNOTIFY pshn; + RECT rcPage; + int neww,newh; + + DBWriteContactSettingByte(NULL,"Options","Expert",(BYTE)expert); + pshn.hdr.idFrom=0; + pshn.lParam=expert; + pshn.hdr.code=PSN_EXPERTCHANGED; + for(i=0;ipageCount;i++) { + if(dat->opd[i].hwnd==NULL) continue; + if (!CheckPageShow( hdlg, dat, i ) ) continue; + //if (( dat->opd[i].flags & ODPF_SIMPLEONLY ) && expert) continue; + //if (( dat->opd[i].flags & ODPF_EXPERTONLY ) && !expert) continue; + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + + for(j=0;jopd[i].nExpertOnlyControls;j++) + ShowWindow(GetDlgItem(dat->opd[i].hwnd,dat->opd[i].expertOnlyControls[j]),expert?SW_SHOW:SW_HIDE); + + dat->opd[i].insideTab = IsInsideTab( hdlg, dat, i ); + + GetWindowRect(dat->opd[i].hwnd,&rcPage); + if(dat->opd[i].simpleBottomControlId) newh=expert?dat->opd[i].expertHeight:dat->opd[i].simpleHeight; + else newh=rcPage.bottom-rcPage.top; + if(dat->opd[i].simpleRightControlId) neww=expert?dat->opd[i].expertWidth:dat->opd[i].simpleWidth; + else neww=rcPage.right-rcPage.left; + if(i==dat->currentPage) { + POINT ptStart,ptEnd,ptNow; + DWORD thisTick,startTick; + RECT rc; + + ptNow.x=ptNow.y=0; + ClientToScreen(hdlg,&ptNow); + GetWindowRect(dat->opd[i].hwnd,&rc); + ptStart.x=rc.left-ptNow.x; + ptStart.y=rc.top-ptNow.y; + if (dat->opd[i].insideTab) { + ptEnd.x=(dat->rcTab.left+dat->rcTab.right-neww)>>1; + ptEnd.y=dat->rcTab.top; + } else { + ptEnd.x=(dat->rcDisplay.left+dat->rcDisplay.right-neww)>>1; + ptEnd.y=(dat->rcDisplay.top+dat->rcDisplay.bottom-newh)>>1; + } + if(abs(ptEnd.x-ptStart.x)>5 || abs(ptEnd.y-ptStart.y)>5) { + startTick=GetTickCount(); + SetWindowPos(dat->opd[i].hwnd,HWND_TOP,0,0,min(neww,rcPage.right),min(newh,rcPage.bottom),SWP_NOMOVE); + UpdateWindow(dat->opd[i].hwnd); + for(;;) { + thisTick=GetTickCount(); + if(thisTick>startTick+100) break; + ptNow.x=ptStart.x+(ptEnd.x-ptStart.x)*(int)(thisTick-startTick)/100; + ptNow.y=ptStart.y+(ptEnd.y-ptStart.y)*(int)(thisTick-startTick)/100; + SetWindowPos(dat->opd[i].hwnd,0,ptNow.x,ptNow.y,0,0,SWP_NOZORDER|SWP_NOSIZE); + } + } + if (dat->opd[i].insideTab) + ShowWindow(GetDlgItem(hdlg,IDC_TAB),SW_SHOW); + else + ShowWindow(GetDlgItem(hdlg,IDC_TAB),SW_HIDE); + } + + if (dat->opd[i].insideTab) { + SetWindowPos(dat->opd[i].hwnd,HWND_TOP,(dat->rcTab.left+dat->rcTab.right-neww)>>1,dat->rcTab.top,neww,newh,0); + ThemeDialogBackground(dat->opd[i].hwnd,TRUE); + } else { + SetWindowPos(dat->opd[i].hwnd,HWND_TOP,(dat->rcDisplay.left+dat->rcDisplay.right-neww)>>1,(dat->rcDisplay.top+dat->rcDisplay.bottom-newh)>>1,neww,newh,0); + ThemeDialogBackground(dat->opd[i].hwnd,FALSE); + } + } + SaveOptionsTreeState(hdlg); + SendMessage(hdlg,DM_REBUILDPAGETREE,0,0); + break; + } + case IDCANCEL: + { int i; + PSHNOTIFY pshn; + pshn.hdr.idFrom=0; + pshn.lParam=0; + pshn.hdr.code=PSN_RESET; + for(i=0;ipageCount;i++) { + if(dat->opd[i].hwnd==NULL || !dat->opd[i].changed) continue; + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } + DestroyWindow(hdlg); + break; + } + case IDOK: + case IDC_APPLY: + { + int i; + PSHNOTIFY pshn; + + if (LOWORD(wParam) == IDOK && GetParent(GetFocus()) == GetDlgItem(hdlg, IDC_KEYWORD_FILTER)) + return TRUE; + + EnableWindow(GetDlgItem(hdlg,IDC_APPLY),FALSE); + SetFocus(hwndTree); + if(dat->currentPage!=(-1)) { + pshn.hdr.idFrom=0; + pshn.lParam=0; + pshn.hdr.code=PSN_KILLACTIVE; + pshn.hdr.hwndFrom=dat->opd[dat->currentPage].hwnd; + if(SendMessage(dat->opd[dat->currentPage].hwnd,WM_NOTIFY,0,(LPARAM)&pshn)) + break; + } + + pshn.hdr.code=PSN_APPLY; + for(i=0;ipageCount;i++) { + if(dat->opd[i].hwnd==NULL || !dat->opd[i].changed) continue; + dat->opd[i].changed=0; + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + if(SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn)==PSNRET_INVALID_NOCHANGEPAGE) { + dat->hCurrentPage=dat->opd[i].hTreeItem; + TreeView_SelectItem(hwndTree,dat->hCurrentPage); + if(dat->currentPage!=(-1)) ShowWindow(dat->opd[dat->currentPage].hwnd,SW_HIDE); + dat->currentPage=i; + if (dat->currentPage != (-1)) ShowWindow(dat->opd[dat->currentPage].hwnd,SW_SHOW); + return 0; + } } + + if ( LOWORD( wParam ) == IDOK ) + DestroyWindow(hdlg); + break; + } } + break; + + case WM_DESTROY: + if ( FilterTimerId ) KillTimer ( hdlg, FilterTimerId ); + DestroyWindow ( hFilterSearchWnd ); + ClearFilterStrings(); + dat->szFilterString[0]=0; + + SaveOptionsTreeState( hdlg ); + Window_FreeIcon_IcoLib( hdlg ); + + if ( dat->currentPage != -1 ) { + if ( dat->opd[dat->currentPage].pszTab ) + DBWriteContactSettingTString( NULL, "Options", "LastTab", dat->opd[dat->currentPage].pszTab ); + else DBDeleteContactSetting( NULL, "Options", "LastTab" ); + if ( dat->opd[dat->currentPage].pszGroup ) + DBWriteContactSettingTString( NULL, "Options", "LastGroup", dat->opd[dat->currentPage].pszGroup ); + else DBDeleteContactSetting( NULL, "Options", "LastGroup" ); + DBWriteContactSettingTString( NULL, "Options", "LastPage", dat->opd[dat->currentPage].pszTitle ); + } + else { + DBDeleteContactSetting(NULL,"Options","LastTab"); + DBDeleteContactSetting(NULL,"Options","LastGroup"); + DBDeleteContactSetting(NULL,"Options","LastPage"); + } + Utils_SaveWindowPosition(hdlg, NULL, "Options", ""); + { + int i; + for ( i=0; i < dat->pageCount; i++ ) { + if ( dat->opd[i].hwnd != NULL ) + DestroyWindow(dat->opd[i].hwnd); + mir_free(dat->opd[i].pszGroup); + mir_free(dat->opd[i].pszTab); + mir_free(dat->opd[i].pszTitle); + mir_free(dat->opd[i].pTemplate); + } } + mir_free( dat->opd ); + DeleteObject( dat->hBoldFont ); + mir_free( dat ); + hwndOptions = NULL; + + CallService(MS_MODERNOPT_RESTORE, 0, 0); + break; + } + return FALSE; +} + +static void FreeOptionsData( struct OptionsPageInit* popi ) +{ + int i; + for ( i=0; i < popi->pageCount; i++ ) { + mir_free(( char* )popi->odp[i].pszTitle ); + mir_free( popi->odp[i].pszGroup ); + mir_free( popi->odp[i].pszTab ); + if (( DWORD_PTR )popi->odp[i].pszTemplate & 0xFFFF0000 ) + mir_free((char*)popi->odp[i].pszTemplate); + } + mir_free(popi->odp); +} + +void OpenAccountOptions( PROTOACCOUNT* pa ) +{ + struct OptionsPageInit opi = { 0 }; + if ( pa->ppro == NULL ) + return; + + pa->ppro->OnEvent( EV_PROTO_ONOPTIONS, ( WPARAM )&opi, 0 ); + if ( opi.pageCount > 0 ) { + TCHAR tszTitle[ 100 ]; + OPENOPTIONSDIALOG ood = { 0 }; + PROPSHEETHEADER psh = { 0 }; + + mir_sntprintf( tszTitle, SIZEOF(tszTitle), TranslateT("%s options"), pa->tszAccountName ); + + ood.cbSize = sizeof(ood); + ood.pszGroup = LPGEN("Network"); + ood.pszPage = mir_t2a( pa->tszAccountName ); + + psh.dwSize = sizeof(psh); + psh.dwFlags = PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW; + psh.hwndParent = NULL; + psh.nPages = opi.pageCount; + psh.pStartPage = (LPCTSTR)&ood; + psh.pszCaption = tszTitle; + psh.ppsp = (PROPSHEETPAGE*)opi.odp; + hwndOptions = CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_OPTIONSPAGE),NULL,OptionsDlgProc,(LPARAM)&psh); + mir_free(( void* )ood.pszPage ); + FreeOptionsData( &opi ); +} } + +static void OpenOptionsNow(const char *pszGroup,const char *pszPage,const char *pszTab, bool bSinglePage=false) +{ + if ( IsWindow( hwndOptions )) { + ShowWindow( hwndOptions, SW_RESTORE ); + SetForegroundWindow( hwndOptions ); + if ( pszPage != NULL) { + TCHAR *ptszPage = LangPackPcharToTchar(pszPage); + HTREEITEM hItem = NULL; + if (pszGroup != NULL) { + TCHAR *ptszGroup = LangPackPcharToTchar(pszGroup); + hItem = FindNamedTreeItemAtRoot(GetDlgItem(hwndOptions,IDC_PAGETREE),ptszGroup); + if (hItem != NULL) { + hItem = FindNamedTreeItemAtChildren(GetDlgItem(hwndOptions,IDC_PAGETREE),hItem,ptszPage); + } + mir_free(ptszGroup); + } else { + hItem = FindNamedTreeItemAtRoot(GetDlgItem(hwndOptions,IDC_PAGETREE),ptszPage); + } + if (hItem != NULL) { + TreeView_SelectItem(GetDlgItem(hwndOptions,IDC_PAGETREE),hItem); + } + mir_free(ptszPage); + } + } else { + struct OptionsPageInit opi = { 0 }; + NotifyEventHooks( hOptionsInitEvent, ( WPARAM )&opi, 0 ); + if ( opi.pageCount > 0 ) { + OPENOPTIONSDIALOG ood = { 0 }; + PROPSHEETHEADER psh = { 0 }; + psh.dwSize = sizeof(psh); + psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW; + psh.nPages = opi.pageCount; + ood.pszGroup = pszGroup; + ood.pszPage = pszPage; + ood.pszTab = pszTab; + psh.pStartPage = (LPCTSTR)&ood; //more structure misuse + psh.pszCaption = TranslateT("Miranda IM Options"); + psh.ppsp = (PROPSHEETPAGE*)opi.odp; //blatent misuse of the structure, but what the hell + + hwndOptions = CreateDialogParam(hMirandaInst, + MAKEINTRESOURCE(bSinglePage ? IDD_OPTIONSPAGE : IDD_OPTIONS), + NULL, OptionsDlgProc, (LPARAM)&psh); + + FreeOptionsData( &opi ); +} } } + +static INT_PTR OpenOptions(WPARAM, LPARAM lParam) +{ + OPENOPTIONSDIALOG *ood=(OPENOPTIONSDIALOG*)lParam; + if ( ood == NULL ) + return 1; + + if ( ood->cbSize == OPENOPTIONSDIALOG_OLD_SIZE ) + OpenOptionsNow( ood->pszGroup, ood->pszPage, NULL ); + else if (ood->cbSize == sizeof(OPENOPTIONSDIALOG)) + OpenOptionsNow( ood->pszGroup, ood->pszPage, ood->pszTab ); + else + return 1; + + return 0; +} + +static INT_PTR OpenOptionsPage(WPARAM, LPARAM lParam) +{ + OPENOPTIONSDIALOG *ood=(OPENOPTIONSDIALOG*)lParam; + if ( ood == NULL ) + return 1; + + if ( ood->cbSize == OPENOPTIONSDIALOG_OLD_SIZE ) + OpenOptionsNow( ood->pszGroup, ood->pszPage, NULL, true ); + else if (ood->cbSize == sizeof(OPENOPTIONSDIALOG)) + OpenOptionsNow( ood->pszGroup, ood->pszPage, ood->pszTab, true ); + else + return 1; + + return (INT_PTR)hwndOptions; +} + +static INT_PTR OpenOptionsDialog(WPARAM, LPARAM) +{ + if (hwndOptions || GetAsyncKeyState(VK_CONTROL) || !ServiceExists(MS_MODERNOPT_SHOW)) + OpenOptionsNow(NULL,NULL,NULL); + else + CallService(MS_MODERNOPT_SHOW, 0, 0); + return 0; +} + +static INT_PTR AddOptionsPage(WPARAM wParam,LPARAM lParam) +{ OPTIONSDIALOGPAGE *odp=(OPTIONSDIALOGPAGE*)lParam, *dst; + struct OptionsPageInit *opi=(struct OptionsPageInit*)wParam; + + if(odp==NULL||opi==NULL) return 1; + if(odp->cbSize!=sizeof(OPTIONSDIALOGPAGE) + && odp->cbSize != OPTIONPAGE_OLD_SIZE + && odp->cbSize != OPTIONPAGE_OLD_SIZE2 + && odp->cbSize != OPTIONPAGE_OLD_SIZE3) + return 1; + + opi->odp=(OPTIONSDIALOGPAGE*)mir_realloc(opi->odp,sizeof(OPTIONSDIALOGPAGE)*(opi->pageCount+1)); + dst = opi->odp + opi->pageCount; + memset( dst, 0, sizeof( OPTIONSDIALOGPAGE )); + memcpy( dst, odp, odp->cbSize ); + + if ( odp->ptszTitle != NULL ) { + if ( odp->flags & ODPF_DONTTRANSLATE ) { + #if defined( _UNICODE ) + if ( odp->flags & ODPF_UNICODE ) + dst->ptszTitle = mir_wstrdup( odp->ptszTitle ); + else { + dst->ptszTitle = mir_a2u( odp->pszTitle ); + dst->flags |= ODPF_UNICODE; + } + #else + dst->pszTitle = mir_strdup( odp->pszTitle ); + #endif + } + else { + #if defined( _UNICODE ) + if ( odp->flags & ODPF_UNICODE ) + dst->ptszTitle = mir_wstrdup( TranslateW( odp->ptszTitle )); + else { + dst->ptszTitle = LangPackPcharToTchar( odp->pszTitle ); + dst->flags |= ODPF_UNICODE; + } + #else + dst->pszTitle = mir_strdup( Translate( odp->pszTitle )); + #endif + } + } + + if ( odp->ptszGroup != NULL ) { + #if defined( _UNICODE ) + if ( odp->flags & ODPF_UNICODE ) + dst->ptszGroup = mir_wstrdup( TranslateW( odp->ptszGroup )); + else { + dst->ptszGroup = LangPackPcharToTchar( odp->pszGroup ); + dst->flags |= ODPF_UNICODE; + } + #else + dst->pszGroup = mir_strdup( Translate( odp->pszGroup )); + #endif + } + + if ( odp->cbSize > OPTIONPAGE_OLD_SIZE2 && odp->ptszTab != NULL ) { + #if defined( _UNICODE ) + if ( odp->flags & ODPF_UNICODE ) + dst->ptszTab = mir_wstrdup( TranslateW( odp->ptszTab )); + else { + dst->ptszTab = LangPackPcharToTchar( odp->pszTab ); + dst->flags |= ODPF_UNICODE; + } + #else + dst->pszTab = mir_strdup( Translate( odp->pszTab )); + #endif + } + + if (( DWORD_PTR )odp->pszTemplate & 0xFFFF0000 ) + dst->pszTemplate = mir_strdup( odp->pszTemplate ); + + opi->pageCount++; + return 0; +} + +static int OptModulesLoaded(WPARAM, LPARAM) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_OPTIONS ); + mi.position = 1900000000; + mi.pszName = LPGEN("&Options..."); + mi.pszService = "Options/OptionsCommand"; + CallService( MS_CLIST_ADDMAINMENUITEM, 0, ( LPARAM )&mi ); + return 0; +} + +int ShutdownOptionsModule(WPARAM, LPARAM) +{ + if (IsWindow(hwndOptions)) DestroyWindow(hwndOptions); + hwndOptions=NULL; + + //!!!!!!!!!! UnhookFilterEvents(); + + return 0; +} + +int LoadOptionsModule(void) +{ + hwndOptions=NULL; + hOptionsInitEvent=CreateHookableEvent(ME_OPT_INITIALISE); + CreateServiceFunction(MS_OPT_ADDPAGE,AddOptionsPage); + CreateServiceFunction(MS_OPT_OPENOPTIONS,OpenOptions); + CreateServiceFunction(MS_OPT_OPENOPTIONSPAGE,OpenOptionsPage); + CreateServiceFunction("Options/OptionsCommand",OpenOptionsDialog); + HookEvent(ME_SYSTEM_MODULESLOADED,OptModulesLoaded); + HookEvent(ME_SYSTEM_PRESHUTDOWN,ShutdownOptionsModule); + + //!!!!!!!!!! HookFilterEvents(); + return 0; +} diff --git a/src/modules/plugins/newplugins.cpp b/src/modules/plugins/newplugins.cpp new file mode 100644 index 0000000000..80dddc00dd --- /dev/null +++ b/src/modules/plugins/newplugins.cpp @@ -0,0 +1,1204 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +// block these plugins +#define DEFMOD_REMOVED_UIPLUGINOPTS 21 +#define DEFMOD_REMOVED_PROTOCOLNETLIB 22 + +// basic export prototypes +typedef int (__cdecl * Miranda_Plugin_Load) ( PLUGINLINK * ); +typedef int (__cdecl * Miranda_Plugin_Unload) ( void ); +// version control +typedef PLUGININFO * (__cdecl * Miranda_Plugin_Info) ( DWORD mirandaVersion ); +typedef PLUGININFOEX * (__cdecl * Miranda_Plugin_InfoEx) ( DWORD mirandaVersion ); +// prototype for databases +typedef DATABASELINK * (__cdecl * Database_Plugin_Info) ( void * reserved ); +// prototype for clists +typedef int (__cdecl * CList_Initialise) ( PLUGINLINK * ); +// Interface support +typedef MUUID * (__cdecl * Miranda_Plugin_Interfaces) ( void ); + +typedef struct { // can all be NULL + HINSTANCE hInst; + Miranda_Plugin_Load Load; + Miranda_Plugin_Unload Unload; + Miranda_Plugin_Info Info; + Miranda_Plugin_InfoEx InfoEx; + Miranda_Plugin_Interfaces Interfaces; + Database_Plugin_Info DbInfo; + CList_Initialise clistlink; + PLUGININFOEX * pluginInfo; // must be freed if hInst==NULL then its a copy + DATABASELINK * dblink; // only valid during module being in memory +} BASIC_PLUGIN_INFO; + +#define PCLASS_FAILED 0x1 // not a valid plugin, or API is invalid, pluginname is valid +#define PCLASS_BASICAPI 0x2 // has Load, Unload, MirandaPluginInfo() -> PLUGININFO seems valid, this dll is in memory. +#define PCLASS_DB 0x4 // has DatabasePluginInfo() and is valid as can be, and PCLASS_BASICAPI has to be set too +#define PCLASS_LAST 0x8 // this plugin should be unloaded after everything else +#define PCLASS_OK 0x10 // plugin should be loaded, if DB means nothing +#define PCLASS_LOADED 0x20 // Load() has been called, Unload() should be called. +#define PCLASS_STOPPED 0x40 // wasn't loaded cos plugin name not on white list +#define PCLASS_CLIST 0x80 // a CList implementation +#define PCLASS_SERVICE 0x100 // has Service Mode implementation + +typedef struct pluginEntry { + TCHAR pluginname[64]; + unsigned int pclass; // PCLASS_* + BASIC_PLUGIN_INFO bpi; + struct pluginEntry * nextclass; +} pluginEntry; + +static int sttComparePlugins( const pluginEntry* p1, const pluginEntry* p2 ) +{ return ( int )( p1->bpi.hInst - p2->bpi.hInst ); +} + +static int sttComparePluginsByName( const pluginEntry* p1, const pluginEntry* p2 ) +{ return lstrcmp( p1->pluginname, p2->pluginname ); +} + +LIST pluginList( 10, sttComparePluginsByName ), pluginListAddr( 10, sttComparePlugins ); + +///////////////////////////////////////////////////////////////////////////////// + +#define MAX_MIR_VER ULONG_MAX + +struct PluginUUIDList { + MUUID uuid; + DWORD maxVersion; +} +static const pluginBannedList[] = +{ + {{0x7f65393b, 0x7771, 0x4f3f, { 0xa9, 0xeb, 0x5d, 0xba, 0xf2, 0xb3, 0x61, 0xf1 }}, MAX_MIR_VER}, // png2dib + {{0xe00f1643, 0x263c, 0x4599, { 0xb8, 0x4b, 0x5, 0x3e, 0x5c, 0x51, 0x1d, 0x28 }}, MAX_MIR_VER}, // loadavatars (unicode) + {{0xc9e01eb0, 0xa119, 0x42d2, { 0xb3, 0x40, 0xe8, 0x67, 0x8f, 0x5f, 0xea, 0xd9 }}, MAX_MIR_VER}, // loadavatars (ansi) + {{0xb4ef58c4, 0x4458, 0x4e47, { 0xa7, 0x67, 0x5c, 0xae, 0xe5, 0xe7, 0xc, 0x81 }}, MAX_MIR_VER}, // 0.7.x AIM Protocol + {{0xb529402b, 0x53ba, 0x4c81, { 0x9e, 0x27, 0xd4, 0x31, 0xeb, 0xe8, 0xec, 0x36 }}, MAX_MIR_VER}, // 0.7.x IRC Protocol + {{0x847bb03c, 0x408c, 0x4f9b, { 0xaa, 0x5a, 0xf5, 0xc0, 0xb7, 0xb5, 0x60, 0x1e }}, MAX_MIR_VER}, // 0.7.x ICQ Protocol + {{0x1ee5af12, 0x26b0, 0x4290, { 0x8f, 0x97, 0x16, 0x77, 0xcb, 0xe, 0xfd, 0x2b }}, MAX_MIR_VER}, // 0.7.x Jabber Protocol (Unicode) + {{0xf7f5861d, 0x988d, 0x479d, { 0xa5, 0xbb, 0x80, 0xc7, 0xfa, 0x8a, 0xd0, 0xef }}, MAX_MIR_VER}, // 0.7.x Jabber Protocol (Ansi) + {{0xdc39da8a, 0x8385, 0x4cd9, { 0xb2, 0x98, 0x80, 0x67, 0x7b, 0x8f, 0xe6, 0xe4 }}, MAX_MIR_VER}, // 0.7.x MSN Protocol (Unicode) + {{0x29aa3a80, 0x3368, 0x4b78, { 0x82, 0xc1, 0xdf, 0xc7, 0x29, 0x6a, 0x58, 0x99 }}, MAX_MIR_VER}, // 0.7.x MSN Protocol (Ansi) + {{0xa6648b6c, 0x6fb8, 0x4551, { 0xb4, 0xe7, 0x1, 0x36, 0xf9, 0x16, 0xd4, 0x85 }}, MAX_MIR_VER}, // 0.7.x Yahoo Protocol + {{0x6ca5f042, 0x7a7f, 0x47cc, { 0xa7, 0x15, 0xfc, 0x8c, 0x46, 0xfb, 0xf4, 0x34 }}, PLUGIN_MAKE_VERSION(3, 0, 4, 0)}, // 0.8.x TabSRMM (Unicode) + {{0x5889a3ef, 0x7c95, 0x4249, { 0x98, 0xbb, 0x34, 0xe9, 0x5, 0x3a, 0x6e, 0xa0 }}, PLUGIN_MAKE_VERSION(3, 0, 4, 0)}, // 0.8.x TabSRMM (ANSI) + {{0x84636f78, 0x2057, 0x4302, { 0x8a, 0x65, 0x23, 0xa1, 0x6d, 0x46, 0x84, 0x4c }}, PLUGIN_MAKE_VERSION(2, 9, 0, 4)}, // 0.8.x Scriver (Unicode) + {{0x1e91b6c9, 0xe040, 0x4a6f, { 0xab, 0x56, 0xdf, 0x76, 0x98, 0xfa, 0xcb, 0xf1 }}, PLUGIN_MAKE_VERSION(2, 9, 0, 4)}, // 0.8.x Scriver (ANSI) + {{0x240a91dc, 0x9464, 0x457a, { 0x97, 0x87, 0xff, 0x1e, 0xa8, 0x8e, 0x77, 0xe3 }}, PLUGIN_MAKE_VERSION(0, 9, 0, 0)}, // 0.8.x CList Classic (Unicode) + {{0x552cf71a, 0x249f, 0x4650, { 0xbb, 0x2b, 0x7c, 0xdb, 0x1f, 0xe7, 0xd1, 0x78 }}, PLUGIN_MAKE_VERSION(0, 9, 0, 0)}, // 0.8.x CList Classic (ANSI) + {{0x8f79b4ee, 0xeb48, 0x4a03, { 0x87, 0x3e, 0x27, 0xbe, 0x6b, 0x7e, 0x9a, 0x25 }}, PLUGIN_MAKE_VERSION(0, 9, 1, 0)}, // 0.8.x Clist Nicer (Unicode) + {{0x5a070cec, 0xb2ab, 0x4bbe, { 0x8e, 0x48, 0x9c, 0x8d, 0xcd, 0xda, 0x14, 0xc3 }}, PLUGIN_MAKE_VERSION(0, 9, 1, 0)}, // 0.8.x Clist Nicer (ANSI) + {{0x43909b6, 0xaad8, 0x4d82, { 0x8e, 0xb5, 0x9f, 0x64, 0xcf, 0xe8, 0x67, 0xcd }}, PLUGIN_MAKE_VERSION(0, 9, 0, 8)}, // 0.8.x Clist Modern (Unicode) + {{0xf6588c56, 0x15dc, 0x4cd7, { 0x8c, 0xf9, 0x48, 0xab, 0x6c, 0x5f, 0xd2, 0xf }}, PLUGIN_MAKE_VERSION(0, 9, 0, 8)}, // 0.8.x Clist Modern (ANSI) + {{0x2a417ab9, 0x16f2, 0x472d, { 0x9a, 0xe3, 0x41, 0x51, 0x3, 0xc7, 0x8a, 0x64 }}, PLUGIN_MAKE_VERSION(0, 9, 0, 0)}, // 0.8.x Clist MW (Unicode) + {{0x7ab05d31, 0x9972, 0x4406, { 0x82, 0x3e, 0xe, 0xd7, 0x45, 0xef, 0x7c, 0x56 }}, PLUGIN_MAKE_VERSION(0, 9, 0, 0)} // 0.8.x Clist MW (ANSI) +}; +const int pluginBannedListCount = SIZEOF(pluginBannedList); + +static BOOL bModuleInitialized = FALSE; + +PLUGINLINK pluginCoreLink; +TCHAR mirandabootini[MAX_PATH]; +static DWORD mirandaVersion; +static int serviceModeIdx = -1; +static pluginEntry * pluginListSM; +static pluginEntry * pluginListDb; +static pluginEntry * pluginListUI; +static pluginEntry * pluginList_freeimg; +static pluginEntry * pluginList_crshdmp; +static HANDLE hPluginListHeap = NULL; +static pluginEntry * pluginDefModList[DEFMOD_HIGHEST+1]; // do not free this memory +static int askAboutIgnoredPlugins; + +int InitIni(void); +void UninitIni(void); + +#define PLUGINDISABLELIST "PluginDisable" + +int CallHookSubscribers( HANDLE hEvent, WPARAM wParam, LPARAM lParam ); + +int LoadDatabaseModule(void); + +char * GetPluginNameByInstance( HINSTANCE hInstance ) +{ + int i = 0; + if ( pluginList.getCount() == 0) return NULL; + for (i = 0; i < pluginList.getCount(); i++) + { + pluginEntry* pe = pluginList[i]; + if (pe->bpi.pluginInfo && pe->bpi.hInst == hInstance) + return pe->bpi.pluginInfo->shortName; + } + return NULL; +} + +HINSTANCE GetInstByAddress( void* codePtr ) +{ + int idx; + HINSTANCE result; + pluginEntry p; p.bpi.hInst = ( HINSTANCE )codePtr; + + if ( pluginListAddr.getCount() == 0 ) + return NULL; + + List_GetIndex(( SortedList* )&pluginListAddr, &p, &idx ); + if ( idx > 0 ) + idx--; + + result = pluginListAddr[idx]->bpi.hInst; + + if (result < hMirandaInst && codePtr > hMirandaInst) + result = hMirandaInst; + else if ( idx == 0 && codePtr < ( void* )result ) + result = NULL; + + return result; +} + +static int uuidToString(const MUUID uuid, char *szStr, int cbLen) +{ + if (cbLen<1||!szStr) return 0; + mir_snprintf(szStr, cbLen, "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + uuid.a, uuid.b, uuid.c, uuid.d[0], uuid.d[1], uuid.d[2], uuid.d[3], uuid.d[4], uuid.d[5], uuid.d[6], uuid.d[7]); + return 1; +} + +static int equalUUID(MUUID u1, MUUID u2) +{ + return memcmp(&u1, &u2, sizeof(MUUID))?0:1; +} + +static MUUID miid_last = MIID_LAST; +static MUUID miid_servicemode = MIID_SERVICEMODE; + +static int validInterfaceList(Miranda_Plugin_Interfaces ifaceProc) +{ + MUUID *piface = ( ifaceProc ) ? ifaceProc() : NULL; + int i = 0/*, j*/; + + if (!piface) + return 0; + if (equalUUID(miid_last, piface[0])) + return 0; + /*while (!equalUUID(miid_last, piface[i]) ) { + for (j=0; jInfoEx ) { + if ( pi->cbSize == sizeof(PLUGININFOEX)) + if ( !validInterfaceList(bpi->Interfaces) || isPluginBanned( pi->uuid, pi->version )) + return FALSE; + + bHasValidInfo = TRUE; + } + + if ( !bHasValidInfo ) + if ( bpi->Info && pi->cbSize != sizeof(PLUGININFO)) + return FALSE; + + if ( pi->shortName == NULL || pi->description == NULL || pi->author == NULL || + pi->authorEmail == NULL || pi->copyright == NULL || pi->homepage == NULL ) + return FALSE; + + if ( pi->replacesDefaultModule > DEFMOD_HIGHEST || + pi->replacesDefaultModule == DEFMOD_REMOVED_UIPLUGINOPTS || + pi->replacesDefaultModule == DEFMOD_REMOVED_PROTOCOLNETLIB ) + return FALSE; + + return TRUE; +} + +static int checkAPI(TCHAR* plugin, BASIC_PLUGIN_INFO* bpi, DWORD mirandaVersion, int checkTypeAPI, int* exports) +{ + HINSTANCE h = NULL; + // this is evil but these plugins are buggy/old and people are blaming Miranda + // fontservice plugin is built into the core now + { + TCHAR * p = _tcsrchr(plugin, '\\'); + if ( p != NULL && ++p ) { + int i; + for ( i = 0; i < SIZEOF(modulesToSkip); i++ ) + if ( lstrcmpi( p, modulesToSkip[i] ) == 0 ) + return 0; + } } + + h = LoadLibrary(plugin); + if ( h == NULL ) return 0; + // loaded, check for exports + bpi->Load = (Miranda_Plugin_Load) GetProcAddress(h, "Load"); + bpi->Unload = (Miranda_Plugin_Unload) GetProcAddress(h, "Unload"); + bpi->Info = (Miranda_Plugin_Info) GetProcAddress(h, "MirandaPluginInfo"); + bpi->InfoEx = (Miranda_Plugin_InfoEx) GetProcAddress(h, "MirandaPluginInfoEx"); + bpi->Interfaces = (Miranda_Plugin_Interfaces) GetProcAddress(h, "MirandaPluginInterfaces"); + + // if they were present + if ( bpi->Load && bpi->Unload && ( bpi->Info || ( bpi->InfoEx && bpi->Interfaces ))) { + PLUGININFOEX* pi = 0; + if (bpi->InfoEx) + pi = bpi->InfoEx(mirandaVersion); + else + pi = (PLUGININFOEX*)bpi->Info(mirandaVersion); + { + // similar to the above hack but these plugins are checked for a valid interface first (in case there are updates to the plugin later) + TCHAR* p = _tcsrchr(plugin, '\\'); + if ( pi != NULL && p != NULL && ++p ) { + if ( !bpi->InfoEx || pi->cbSize != sizeof(PLUGININFOEX)) { + int i; + for ( i = 0; i < SIZEOF(expiredModulesToSkip); i++ ) { + if ( lstrcmpi( p, expiredModulesToSkip[i] ) == 0 ) { + FreeLibrary(h); + return 0; + } } } } } + + if ( checkPI( bpi, pi )) { + bpi->pluginInfo = pi; + // basic API is present + if ( checkTypeAPI == CHECKAPI_NONE ) { + bpi->hInst=h; + return 1; + } + // check for DB? + if ( checkTypeAPI == CHECKAPI_DB ) { + bpi->DbInfo = (Database_Plugin_Info) GetProcAddress(h, "DatabasePluginInfo"); + if ( bpi->DbInfo ) { + // fetch internal database function pointers + bpi->dblink = bpi->DbInfo(NULL); + // validate returned link structure + if ( bpi->dblink && bpi->dblink->cbSize==sizeof(DATABASELINK) ) { + bpi->hInst=h; + return 1; + } + // had DB exports + if ( exports != NULL ) *exports=1; + } //if + } //if + + // check clist ? + if ( checkTypeAPI == CHECKAPI_CLIST ) { + bpi->clistlink = (CList_Initialise) GetProcAddress(h, "CListInitialise"); + #if defined( _UNICODE ) + if ( pi->flags & UNICODE_AWARE ) + #endif + if ( bpi->clistlink ) { + // nothing more can be done here, this export is a load function + bpi->hInst=h; + if ( exports != NULL ) *exports=1; + return 1; + } + } + + } // if + if ( exports != NULL ) *exports=1; + } //if + // not found, unload + FreeLibrary(h); + return 0; +} + +// returns true if the given file is .dll exactly +static int valid_library_name(TCHAR *name) +{ + TCHAR * dot = _tcsrchr(name, '.'); + if ( dot != NULL && lstrcmpi(dot + 1, _T("dll")) == 0) + if (dot[4] == 0) + return 1; + + return 0; +} + +// returns true if the given file matches dbx_*.dll, which is used to LoadLibrary() +static int validguess_db_name(TCHAR * name) +{ + int rc = 0; + // this is ONLY SAFE because name -> ffd.cFileName == MAX_PATH + TCHAR x = name[4]; + name[4]=0; + rc = lstrcmpi(name, _T("dbx_")) == 0 || lstrcmpi(name, _T("dbrw")) == 0; + name[4] = x; + return rc; +} + +// returns true if the given file matches clist_*.dll +static int validguess_clist_name(TCHAR * name) +{ + int rc=0; + // argh evil + TCHAR x = name[6]; + name[6] = 0; + rc = lstrcmpi(name, _T("clist_")) == 0; + name[6] = x; + return rc; +} + +// returns true if the given file matches svc_*.dll +static int validguess_servicemode_name(TCHAR * name) +{ + int rc = 0; + // argh evil + TCHAR x = name[4]; + name[4]=0; + rc = lstrcmpi(name, _T("svc_")) == 0; + name[4] = x; + return rc; +} + +// perform any API related tasks to freeing +static void Plugin_Uninit(pluginEntry * p) +{ + // if it was an installed database plugin, call its unload + if ( p->pclass & PCLASS_DB ) + p->bpi.dblink->Unload( p->pclass & PCLASS_OK ); + + // if the basic API check had passed, call Unload if Load() was ever called + if ( p->pclass & PCLASS_LOADED ) + p->bpi.Unload(); + + // release the library + if ( p->bpi.hInst != NULL ) { + // we need to kill all resources which belong to that DLL before calling FreeLibrary + KillModuleEventHooks( p->bpi.hInst ); + KillModuleServices( p->bpi.hInst ); + + FreeLibrary( p->bpi.hInst ); + ZeroMemory( &p->bpi, sizeof( p->bpi )); + } + pluginList.remove( p ); + pluginListAddr.remove( p ); +} + +typedef BOOL (*SCAN_PLUGINS_CALLBACK) ( WIN32_FIND_DATA * fd, TCHAR * path, WPARAM wParam, LPARAM lParam ); + +static void enumPlugins(SCAN_PLUGINS_CALLBACK cb, WPARAM wParam, LPARAM lParam) +{ + TCHAR exe[MAX_PATH]; + TCHAR search[MAX_PATH]; + TCHAR * p = 0; + // get miranda's exe path + GetModuleFileName(NULL, exe, SIZEOF(exe)); + // find the last \ and null it out, this leaves no trailing slash + p = _tcsrchr(exe, '\\'); if (p) *p = 0; + // create the search filter + mir_sntprintf(search, SIZEOF(search), _T("%s\\Plugins\\*.dll"), exe); + { + // FFFN will return filenames for things like dot dll+ or dot dllx + HANDLE hFind=INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd; + hFind = FindFirstFile(search, &ffd); + if (hFind != INVALID_HANDLE_VALUE) + { + do { + if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && valid_library_name(ffd.cFileName)) + { + cb(&ffd, exe, wParam, lParam); + } //if + } while (FindNextFile(hFind, &ffd)); + FindClose(hFind); + } //if + } +} + +// this is called by the db module to return all DBs plugins, then when it finds the one it likes the others are unloaded +static INT_PTR PluginsEnum(WPARAM, LPARAM lParam) +{ + PLUGIN_DB_ENUM * de = (PLUGIN_DB_ENUM *) lParam; + pluginEntry * x = pluginListDb; + if ( de == NULL || de->cbSize != sizeof(PLUGIN_DB_ENUM) || de->pfnEnumCallback == NULL ) return 1; + while ( x != NULL ) + { + int rc = de->pfnEnumCallback(StrConvA(x->pluginname), x->bpi.dblink, de->lParam); + if (rc == DBPE_DONE) + { + // this db has been picked, get rid of all the others + pluginEntry * y = pluginListDb, * n; + while ( y != NULL ) + { + n = y->nextclass; + if ( x != y ) + Plugin_Uninit(y); + y = n; + } // while + x->pclass |= PCLASS_LOADED | PCLASS_OK | PCLASS_LAST; + return 0; + } + else if ( rc == DBPE_HALT ) return 1; + x = x->nextclass; + } // while + return pluginListDb != NULL ? 1 : -1; +} + +static INT_PTR PluginsGetDefaultArray(WPARAM, LPARAM) +{ + return (INT_PTR)&pluginDefModList; +} + +// called in the first pass to create pluginEntry* structures and validate database plugins +static BOOL scanPluginsDir (WIN32_FIND_DATA * fd, TCHAR * path, WPARAM, LPARAM) +{ + int isdb = validguess_db_name(fd->cFileName); + BASIC_PLUGIN_INFO bpi; + pluginEntry* p = (pluginEntry*)HeapAlloc(hPluginListHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, sizeof(pluginEntry)); + _tcsncpy(p->pluginname, fd->cFileName, SIZEOF(p->pluginname)); + // plugin name suggests its a db module, load it right now + if ( isdb ) + { + TCHAR buf[MAX_PATH]; + mir_sntprintf(buf, SIZEOF(buf), _T("%s\\Plugins\\%s"), path, fd->cFileName); + if (checkAPI(buf, &bpi, mirandaVersion, CHECKAPI_DB, NULL)) + { + // db plugin is valid + p->pclass |= (PCLASS_DB | PCLASS_BASICAPI); + // copy the dblink stuff + p->bpi=bpi; + // keep a faster list. + if ( pluginListDb != NULL ) p->nextclass = pluginListDb; + pluginListDb=p; + } + else + // didn't have basic APIs or DB exports - failed. + p->pclass |= PCLASS_FAILED; + } + else if (validguess_clist_name(fd->cFileName)) + { + // keep a note of this plugin for later + if ( pluginListUI != NULL ) p->nextclass=pluginListUI; + pluginListUI=p; + p->pclass |= PCLASS_CLIST; + } + else if (validguess_servicemode_name(fd->cFileName)) + { + TCHAR buf[MAX_PATH]; + mir_sntprintf(buf, SIZEOF(buf), _T("%s\\Plugins\\%s"), path, fd->cFileName); + if (checkAPI(buf, &bpi, mirandaVersion, CHECKAPI_NONE, NULL)) + { + p->pclass |= (PCLASS_OK | PCLASS_BASICAPI); + p->bpi = bpi; + if (bpi.Interfaces) + { + int i = 0; + MUUID *piface = bpi.Interfaces(); + while (!equalUUID(miid_last, piface[i])) + { + if (!equalUUID(miid_servicemode, piface[i++])) + continue; + p->pclass |= (PCLASS_SERVICE); + if ( pluginListSM != NULL ) p->nextclass = pluginListSM; + pluginListSM=p; + if (pluginList_crshdmp == NULL && lstrcmpi(fd->cFileName, _T("svc_crshdmp.dll")) == 0) + { + pluginList_crshdmp = p; + p->pclass |= PCLASS_LAST; + } + break; + } + } + } + else + // didn't have basic APIs or DB exports - failed. + p->pclass |= PCLASS_FAILED; + } + else if (pluginList_freeimg == NULL && lstrcmpi(fd->cFileName, _T("advaimg.dll")) == 0) + pluginList_freeimg = p; + + // add it to the list + pluginList.insert( p ); + return TRUE; +} + +static void SetPluginOnWhiteList(TCHAR * pluginname, int allow) +{ + DBWriteContactSettingByte(NULL, PLUGINDISABLELIST, StrConvA(pluginname), allow == 0); +} + +// returns 1 if the plugin should be enabled within this profile, filename is always lower case +static int isPluginOnWhiteList(TCHAR * pluginname) +{ + char* pluginnameA = _strlwr(mir_t2a(pluginname)); + int rc = DBGetContactSettingByte(NULL, PLUGINDISABLELIST, pluginnameA, 0); + mir_free(pluginnameA); + if ( rc != 0 && askAboutIgnoredPlugins ) + { + TCHAR buf[256]; + mir_sntprintf(buf, SIZEOF(buf), TranslateT("'%s' is disabled, re-enable?"), pluginname); + if (MessageBox(NULL, buf, TranslateT("Re-enable Miranda plugin?"), MB_YESNO | MB_ICONQUESTION) == IDYES) + { + SetPluginOnWhiteList(pluginname, 1); + rc = 0; + } + } + + return rc == 0; +} + +static pluginEntry* getCListModule(TCHAR * exe, TCHAR * slice, int useWhiteList) +{ + pluginEntry * p = pluginListUI; + BASIC_PLUGIN_INFO bpi; + while ( p != NULL ) + { + mir_sntprintf(slice, &exe[MAX_PATH] - slice, _T("\\Plugins\\%s"), p->pluginname); + CharLower(p->pluginname); + if ( useWhiteList ? isPluginOnWhiteList(p->pluginname) : 1 ) { + if ( checkAPI(exe, &bpi, mirandaVersion, CHECKAPI_CLIST, NULL) ) { + p->bpi = bpi; + p->pclass |= PCLASS_LAST | PCLASS_OK | PCLASS_BASICAPI; + pluginListAddr.insert( p ); + if ( bpi.clistlink(&pluginCoreLink) == 0 ) { + p->bpi = bpi; + p->pclass |= PCLASS_LOADED; + return p; + } + else Plugin_Uninit( p ); + } //if + } //if + p = p->nextclass; + } + return NULL; +} + +int UnloadPlugin(TCHAR* buf, int bufLen) +{ + int i; + for ( i = pluginList.getCount()-1; i >= 0; i-- ) + { + pluginEntry* p = pluginList[i]; + if (!_tcsicmp( p->pluginname, buf)) + { + GetModuleFileName( p->bpi.hInst, buf, bufLen); + Plugin_Uninit( p ); + return TRUE; + } + } + + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// +// Service plugins functions + +char **GetSeviceModePluginsList(void) +{ + int i = 0; + char **list = NULL; + pluginEntry * p = pluginListSM; + while ( p != NULL ) { + i++; + p = p->nextclass; + } + if ( i ) { + list = (char**)mir_calloc( (i + 1) * sizeof(char*) ); + p = pluginListSM; + i = 0; + while ( p != NULL ) { + list[i++] = p->bpi.pluginInfo->shortName; + p = p->nextclass; + } + } + return list; +} + +void SetServiceModePlugin( int idx ) +{ + serviceModeIdx = idx; +} + +int LoadServiceModePlugin(void) +{ + int i = 0; + pluginEntry * p = pluginListSM; + + if ( serviceModeIdx < 0 ) + return 0; + + while ( p != NULL ) { + if ( serviceModeIdx == i++ ) { + if ( p->bpi.Load(&pluginCoreLink) == 0 ) { + p->pclass |= PCLASS_LOADED; + if ( CallService( MS_SERVICEMODE_LAUNCH, 0, 0 ) != CALLSERVICE_NOTFOUND ) + return 1; + else { + MessageBox(NULL, TranslateT("Unable to load plugin in Service Mode!"), p->pluginname, 0); + return -1; + } + } + Plugin_Uninit( p ); + return -1; + } + p = p->nextclass; + } + return -1; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// +// Event hook to unload all non-core plugins +// hooked very late, after all the internal plugins, blah + +void UnloadNewPlugins(void) +{ + int i; + + // unload everything but the special db/clist plugins + for ( i = pluginList.getCount()-1; i >= 0; i-- ) { + pluginEntry* p = pluginList[i]; + if ( !(p->pclass & PCLASS_LAST) && (p->pclass & PCLASS_OK)) + Plugin_Uninit( p ); +} } + +///////////////////////////////////////////////////////////////////////////////////////// +// +// Plugins options page dialog + +typedef struct +{ + int flags; + char* author; + char* authorEmail; + char* description; + char* copyright; + char* homepage; + MUUID uuid; +} + PluginListItemData; + +static BOOL dialogListPlugins(WIN32_FIND_DATA * fd, TCHAR * path, WPARAM, LPARAM lParam) +{ + LVITEM it; + int iRow; + HWND hwndList=(HWND)lParam; + BASIC_PLUGIN_INFO pi; + int exports=0; + TCHAR buf[MAX_PATH]; + int isdb = 0; + HINSTANCE gModule; + PluginListItemData* dat; + + mir_sntprintf(buf, SIZEOF(buf), _T("%s\\Plugins\\%s"), path, fd->cFileName); + CharLower(fd->cFileName); + gModule = GetModuleHandle(buf); + if ( checkAPI(buf, &pi, mirandaVersion, CHECKAPI_NONE, &exports) == 0 ) { + // failed to load anything, but if exports were good, show some info. + return TRUE; + } + isdb = pi.pluginInfo->replacesDefaultModule == DEFMOD_DB; + ZeroMemory(&it, sizeof(it)); + it.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + it.pszText = fd->cFileName; + it.iImage = ( pi.pluginInfo->flags & 1 ) ? 0 : 1; + it.lParam = (LPARAM)( dat = (PluginListItemData*)mir_alloc( sizeof( PluginListItemData ))); + iRow=SendMessage( hwndList, LVM_INSERTITEM, 0, (LPARAM)&it ); + if ( isPluginOnWhiteList(fd->cFileName) ) + ListView_SetItemState(hwndList, iRow, !isdb ? 0x2000 : 0x3000, LVIS_STATEIMAGEMASK); + if ( iRow != -1 ) { + dat->flags = pi.pluginInfo->replacesDefaultModule; + dat->author = mir_strdup( pi.pluginInfo->author ); + dat->authorEmail = mir_strdup( pi.pluginInfo->authorEmail ); + dat->copyright = mir_strdup( pi.pluginInfo->copyright ); + dat->description = mir_strdup( pi.pluginInfo->description ); + dat->homepage = mir_strdup( pi.pluginInfo->homepage ); + if ( pi.pluginInfo->cbSize == sizeof( PLUGININFOEX )) + dat->uuid = pi.pluginInfo->uuid; + else + memset( &dat->uuid, 0, sizeof(dat->uuid)); + + TCHAR *shortNameT = mir_a2t(pi.pluginInfo->shortName); + ListView_SetItemText(hwndList, iRow, 1, shortNameT); + mir_free(shortNameT); + + mir_sntprintf(buf, SIZEOF(buf), _T("%d.%d.%d.%d"), HIBYTE(HIWORD(pi.pluginInfo->version)), + LOBYTE(HIWORD(pi.pluginInfo->version)), HIBYTE(LOWORD(pi.pluginInfo->version)), + LOBYTE(LOWORD(pi.pluginInfo->version))); + ListView_SetItemText(hwndList, iRow, 2, buf); + + it.mask = LVIF_IMAGE; + it.iItem = iRow; + it.iSubItem = 3; + it.iImage = ( gModule != NULL ) ? 2 : 3; + ListView_SetItem( hwndList, &it ); + } + else mir_free( dat ); + FreeLibrary(pi.hInst); + return TRUE; +} + +static void RemoveAllItems( HWND hwnd ) +{ + LVITEM lvi; + lvi.mask = LVIF_PARAM; + lvi.iItem = 0; + while ( ListView_GetItem( hwnd, &lvi )) { + PluginListItemData* dat = ( PluginListItemData* )lvi.lParam; + mir_free( dat->author ); + mir_free( dat->authorEmail ); + mir_free( dat->copyright ); + mir_free( dat->description ); + mir_free( dat->homepage ); + mir_free( dat ); + lvi.iItem ++; +} } + +INT_PTR CALLBACK DlgPluginOpt(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + { + HWND hwndList=GetDlgItem(hwndDlg,IDC_PLUGLIST); + LVCOLUMN col; + HIMAGELIST hIml = ImageList_Create(16, 16, ILC_MASK | (IsWinVerXPPlus()? ILC_COLOR32 : ILC_COLOR16), 4, 0); + ImageList_AddIcon_IconLibLoaded( hIml, SKINICON_OTHER_UNICODE ); + ImageList_AddIcon_IconLibLoaded( hIml, SKINICON_OTHER_ANSI ); + ImageList_AddIcon_IconLibLoaded( hIml, SKINICON_OTHER_LOADED ); + ImageList_AddIcon_IconLibLoaded( hIml, SKINICON_OTHER_NOTLOADED ); + ListView_SetImageList( hwndList, hIml, LVSIL_SMALL ); + + TranslateDialogDefault(hwndDlg); + + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = TranslateT("Plugin"); + col.cx = 70;//max = 140; + ListView_InsertColumn(hwndList,0,&col); + + col.pszText=TranslateT("Name"); + col.cx = 70;//max = 220; + ListView_InsertColumn(hwndList,1,&col); + + col.pszText=TranslateT("Version"); + col.cx=55; + ListView_InsertColumn(hwndList,2,&col); + + col.pszText=_T(""); + col.cx=20; + ListView_InsertColumn(hwndList,3,&col); + //ListView_InsertColumn(hwndList,4,&col); + + // XXX: Won't work on windows 95 without IE3+ or 4.70 + ListView_SetExtendedListViewStyleEx( hwndList, 0, LVS_EX_SUBITEMIMAGES | LVS_EX_CHECKBOXES | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT ); + // scan the plugin dir for plugins, cos + enumPlugins( dialogListPlugins, ( WPARAM )hwndDlg, ( LPARAM )hwndList ); + // sort out the headers + { + int w, max; + + ListView_SetColumnWidth( hwndList, 0, LVSCW_AUTOSIZE ); // dll name + w = ListView_GetColumnWidth( hwndList, 0 ); + if (w>140) { + ListView_SetColumnWidth( hwndList, 0, 140 ); + w = 140; + } + max = w<140? 220+140-w:220; + ListView_SetColumnWidth( hwndList, 1, LVSCW_AUTOSIZE ); // short name + w = ListView_GetColumnWidth( hwndList, 1 ); + if (w>max) + ListView_SetColumnWidth( hwndList, 1, max ); + } + return TRUE; + } + case WM_NOTIFY: + { + NMLISTVIEW * hdr = (NMLISTVIEW *) lParam; + if ( hdr && hdr->hdr.code == LVN_ITEMCHANGED && hdr->uOldState != 0 + && (hdr->uNewState == 0x1000 || hdr->uNewState == 0x2000 ) && IsWindowVisible(hdr->hdr.hwndFrom) ) { + HWND hwndList = GetDlgItem(hwndDlg,IDC_PLUGLIST); + PluginListItemData* dat; + int iRow; + LVITEM it; + it.mask=LVIF_PARAM | LVIF_STATE; + it.iItem = hdr->iItem; + if ( !ListView_GetItem( hwndList, &it )) + break; + + dat = ( PluginListItemData* )it.lParam; + if ( dat->flags == DEFMOD_DB ) { + ListView_SetItemState(hwndList, hdr->iItem, 0x3000, LVIS_STATEIMAGEMASK); + return FALSE; + } + // if enabling and replaces, find all other replaces and toggle off + if ( hdr->uNewState & 0x2000 && dat->flags != 0 ) { + for ( iRow=0; iRow != -1; ) { + if ( iRow != hdr->iItem ) { + LVITEM dt; + dt.mask = LVIF_PARAM; + dt.iItem = iRow; + if ( ListView_GetItem( hwndList, &dt )) { + PluginListItemData* dat2 = ( PluginListItemData* )dt.lParam; + if ( dat2->flags == dat->flags ) { + // the lParam is unset, so when the check is unset the clist block doesnt trigger + int lParam = dat2->flags; + dat2->flags = 0; + ListView_SetItemState(hwndList, iRow, 0x1000, LVIS_STATEIMAGEMASK ); + dat2->flags = lParam; + } } } + + iRow = ListView_GetNextItem( hwndList, iRow, LVNI_ALL ); + } } + + ShowWindow( GetDlgItem(hwndDlg, IDC_RESTART ), TRUE ); + SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 ); + break; + } + + if ( hdr && hdr->hdr.code == LVN_ITEMCHANGED && IsWindowVisible(hdr->hdr.hwndFrom) && hdr->iItem != -1 ) { + TCHAR buf[1024]; + int sel = hdr->uNewState & LVIS_SELECTED; + HWND hwndList = GetDlgItem(hwndDlg, IDC_PLUGLIST); + LVITEM lvi = { 0 }; + lvi.mask = LVIF_PARAM; + lvi.iItem = hdr->iItem; + if ( ListView_GetItem( hwndList, &lvi )) { + PluginListItemData* dat = ( PluginListItemData* )lvi.lParam; + + ListView_GetItemText(hwndList, hdr->iItem, 1, buf, SIZEOF(buf)); + SetWindowText(GetDlgItem(hwndDlg,IDC_PLUGININFOFRAME),sel ? buf : _T("")); + + SetWindowTextA(GetDlgItem(hwndDlg,IDC_PLUGINAUTHOR), sel ? dat->author : "" ); + SetWindowTextA(GetDlgItem(hwndDlg,IDC_PLUGINEMAIL), sel ? dat->authorEmail : "" ); + { + TCHAR* p = LangPackPcharToTchar( dat->description ); + SetWindowText(GetDlgItem(hwndDlg,IDC_PLUGINLONGINFO), sel ? p : _T("")); + mir_free( p ); + } + SetWindowTextA(GetDlgItem(hwndDlg,IDC_PLUGINCPYR), sel ? dat->copyright : "" ); + SetWindowTextA(GetDlgItem(hwndDlg,IDC_PLUGINURL), sel ? dat->homepage : "" ); + if(equalUUID(miid_last, dat->uuid)) + SetWindowText(GetDlgItem(hwndDlg,IDC_PLUGINPID), sel ? TranslateT("") : _T("")); + else + { + char szUID[128]; + uuidToString( dat->uuid, szUID, sizeof(szUID)); + SetWindowTextA(GetDlgItem(hwndDlg,IDC_PLUGINPID), sel ? szUID : "" ); + } + } } + + if ( hdr && hdr->hdr.code == PSN_APPLY ) { + HWND hwndList=GetDlgItem(hwndDlg,IDC_PLUGLIST); + int iRow; + int iState; + TCHAR buf[1024]; + for (iRow=0 ; iRow != (-1) ; ) { + ListView_GetItemText(hwndList, iRow, 0, buf, SIZEOF(buf)); + iState=ListView_GetItemState(hwndList, iRow, LVIS_STATEIMAGEMASK); + SetPluginOnWhiteList(buf, iState&0x2000 ? 1 : 0); + iRow=ListView_GetNextItem(hwndList, iRow, LVNI_ALL); + } } + break; + } + + case WM_COMMAND: + if ( HIWORD(wParam) == STN_CLICKED ) { + switch (LOWORD(wParam)) { + case IDC_PLUGINEMAIL: + case IDC_PLUGINURL: + { + char buf[512]; + char * p = &buf[7]; + lstrcpyA(buf,"mailto:"); + if ( GetWindowTextA(GetDlgItem(hwndDlg, LOWORD(wParam)), p, SIZEOF(buf) - 7) ) { + CallService(MS_UTILS_OPENURL,0,(LPARAM) (LOWORD(wParam)==IDC_PLUGINEMAIL ? buf : p) ); + } + break; + } + case IDC_GETMOREPLUGINS: + { + CallService(MS_UTILS_OPENURL,0,(LPARAM) "http://addons.miranda-im.org/index.php?action=display&id=1" ); + break; + } + } } + break; + + case WM_DESTROY: + RemoveAllItems( GetDlgItem( hwndDlg, IDC_PLUGLIST )); + break; + } + return FALSE; +} + +static int PluginOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.hInstance = hMirandaInst; + odp.pfnDlgProc = DlgPluginOpt; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_PLUGINS); + odp.position = 1300000000; + odp.pszTitle = LPGEN("Plugins"); + odp.flags = ODPF_BOLDGROUPS; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// +// Loads all plugins + +int LoadNewPluginsModule(void) +{ + TCHAR exe[MAX_PATH]; + TCHAR* slice; + pluginEntry* p; + pluginEntry* clist = NULL; + int useWhiteList, i; + bool msgModule = false; + + // make full path to the plugin + GetModuleFileName(NULL, exe, SIZEOF(exe)); + slice = _tcsrchr(exe, '\\'); + if (slice) *slice = 0; + + // remember some useful options + askAboutIgnoredPlugins=(UINT) GetPrivateProfileInt( _T("PluginLoader"), _T("AskAboutIgnoredPlugins"), 0, mirandabootini); + + // if Crash Dumper is present, load it to provide Crash Reports + if (pluginList_crshdmp != NULL && isPluginOnWhiteList(pluginList_crshdmp->pluginname)) + { + if ( pluginList_crshdmp->bpi.Load(&pluginCoreLink) == 0 ) + pluginList_crshdmp->pclass |= PCLASS_LOADED | PCLASS_LAST; + else + Plugin_Uninit( pluginList_crshdmp ); + } + + // if freeimage is present, load it to provide the basic core functions + if ( pluginList_freeimg != NULL ) { + BASIC_PLUGIN_INFO bpi; + mir_sntprintf(slice, &exe[SIZEOF(exe)] - slice, _T("\\Plugins\\%s"), pluginList_freeimg->pluginname); + if ( checkAPI(exe, &bpi, mirandaVersion, CHECKAPI_NONE, NULL) ) { + pluginList_freeimg->bpi = bpi; + pluginList_freeimg->pclass |= PCLASS_OK | PCLASS_BASICAPI; + if ( bpi.Load(&pluginCoreLink) == 0 ) + pluginList_freeimg->pclass |= PCLASS_LOADED; + else + Plugin_Uninit( pluginList_freeimg ); + } } + + // first load the clist cos alot of plugins need that to be present at Load() + for ( useWhiteList = 1; useWhiteList >= 0 && clist == NULL; useWhiteList-- ) + clist=getCListModule(exe, slice, useWhiteList); + /* the loop above will try and get one clist DLL to work, if all fail then just bail now */ + if ( clist == NULL ) { + // result = 0, no clist_* can be found + if ( pluginListUI ) + MessageBox(NULL, TranslateT("Unable to start any of the installed contact list plugins, I even ignored your preferences for which contact list couldn't load any."), _T("Miranda IM"), MB_OK | MB_ICONINFORMATION); + else + MessageBox(NULL, TranslateT("Can't find a contact list plugin! you need clist_classic or any other clist plugin.") , _T("Miranda IM"), MB_OK | MB_ICONINFORMATION); + return 1; + } + + /* enable and disable as needed */ + p = pluginListUI; + while ( p != NULL ) { + SetPluginOnWhiteList(p->pluginname, clist != p ? 0 : 1 ); + p = p->nextclass; + } + /* now loop thru and load all the other plugins, do this in one pass */ + + for ( i=0; i < pluginList.getCount(); i++ ) { + p = pluginList[i]; + CharLower(p->pluginname); + if (!(p->pclass & (PCLASS_LOADED | PCLASS_DB | PCLASS_CLIST))) + { + if (isPluginOnWhiteList(p->pluginname)) + { + BASIC_PLUGIN_INFO bpi; + mir_sntprintf(slice, &exe[SIZEOF(exe)] - slice, _T("\\Plugins\\%s"), p->pluginname); + if ( checkAPI(exe, &bpi, mirandaVersion, CHECKAPI_NONE, NULL) ) { + int rm = bpi.pluginInfo->replacesDefaultModule; + p->bpi = bpi; + p->pclass |= PCLASS_OK | PCLASS_BASICAPI; + + if ( pluginDefModList[rm] == NULL ) { + pluginListAddr.insert( p ); + if ( bpi.Load(&pluginCoreLink) == 0 ) { + p->pclass |= PCLASS_LOADED; + msgModule |= (bpi.pluginInfo->replacesDefaultModule == DEFMOD_SRMESSAGE); + } + else { + Plugin_Uninit( p ); + i--; + } + if ( rm ) pluginDefModList[rm]=p; + } //if + else { + SetPluginOnWhiteList( p->pluginname, 0 ); + Plugin_Uninit( p ); + i--; + } + } + else p->pclass |= PCLASS_FAILED; + } + else { + Plugin_Uninit( p ); + i--; + } + } + else if ( p->bpi.hInst != NULL ) + { + pluginListAddr.insert( p ); + p->pclass |= PCLASS_LOADED; + } + } + if (!msgModule) + MessageBox(NULL, TranslateT("No messaging plugins loaded. Please install/enable one of the messaging plugins, for instance, \"srmm.dll\""), _T("Miranda IM"), MB_OK | MB_ICONINFORMATION); + + HookEvent(ME_OPT_INITIALISE, PluginOptionsInit); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// +// Plugins module initialization +// called before anything real is loaded, incl. database + +int LoadNewPluginsModuleInfos(void) +{ + bModuleInitialized = TRUE; + + hPluginListHeap=HeapCreate(HEAP_NO_SERIALIZE, 0, 0); + mirandaVersion = (DWORD)CallService(MS_SYSTEM_GETVERSION, 0, 0); + // + CreateServiceFunction(MS_PLUGINS_ENUMDBPLUGINS, PluginsEnum); + CreateServiceFunction(MS_PLUGINS_GETDISABLEDEFAULTARRAY, PluginsGetDefaultArray); + // make sure plugins can get internal core APIs + pluginCoreLink.CallService = CallService; + pluginCoreLink.ServiceExists = ServiceExists; + pluginCoreLink.CreateServiceFunction = CreateServiceFunction; + pluginCoreLink.CreateServiceFunctionParam = CreateServiceFunctionParam; + pluginCoreLink.CreateServiceFunctionObj = CreateServiceFunctionObj; + pluginCoreLink.CreateServiceFunctionObjParam = CreateServiceFunctionObjParam; + pluginCoreLink.CreateTransientServiceFunction = CreateServiceFunction; + pluginCoreLink.DestroyServiceFunction = DestroyServiceFunction; + pluginCoreLink.CreateHookableEvent = CreateHookableEvent; + pluginCoreLink.DestroyHookableEvent = DestroyHookableEvent; + pluginCoreLink.HookEvent = HookEvent; + pluginCoreLink.HookEventParam = HookEventParam; + pluginCoreLink.HookEventObj = HookEventObj; + pluginCoreLink.HookEventObjParam = HookEventObjParam; + pluginCoreLink.HookEventMessage = HookEventMessage; + pluginCoreLink.UnhookEvent = UnhookEvent; + pluginCoreLink.NotifyEventHooks = NotifyEventHooks; + pluginCoreLink.SetHookDefaultForHookableEvent = SetHookDefaultForHookableEvent; + pluginCoreLink.CallServiceSync = CallServiceSync; + pluginCoreLink.CallFunctionAsync = CallFunctionAsync; + pluginCoreLink.NotifyEventHooksDirect = CallHookSubscribers; + pluginCoreLink.CallProtoService = CallProtoService; + pluginCoreLink.CallContactService = CallContactService; + pluginCoreLink.KillObjectServices = KillObjectServices; + pluginCoreLink.KillObjectEventHooks = KillObjectEventHooks; + + // remember where the mirandaboot.ini goes + pathToAbsoluteT(_T("mirandaboot.ini"), mirandabootini, NULL); + // look for all *.dll's + enumPlugins(scanPluginsDir, 0, 0); + // the database will select which db plugin to use, or fail if no profile is selected + if (LoadDatabaseModule()) return 1; + InitIni(); + // could validate the plugin entries here but internal modules arent loaded so can't call Load() in one pass + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// +// Plugins module unloading +// called at the end of module chain unloading, just modular engine left at this point + +void UnloadNewPluginsModule(void) +{ + int i; + + if ( !bModuleInitialized ) return; + + // unload everything but the DB + for ( i = pluginList.getCount()-1; i >= 0; i-- ) { + pluginEntry* p = pluginList[i]; + if ( !(p->pclass & PCLASS_DB) && p != pluginList_crshdmp ) + Plugin_Uninit( p ); + } + + if ( pluginList_crshdmp ) + Plugin_Uninit( pluginList_crshdmp ); + + // unload the DB + for ( i = pluginList.getCount()-1; i >= 0; i-- ) { + pluginEntry* p = pluginList[i]; + Plugin_Uninit( p ); + } + + if ( hPluginListHeap ) HeapDestroy(hPluginListHeap); + hPluginListHeap=0; + + pluginList.destroy(); + pluginListAddr.destroy(); + UninitIni(); +} diff --git a/src/modules/protocols/protoaccs.cpp b/src/modules/protocols/protoaccs.cpp new file mode 100644 index 0000000000..b4a2b64f4f --- /dev/null +++ b/src/modules/protocols/protoaccs.cpp @@ -0,0 +1,638 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#include "../clist/clc.h" + +bool CheckProtocolOrder(void); +void BuildProtoMenus(); + +static BOOL bModuleInitialized = FALSE; + +static int CompareAccounts( const PROTOACCOUNT* p1, const PROTOACCOUNT* p2 ) +{ + return lstrcmpA( p1->szModuleName, p2->szModuleName ); +} + +LIST accounts( 10, CompareAccounts ); + +///////////////////////////////////////////////////////////////////////////////////////// + +static int EnumDbModules(const char *szModuleName, DWORD ofsModuleName, LPARAM lParam) +{ + DBVARIANT dbv; + if ( !DBGetContactSettingString( NULL, szModuleName, "AM_BaseProto", &dbv )) { + if (!Proto_GetAccount( szModuleName )) { + PROTOACCOUNT* pa = ( PROTOACCOUNT* )mir_calloc( sizeof( PROTOACCOUNT )); + pa->cbSize = sizeof( *pa ); + pa->type = PROTOTYPE_PROTOCOL; + pa->szModuleName = mir_strdup( szModuleName ); + pa->szProtoName = mir_strdup( dbv.pszVal ); + pa->tszAccountName = mir_a2t( szModuleName ); + pa->bIsVisible = TRUE; + pa->bIsEnabled = FALSE; + pa->iOrder = accounts.getCount(); + accounts.insert( pa ); + } + DBFreeVariant( &dbv ); + } + return 0; +} + +void LoadDbAccounts(void) +{ + DBVARIANT dbv; + int ver = DBGetContactSettingDword( NULL, "Protocols", "PrVer", -1 ); + int count = DBGetContactSettingDword( NULL, "Protocols", "ProtoCount", 0 ), i; + + for ( i=0; i < count; i++ ) { + char buf[10]; + _itoa( i, buf, 10 ); + if ( DBGetContactSettingString( NULL, "Protocols", buf, &dbv )) + continue; + + PROTOACCOUNT* pa = ( PROTOACCOUNT* )mir_calloc( sizeof( PROTOACCOUNT )); + if ( pa == NULL ) { + DBFreeVariant( &dbv ); + continue; + } + pa->cbSize = sizeof( *pa ); + pa->type = PROTOTYPE_PROTOCOL; + pa->szModuleName = mir_strdup( dbv.pszVal ); + DBFreeVariant( &dbv ); + + _itoa( OFFSET_VISIBLE+i, buf, 10 ); + pa->bIsVisible = DBGetContactSettingDword( NULL, "Protocols", buf, 1 ); + + _itoa( OFFSET_PROTOPOS+i, buf, 10 ); + pa->iOrder = DBGetContactSettingDword( NULL, "Protocols", buf, 1 ); + + if ( ver >= 4 ) { + DBFreeVariant( &dbv ); + _itoa( OFFSET_NAME+i, buf, 10 ); + if ( !DBGetContactSettingTString( NULL, "Protocols", buf, &dbv )) { + pa->tszAccountName = mir_tstrdup( dbv.ptszVal ); + DBFreeVariant( &dbv ); + } + + _itoa( OFFSET_ENABLED+i, buf, 10 ); + pa->bIsEnabled = DBGetContactSettingDword( NULL, "Protocols", buf, 1 ); + + if ( !DBGetContactSettingString( NULL, pa->szModuleName, "AM_BaseProto", &dbv )) { + pa->szProtoName = mir_strdup( dbv.pszVal ); + DBFreeVariant( &dbv ); + } + } + else pa->bIsEnabled = TRUE; + + if ( !pa->szProtoName ) { + pa->szProtoName = mir_strdup( pa->szModuleName ); + DBWriteContactSettingString( NULL, pa->szModuleName, "AM_BaseProto", pa->szProtoName ); + } + + if ( !pa->tszAccountName ) + pa->tszAccountName = mir_a2t( pa->szModuleName ); + + accounts.insert( pa ); + } + + if (CheckProtocolOrder()) WriteDbAccounts(); + + int anum = accounts.getCount(); + CallService(MS_DB_MODULES_ENUM, 0, (LPARAM)EnumDbModules); + if (anum != accounts.getCount()) WriteDbAccounts(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int arrlen; + char **pszSettingName; +} + enumDB_ProtoProcParam; + +static int enumDB_ProtoProc( const char* szSetting, LPARAM lParam ) +{ + if ( szSetting ) { + enumDB_ProtoProcParam* p = ( enumDB_ProtoProcParam* )lParam; + + p->arrlen++; + p->pszSettingName = ( char** )mir_realloc( p->pszSettingName, p->arrlen*sizeof( char* )); + p->pszSettingName[ p->arrlen-1 ] = mir_strdup( szSetting ); + } + return 0; +} + +void WriteDbAccounts() +{ + int i; + + // enum all old settings to delete + enumDB_ProtoProcParam param = { 0, NULL }; + + DBCONTACTENUMSETTINGS dbces; + dbces.pfnEnumProc = enumDB_ProtoProc; + dbces.szModule = "Protocols"; + dbces.ofsSettings = 0; + dbces.lParam = ( LPARAM )¶m; + CallService( MS_DB_CONTACT_ENUMSETTINGS, 0, ( LPARAM )&dbces ); + + // delete all settings + if ( param.arrlen ) { + int i; + for ( i=0; i < param.arrlen; i++ ) { + DBDeleteContactSetting( 0, "Protocols", param.pszSettingName[i] ); + mir_free( param.pszSettingName[i] ); + } + mir_free( param.pszSettingName ); + } + + // write new data + for ( i=0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + + char buf[ 20 ]; + _itoa( i, buf, 10 ); + DBWriteContactSettingString( NULL, "Protocols", buf, pa->szModuleName ); + + _itoa( OFFSET_PROTOPOS+i, buf, 10 ); + DBWriteContactSettingDword( NULL, "Protocols", buf, pa->iOrder ); + + _itoa( OFFSET_VISIBLE+i, buf, 10 ); + DBWriteContactSettingDword( NULL, "Protocols", buf, pa->bIsVisible ); + + _itoa( OFFSET_ENABLED+i, buf, 10 ); + DBWriteContactSettingDword( NULL, "Protocols", buf, pa->bIsEnabled ); + + _itoa( OFFSET_NAME+i, buf, 10 ); + DBWriteContactSettingTString( NULL, "Protocols", buf, pa->tszAccountName ); + } + + DBDeleteContactSetting( 0, "Protocols", "ProtoCount" ); + DBWriteContactSettingDword( 0, "Protocols", "ProtoCount", accounts.getCount() ); + DBWriteContactSettingDword( 0, "Protocols", "PrVer", 4 ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +static int OnContactDeleted(WPARAM wParam, LPARAM lParam) +{ + const HANDLE hContact = (HANDLE)wParam; + if (hContact) + { + PROTOACCOUNT* pa = Proto_GetAccount(hContact); + + if (Proto_IsAccountEnabled(pa) && pa->ppro) + pa->ppro->OnEvent(EV_PROTO_ONCONTACTDELETED, wParam, lParam); + } + return 0; +} + +static int OnDbSettingsChanged(WPARAM wParam, LPARAM lParam) +{ + const HANDLE hContact = (HANDLE)wParam; + if (hContact) + { + PROTOACCOUNT* pa = Proto_GetAccount(hContact); + if (Proto_IsAccountEnabled(pa) && pa->ppro) + pa->ppro->OnEvent(EV_PROTO_DBSETTINGSCHANGED, wParam, lParam); + } + return 0; +} + +static int InitializeStaticAccounts( WPARAM, LPARAM ) +{ + int count = 0; + + for ( int i = 0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if ( !pa->ppro || !Proto_IsAccountEnabled( pa )) + continue; + + pa->ppro->OnEvent( EV_PROTO_ONLOAD, 0, 0 ); + + if ( !pa->bOldProto ) + count++; + } + + BuildProtoMenus(); + + if ( count == 0 && !DBGetContactSettingByte( NULL, "FirstRun", "AccManager", 0 )) { + DBWriteContactSettingByte( NULL, "FirstRun", "AccManager", 1 ); + CallService( MS_PROTO_SHOWACCMGR, 0, 0 ); + } + return 0; +} + +static int UninitializeStaticAccounts( WPARAM, LPARAM ) +{ + for ( int i = 0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + if ( !pa->ppro || !Proto_IsAccountEnabled( pa )) + continue; + + pa->ppro->OnEvent( EV_PROTO_ONREADYTOEXIT, 0, 0 ); + pa->ppro->OnEvent( EV_PROTO_ONEXIT, 0, 0 ); + } + return 0; +} + +int LoadAccountsModule( void ) +{ + int i; + + bModuleInitialized = TRUE; + + for ( i = 0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[i]; + pa->bDynDisabled = !Proto_IsProtocolLoaded( pa->szProtoName ); + if ( pa->ppro ) + continue; + + if (!Proto_IsAccountEnabled( pa )) { + pa->type = PROTOTYPE_DISPROTO; + continue; + } + + if ( !ActivateAccount( pa )) { + pa->bDynDisabled = TRUE; + pa->type = PROTOTYPE_DISPROTO; + } } + + HookEvent( ME_SYSTEM_MODULESLOADED, InitializeStaticAccounts ); + HookEvent( ME_SYSTEM_PRESHUTDOWN, UninitializeStaticAccounts ); + HookEvent( ME_DB_CONTACT_DELETED, OnContactDeleted ); + HookEvent( ME_DB_CONTACT_SETTINGCHANGED, OnDbSettingsChanged ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR stub1( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ( INT_PTR )ppi->AddToList( wParam, (PROTOSEARCHRESULT*)lParam ); +} + +static INT_PTR stub2( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ( INT_PTR )ppi->AddToListByEvent( HIWORD(wParam), LOWORD(wParam), (HANDLE)lParam ); +} + +static INT_PTR stub3( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM ) +{ return ( INT_PTR )ppi->Authorize(( HANDLE )wParam ); +} + +static INT_PTR stub4( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ( INT_PTR )ppi->AuthDeny(( HANDLE )wParam, StrConvT(( const char* )lParam )); +} + +static INT_PTR stub7( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ( INT_PTR )ppi->ChangeInfo( wParam, ( void* )lParam ); +} + +static INT_PTR stub11( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ PROTOFILERESUME* pfr = ( PROTOFILERESUME* )lParam; + return ( INT_PTR )ppi->FileResume(( HANDLE )wParam, &pfr->action, (const PROTOCHAR**)&pfr->szFilename ); +} + +static INT_PTR stub12( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ( INT_PTR )ppi->GetCaps( wParam, (HANDLE)lParam ); +} + +static INT_PTR stub13( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM ) +{ return ( INT_PTR )ppi->GetIcon( wParam ); +} + +static INT_PTR stub15( PROTO_INTERFACE* ppi, WPARAM, LPARAM lParam ) +{ return ( INT_PTR )ppi->SearchBasic( StrConvT(( char* )lParam )); +} + +static INT_PTR stub16( PROTO_INTERFACE* ppi, WPARAM, LPARAM lParam ) +{ return ( INT_PTR )ppi->SearchByEmail( StrConvT(( char* )lParam )); +} + +static INT_PTR stub17( PROTO_INTERFACE* ppi, WPARAM, LPARAM lParam ) +{ PROTOSEARCHBYNAME* psbn = ( PROTOSEARCHBYNAME* )lParam; + return ( INT_PTR )ppi->SearchByName( StrConvT(( char* )psbn->pszNick ), + StrConvT(( char* )psbn->pszFirstName ), StrConvT(( char* )psbn->pszLastName )); +} + +static INT_PTR stub18( PROTO_INTERFACE* ppi, WPARAM, LPARAM lParam ) +{ return ( INT_PTR )ppi->SearchAdvanced(( HWND )lParam ); +} + +static INT_PTR stub19( PROTO_INTERFACE* ppi, WPARAM, LPARAM lParam ) +{ return ( INT_PTR )ppi->CreateExtendedSearchUI (( HWND )lParam ); +} + +static INT_PTR stub22( PROTO_INTERFACE* ppi, WPARAM, LPARAM lParam ) +{ CCSDATA *ccs = ( CCSDATA* )lParam; + ppi->RecvMsg( ccs->hContact, ( PROTORECVEVENT* )ccs->lParam ); + return 0; +} + +static INT_PTR stub29( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM ) +{ return ( INT_PTR )ppi->SetStatus( wParam ); +} + +static INT_PTR stub33( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ( INT_PTR )ppi->SetAwayMsg( wParam, StrConvT(( const char* )lParam )); +} + +static INT_PTR stub41( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ lstrcpynA(( char* )lParam, ppi->m_szModuleName, wParam ); + return 0; +} + +static INT_PTR stub42( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ return ppi->m_iStatus; +} + +#ifdef _UNICODE + +static INT_PTR stub43( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ + PROTO_AVATAR_INFORMATION* p = ( PROTO_AVATAR_INFORMATION* )lParam; + + PROTO_AVATAR_INFORMATIONW tmp = { 0 }; + tmp.cbSize = sizeof( tmp ); + tmp.hContact = p->hContact; + int result = CallProtoService( ppi->m_szModuleName, PS_GETAVATARINFOW, wParam, ( LPARAM )&tmp ); + + p->format = tmp.format; + + wchar_t filename[MAX_PATH]; + wcscpy(filename, tmp.filename); + GetShortPathNameW(tmp.filename, filename, SIZEOF(filename)); + + WideCharToMultiByte( CP_ACP, 0, filename, -1, p->filename, MAX_PATH, 0, 0 ); + return result; +} + +static INT_PTR stub44( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ + wchar_t* buf = ( wchar_t* )_alloca( sizeof(wchar_t) * (lParam + 1)); + int result = CallProtoService( ppi->m_szModuleName, PS_GETMYAVATARW, WPARAM( buf ), lParam ); + if ( result == 0 ) + { + wchar_t* filename = ( wchar_t* )_alloca( sizeof(wchar_t) * (lParam + 1)); + wcscpy(filename, buf); + GetShortPathNameW(buf, filename, lParam + 1); + + WideCharToMultiByte( CP_ACP, 0, filename, -1, ( char* )wParam, lParam, 0, 0 ); + } + + return result; +} + +static INT_PTR stub45( PROTO_INTERFACE* ppi, WPARAM wParam, LPARAM lParam ) +{ + return CallProtoService( ppi->m_szModuleName, PS_SETMYAVATARW, wParam, ( LPARAM )( LPCTSTR )StrConvT(( char* )lParam )); +} + +#endif + +static HANDLE CreateProtoServiceEx( const char* szModule, const char* szService, MIRANDASERVICEOBJ pFunc, void* param ) +{ + char tmp[100]; + mir_snprintf( tmp, sizeof( tmp ), "%s%s", szModule, szService ); + return CreateServiceFunctionObj( tmp, pFunc, param ); +} + +BOOL ActivateAccount( PROTOACCOUNT* pa ) +{ + PROTO_INTERFACE* ppi; + PROTOCOLDESCRIPTOR* ppd = Proto_IsProtocolLoaded( pa->szProtoName ); + if ( ppd == NULL ) + return FALSE; + + if ( ppd->fnInit == NULL ) + return FALSE; + + ppi = ppd->fnInit( pa->szModuleName, pa->tszAccountName ); + if ( ppi == NULL ) + return FALSE; + + pa->type = PROTOTYPE_PROTOCOL; + pa->ppro = ppi; + ppi->m_iDesiredStatus = ppi->m_iStatus = ID_STATUS_OFFLINE; + CreateProtoServiceEx( pa->szModuleName, PS_ADDTOLIST, (MIRANDASERVICEOBJ)stub1, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_ADDTOLISTBYEVENT, (MIRANDASERVICEOBJ)stub2, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_AUTHALLOW, (MIRANDASERVICEOBJ)stub3, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_AUTHDENY, (MIRANDASERVICEOBJ)stub4, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_CHANGEINFO, (MIRANDASERVICEOBJ)stub7, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_FILERESUME, (MIRANDASERVICEOBJ)stub11, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_GETCAPS, (MIRANDASERVICEOBJ)stub12, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_LOADICON, (MIRANDASERVICEOBJ)stub13, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_BASICSEARCH, (MIRANDASERVICEOBJ)stub15, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_SEARCHBYEMAIL, (MIRANDASERVICEOBJ)stub16, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_SEARCHBYNAME, (MIRANDASERVICEOBJ)stub17, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_SEARCHBYADVANCED, (MIRANDASERVICEOBJ)stub18, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_CREATEADVSEARCHUI, (MIRANDASERVICEOBJ)stub19, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PSR_MESSAGE, (MIRANDASERVICEOBJ)stub22, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_SETSTATUS, (MIRANDASERVICEOBJ)stub29, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_SETAWAYMSG, (MIRANDASERVICEOBJ)stub33, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_GETNAME, (MIRANDASERVICEOBJ)stub41, pa->ppro ); + CreateProtoServiceEx( pa->szModuleName, PS_GETSTATUS, (MIRANDASERVICEOBJ)stub42, pa->ppro ); + +#ifdef _UNICODE + char szServiceName[ 200 ]; + mir_snprintf( szServiceName, SIZEOF(szServiceName), "%s%s", pa->szModuleName, PS_GETAVATARINFO ); + if ( !ServiceExists( szServiceName )) { + mir_snprintf( szServiceName, SIZEOF(szServiceName), "%s%s", pa->szModuleName, PS_GETAVATARINFOW ); + if ( ServiceExists( szServiceName )) + CreateProtoServiceEx( pa->szModuleName, PS_GETAVATARINFO, (MIRANDASERVICEOBJ)stub43, pa->ppro ); + } + + mir_snprintf( szServiceName, SIZEOF(szServiceName), "%s%s", pa->szModuleName, PS_GETMYAVATAR ); + if ( !ServiceExists( szServiceName )) { + mir_snprintf( szServiceName, SIZEOF(szServiceName), "%s%s", pa->szModuleName, PS_GETMYAVATARW ); + if ( ServiceExists( szServiceName )) + CreateProtoServiceEx( pa->szModuleName, PS_GETMYAVATAR, (MIRANDASERVICEOBJ)stub44, pa->ppro ); + } + + mir_snprintf( szServiceName, SIZEOF(szServiceName), "%s%s", pa->szModuleName, PS_SETMYAVATAR ); + if ( !ServiceExists( szServiceName )) { + mir_snprintf( szServiceName, SIZEOF(szServiceName), "%s%s", pa->szModuleName, PS_SETMYAVATARW ); + if ( ServiceExists( szServiceName )) + CreateProtoServiceEx( pa->szModuleName, PS_SETMYAVATAR, (MIRANDASERVICEOBJ)stub45, pa->ppro ); + } + #endif + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +struct DeactivationThreadParam +{ + tagPROTO_INTERFACE* ppro; + pfnUninitProto fnUninit; + bool bIsDynamic; + bool bErase; +}; + +pfnUninitProto GetProtocolDestructor( char* szProto ); + +static int DeactivationThread( DeactivationThreadParam* param ) +{ + tagPROTO_INTERFACE* p = ( tagPROTO_INTERFACE* )param->ppro; + p->SetStatus(ID_STATUS_OFFLINE); + + char * szModuleName = NEWSTR_ALLOCA(p->m_szModuleName); + + if ( param->bIsDynamic ) { + p->OnEvent( EV_PROTO_ONREADYTOEXIT, 0, 0 ); + p->OnEvent( EV_PROTO_ONEXIT, 0, 0 ); + } + + KillObjectThreads( p ); // waits for them before terminating + KillObjectEventHooks( p ); // untie an object from the outside world + + if ( param->bErase ) + p->OnEvent( EV_PROTO_ONERASE, 0, 0 ); + + if ( param->fnUninit ) + param->fnUninit( p ); + + KillObjectServices( p ); + + if ( param->bErase ) + EraseAccount( szModuleName ); + + delete param; + return 0; +} + +void DeactivateAccount( PROTOACCOUNT* pa, bool bIsDynamic, bool bErase ) +{ + if ( pa->ppro == NULL ) { + if ( bErase ) + EraseAccount( pa->szModuleName ); + return; + } + + if ( pa->hwndAccMgrUI ) { + DestroyWindow(pa->hwndAccMgrUI); + pa->hwndAccMgrUI = NULL; + pa->bAccMgrUIChanged = FALSE; + } + + DeactivationThreadParam* param = new DeactivationThreadParam; + param->ppro = pa->ppro; + param->fnUninit = GetProtocolDestructor( pa->szProtoName ); + param->bIsDynamic = bIsDynamic; + param->bErase = bErase; + pa->ppro = NULL; + pa->type = PROTOTYPE_DISPROTO; + if ( bIsDynamic ) + mir_forkthread(( pThreadFunc )DeactivationThread, param ); + else + DeactivationThread( param ); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void EraseAccount( const char* pszModuleName ) +{ + DBVARIANT dbv; + DBCONTACTGETSETTING dbcgs; + char szProtoName[32]; + + dbcgs.pValue = &dbv; + dbcgs.szModule = "Protocol"; + dbcgs.szSetting = "p"; + + // remove protocol contacts first + HANDLE hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ); + while ( hContact != NULL ) { + HANDLE h1 = hContact; + hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM )h1, 0 ); + + dbv.type = DBVT_ASCIIZ; + dbv.pszVal = szProtoName; + dbv.cchVal = SIZEOF(szProtoName); + + if ( CallService( MS_DB_CONTACT_GETSETTINGSTATIC, ( WPARAM )h1, ( LPARAM )&dbcgs )) + continue; + + if ( !lstrcmpA( szProtoName, pszModuleName )) + CallService( MS_DB_CONTACT_DELETE, ( WPARAM )h1, 0 ); + } + + // remove all protocol settings + CallService( MS_DB_MODULE_DELETE, 0, ( LPARAM )pszModuleName ); +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +void UnloadAccount( PROTOACCOUNT* pa, bool bIsDynamic, bool bErase ) +{ + DeactivateAccount( pa, bIsDynamic, bErase ); + + mir_free( pa->tszAccountName ); + mir_free( pa->szProtoName ); + // szModuleName should be freed only on a program's exit. + // otherwise many plugins dependand on static protocol names will crash! + // do NOT fix this 'leak', please + if ( !bIsDynamic ) { + mir_free( pa->szModuleName ); + mir_free( pa ); + } +} + +void UnloadAccountsModule() +{ + int i; + + if ( !bModuleInitialized ) return; + + for( i=accounts.getCount()-1; i >= 0; i-- ) { + PROTOACCOUNT* pa = accounts[ i ]; + UnloadAccount( pa, false, false ); + accounts.remove(i); + } + + accounts.destroy(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void BuildProtoMenus() +{ + for ( int i = 0; i < accounts.getCount(); i++ ) { + PROTOACCOUNT* pa = accounts[ i ]; + if ( cli.pfnGetProtocolVisibility( pa->szModuleName ) == 0 ) + continue; + + if ( pa->ppro ) + pa->ppro->OnEvent( EV_PROTO_ONMENU, 0, 0 ); + } +} + +void RebuildProtoMenus( int iNewValue ) +{ + DBWriteContactSettingByte( NULL, "CList", "MoveProtoMenus", iNewValue ); + + RebuildMenuOrder(); + BuildProtoMenus(); +} diff --git a/src/modules/protocols/protochains.cpp b/src/modules/protocols/protochains.cpp new file mode 100644 index 0000000000..f7f3729806 --- /dev/null +++ b/src/modules/protocols/protochains.cpp @@ -0,0 +1,274 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include + +//Protocol chain is list of integers "0".."n", with network protocol named "p" +INT_PTR Proto_CallContactService(WPARAM wParam,LPARAM lParam) +//note that this is ChainSend() too, due to a quirk of function definitions +{ + CCSDATA *ccs=(CCSDATA*)lParam; + int i; + char str[10]; + DBVARIANT dbv; + INT_PTR ret; + PROTOACCOUNT* pa; + + if ( wParam == (WPARAM)(-1)) + return 1; + + for ( i = wParam;; i++ ) { + _itoa( i, str, 10 ); + if ( DBGetContactSettingString( ccs->hContact, "_Filter", str, &dbv )) + break; + + if (( ret = CallProtoService( dbv.pszVal, ccs->szProtoService, i+1, lParam )) != CALLSERVICE_NOTFOUND ) { + //chain was started, exit + mir_free( dbv.pszVal ); + return ret; + } + mir_free( dbv.pszVal ); + } + if ( DBGetContactSettingString( ccs->hContact, "Protocol", "p", &dbv )) + return 1; + + pa = Proto_GetAccount( dbv.pszVal ); + if ( pa == NULL || pa->ppro == NULL ) + ret = 1; + else { + if ( pa->bOldProto ) + ret = CallProtoServiceInt( ccs->hContact, dbv.pszVal, ccs->szProtoService, (WPARAM)(-1), ( LPARAM)ccs ); + else + ret = CallProtoServiceInt( ccs->hContact, dbv.pszVal, ccs->szProtoService, ccs->wParam, ccs->lParam ); + if ( ret == CALLSERVICE_NOTFOUND ) + ret = 1; + } + + mir_free(dbv.pszVal); + return ret; +} + +static INT_PTR CallRecvChain(WPARAM wParam,LPARAM lParam) +{ + CCSDATA *ccs=(CCSDATA*)lParam; + int i; + INT_PTR ret; + char str[10]; + DBVARIANT dbv; + PROTOACCOUNT* pa; + + if ( wParam == (WPARAM)(-1)) return 1; //shouldn't happen - sanity check + if ( wParam == 0 ) { //begin processing by finding end of chain + for( ;;wParam++ ) { + _itoa( wParam, str, 10 ); + if ( DBGetContactSettingString( ccs->hContact, "_Filter", str, &dbv )) + break; + mir_free(dbv.pszVal); + } + } + else wParam--; + + for ( i = wParam-1; i >= 0; i-- ) { + _itoa( i, str, 10 ); + if ( DBGetContactSettingString( ccs->hContact, "_Filter", str, &dbv )) //never happens + return 1; + + if (( ret = CallProtoService( dbv.pszVal, ccs->szProtoService, i+1, lParam )) != CALLSERVICE_NOTFOUND ) { + //chain was started, exit + mir_free( dbv.pszVal ); + return ret; + } + mir_free( dbv.pszVal ); + } + + //end of chain, call network protocol again + if ( DBGetContactSettingString( ccs->hContact, "Protocol", "p", &dbv )) + return 1; + + pa = Proto_GetAccount( dbv.pszVal ); + if ( pa == NULL || pa->ppro == NULL ) + ret = 1; + else { + if ( pa->bOldProto ) + ret = CallProtoServiceInt( ccs->hContact, dbv.pszVal, ccs->szProtoService, (WPARAM)(-1), ( LPARAM)ccs ); + else + ret = CallProtoServiceInt( ccs->hContact, dbv.pszVal, ccs->szProtoService, ccs->wParam, ccs->lParam ); + if ( ret == CALLSERVICE_NOTFOUND ) + ret = 1; + } + + mir_free( dbv.pszVal ); + return ret; +} + +static INT_PTR Proto_ChainRecv(WPARAM wParam,LPARAM lParam) +{ + /* this will switch threads just like before */ + return CallServiceSync(MS_PROTO_CHAINRECV "ThreadSafe",wParam,lParam); +} + +PROTOACCOUNT* __fastcall Proto_GetAccount(HANDLE hContact) +{ + DBVARIANT dbv; + DBCONTACTGETSETTING dbcgs; + char name[32]; + + dbv.type = DBVT_ASCIIZ; + dbv.pszVal = name; + dbv.cchVal = SIZEOF(name); + dbcgs.pValue = &dbv; + dbcgs.szModule = "Protocol"; + dbcgs.szSetting = "p"; + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&dbcgs)) + return 0; + + return Proto_GetAccount((char* )dbv.pszVal); +} + +static INT_PTR Proto_GetContactBaseProto(WPARAM wParam, LPARAM) +{ + PROTOACCOUNT* pa = Proto_GetAccount((HANDLE)wParam); + return (INT_PTR)(Proto_IsAccountEnabled( pa ) ? pa->szModuleName : NULL); +} + +static INT_PTR Proto_GetContactBaseAccount(WPARAM wParam, LPARAM) +{ + PROTOACCOUNT* pa = Proto_GetAccount((HANDLE)wParam); + return (INT_PTR)(pa ? pa->szModuleName : NULL); +} + +static INT_PTR Proto_IsProtoOnContact(WPARAM wParam,LPARAM lParam) +{ + int i; + char str[10]; + DBVARIANT dbv; + + if (!lParam) return 0; + + if(!DBGetContactSettingString((HANDLE)wParam,"Protocol","p",&dbv)) { + if(!_stricmp((char*)lParam,dbv.pszVal)) { + mir_free(dbv.pszVal); + return -1; + } + mir_free(dbv.pszVal); + } + for(i=0;;i++) { + _itoa(i,str,10); + if(DBGetContactSettingString((HANDLE)wParam,"_Filter",str,&dbv)) break; + if(!strcmp((char*)lParam,dbv.pszVal)) { + mir_free(dbv.pszVal); + return i+1; + } + mir_free(dbv.pszVal); + } + return 0; +} + +static INT_PTR Proto_AddToContact(WPARAM wParam,LPARAM lParam) +{ + PROTOCOLDESCRIPTOR *pd,*pdCompare; + + pd = Proto_IsProtocolLoaded(( char* )lParam ); + if ( pd == NULL ) { + PROTOACCOUNT* pa = Proto_GetAccount(( char* )lParam ); + if ( pa ) { + DBWriteContactSettingString((HANDLE)wParam,"Protocol","p",(char*)lParam); + return 0; + } + return 1; + } + + if ( pd->type == PROTOTYPE_PROTOCOL ) { + DBWriteContactSettingString((HANDLE)wParam,"Protocol","p",(char*)lParam); + return 0; + } + if(Proto_IsProtoOnContact(wParam,lParam)) return 1; + { /* v:0.3.3 + PROTO FILTERS ARE NOW KEPT IN THEIR OWN DB MODULE! */ + int i; + char str[10],*lastProto; + DBVARIANT dbv; + + for(i=0;;i++) { + _itoa(i,str,10); + if(DBGetContactSettingString((HANDLE)wParam,"_Filter",str,&dbv)) break; + pdCompare = Proto_IsProtocolLoaded(( char* )dbv.pszVal ); + mir_free(dbv.pszVal); + if(pdCompare==NULL) continue; + if(pd->type > pdCompare->type) break; + } + //put the new module at position i + lastProto=mir_strdup((char*)lParam); + for(;;i++) { + _itoa(i,str,10); + if(DBGetContactSettingString((HANDLE)wParam,"_Filter",str,&dbv)) { + DBWriteContactSettingString((HANDLE)wParam,"_Filter",str,lastProto); + mir_free(lastProto); + break; + } + DBWriteContactSettingString((HANDLE)wParam,"_Filter",str,lastProto); + mir_free(lastProto); + lastProto=dbv.pszVal; + } + } + return 0; +} + +static INT_PTR Proto_RemoveFromContact(WPARAM wParam,LPARAM lParam) +{ + int i; + DBVARIANT dbv; + char str[10]; + + i = Proto_IsProtoOnContact(wParam,lParam); + if(!i) return 1; + if(i==-1) + DBDeleteContactSetting((HANDLE)wParam,"Protocol","p"); + else { + for(i--;;i++) { //we have to decrease i, as Proto_IsOnContact returns +1 more number than read from database + _itoa(i+1,str,10); + if(0!=DBGetContactSettingString((HANDLE)wParam,"_Filter",str,&dbv)) { + _itoa(i,str,10); + DBDeleteContactSetting((HANDLE)wParam,"_Filter",str); + break; + } + _itoa(i,str,10); + DBWriteContactSettingString((HANDLE)wParam,"_Filter",str,dbv.pszVal); + mir_free(dbv.pszVal); + } + } + return 0; +} + +int LoadProtoChains(void) +{ + CreateServiceFunction(MS_PROTO_CALLCONTACTSERVICE,Proto_CallContactService); + CreateServiceFunction(MS_PROTO_CHAINSEND,Proto_CallContactService); + CreateServiceFunction(MS_PROTO_CHAINRECV,Proto_ChainRecv); + CreateServiceFunction(MS_PROTO_CHAINRECV "ThreadSafe",CallRecvChain); + CreateServiceFunction(MS_PROTO_GETCONTACTBASEPROTO,Proto_GetContactBaseProto); + CreateServiceFunction(MS_PROTO_GETCONTACTBASEACCOUNT,Proto_GetContactBaseAccount); + CreateServiceFunction(MS_PROTO_ISPROTOONCONTACT,Proto_IsProtoOnContact); + CreateServiceFunction(MS_PROTO_ADDTOCONTACT,Proto_AddToContact); + CreateServiceFunction(MS_PROTO_REMOVEFROMCONTACT,Proto_RemoveFromContact); + return 0; +} diff --git a/src/modules/protocols/protocols.cpp b/src/modules/protocols/protocols.cpp new file mode 100644 index 0000000000..60b53aeaf4 --- /dev/null +++ b/src/modules/protocols/protocols.cpp @@ -0,0 +1,844 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +int LoadProtoChains(void); +int LoadProtoOptions( void ); + +HANDLE hAccListChanged; +static HANDLE hAckEvent,hTypeEvent; +static BOOL bModuleInitialized = FALSE; + +typedef struct +{ + const char* name; + int id; +} + TServiceListItem; + +static int CompareServiceItems( const TServiceListItem* p1, const TServiceListItem* p2 ) +{ return strcmp( p1->name, p2->name ); +} + +static LIST serviceItems( 10, CompareServiceItems ); + +//------------------------------------------------------------------------------------ + +static int CompareProtos( const PROTOCOLDESCRIPTOR* p1, const PROTOCOLDESCRIPTOR* p2 ) +{ return strcmp( p1->szName, p2->szName ); +} + +static LIST protos( 10, CompareProtos ); + +static INT_PTR Proto_BroadcastAck(WPARAM wParam, LPARAM lParam) +{ +#ifdef _UNICODE + ACKDATA *ack = (ACKDATA*)lParam; + if (ack && ack->type == ACKTYPE_AVATAR && ack->hProcess) + { + PROTO_AVATAR_INFORMATION* ai = (PROTO_AVATAR_INFORMATION*)ack->hProcess; + if (ai->cbSize == sizeof(PROTO_AVATAR_INFORMATION)) + { + PROTO_AVATAR_INFORMATIONW aiw = { sizeof(aiw), ai->hContact, ai->format }; + MultiByteToWideChar(CP_ACP, 0, ai->filename, -1, aiw.filename, SIZEOF(aiw.filename)); + + ack->hProcess = &aiw; + } + } +#endif + + return NotifyEventHooks(hAckEvent, wParam, lParam); +} + +INT_PTR __fastcall MyCallProtoService( const char *szModule, const char *szService, WPARAM wParam, LPARAM lParam ); +void FreeFilesMatrix( TCHAR ***files ); + +PROTOCOLDESCRIPTOR* __fastcall Proto_IsProtocolLoaded( const char* szProtoName ) +{ + if ( szProtoName ) { + PROTOCOLDESCRIPTOR tmp; + tmp.szName = ( char* )szProtoName; + return protos.find( &tmp ); + } + return NULL; +} + +INT_PTR srvProto_IsLoaded(WPARAM, LPARAM lParam) +{ + return (INT_PTR)Proto_GetAccount(( char* )lParam ); +} + +INT_PTR Proto_EnumProtocols(WPARAM wParam,LPARAM lParam) +{ + *( int* )wParam = protos.getCount(); + *( PROTOCOLDESCRIPTOR*** )lParam = protos.getArray(); + return 0; +} + +static PROTO_INTERFACE* defInitProto( const char* szModuleName, const TCHAR* ) +{ + return AddDefaultAccount( szModuleName ); +} + +static INT_PTR Proto_RegisterModule(WPARAM, LPARAM lParam) +{ + PROTOCOLDESCRIPTOR* pd = ( PROTOCOLDESCRIPTOR* )lParam, *p; + if ( pd->cbSize != sizeof( PROTOCOLDESCRIPTOR ) && pd->cbSize != PROTOCOLDESCRIPTOR_V3_SIZE ) + return 1; + + p = ( PROTOCOLDESCRIPTOR* )mir_alloc( sizeof( PROTOCOLDESCRIPTOR )); + if ( !p ) + return 2; + + if ( pd->cbSize == PROTOCOLDESCRIPTOR_V3_SIZE ) { + memset( p, 0, sizeof( PROTOCOLDESCRIPTOR )); + p->cbSize = PROTOCOLDESCRIPTOR_V3_SIZE; + p->type = pd->type; + if ( p->type == PROTOTYPE_PROTOCOL ) { + // let's create a new container + PROTO_INTERFACE* ppi = AddDefaultAccount( pd->szName ); + if ( ppi ) { + PROTOACCOUNT* pa = Proto_GetAccount( pd->szName ); + if ( pa == NULL ) { + pa = (PROTOACCOUNT*)mir_calloc( sizeof( PROTOACCOUNT )); + pa->cbSize = sizeof(PROTOACCOUNT); + pa->type = PROTOTYPE_PROTOCOL; + pa->szModuleName = mir_strdup( pd->szName ); + pa->szProtoName = mir_strdup( pd->szName ); + pa->tszAccountName = mir_a2t( pd->szName ); + pa->bIsVisible = pa->bIsEnabled = TRUE; + pa->iOrder = accounts.getCount(); + accounts.insert( pa ); + } + pa->bOldProto = TRUE; + pa->ppro = ppi; + p->fnInit = defInitProto; + p->fnUninit = FreeDefaultAccount; + } + } + } + else *p = *pd; + p->szName = mir_strdup( pd->szName ); + protos.insert( p ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Basic core services + +static INT_PTR Proto_RecvFile(WPARAM,LPARAM lParam) +{ + CCSDATA* ccs = ( CCSDATA* )lParam; + PROTORECVEVENT* pre = ( PROTORECVEVENT* )ccs->lParam; + char* szFile = pre->szMessage + sizeof( DWORD ); + char* szDescr = szFile + strlen( szFile ) + 1; + + // Suppress the standard event filter + if ( pre->lParam != NULL ) + *( DWORD* )pre->szMessage = 0; + + DBEVENTINFO dbei = { 0 }; + dbei.cbSize = sizeof( dbei ); + dbei.szModule = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0); + dbei.timestamp = pre->timestamp; + dbei.flags = ( pre->flags & PREF_CREATEREAD ) ? DBEF_READ : 0; + dbei.flags |= ( pre->flags & PREF_UTF ) ? DBEF_UTF : 0; + dbei.eventType = EVENTTYPE_FILE; + dbei.cbBlob = (DWORD)(sizeof( DWORD ) + strlen( szFile ) + strlen( szDescr ) + 2); + dbei.pBlob = ( PBYTE )pre->szMessage; + HANDLE hdbe = ( HANDLE )CallService( MS_DB_EVENT_ADD, ( WPARAM )ccs->hContact, ( LPARAM )&dbei ); + + if ( pre->lParam != NULL ) + PushFileEvent( ccs->hContact, hdbe, pre->lParam ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static void sttRecvCreateBlob( DBEVENTINFO& dbei, int fileCount, char** pszFiles, char* szDescr ) +{ + dbei.cbBlob = sizeof( DWORD ); + { + for ( int i=0; i < fileCount; i++ ) + dbei.cbBlob += lstrlenA( pszFiles[i] ) + 1; + } + + dbei.cbBlob += lstrlenA( szDescr ) + 1; + + if (( dbei.pBlob = ( BYTE* )mir_alloc( dbei.cbBlob )) == 0 ) + return; + + *( DWORD* )dbei.pBlob = 0; + BYTE* p = dbei.pBlob + sizeof( DWORD ); + for ( int i=0; i < fileCount; i++ ) { + strcpy(( char* )p, pszFiles[i] ); + p += lstrlenA( pszFiles[i] ) + 1; + } + strcpy(( char* )p, ( szDescr == NULL ) ? "" : szDescr ); +} + +static INT_PTR Proto_RecvFileT(WPARAM,LPARAM lParam) +{ + CCSDATA* ccs = ( CCSDATA* )lParam; + PROTORECVFILET* pre = ( PROTORECVFILET* )ccs->lParam; + if ( pre->fileCount == 0 ) + return 0; + + DBEVENTINFO dbei = { 0 }; + dbei.cbSize = sizeof( dbei ); + dbei.szModule = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0); + dbei.timestamp = pre->timestamp; + dbei.flags = ( pre->flags & PREF_CREATEREAD ) ? DBEF_READ : 0; + dbei.eventType = EVENTTYPE_FILE; + + char** pszFiles = ( char** )alloca( pre->fileCount * sizeof(char*)); + { + for ( int i=0; i < pre->fileCount; i++ ) + pszFiles[i] = Utf8EncodeT( pre->ptszFiles[i] ); + } + char* szDescr = Utf8EncodeT( pre->tszDescription ); + dbei.flags |= DBEF_UTF; + sttRecvCreateBlob( dbei, pre->fileCount, pszFiles, szDescr ); + { + for ( int i=0; i < pre->fileCount; i++ ) + mir_free( pszFiles[i] ); + } + mir_free( szDescr ); + + HANDLE hdbe = ( HANDLE )CallService( MS_DB_EVENT_ADD, ( WPARAM )ccs->hContact, ( LPARAM )&dbei ); + + PushFileEvent( ccs->hContact, hdbe, pre->lParam ); + mir_free( dbei.pBlob ); + return 0; +} + +static INT_PTR Proto_RecvMessage(WPARAM,LPARAM lParam) +{ + CCSDATA *ccs = ( CCSDATA* )lParam; + PROTORECVEVENT *pre = ( PROTORECVEVENT* )ccs->lParam; + + if (pre->szMessage == NULL) return NULL; + + DBEVENTINFO dbei = { 0 }; + dbei.cbSize = sizeof( dbei ); + dbei.szModule = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0); + dbei.timestamp = pre->timestamp; + dbei.eventType = EVENTTYPE_MESSAGE; + dbei.cbBlob = (DWORD)strlen( pre->szMessage ) + 1; + if ( pre->flags & PREF_CREATEREAD ) + dbei.flags |= DBEF_READ; + if ( pre->flags & PREF_UTF ) + dbei.flags |= DBEF_UTF; + if ( pre->flags & PREF_UNICODE ) + dbei.cbBlob += sizeof( wchar_t )*( (DWORD)wcslen(( wchar_t* )&pre->szMessage[dbei.cbBlob+1] )+1 ); + + dbei.pBlob = ( PBYTE ) pre->szMessage; + return CallService( MS_DB_EVENT_ADD, ( WPARAM ) ccs->hContact, ( LPARAM )&dbei ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// User Typing Notification services + +static int Proto_ValidTypingContact(HANDLE hContact, char *szProto) +{ + if ( !hContact || !szProto ) + return 0; + + return ( CallProtoService(szProto,PS_GETCAPS,PFLAGNUM_4,0) & PF4_SUPPORTTYPING ) ? 1 : 0; +} + +static INT_PTR Proto_SelfIsTyping(WPARAM wParam,LPARAM lParam) +{ + if ( lParam == PROTOTYPE_SELFTYPING_OFF || lParam == PROTOTYPE_SELFTYPING_ON ) { + char* szProto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, wParam, 0 ); + if ( !szProto ) + return 0; + + if ( Proto_ValidTypingContact(( HANDLE )wParam, szProto )) + CallProtoService( szProto, PSS_USERISTYPING, wParam, lParam ); + } + + return 0; +} + +static INT_PTR Proto_ContactIsTyping(WPARAM wParam,LPARAM lParam) +{ + int type = (int)lParam; + char *szProto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, wParam, 0 ); + if ( !szProto ) + return 0; + + if ( CallService( MS_IGNORE_ISIGNORED, wParam, IGNOREEVENT_TYPINGNOTIFY )) + return 0; + + if ( type < PROTOTYPE_CONTACTTYPING_OFF ) + return 0; + + if ( Proto_ValidTypingContact(( HANDLE )wParam, szProto )) + NotifyEventHooks( hTypeEvent, wParam, lParam ); + + return 0; +} + +void Proto_SetStatus(const char* szProto, unsigned status) +{ + if (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND) + { + TCHAR* awayMsg = (TCHAR* )CallService(MS_AWAYMSG_GETSTATUSMSGW, (WPARAM) status, (LPARAM) szProto); + if ((INT_PTR)awayMsg == CALLSERVICE_NOTFOUND) + { + char* awayMsgA = (char*)CallService(MS_AWAYMSG_GETSTATUSMSG, (WPARAM) status, (LPARAM) szProto); + if ((INT_PTR)awayMsgA != CALLSERVICE_NOTFOUND) + { + awayMsg = mir_a2t(awayMsgA); + mir_free(awayMsgA); + } + } + if ((INT_PTR)awayMsg != CALLSERVICE_NOTFOUND) + { + CallProtoService(szProto, PS_SETAWAYMSGT, status, (LPARAM) awayMsg); + mir_free(awayMsg); + } } + CallProtoService(szProto, PS_SETSTATUS, status, 0); +} + +#ifdef _UNICODE +char** __fastcall Proto_FilesMatrixA( wchar_t **files ) +{ + if ( files == NULL ) return NULL; + + int count = 0; + while( files[ count++ ] ); + + char** filesA = ( char** )mir_alloc( count * sizeof( char* )); + for( int i = 0; i < count; ++i ) + filesA[ i ] = mir_u2a( files[ i ] ); + + return filesA; +} + +static wchar_t** __fastcall Proto_FilesMatrixU( char **files ) +{ + if ( files == NULL ) return NULL; + + int count = 0; + while( files[ count++ ] ); + + wchar_t** filesU = ( wchar_t** )mir_alloc( count * sizeof( wchar_t* )); + for( int i = 0; i < count; ++i ) + filesU[ i ] = mir_a2u( files[ i ] ); + + return filesU; +} +#endif + +///////////////////////////////////////////////////////////////////////////////////////// +// 0.8.0+ - accounts + +PROTOACCOUNT* __fastcall Proto_GetAccount( const char* accName ) +{ + int idx; + PROTOACCOUNT temp; + temp.szModuleName = ( char* )accName; + if (( idx = accounts.getIndex( &temp )) == -1 ) + return NULL; + + return accounts[idx]; +} + +static INT_PTR srvProto_GetAccount(WPARAM, LPARAM lParam) +{ + return ( INT_PTR )Proto_GetAccount(( char* )lParam ); +} + +static INT_PTR Proto_EnumAccounts(WPARAM wParam, LPARAM lParam) +{ + *( int* )wParam = accounts.getCount(); + *( PROTOACCOUNT*** )lParam = accounts.getArray(); + return 0; +} + +bool __fastcall Proto_IsAccountEnabled( PROTOACCOUNT* pa ) +{ + return pa && (( pa->bIsEnabled && !pa->bDynDisabled ) || pa->bOldProto ); +} + +static INT_PTR srvProto_IsAccountEnabled(WPARAM, LPARAM lParam) +{ + return ( INT_PTR )Proto_IsAccountEnabled(( PROTOACCOUNT* )lParam); +} + +bool __fastcall Proto_IsAccountLocked( PROTOACCOUNT* pa ) +{ + return pa && DBGetContactSettingByte(NULL, pa->szModuleName, "LockMainStatus", 0) != 0; +} + +static INT_PTR srvProto_IsAccountLocked(WPARAM, LPARAM lParam) +{ + return ( INT_PTR )Proto_IsAccountLocked( Proto_GetAccount(( char* )lParam )); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CallProtoServiceInt( HANDLE hContact, const char *szModule, const char *szService, WPARAM wParam, LPARAM lParam ) +{ + PROTOACCOUNT* pa = Proto_GetAccount( szModule ); + if ( pa && !pa->bOldProto ) { + PROTO_INTERFACE* ppi; + if (( ppi = pa->ppro ) == NULL ) + return CALLSERVICE_NOTFOUND; + else { + TServiceListItem *item = serviceItems.find(( TServiceListItem* )&szService ); + if ( item ) { + switch( item->id ) { + case 1: +#ifdef _UNICODE + if ( ppi->m_iVersion > 1 || !((( PROTOSEARCHRESULT* )lParam)->flags & PSR_UNICODE)) + return ( INT_PTR )ppi->AddToList( wParam, (PROTOSEARCHRESULT*)lParam ); + else { + PROTOSEARCHRESULT *psr = ( PROTOSEARCHRESULT* )lParam; + PROTOSEARCHRESULT *psra =( PROTOSEARCHRESULT* )mir_alloc( psr->cbSize ); + memcpy( psra, psr, psr->cbSize ); + psra->nick = ( PROTOCHAR* )mir_u2a( psr->nick ); + psra->firstName = ( PROTOCHAR* )mir_u2a( psr->firstName ); + psra->lastName = ( PROTOCHAR* )mir_u2a( psr->lastName ); + psra->email = ( PROTOCHAR* )mir_u2a( psr->email ); + + INT_PTR res = ( INT_PTR )ppi->AddToList( wParam, psra ); + + mir_free( psra->nick ); + mir_free( psra->firstName ); + mir_free( psra->lastName ); + mir_free( psra->email ); + mir_free( psra ); + + return res; + } +#else + return ( INT_PTR )ppi->AddToList( wParam, (PROTOSEARCHRESULT*)lParam ); +#endif + case 2: return ( INT_PTR )ppi->AddToListByEvent( LOWORD(wParam), HIWORD(wParam), (HANDLE)lParam ); + case 3: return ( INT_PTR )ppi->Authorize( ( HANDLE )wParam ); + case 4: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->AuthDeny(( HANDLE )wParam, StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->AuthDeny(( HANDLE )wParam, ( PROTOCHAR* )lParam ); + case 5: return ( INT_PTR )ppi->AuthRecv( hContact, ( PROTORECVEVENT* )lParam ); + case 6: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->AuthRequest( hContact, StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->AuthRequest( hContact, ( PROTOCHAR* )lParam ); + case 7: return ( INT_PTR )ppi->ChangeInfo( wParam, ( void* )lParam ); + case 8: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->FileAllow( hContact, ( HANDLE )wParam, StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->FileAllow( hContact, ( HANDLE )wParam, ( PROTOCHAR* )lParam ); + case 9: return ( INT_PTR )ppi->FileCancel( hContact, ( HANDLE )wParam ); + case 10: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->FileDeny( hContact, ( HANDLE )wParam, StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->FileDeny( hContact, ( HANDLE )wParam, ( PROTOCHAR* )lParam ); + case 11: { + PROTOFILERESUME* pfr = ( PROTOFILERESUME* )lParam; +#ifdef _UNICODE + if ( ppi->m_iVersion > 1 ) { + PROTOCHAR* szFname = mir_a2t(( char* )pfr->szFilename ); + INT_PTR res = ( INT_PTR )ppi->FileResume(( HANDLE )wParam, &pfr->action, + ( const PROTOCHAR** )&szFname); + mir_free(( PROTOCHAR* )pfr->szFilename ); + pfr->szFilename = ( PROTOCHAR* )mir_t2a( szFname ); mir_free( szFname ); + } + else +#endif + return ( INT_PTR )ppi->FileResume(( HANDLE )wParam, &pfr->action, + ( const PROTOCHAR** )&pfr->szFilename ); + } + case 12: return ( INT_PTR )ppi->GetCaps( wParam, (HANDLE)lParam ); + case 13: return ( INT_PTR )ppi->GetIcon( wParam ); + case 14: return ( INT_PTR )ppi->GetInfo( hContact, wParam );; + case 15: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SearchBasic( StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->SearchBasic(( TCHAR* )lParam ); + case 16: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SearchByEmail( StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->SearchByEmail(( TCHAR* )lParam ); + case 17: { + PROTOSEARCHBYNAME* psbn = ( PROTOSEARCHBYNAME* )lParam; + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SearchByName( StrConvT(( char* )psbn->pszNick ), + StrConvT(( char* )psbn->pszFirstName ), StrConvT(( char* )psbn->pszLastName )); + else + return ( INT_PTR )ppi->SearchByName( psbn->pszNick, psbn->pszFirstName, psbn->pszLastName ); + } + case 18: return ( INT_PTR )ppi->SearchAdvanced( ( HWND )lParam ); + case 19: return ( INT_PTR )ppi->CreateExtendedSearchUI ( ( HWND )lParam ); + case 20: return ( INT_PTR )ppi->RecvContacts( hContact, ( PROTORECVEVENT* )lParam ); + case 21: return ( INT_PTR )ppi->RecvFile( hContact, ( PROTOFILEEVENT* )lParam ); + case 22: return ( INT_PTR )ppi->RecvMsg( hContact, ( PROTORECVEVENT* )lParam ); + case 23: return ( INT_PTR )ppi->RecvUrl( hContact, ( PROTORECVEVENT* )lParam ); + case 24: return ( INT_PTR )ppi->SendContacts( hContact, LOWORD( wParam ), HIWORD( wParam ), + ( HANDLE* )lParam ); + case 25: +#ifdef _UNICODE + if ( ppi->m_iVersion > 1 ) { + TCHAR** files = Proto_FilesMatrixU(( char** )lParam ); + INT_PTR res = ( INT_PTR )ppi->SendFile( hContact, StrConvT(( char* )wParam ), ( TCHAR** )files ); + if ( res == 0 ) FreeFilesMatrix( &files ); + return res; + } + else +#endif + return ( INT_PTR )ppi->SendFile( hContact, ( TCHAR* )wParam, ( TCHAR** )lParam ); + case 26: return ( INT_PTR )ppi->SendMsg( hContact, wParam, ( const char* )lParam ); + case 27: return ( INT_PTR )ppi->SendUrl( hContact, wParam, ( const char* )lParam ); + case 28: return ( INT_PTR )ppi->SetApparentMode( hContact, wParam ); + case 29: return ( INT_PTR )ppi->SetStatus( wParam ); + case 30: return ( INT_PTR )ppi->GetAwayMsg( hContact ); + case 31: return ( INT_PTR )ppi->RecvAwayMsg( hContact, wParam, ( PROTORECVEVENT* )lParam ); + case 32: return ( INT_PTR )ppi->SendAwayMsg( hContact, ( HANDLE )wParam, ( const char* )lParam ); + case 33: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SetAwayMsg( wParam, StrConvT(( char* )lParam )); + else + return ( INT_PTR )ppi->SetAwayMsg( wParam, ( TCHAR* )lParam ); + case 34: return ( INT_PTR )ppi->UserIsTyping( ( HANDLE )wParam, lParam ); + case 35: lstrcpynA(( char* )lParam, ppi->m_szModuleName, wParam ); return 0; + case 36: return ppi->m_iStatus; + +#ifdef _UNICODE + case 100: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SetAwayMsg( wParam, ( TCHAR* )lParam ); + else + return ( INT_PTR )ppi->SetAwayMsg( wParam, StrConvA(( TCHAR* )lParam )); + case 102: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SendFile( hContact, ( TCHAR* )wParam, ( TCHAR** )lParam ); + else { + char** files = Proto_FilesMatrixA(( TCHAR** )lParam ); + INT_PTR res = ( INT_PTR )ppi->SendFile( hContact, StrConvA(( TCHAR* )wParam ), ( TCHAR** )files ); + if ( res == 0 ) FreeFilesMatrix(( TCHAR*** )&files ); + return res; + } + case 103: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->FileAllow( hContact, ( HANDLE )wParam, ( TCHAR* )lParam ); + else + return ( INT_PTR )ppi->FileAllow( hContact, ( HANDLE )wParam, StrConvA(( TCHAR* )lParam )); + case 104: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->FileDeny( hContact, ( HANDLE )wParam, ( TCHAR* )lParam ); + else + return ( INT_PTR )ppi->FileDeny( hContact, ( HANDLE )wParam, StrConvA(( TCHAR* )lParam )); + case 105: { + PROTOFILERESUME* pfr = ( PROTOFILERESUME* )lParam; + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->FileResume(( HANDLE )wParam, &pfr->action, + ( const PROTOCHAR** )&pfr->szFilename ); + else { + char* szFname = mir_t2a( pfr->szFilename ); + INT_PTR res = ( INT_PTR )ppi->FileResume(( HANDLE )wParam, &pfr->action, + ( const PROTOCHAR** )&szFname); + mir_free( szFname ); + } } + case 106: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->AuthRequest( hContact, ( const TCHAR* )lParam ); + else + return ( INT_PTR )ppi->AuthRequest( hContact, StrConvA(( const TCHAR* )lParam )); + case 107: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->AuthDeny(( HANDLE )wParam, ( const TCHAR* )lParam ); + else + return ( INT_PTR )ppi->AuthDeny(( HANDLE )wParam, StrConvA(( const TCHAR* )lParam )); + case 108: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SearchBasic(( const TCHAR* )lParam ); + else + return ( INT_PTR )ppi->SearchBasic(StrConvA(( const TCHAR* )lParam )); + case 109: { + PROTOSEARCHBYNAME* psbn = ( PROTOSEARCHBYNAME* )lParam; + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SearchByName( psbn->pszNick, psbn->pszFirstName, psbn->pszLastName ); + else + return ( INT_PTR )ppi->SearchByName( StrConvA(( TCHAR* )psbn->pszNick ), + StrConvA(( TCHAR* )psbn->pszFirstName ), StrConvA(( TCHAR* )psbn->pszLastName )); + } + case 110: + if ( ppi->m_iVersion > 1 ) + return ( INT_PTR )ppi->SearchByEmail(( const TCHAR* )lParam ); + else + return ( INT_PTR )ppi->SearchByEmail(StrConvA(( const TCHAR* )lParam )); +#endif + } } } } + +#ifdef _UNICODE + if ( strcmp( szService, PS_ADDTOLIST ) == 0 ) { + PROTOSEARCHRESULT *psr = ( PROTOSEARCHRESULT* )lParam; + PROTOSEARCHRESULT *psra =( PROTOSEARCHRESULT* )mir_alloc( psr->cbSize ); + memcpy( psra, psr, psr->cbSize ); + psra->nick = ( PROTOCHAR* )mir_u2a( psr->nick ); + psra->firstName = ( PROTOCHAR* )mir_u2a( psr->firstName ); + psra->lastName = ( PROTOCHAR* )mir_u2a( psr->lastName ); + psra->email = ( PROTOCHAR* )mir_u2a( psr->email ); + + INT_PTR res = MyCallProtoService( szModule, szService, wParam, ( LPARAM )psra ); + + mir_free( psra->nick ); + mir_free( psra->firstName ); + mir_free( psra->lastName ); + mir_free( psra->email ); + mir_free( psra ); + + return res; + } +#endif + + INT_PTR res = MyCallProtoService( szModule, szService, wParam, lParam ); + +#ifdef _UNICODE + if ( res == CALLSERVICE_NOTFOUND && pa && pa->bOldProto && pa->ppro && strchr( szService, 'W' )) { + TServiceListItem *item = serviceItems.find(( TServiceListItem* )&szService ); + if ( !item ) return res; + + switch( item->id ) { + case 100: + return ( INT_PTR )pa->ppro->SetAwayMsg( wParam, ( TCHAR* )lParam ); + case 102: { + CCSDATA *ccs = ( CCSDATA* )lParam; + return ( INT_PTR )pa->ppro->SendFile( ccs->hContact, ( TCHAR* )ccs->wParam, ( TCHAR** )ccs->lParam ); + } + case 103: { + CCSDATA *ccs = ( CCSDATA* )lParam; + return ( INT_PTR )pa->ppro->FileAllow( ccs->hContact, ( HANDLE )ccs->wParam, ( TCHAR* )ccs->lParam ); + } + case 104: { + CCSDATA *ccs = ( CCSDATA* )lParam; + return ( INT_PTR )pa->ppro->FileDeny( ccs->hContact, ( HANDLE )ccs->wParam, ( TCHAR* )ccs->lParam ); + } + case 105: { + PROTOFILERESUME* pfr = ( PROTOFILERESUME* )lParam; + return ( INT_PTR )pa->ppro->FileResume(( HANDLE )wParam, &pfr->action, &pfr->szFilename ); + } + case 106: { + CCSDATA *ccs = ( CCSDATA* )lParam; + return ( INT_PTR )pa->ppro->AuthRequest( ccs->hContact, ( const TCHAR* )ccs->lParam ); + } + case 107: + return ( INT_PTR )pa->ppro->AuthDeny(( HANDLE )wParam, ( const TCHAR* )lParam ); + case 108: + return ( INT_PTR )pa->ppro->SearchBasic(( const TCHAR* )lParam ); + case 109: { + PROTOSEARCHBYNAME* psbn = ( PROTOSEARCHBYNAME* )lParam; + return ( INT_PTR )pa->ppro->SearchByName( psbn->pszNick, psbn->pszFirstName, psbn->pszLastName ); + } + case 110: + return ( INT_PTR )pa->ppro->SearchByEmail(( const TCHAR* )lParam ); + } } +#endif + + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CallContactService( HANDLE hContact, const char *szProtoService, WPARAM wParam, LPARAM lParam ) +{ + int i; + DBVARIANT dbv; + INT_PTR ret; + PROTOACCOUNT* pa; + CCSDATA ccs = { hContact, szProtoService, wParam, lParam }; + + for ( i = 0;; i++ ) { + char str[10]; + _itoa( i, str, 10 ); + if ( DBGetContactSettingString( hContact, "_Filter", str, &dbv )) + break; + + if (( ret = CallProtoServiceInt( hContact, dbv.pszVal, szProtoService, i+1, ( LPARAM)&ccs )) != CALLSERVICE_NOTFOUND ) { + //chain was started, exit + mir_free( dbv.pszVal ); + return ret; + } + mir_free( dbv.pszVal ); + } + if ( DBGetContactSettingString( hContact, "Protocol", "p", &dbv )) + return 1; + + pa = Proto_GetAccount( dbv.pszVal ); + if ( pa == NULL || pa->ppro == NULL ) + ret = 1; + else { + if ( pa->bOldProto ) + ret = CallProtoServiceInt( hContact, dbv.pszVal, szProtoService, (WPARAM)(-1), ( LPARAM)&ccs ); + else + ret = CallProtoServiceInt( hContact, dbv.pszVal, szProtoService, wParam, lParam ); + if ( ret == CALLSERVICE_NOTFOUND ) + ret = 1; + } + + mir_free( dbv.pszVal ); + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static void InsertServiceListItem( int id, const char* szName ) +{ + TServiceListItem* p = ( TServiceListItem* )mir_alloc( sizeof( TServiceListItem )); + p->id = id; + p->name = szName; + serviceItems.insert( p ); +} + +int LoadProtocolsModule(void) +{ + bModuleInitialized = TRUE; + + if ( LoadProtoChains() ) + return 1; + + InsertServiceListItem( 1, PS_ADDTOLIST ); + InsertServiceListItem( 2, PS_ADDTOLISTBYEVENT ); + InsertServiceListItem( 3, PS_AUTHALLOW ); + InsertServiceListItem( 4, PS_AUTHDENY ); + InsertServiceListItem( 5, PSR_AUTH ); + InsertServiceListItem( 6, PSS_AUTHREQUEST ); + InsertServiceListItem( 7, PS_CHANGEINFO ); + InsertServiceListItem( 8, PSS_FILEALLOW ); + InsertServiceListItem( 9, PSS_FILECANCEL ); + InsertServiceListItem( 10, PSS_FILEDENY ); + InsertServiceListItem( 11, PS_FILERESUME ); + InsertServiceListItem( 12, PS_GETCAPS ); + InsertServiceListItem( 13, PS_LOADICON ); + InsertServiceListItem( 14, PSS_GETINFO ); + InsertServiceListItem( 15, PS_BASICSEARCH ); + InsertServiceListItem( 16, PS_SEARCHBYEMAIL ); + InsertServiceListItem( 17, PS_SEARCHBYNAME ); + InsertServiceListItem( 18, PS_SEARCHBYADVANCED ); + InsertServiceListItem( 19, PS_CREATEADVSEARCHUI ); + InsertServiceListItem( 20, PSR_CONTACTS ); + InsertServiceListItem( 21, PSR_FILE ); + InsertServiceListItem( 22, PSR_MESSAGE ); + InsertServiceListItem( 23, PSR_URL ); + InsertServiceListItem( 24, PSS_CONTACTS ); + InsertServiceListItem( 25, PSS_FILE ); + InsertServiceListItem( 26, PSS_MESSAGE ); + InsertServiceListItem( 27, PSS_URL ); + InsertServiceListItem( 28, PSS_SETAPPARENTMODE ); + InsertServiceListItem( 29, PS_SETSTATUS ); + InsertServiceListItem( 30, PSS_GETAWAYMSG ); + InsertServiceListItem( 31, PSR_AWAYMSG ); + InsertServiceListItem( 32, PSS_AWAYMSG ); + InsertServiceListItem( 33, PS_SETAWAYMSG ); + InsertServiceListItem( 34, PSS_USERISTYPING ); + InsertServiceListItem( 35, PS_GETNAME ); + InsertServiceListItem( 36, PS_GETSTATUS ); + +#ifdef _UNICODE + InsertServiceListItem( 100, PS_SETAWAYMSGW ); + InsertServiceListItem( 102, PSS_FILEW ); + InsertServiceListItem( 103, PSS_FILEALLOWW ); + InsertServiceListItem( 104, PSS_FILEDENYW ); + InsertServiceListItem( 105, PS_FILERESUMEW ); + InsertServiceListItem( 106, PSS_AUTHREQUESTW ); + InsertServiceListItem( 107, PS_AUTHDENYW ); + InsertServiceListItem( 108, PS_BASICSEARCHW ); + InsertServiceListItem( 109, PS_SEARCHBYNAMEW ); + InsertServiceListItem( 110, PS_SEARCHBYEMAILW ); +#endif + + hAckEvent = CreateHookableEvent(ME_PROTO_ACK); + hTypeEvent = CreateHookableEvent(ME_PROTO_CONTACTISTYPING); + hAccListChanged = CreateHookableEvent(ME_PROTO_ACCLISTCHANGED); + + CreateServiceFunction( MS_PROTO_BROADCASTACK, Proto_BroadcastAck ); + CreateServiceFunction( MS_PROTO_ISPROTOCOLLOADED, srvProto_IsLoaded ); + CreateServiceFunction( MS_PROTO_ENUMPROTOS, Proto_EnumProtocols ); + CreateServiceFunction( MS_PROTO_REGISTERMODULE, Proto_RegisterModule ); + CreateServiceFunction( MS_PROTO_SELFISTYPING, Proto_SelfIsTyping ); + CreateServiceFunction( MS_PROTO_CONTACTISTYPING, Proto_ContactIsTyping ); + + CreateServiceFunction( MS_PROTO_RECVFILE, Proto_RecvFile ); + CreateServiceFunction( MS_PROTO_RECVFILET, Proto_RecvFileT ); + CreateServiceFunction( MS_PROTO_RECVMSG, Proto_RecvMessage ); + + CreateServiceFunction( "Proto/EnumProtocols", Proto_EnumAccounts ); + CreateServiceFunction( MS_PROTO_ENUMACCOUNTS, Proto_EnumAccounts ); + CreateServiceFunction( MS_PROTO_GETACCOUNT, srvProto_GetAccount ); + + CreateServiceFunction( MS_PROTO_ISACCOUNTENABLED, srvProto_IsAccountEnabled ); + CreateServiceFunction( MS_PROTO_ISACCOUNTLOCKED, srvProto_IsAccountLocked ); + + return LoadProtoOptions(); +} + +void UnloadProtocolsModule() +{ + int i; + + if ( !bModuleInitialized ) return; + + if ( hAckEvent ) { + DestroyHookableEvent(hAckEvent); + hAckEvent = NULL; + } + if ( hAccListChanged ) { + DestroyHookableEvent(hAccListChanged); + hAccListChanged = NULL; + } + + if ( protos.getCount() ) { + for( i=0; i < protos.getCount(); i++ ) { + mir_free( protos[i]->szName); + mir_free( protos[i] ); + } + protos.destroy(); + } + + for ( i=0; i < serviceItems.getCount(); i++ ) + mir_free( serviceItems[i] ); + serviceItems.destroy(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +pfnUninitProto GetProtocolDestructor( char* szProto ) +{ + int idx; + PROTOCOLDESCRIPTOR temp; + temp.szName = szProto; + if (( idx = protos.getIndex( &temp )) != -1 ) + return protos[idx]->fnUninit; + + return NULL; +} diff --git a/src/modules/protocols/protodir.cpp b/src/modules/protocols/protodir.cpp new file mode 100644 index 0000000000..7a110268ef --- /dev/null +++ b/src/modules/protocols/protodir.cpp @@ -0,0 +1,248 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2005 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#if 0 + +extern HANDLE hCacheHeap; + +/* + + the id cache has id/proto against hContact to lookup ID's fast to resolve to hContact, + the protoBaseCache has hContact's sorted, so can lookup hContact->proto fast, these two caches + share the same data, they're indexes, each entry might not have an "id" set. + + There is a small cache which maintains a protocol list + +*/ + +/* + + The information we need to cache is not readily available, data has to be gathered at startup + when a new contact is created/deleted, when a new proto registers and so on. + + The information we get at startup includes walking the contact chain and reading Protocol/p which + will give us the protocol the contact is on, all this info is stored in contactEntry's within + protoCache ONLY - contactCache is EMPTY at this point. + + We can not fetch the id of the contact because this information is only in SOME protocol plugins, + this is a problem but we'll hook all proto registrations and ask each proto if it supports the + returning this ID name, if not - it won't even use our id <-> contact look so no biggie! + +*/ + +typedef struct { + char * proto; // within proto cache + char * id; // optional + HANDLE hContact; +} contactEntry; + +typedef struct { + CRITICAL_SECTION csLock; + SortedList contactCache; // index for id/proto -> hContact + SortedList protoCache; // index for hContact -> proto/id + SortedList protoNameCache; // index of protocol names +} contactDir; + +static contactDir condir; + +// compare's id/proto and return's hContact's +int contactCacheCompare(void * a, void * b) +{ + contactEntry * x = (contactEntry *) a; + contactEntry * y = (contactEntry *) b; + int rc=0; + // same protocol? + rc = strcmp(x->proto, y->proto); + if ( rc == 0 ) { + // same id? id's might be missing + if ( x->id && y->id ) rc = strcmp(x->id, y->id); + } + return rc; +} + +// compares hContact's and returns associated data +int protoCacheCompare(void * a, void * b) +{ + contactEntry * x = (contactEntry *) a; + contactEntry * y = (contactEntry *) b; + if ( x->hContact < y->hContact ) return -1; + if ( x->hContact > y->hContact ) return 1; + return 0; +} + +// keeps a list of protocol names +int protoNameCacheCompare(void * a, void * b) +{ + return strcmp( (char *)a, (char*)b ); +} + +// cache the protocol string so that its not allocated per contact but shared +char * contactDir_Proto_Add(contactDir * cd, char * proto) +{ + int index = 0 ; + char * szCache = 0; + EnterCriticalSection(&cd->csLock); + if ( List_GetIndex(&cd->protoNameCache, proto, &index) ) szCache = cd->protoNameCache.items[index]; + else { + szCache = HeapAlloc(hCacheHeap, HEAP_NO_SERIALIZE, strlen(proto)+1); + strcpy(szCache, proto); + List_Insert(&cd->protoNameCache, szCache, index); + } + LeaveCriticalSection(&cd->csLock); + return szCache; +} + +// thread safe +char * contactDir_Proto_Get(contactDir * cd, HANDLE hContact) +{ + char * szCache = 0; + int index = 0; + contactEntry e; + e.hContact=hContact; + e.proto=""; + e.id=""; + EnterCriticalSection(&cd->csLock); + if ( List_GetIndex(&cd->protoCache, &e, &index) ) { + contactEntry * p = cd->protoCache.items[index]; + szCache = p->proto; + } + LeaveCriticalSection(&cd->csLock); + return szCache; +} + +// thread tolerant, if updating id dont pass proto, if updating proto dont pass id +void contactDir_Contact_Add(contactDir * cd, HANDLE hContact, char * proto, char * id) +{ + // if a contact is gonna exist anywhere it's going to be in the ->protoCache which has a key of hContact + // if id is not null then the contact should be indexed via the ->contactCache instead + if ( id == NULL ) { + int index = 0; + contactEntry e; + e.hContact=hContact; + e.proto = proto; + e.id = ""; + EnterCriticalSection(&cd->csLock); + if ( List_GetIndex(&cd->protoCache, &e, &index) ) { + contactEntry * p = cd->protoCache.items[index]; + // this hContact is in the cache, protcol changing? + p->proto = contactDir_Proto_Add(cd, proto); // just replace the old pointer + } else { + contactEntry * p = 0; + // this hContact isn't in the cache, add it + p = HeapAlloc(hCacheHeap, HEAP_NO_SERIALIZE, sizeof(contactEntry)); + p->proto = contactDir_Proto_Add(cd, proto); + p->id = 0; + p->hContact = hContact; + // add it + List_Insert(&cd->protoCache, p, index); + } + LeaveCriticalSection(&cd->csLock); + } else { + // this contact HAS to be in ->protoCache since it was added during startup + // need to find the contactEntry* that should already exist for it + } //if +} + +// only expected to be called at startup. +void contactDir_Proto_Walk(contactDir * cd) +{ + HANDLE hContact; + char buf[128]; + DBCONTACTGETSETTING gsProto; + DBVARIANT dbvProto; + // setup the read structure + gsProto.szModule="Protocol"; + gsProto.szSetting="p"; + gsProto.pValue = &dbvProto; + // this might not work + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); + while ( hContact ) { + // and how we'll get the reset + dbvProto.type=DBVT_ASCIIZ; + dbvProto.pszVal = (char *) &buf; + dbvProto.cchVal = SIZEOF(buf); + // figure out what hContact/Protocol/p is + if ( CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)hContact, (LPARAM)&gsProto) == 0 ) { + contactDir_Contact_Add(cd, hContact, buf, NULL); + } + // find next + hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0); + } +} + +// ctor/dtor + +void contactDir_Init(contactDir * cd) +{ + InitializeCriticalSection(&cd->csLock); + cd->contactCache.increment=50; + cd->contactCache.sortFunc=contactCacheCompare; + cd->protoCache.increment=50; + cd->protoCache.sortFunc=protoCacheCompare; + cd->protoNameCache.increment=5; + cd->protoNameCache.sortFunc=protoNameCacheCompare; + // build a list of all hContact's and what proto's they are on + contactDir_Proto_Walk(cd); +} + +void contactDir_Deinit(contactDir * cd) +{ + List_Destroy(&cd->contactCache); + List_Destroy(&cd->protoCache); + List_Destroy(&cd->protoNameCache); + DeleteCriticalSection(&cd->csLock); +} + +static int contactDirGetProto(WPARAM wParam, LPARAM lParam) +{ + return (int) contactDir_Proto_Get(&condir,(HANDLE)wParam); +} + +#endif + +void InitContactDir(void) +{ + return; + //contactDir_Init(&condir); + //CreateServiceFunction(MS_PROTODIR_PROTOFROMCONTACT, contactDirGetProto); +} + + +void UninitContactDir(void) +{ + return; +#if 0 + { + int j; + for ( j = 0; j< condir.protoCache.realCount; j++) { + char buf[128]; + contactEntry * p = condir.protoCache.items[j]; + mir_snprintf(buf,SIZEOF(buf)," [%s] %s @ %x \n", p->proto, p->id ? p->id : "", p->hContact); + OutputDebugString(buf); + } + } + contactDir_Deinit(&condir); +#endif +} diff --git a/src/modules/protocols/protoint.cpp b/src/modules/protocols/protoint.cpp new file mode 100644 index 0000000000..dec59cce98 --- /dev/null +++ b/src/modules/protocols/protoint.cpp @@ -0,0 +1,271 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +char** __fastcall Proto_FilesMatrixA( TCHAR **files ); +void FreeFilesMatrix( TCHAR ***files ); + +INT_PTR __fastcall MyCallProtoService( const char *szModule, const char *szService, WPARAM wParam, LPARAM lParam ) +{ + char str[MAXMODULELABELLENGTH]; + mir_snprintf( str, sizeof(str), "%s%s", szModule, szService ); + return CallService(str,wParam,lParam); +} + +struct DEFAULT_PROTO_INTERFACE : public PROTO_INTERFACE +{ + HANDLE __cdecl AddToList( int flags, PROTOSEARCHRESULT* psr ) + { return ( HANDLE )MyCallProtoService( m_szModuleName, PS_ADDTOLIST, flags, (LPARAM)psr ); + } + + HANDLE __cdecl AddToListByEvent( int flags, int iContact, HANDLE hDbEvent ) + { return ( HANDLE )MyCallProtoService( m_szModuleName, PS_ADDTOLISTBYEVENT, MAKELONG(flags, iContact), (LPARAM)hDbEvent ); + } + + int __cdecl Authorize( HANDLE hContact ) + { return ( int )MyCallProtoService( m_szModuleName, PS_AUTHALLOW, (WPARAM)hContact, 0 ); + } + + int __cdecl AuthDeny( HANDLE hContact, const TCHAR* szReason ) + { return ( int )MyCallProtoService( m_szModuleName, PS_AUTHDENY, (WPARAM)hContact, (LPARAM)StrConvA(szReason)); + } + + int __cdecl AuthRecv( HANDLE hContact, PROTORECVEVENT* evt ) + { CCSDATA ccs = { hContact, PSR_AUTH, 0, (LPARAM)evt }; + return ( int )MyCallProtoService( m_szModuleName, PSR_AUTH, 0, (LPARAM)&ccs ); + } + + int __cdecl AuthRequest( HANDLE hContact, const TCHAR* szMessage ) + { CCSDATA ccs = { hContact, PSS_AUTHREQUEST, 0, (LPARAM)szMessage }; + ccs.lParam = ( LPARAM )mir_t2a( szMessage ); + int res = ( int )MyCallProtoService( m_szModuleName, PSS_AUTHREQUEST, 0, (LPARAM)&ccs ); + mir_free(( char* )ccs.lParam ); + return res; + } + + HANDLE __cdecl ChangeInfo( int iInfoType, void* pInfoData ) + { return ( HANDLE )MyCallProtoService( m_szModuleName, PS_CHANGEINFO, iInfoType, ( LPARAM )pInfoData ); + } + + HANDLE __cdecl FileAllow( HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath ) + { CCSDATA ccs = { hContact, PSS_FILEALLOW, (WPARAM)hTransfer, (LPARAM)szPath }; +#ifdef _UNICODE + ccs.lParam = ( LPARAM )mir_t2a( szPath ); + HANDLE res = ( HANDLE )MyCallProtoService( m_szModuleName, PSS_FILEALLOW, 0, (LPARAM)&ccs ); + mir_free(( char* )ccs.lParam ); + return res; +#else + return ( HANDLE )MyCallProtoService( m_szModuleName, PSS_FILEALLOW, 0, (LPARAM)&ccs ); +#endif + } + + int __cdecl FileCancel( HANDLE hContact, HANDLE hTransfer ) + { CCSDATA ccs = { hContact, PSS_FILECANCEL, (WPARAM)hTransfer, 0 }; + return ( int )MyCallProtoService( m_szModuleName, PSS_FILECANCEL, 0, (LPARAM)&ccs ); + } + + int __cdecl FileDeny( HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szReason ) + { CCSDATA ccs = { hContact, PSS_FILEDENY, (WPARAM)hTransfer, (LPARAM)szReason }; +#ifdef _UNICODE + ccs.lParam = ( LPARAM )mir_t2a( szReason ); + int res = ( int )MyCallProtoService( m_szModuleName, PSS_FILEDENY, 0, (LPARAM)&ccs ); + mir_free(( char* )ccs.lParam ); + return res; +#else + return ( int )MyCallProtoService( m_szModuleName, PSS_FILEDENY, 0, (LPARAM)&ccs ); +#endif + } + + int __cdecl FileResume( HANDLE hTransfer, int* action, const PROTOCHAR** szFilename ) + { PROTOFILERESUME pfr = { *action, *szFilename }; +#ifdef _UNICODE + pfr.szFilename = ( PROTOCHAR* )mir_t2a( pfr.szFilename ); + int res = ( int )MyCallProtoService( m_szModuleName, PS_FILERESUME, ( WPARAM )hTransfer, ( LPARAM )&pfr); + mir_free(( PROTOCHAR* )*szFilename ); + *action = pfr.action; *szFilename = (PROTOCHAR*)pfr.szFilename; +#else + int res = ( int )MyCallProtoService( m_szModuleName, PS_FILERESUME, ( WPARAM )hTransfer, ( LPARAM )&pfr ); + *action = pfr.action; *szFilename = (PROTOCHAR*)pfr.szFilename; +#endif + return res; + } + + DWORD_PTR __cdecl GetCaps( int type, HANDLE hContact ) + { return ( DWORD_PTR )MyCallProtoService( m_szModuleName, PS_GETCAPS, type, (LPARAM)hContact ); + } + + HICON __cdecl GetIcon( int iconIndex ) + { return ( HICON )MyCallProtoService( m_szModuleName, PS_LOADICON, iconIndex, 0 ); + } + + int __cdecl GetInfo( HANDLE hContact, int flags ) + { CCSDATA ccs = { hContact, PSS_GETINFO, flags, 0 }; + return MyCallProtoService( m_szModuleName, PSS_GETINFO, 0, (LPARAM)&ccs ); + } + + HANDLE __cdecl SearchBasic( const PROTOCHAR* id ) + { return ( HANDLE )MyCallProtoService( m_szModuleName, PS_BASICSEARCH, 0, ( LPARAM )StrConvA( id )); + } + + HANDLE __cdecl SearchByEmail( const PROTOCHAR* email ) + { return ( HANDLE )MyCallProtoService( m_szModuleName, PS_SEARCHBYEMAIL, 0, ( LPARAM )StrConvA( email )); + } + + HANDLE __cdecl SearchByName( const PROTOCHAR* nick, const PROTOCHAR* firstName, const PROTOCHAR* lastName ) + { PROTOSEARCHBYNAME psn; +#ifdef _UNICODE + psn.pszNick = ( PROTOCHAR* )mir_t2a( nick ); + psn.pszFirstName = ( PROTOCHAR* )mir_t2a( firstName ); + psn.pszLastName = ( PROTOCHAR* )mir_t2a( lastName ); + HANDLE res = ( HANDLE )MyCallProtoService( m_szModuleName, PS_SEARCHBYNAME, 0, ( LPARAM )&psn ); + mir_free( psn.pszNick ); + mir_free( psn.pszFirstName ); + mir_free( psn.pszLastName ); + return res; +#else + psn.pszNick = ( char* )nick; + psn.pszFirstName = ( char* )firstName; + psn.pszLastName = ( char* )lastName; + return ( HANDLE )MyCallProtoService( m_szModuleName, PS_SEARCHBYNAME, 0, ( LPARAM )&psn ); +#endif + } + + HWND __cdecl SearchAdvanced( HWND owner ) + { return ( HWND )MyCallProtoService( m_szModuleName, PS_SEARCHBYADVANCED, 0, ( LPARAM )owner ); + } + + HWND __cdecl CreateExtendedSearchUI( HWND owner ) + { return ( HWND )MyCallProtoService( m_szModuleName, PS_CREATEADVSEARCHUI, 0, ( LPARAM )owner ); + } + + int __cdecl RecvContacts( HANDLE hContact, PROTORECVEVENT* evt ) + { CCSDATA ccs = { hContact, PSR_CONTACTS, 0, (LPARAM)evt }; + return ( int )MyCallProtoService( m_szModuleName, PSR_CONTACTS, 0, (LPARAM)&ccs ); + } + + int __cdecl RecvFile( HANDLE hContact, PROTOFILEEVENT* evt ) + { CCSDATA ccs = { hContact, PSR_FILE, 0, (LPARAM)evt }; + return MyCallProtoService( m_szModuleName, PSR_FILE, 0, (LPARAM)&ccs ); + } + + int __cdecl RecvMsg( HANDLE hContact, PROTORECVEVENT* evt ) + { CCSDATA ccs = { hContact, PSR_MESSAGE, 0, (LPARAM)evt }; + return ( int )MyCallProtoService( m_szModuleName, PSR_MESSAGE, 0, (LPARAM)&ccs ); + } + + int __cdecl RecvUrl( HANDLE hContact, PROTORECVEVENT* evt ) + { CCSDATA ccs = { hContact, PSR_URL, 0, (LPARAM)evt }; + return ( int )MyCallProtoService( m_szModuleName, PSR_URL, 0, (LPARAM)&ccs ); + } + + int __cdecl SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList ) + { CCSDATA ccs = { hContact, PSS_CONTACTS, MAKEWPARAM(flags,nContacts), (LPARAM)hContactsList }; + return ( int )MyCallProtoService( m_szModuleName, PSS_CONTACTS, 0, (LPARAM)&ccs ); + } + + HANDLE __cdecl SendFile( HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles ) + { CCSDATA ccs = { hContact, PSS_FILE, (WPARAM)szDescription, (LPARAM)ppszFiles }; +#ifdef _UNICODE + ccs.wParam = ( WPARAM )mir_t2a( szDescription ); + ccs.lParam = ( LPARAM )Proto_FilesMatrixA( ppszFiles ); + HANDLE res = ( HANDLE )MyCallProtoService( m_szModuleName, PSS_FILE, 0, ( LPARAM )&ccs ); + if ( res == 0 ) FreeFilesMatrix(( TCHAR*** )&ccs.lParam ); + mir_free(( char* )ccs.wParam ); + return res; +#else + return ( HANDLE )MyCallProtoService( m_szModuleName, PSS_FILE, 0, (LPARAM)&ccs ); +#endif + } + + int __cdecl SendMsg( HANDLE hContact, int flags, const char* msg ) + { CCSDATA ccs = { hContact, PSS_MESSAGE, flags, (LPARAM)msg }; + return ( int )MyCallProtoService( m_szModuleName, PSS_MESSAGE, 0, (LPARAM)&ccs ); + } + + int __cdecl SendUrl( HANDLE hContact, int flags, const char* url ) + { CCSDATA ccs = { hContact, PSS_URL, flags, (LPARAM)url }; + return ( int )MyCallProtoService( m_szModuleName, PSS_URL, 0, (LPARAM)&ccs ); + } + + int __cdecl SetApparentMode( HANDLE hContact, int mode ) + { CCSDATA ccs = { hContact, PSS_SETAPPARENTMODE, mode, 0 }; + return ( int )MyCallProtoService( m_szModuleName, PSS_SETAPPARENTMODE, 0, (LPARAM)&ccs ); + } + + int __cdecl SetStatus( int iNewStatus ) + { return ( int )MyCallProtoService( m_szModuleName, PS_SETSTATUS, iNewStatus, 0 ); + } + + HANDLE __cdecl GetAwayMsg( HANDLE hContact ) + { CCSDATA ccs = { hContact, PSS_GETAWAYMSG, 0, 0 }; + return ( HANDLE )MyCallProtoService( m_szModuleName, PSS_GETAWAYMSG, 0, (LPARAM)&ccs ); + } + + int __cdecl RecvAwayMsg( HANDLE hContact, int statusMode, PROTORECVEVENT* evt ) + { CCSDATA ccs = { hContact, PSR_AWAYMSG, statusMode, (LPARAM)evt }; + return ( int )MyCallProtoService( m_szModuleName, PSR_AWAYMSG, 0, (LPARAM)&ccs ); + } + + int __cdecl SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg ) + { CCSDATA ccs = { hContact, PSS_AWAYMSG, (WPARAM)hProcess, (LPARAM)msg }; + return ( int )MyCallProtoService( m_szModuleName, PSS_AWAYMSG, 0, (LPARAM)&ccs ); + } + + int __cdecl SetAwayMsg( int iStatus, const TCHAR* msg ) + { return ( int )MyCallProtoService( m_szModuleName, PS_SETAWAYMSG, iStatus, (LPARAM)StrConvA(msg)); + } + + int __cdecl UserIsTyping( HANDLE hContact, int type ) + { CCSDATA ccs = { hContact, PSS_USERISTYPING, (WPARAM)hContact, type }; + return MyCallProtoService( m_szModuleName, PSS_USERISTYPING, 0, (LPARAM)&ccs ); + } + + int __cdecl OnEvent( PROTOEVENTTYPE, WPARAM, LPARAM ) + { + return 0; + } +}; + +// creates the default protocol container for compatibility with the old plugins + +PROTO_INTERFACE* AddDefaultAccount( const char* szProtoName ) +{ + PROTO_INTERFACE* ppi = new DEFAULT_PROTO_INTERFACE; + if ( ppi != NULL ) { + ppi->m_iVersion = 1; + ppi->m_szModuleName = mir_strdup( szProtoName ); + ppi->m_szProtoName = mir_strdup( szProtoName ); + ppi->m_tszUserName = mir_a2t( szProtoName ); + } + return ppi; +} + +int FreeDefaultAccount( PROTO_INTERFACE* ppi ) +{ + mir_free( ppi->m_szModuleName ); + mir_free( ppi->m_szProtoName ); + mir_free( ppi->m_tszUserName ); + delete ppi; + return 0; +} diff --git a/src/modules/protocols/protoopts.cpp b/src/modules/protocols/protoopts.cpp new file mode 100644 index 0000000000..3d9c0a2b36 --- /dev/null +++ b/src/modules/protocols/protoopts.cpp @@ -0,0 +1,1084 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#define LBN_MY_CHECK 0x1000 +#define LBN_MY_RENAME 0x1001 + +#define WM_MY_REFRESH (WM_USER+0x1000) +#define WM_MY_RENAME (WM_USER+0x1001) + +INT_PTR Proto_EnumProtocols( WPARAM, LPARAM ); +bool CheckProtocolOrder(void); + +#define errMsg \ +"WARNING! The account is going to be deleted. It means that all its \ +settings, contacts and histories will be also erased.\n\n\ +Are you absolutely sure?" + +#define upgradeMsg \ +"Your account was successfully upgraded. \ +To activate it, restart of Miranda is needed.\n\n\ +If you want to restart Miranda now, press Yes, if you want to upgrade another account, press No" + +#define legacyMsg \ +"This account uses legacy protocol plugin. \ +Use Miranda IM options dialogs to change it's preferences." + +#define welcomeMsg \ +"Welcome to Miranda IM's account manager!\n \ +Here you can set up your IM accounts.\n\n \ +Select an account from the list on the left to see the available options. \ +Alternatively, just click on the Plus sign underneath the list to set up a new IM account." + +static HWND hAccMgr = NULL; + +extern HANDLE hAccListChanged; + +int UnloadPlugin( TCHAR* buf, int bufLen ); + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Account edit form +// Gets PROTOACCOUNT* as a parameter, or NULL to edit a new one + +typedef struct +{ + int action; + PROTOACCOUNT* pa; +} + AccFormDlgParam; + +static INT_PTR CALLBACK AccFormDlgProc(HWND hwndDlg,UINT message, WPARAM wParam, LPARAM lParam) +{ + switch( message ) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + PROTOCOLDESCRIPTOR** proto; + int protoCount, i, cnt = 0; + Proto_EnumProtocols(( WPARAM )&protoCount, ( LPARAM )&proto ); + for ( i=0; i < protoCount; i++ ) { + PROTOCOLDESCRIPTOR* pd = proto[i]; + if ( pd->type == PROTOTYPE_PROTOCOL && pd->cbSize == sizeof( *pd )) { + SendDlgItemMessageA( hwndDlg, IDC_PROTOTYPECOMBO, CB_ADDSTRING, 0, (LPARAM)proto[i]->szName ); + ++cnt; + } + } + SendDlgItemMessage( hwndDlg, IDC_PROTOTYPECOMBO, CB_SETCURSEL, 0, 0 ); + EnableWindow( GetDlgItem( hwndDlg, IDOK ), cnt != 0 ); + + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam ); + AccFormDlgParam* param = ( AccFormDlgParam* )lParam; + + if ( param->action == PRAC_ADDED ) // new account + SetWindowText( hwndDlg, TranslateT( "Create new account" )); + else { + TCHAR str[200]; + if ( param->action == PRAC_CHANGED ) { // update + EnableWindow( GetDlgItem( hwndDlg, IDC_PROTOTYPECOMBO ), FALSE ); + mir_sntprintf( str, SIZEOF(str), _T("%s: %s"), TranslateT( "Editing account" ), param->pa->tszAccountName ); + } + else mir_sntprintf( str, SIZEOF(str), _T("%s: %s"), TranslateT( "Upgrading account" ), param->pa->tszAccountName ); + + SetWindowText( hwndDlg, str ); + SetDlgItemText( hwndDlg, IDC_ACCNAME, param->pa->tszAccountName ); + SetDlgItemTextA( hwndDlg, IDC_ACCINTERNALNAME, param->pa->szModuleName ); + SendDlgItemMessageA( hwndDlg, IDC_PROTOTYPECOMBO, CB_SELECTSTRING, -1, (LPARAM)param->pa->szProtoName ); + + EnableWindow( GetDlgItem( hwndDlg, IDC_ACCINTERNALNAME ), FALSE ); + } + SendDlgItemMessage( hwndDlg, IDC_ACCINTERNALNAME, EM_LIMITTEXT, 40, 0 ); + } + return TRUE; + + case WM_COMMAND: + switch( LOWORD(wParam)) { + case IDOK: + { + AccFormDlgParam* param = ( AccFormDlgParam* )GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + PROTOACCOUNT* pa = param->pa; + + if ( param->action == PRAC_ADDED ) { + char buf[200]; + GetDlgItemTextA( hwndDlg, IDC_ACCINTERNALNAME, buf, SIZEOF( buf )); + rtrim( buf ); + if ( buf[0] ) { + for (int i = 0; i < accounts.getCount(); ++i) + if (_stricmp(buf, accounts[i]->szModuleName) == 0) + return FALSE; + } } + + switch( param->action ) { + case PRAC_UPGRADED: + { + int idx; + BOOL oldProto = pa->bOldProto; + TCHAR szPlugin[MAX_PATH]; + mir_sntprintf(szPlugin, SIZEOF(szPlugin), _T("%s.dll"), StrConvT(pa->szProtoName)); + idx = accounts.getIndex(pa); + UnloadAccount(pa, false, false); + accounts.remove(idx); + if (oldProto && UnloadPlugin(szPlugin, SIZEOF(szPlugin))) + { + TCHAR szNewName[MAX_PATH]; + mir_sntprintf(szNewName, SIZEOF(szNewName), _T("%s~"), szPlugin); + MoveFile(szPlugin, szNewName); + } + } + // fall through + + case PRAC_ADDED: + pa = (PROTOACCOUNT*)mir_calloc( sizeof( PROTOACCOUNT )); + pa->cbSize = sizeof( PROTOACCOUNT ); + pa->bIsEnabled = TRUE; + pa->bIsVisible = TRUE; + + pa->iOrder = accounts.getCount(); + pa->type = PROTOTYPE_PROTOCOL; + break; + } + { + TCHAR buf[256]; + GetDlgItemText( hwndDlg, IDC_ACCNAME, buf, SIZEOF( buf )); + mir_free(pa->tszAccountName); + pa->tszAccountName = mir_tstrdup( buf ); + } + if ( param->action == PRAC_ADDED || param->action == PRAC_UPGRADED ) + { + char buf[200]; + GetDlgItemTextA( hwndDlg, IDC_PROTOTYPECOMBO, buf, SIZEOF( buf )); + pa->szProtoName = mir_strdup( buf ); + GetDlgItemTextA( hwndDlg, IDC_ACCINTERNALNAME, buf, SIZEOF( buf )); + rtrim( buf ); + if ( buf[0] == 0 ) { + int count = 1; + for( ;; ) { + DBVARIANT dbv; + mir_snprintf( buf, SIZEOF(buf), "%s_%d", pa->szProtoName, count++ ); + if ( DBGetContactSettingString( NULL, buf, "AM_BaseProto", &dbv )) + break; + DBFreeVariant( &dbv ); + } } + pa->szModuleName = mir_strdup( buf ); + + if ( !pa->tszAccountName[0] ) { + mir_free(pa->tszAccountName); + pa->tszAccountName = mir_a2t(buf); + } + + DBWriteContactSettingString( NULL, pa->szModuleName, "AM_BaseProto", pa->szProtoName ); + accounts.insert( pa ); + + if ( ActivateAccount( pa )) { + pa->ppro->OnEvent( EV_PROTO_ONLOAD, 0, 0 ); + if (!DBGetContactSettingByte(NULL, "CList", "MoveProtoMenus", FALSE)) + pa->ppro->OnEvent( EV_PROTO_ONMENU, 0, 0 ); + } + else pa->type = PROTOTYPE_DISPROTO; + } + + WriteDbAccounts(); + NotifyEventHooks( hAccListChanged, param->action, ( LPARAM )pa ); + + SendMessage( GetParent(hwndDlg), WM_MY_REFRESH, 0, 0 ); + } + + EndDialog( hwndDlg, TRUE ); + break; + + case IDCANCEL: + EndDialog( hwndDlg, FALSE ); + break; + } + } + + return FALSE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Accounts manager + +struct TAccMgrData +{ + HFONT hfntTitle, hfntText; + int titleHeight, textHeight; + int selectedHeight, normalHeight; + int iSelected; +}; + +struct TAccListData +{ + WNDPROC oldWndProc; + int iItem; + RECT rcCheck; + + HWND hwndEdit; + WNDPROC oldEditProc; +}; + +static void sttClickButton(HWND hwndDlg, int idcButton) +{ + if (IsWindowEnabled(GetDlgItem(hwndDlg, idcButton))) + PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(idcButton, BN_CLICKED), (LPARAM)GetDlgItem(hwndDlg, idcButton)); +} + +static LRESULT CALLBACK sttEditSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_KEYDOWN: + switch (wParam) { + case VK_RETURN: + DestroyWindow(hwnd); + return 0; + + case VK_ESCAPE: + SetWindowLongPtr(hwnd, GWLP_WNDPROC, GetWindowLongPtr(hwnd, GWLP_USERDATA)); + DestroyWindow(hwnd); + return 0; + } + break; + + case WM_GETDLGCODE: + if (wParam == VK_RETURN || wParam == VK_ESCAPE) + return DLGC_WANTMESSAGE; + break; + + case WM_KILLFOCUS: + { + int length = GetWindowTextLength(hwnd) + 1; + TCHAR *str = ( TCHAR* )mir_alloc(sizeof(TCHAR) * length); + GetWindowText(hwnd, str, length); + SendMessage(GetParent(GetParent(hwnd)), WM_COMMAND, MAKEWPARAM(GetWindowLongPtr(GetParent(hwnd), GWL_ID), LBN_MY_RENAME), (LPARAM)str); + } + DestroyWindow(hwnd); + return 0; + } + return CallWindowProc((WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA), hwnd, msg, wParam, lParam); +} + +static LRESULT CALLBACK AccListWndProc(HWND hwnd,UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct TAccListData *dat = (struct TAccListData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if ( !dat ) + return DefWindowProc(hwnd, msg, wParam, lParam); + + switch (msg) { + case WM_LBUTTONDOWN: + { + POINT pt = {LOWORD(lParam), HIWORD(lParam)}; + int iItem = LOWORD(SendMessage(hwnd, LB_ITEMFROMPOINT, 0, lParam)); + ListBox_GetItemRect(hwnd, iItem, &dat->rcCheck); + + dat->rcCheck.right = dat->rcCheck.left + GetSystemMetrics(SM_CXSMICON) + 4; + dat->rcCheck.bottom = dat->rcCheck.top + GetSystemMetrics(SM_CYSMICON) + 4; + if (PtInRect(&dat->rcCheck, pt)) + dat->iItem = iItem; + else + dat->iItem = -1; + } + break; + + case WM_LBUTTONUP: + { + POINT pt = {LOWORD(lParam), HIWORD(lParam)}; + if ((dat->iItem >= 0) && PtInRect(&dat->rcCheck, pt)) + PostMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetWindowLongPtr(hwnd, GWL_ID), LBN_MY_CHECK), (LPARAM)dat->iItem); + dat->iItem = -1; + } + break; + + case WM_CHAR: + if (wParam == ' ') { + int iItem = ListBox_GetCurSel(hwnd); + if (iItem >= 0) + PostMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetWindowLongPtr(hwnd, GWL_ID), LBN_MY_CHECK), (LPARAM)iItem); + return 0; + } + + if (wParam == 10 /* enter */) + return 0; + + break; + + case WM_GETDLGCODE: + if (wParam == VK_RETURN) + return DLGC_WANTMESSAGE; + break; + + case WM_MY_RENAME: + { + RECT rc; + struct TAccMgrData *parentDat = (struct TAccMgrData *)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + PROTOACCOUNT *pa = (PROTOACCOUNT *)ListBox_GetItemData(hwnd, ListBox_GetCurSel(hwnd)); + if (!pa || pa->bOldProto || pa->bDynDisabled) + return 0; + + ListBox_GetItemRect(hwnd, ListBox_GetCurSel(hwnd), &rc); + rc.left += 2*GetSystemMetrics(SM_CXSMICON) + 4; + rc.bottom = rc.top + max(GetSystemMetrics(SM_CXSMICON), parentDat->titleHeight) + 4 - 1; + ++rc.top; --rc.right; + + dat->hwndEdit = CreateWindow(_T("EDIT"), pa->tszAccountName, WS_CHILD|WS_BORDER|ES_AUTOHSCROLL, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, hwnd, NULL, hMirandaInst, NULL); + SetWindowLongPtr(dat->hwndEdit, GWLP_USERDATA, SetWindowLongPtr(dat->hwndEdit, GWLP_WNDPROC, (LONG_PTR)sttEditSubclassProc)); + SendMessage(dat->hwndEdit, WM_SETFONT, (WPARAM)parentDat->hfntTitle, 0); + SendMessage(dat->hwndEdit, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN|EC_USEFONTINFO, 0); + SendMessage(dat->hwndEdit, EM_SETSEL, 0, (LPARAM) (-1)); + ShowWindow(dat->hwndEdit, SW_SHOW); + } + SetFocus(dat->hwndEdit); + break; + + case WM_KEYDOWN: + switch (wParam) { + case VK_F2: + PostMessage(hwnd, WM_MY_RENAME, 0, 0); + return 0; + + case VK_INSERT: + sttClickButton(GetParent(hwnd), IDC_ADD); + return 0; + + case VK_DELETE: + sttClickButton(GetParent(hwnd), IDC_REMOVE); + return 0; + + case VK_RETURN: + if (GetAsyncKeyState(VK_CONTROL)) + sttClickButton(GetParent(hwnd), IDC_EDIT); + else + sttClickButton(GetParent(hwnd), IDOK); + return 0; + } + break; + } + + return CallWindowProc(dat->oldWndProc, hwnd, msg, wParam, lParam); +} + +static void sttSubclassAccList(HWND hwnd, BOOL subclass) +{ + if (subclass) { + struct TAccListData *dat = (struct TAccListData *)mir_alloc(sizeof(struct TAccListData)); + dat->iItem = -1; + dat->oldWndProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)dat); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)AccListWndProc); + } + else { + struct TAccListData *dat = (struct TAccListData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)dat->oldWndProc); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + mir_free(dat); +} } + +static void sttSelectItem(struct TAccMgrData *dat, HWND hwndList, int iItem) +{ + if ((dat->iSelected != iItem) && (dat->iSelected >= 0)) + ListBox_SetItemHeight(hwndList, dat->iSelected, dat->normalHeight); + + dat->iSelected = iItem; + ListBox_SetItemHeight(hwndList, dat->iSelected, dat->selectedHeight); + RedrawWindow(hwndList, NULL, NULL, RDW_INVALIDATE); +} + +static void sttUpdateAccountInfo(HWND hwndDlg, struct TAccMgrData *dat) +{ + HWND hwndList = GetDlgItem(hwndDlg, IDC_ACCLIST); + int curSel = ListBox_GetCurSel( hwndList ); + if ( curSel != LB_ERR ) { + HWND hwnd; + char svc[MAXMODULELABELLENGTH]; + + PROTOACCOUNT *pa = (PROTOACCOUNT *)ListBox_GetItemData(hwndList, curSel); + if ( pa ) { + EnableWindow( GetDlgItem( hwndDlg, IDC_UPGRADE ), pa->bOldProto || pa->bDynDisabled ); + EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT ), !pa->bOldProto && !pa->bDynDisabled ); + EnableWindow( GetDlgItem( hwndDlg, IDC_REMOVE ), TRUE ); + EnableWindow( GetDlgItem( hwndDlg, IDC_OPTIONS ), pa->ppro != 0 ); + + if ( dat->iSelected >= 0 ) { + PROTOACCOUNT *pa_old = (PROTOACCOUNT *)ListBox_GetItemData(hwndList, dat->iSelected); + if (pa_old && pa_old != pa && pa_old->hwndAccMgrUI) + ShowWindow(pa_old->hwndAccMgrUI, SW_HIDE); + } + + if ( pa->hwndAccMgrUI ) { + ShowWindow(GetDlgItem(hwndDlg, IDC_TXT_INFO), SW_HIDE); + ShowWindow(pa->hwndAccMgrUI, SW_SHOW); + } + else if ( !pa->ppro ) { + ShowWindow(GetDlgItem(hwndDlg, IDC_TXT_INFO), SW_SHOW); + SetWindowText(GetDlgItem(hwndDlg, IDC_TXT_INFO), TranslateT("Account is disabled. Please activate it to access options.")); + } + else { + mir_snprintf(svc, SIZEOF(svc), "%s%s", pa->szModuleName, PS_CREATEACCMGRUI); + hwnd = (HWND)CallService(svc, 0, (LPARAM)hwndDlg); + if (hwnd && (hwnd != (HWND)CALLSERVICE_NOTFOUND)) { + RECT rc; + + ShowWindow(GetDlgItem(hwndDlg, IDC_TXT_INFO), SW_HIDE); + + GetWindowRect(GetDlgItem(hwndDlg, IDC_TXT_INFO), &rc); + MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc, 2); + SetWindowPos(hwnd, hwndList, rc.left, rc.top, 0, 0, SWP_NOSIZE|SWP_SHOWWINDOW); + + pa->hwndAccMgrUI = hwnd; + } + else { + ShowWindow(GetDlgItem(hwndDlg, IDC_TXT_INFO), SW_SHOW); + SetWindowText(GetDlgItem(hwndDlg, IDC_TXT_INFO), TranslateT(legacyMsg)); + } } + return; + } } + + EnableWindow( GetDlgItem( hwndDlg, IDC_UPGRADE ), FALSE ); + EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT ), FALSE ); + EnableWindow( GetDlgItem( hwndDlg, IDC_REMOVE ), FALSE ); + EnableWindow( GetDlgItem( hwndDlg, IDC_OPTIONS ), FALSE ); + + ShowWindow(GetDlgItem(hwndDlg, IDC_TXT_INFO), SW_SHOW); + SetWindowText(GetDlgItem(hwndDlg, IDC_TXT_INFO), TranslateT(welcomeMsg)); +} + +INT_PTR CALLBACK AccMgrDlgProc(HWND hwndDlg,UINT message, WPARAM wParam, LPARAM lParam) +{ + struct TAccMgrData *dat = (struct TAccMgrData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(message) { + case WM_INITDIALOG: + { + TAccMgrData *dat = (TAccMgrData *)mir_alloc(sizeof(TAccMgrData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + TranslateDialogDefault(hwndDlg); + Window_SetIcon_IcoLib( hwndDlg, SKINICON_OTHER_ACCMGR ); + + Button_SetIcon_IcoLib(hwndDlg, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("New account")); + Button_SetIcon_IcoLib(hwndDlg, IDC_EDIT, SKINICON_OTHER_RENAME, LPGEN("Edit")); + Button_SetIcon_IcoLib(hwndDlg, IDC_REMOVE, SKINICON_OTHER_DELETE, LPGEN("Remove account")); + Button_SetIcon_IcoLib(hwndDlg, IDC_OPTIONS, SKINICON_OTHER_OPTIONS, LPGEN( "Configure...")); + Button_SetIcon_IcoLib(hwndDlg, IDC_UPGRADE, SKINICON_OTHER_ACCMGR, LPGEN("Upgrade account")); + + EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_REMOVE), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_OPTIONS), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_UPGRADE), FALSE); + + { + LOGFONT lf; + HDC hdc; + HFONT hfnt; + TEXTMETRIC tm; + + GetObject((HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0), sizeof(lf), &lf); + dat->hfntText = CreateFontIndirect(&lf); + + GetObject((HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0), sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + dat->hfntTitle = CreateFontIndirect(&lf); + + hdc = GetDC(hwndDlg); + hfnt = ( HFONT )SelectObject(hdc, dat->hfntTitle); + GetTextMetrics(hdc, &tm); + dat->titleHeight = tm.tmHeight; + SelectObject(hdc, dat->hfntText); + GetTextMetrics(hdc, &tm); + dat->textHeight = tm.tmHeight; + SelectObject(hdc, hfnt); + ReleaseDC(hwndDlg, hdc); + + dat->normalHeight = 4 + max(dat->titleHeight, GetSystemMetrics(SM_CYSMICON)); + dat->selectedHeight = dat->normalHeight + 4 + 2 * dat->textHeight; + + SendDlgItemMessage(hwndDlg, IDC_NAME, WM_SETFONT, (WPARAM)dat->hfntTitle, 0); + SendDlgItemMessage(hwndDlg, IDC_TXT_ACCOUNT, WM_SETFONT, (WPARAM)dat->hfntTitle, 0); + SendDlgItemMessage(hwndDlg, IDC_TXT_ADDITIONAL, WM_SETFONT, (WPARAM)dat->hfntTitle, 0); + } + + dat->iSelected = -1; + sttSubclassAccList(GetDlgItem(hwndDlg, IDC_ACCLIST), TRUE); + SendMessage( hwndDlg, WM_MY_REFRESH, 0, 0 ); + + Utils_RestoreWindowPositionNoSize(hwndDlg, NULL, "AccMgr", ""); + } + return TRUE; + + case WM_CTLCOLORSTATIC: + switch ( GetDlgCtrlID(( HWND )lParam )) { + case IDC_WHITERECT: + case IDC_NAME: + SetBkColor(( HDC )wParam, GetSysColor( COLOR_WINDOW )); + return ( INT_PTR )GetSysColorBrush( COLOR_WINDOW ); + } + break; + + case WM_MEASUREITEM: + { + LPMEASUREITEMSTRUCT lps = (LPMEASUREITEMSTRUCT)lParam; + PROTOACCOUNT *acc = (PROTOACCOUNT *)lps->itemData; + + if ((lps->CtlID != IDC_ACCLIST) || !acc) + break; + + lps->itemWidth = 10; + lps->itemHeight = dat->normalHeight; + } + return TRUE; + + case WM_DRAWITEM: + { + int tmp, size, length; + TCHAR *text; + HICON hIcon; + HBRUSH hbrBack; + SIZE sz; + + int cxIcon = GetSystemMetrics(SM_CXSMICON); + int cyIcon = GetSystemMetrics(SM_CYSMICON); + + LPDRAWITEMSTRUCT lps = (LPDRAWITEMSTRUCT)lParam; + PROTOACCOUNT *acc = (PROTOACCOUNT *)lps->itemData; + + if ((lps->CtlID != IDC_ACCLIST) || (lps->itemID == -1) || !acc) + break; + + SetBkMode(lps->hDC, TRANSPARENT); + if (lps->itemState & ODS_SELECTED) { + hbrBack = GetSysColorBrush(COLOR_HIGHLIGHT); + SetTextColor(lps->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + else { + hbrBack = GetSysColorBrush(COLOR_WINDOW); + SetTextColor(lps->hDC, GetSysColor(COLOR_WINDOWTEXT)); + } + FillRect(lps->hDC, &lps->rcItem, hbrBack); + + lps->rcItem.left += 2; + lps->rcItem.top += 2; + lps->rcItem.bottom -= 2; + + if ( acc->bOldProto ) + tmp = SKINICON_OTHER_ON; + else if ( acc->bDynDisabled ) + tmp = SKINICON_OTHER_OFF; + else + tmp = acc->bIsEnabled ? SKINICON_OTHER_TICK : SKINICON_OTHER_NOTICK; + + hIcon = LoadSkinnedIcon(tmp); + DrawIconEx(lps->hDC, lps->rcItem.left, lps->rcItem.top, hIcon, cxIcon, cyIcon, 0, hbrBack, DI_NORMAL); + IconLib_ReleaseIcon(hIcon, 0); + + lps->rcItem.left += cxIcon + 2; + + if (acc->ppro) { + hIcon = acc->ppro->GetIcon( PLI_PROTOCOL | PLIF_SMALL ); + DrawIconEx(lps->hDC, lps->rcItem.left, lps->rcItem.top, hIcon, cxIcon, cyIcon, 0, hbrBack, DI_NORMAL); + DestroyIcon(hIcon); + } + lps->rcItem.left += cxIcon + 2; + + length = SendDlgItemMessage(hwndDlg, IDC_ACCLIST, LB_GETTEXTLEN, lps->itemID, 0); + size = max(length+1, 256); + text = (TCHAR *)_alloca(sizeof(TCHAR) * size); + SendDlgItemMessage(hwndDlg, IDC_ACCLIST, LB_GETTEXT, lps->itemID, (LPARAM)text); + + SelectObject(lps->hDC, dat->hfntTitle); + tmp = lps->rcItem.bottom; + lps->rcItem.bottom = lps->rcItem.top + max(cyIcon, dat->titleHeight); + DrawText(lps->hDC, text, -1, &lps->rcItem, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_END_ELLIPSIS|DT_VCENTER); + lps->rcItem.bottom = tmp; + GetTextExtentPoint32(lps->hDC, text, length, &sz); + lps->rcItem.top += max(cxIcon, sz.cy) + 2; + + if (lps->itemID == (unsigned)dat->iSelected) { + SelectObject(lps->hDC, dat->hfntText); + mir_sntprintf(text, size, _T("%s: ") _T(TCHAR_STR_PARAM), TranslateT("Protocol"), acc->szProtoName); + length = lstrlen(text); + DrawText(lps->hDC, text, -1, &lps->rcItem, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_END_ELLIPSIS); + GetTextExtentPoint32(lps->hDC, text, length, &sz); + lps->rcItem.top += sz.cy + 2; + + if (acc->ppro && Proto_IsProtocolLoaded(acc->szProtoName)) { + char *szIdName; + TCHAR *tszIdName; + CONTACTINFO ci = { 0 }; + + szIdName = (char *)acc->ppro->GetCaps( PFLAG_UNIQUEIDTEXT, 0 ); + tszIdName = szIdName ? mir_a2t(szIdName) : mir_tstrdup(TranslateT("Account ID")); + + ci.cbSize = sizeof(ci); + ci.hContact = NULL; + ci.szProto = acc->szModuleName; + ci.dwFlag = CNF_UNIQUEID | CNF_TCHAR; + if ( !CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) { + switch (ci.type) { + case CNFT_ASCIIZ: + mir_sntprintf( text, size, _T("%s: %s"), tszIdName, ci.pszVal ); + mir_free(ci.pszVal); + break; + case CNFT_DWORD: + mir_sntprintf( text, size, _T("%s: %d"), tszIdName, ci.dVal ); + break; + } + } + else mir_sntprintf(text, size, _T("%s: %s"), tszIdName, TranslateT("")); + mir_free(tszIdName); + } + else mir_sntprintf(text, size, TranslateT("Protocol is not loaded.")); + + length = lstrlen(text); + DrawText(lps->hDC, text, -1, &lps->rcItem, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_END_ELLIPSIS); + GetTextExtentPoint32(lps->hDC, text, length, &sz); + lps->rcItem.top += sz.cy + 2; + } } + return TRUE; + + case WM_MY_REFRESH: + { + HWND hList = GetDlgItem(hwndDlg, IDC_ACCLIST); + int i = ListBox_GetCurSel(hList); + PROTOACCOUNT *acc = (i == LB_ERR) ? NULL : (PROTOACCOUNT *)ListBox_GetItemData(hList, i); + + dat->iSelected = -1; + SendMessage( hList, LB_RESETCONTENT, 0, 0 ); + for (i = 0; i < accounts.getCount(); ++i) { + int iItem = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)accounts[i]->tszAccountName); + SendMessage(hList, LB_SETITEMDATA, iItem, (LPARAM)accounts[i]); + + if (accounts[i] == acc) + ListBox_SetCurSel(hList, iItem); + } + + dat->iSelected = ListBox_GetCurSel(hList); // -1 if error => nothing selected in our case + if (dat->iSelected >= 0) + sttSelectItem(dat, hList, dat->iSelected); + else if (acc && acc->hwndAccMgrUI) + ShowWindow(acc->hwndAccMgrUI, SW_HIDE); + + sttUpdateAccountInfo(hwndDlg, dat); + } + break; + + case WM_CONTEXTMENU: + if ( GetWindowLongPtr(( HWND )wParam, GWL_ID ) == IDC_ACCLIST ) { + HWND hwndList = GetDlgItem( hwndDlg, IDC_ACCLIST ); + POINT pt = { (signed short)LOWORD( lParam ), (signed short)HIWORD( lParam ) }; + int iItem = ListBox_GetCurSel( hwndList ); + + if (( pt.x == -1 ) && ( pt.y == -1 )) { + if (iItem != LB_ERR) { + RECT rc; + ListBox_GetItemRect( hwndList, iItem, &rc ); + pt.x = rc.left + GetSystemMetrics(SM_CXSMICON) + 4; + pt.y = rc.top + 4 + max(GetSystemMetrics(SM_CXSMICON), dat->titleHeight); + ClientToScreen( hwndList, &pt ); + } + } + else { + // menu was activated with mouse => find item under cursor & set focus to our control. + POINT ptItem = pt; + ScreenToClient( hwndList, &ptItem ); + iItem = (short)LOWORD(SendMessage(hwndList, LB_ITEMFROMPOINT, 0, MAKELPARAM(ptItem.x, ptItem.y))); + if (iItem != LB_ERR) + { + ListBox_SetCurSel(hwndList, iItem); + sttUpdateAccountInfo(hwndDlg, dat); + sttSelectItem(dat, hwndList, iItem); + SetFocus(hwndList); + } + } + + if ( iItem != LB_ERR ) { + PROTOACCOUNT* pa = (PROTOACCOUNT*)ListBox_GetItemData(hwndList, iItem); + HMENU hMenu = CreatePopupMenu(); + if ( !pa->bOldProto && !pa->bDynDisabled ) + AppendMenu(hMenu, MF_STRING, 1, TranslateT("Rename")); + + AppendMenu(hMenu, MF_STRING, 3, TranslateT("Delete")); + + if ( Proto_IsAccountEnabled( pa )) + AppendMenu(hMenu, MF_STRING, 4, TranslateT("Configure")); + + if ( pa->bOldProto || pa->bDynDisabled ) + AppendMenu(hMenu, MF_STRING, 5, TranslateT("Upgrade")); + + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + AppendMenu(hMenu, MF_STRING, 0, TranslateT("Cancel")); + switch (TrackPopupMenu( hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL )) { + case 1: + PostMessage(hwndList, WM_MY_RENAME, 0, 0); + break; + + case 2: + sttClickButton(hwndDlg, IDC_EDIT); + break; + + case 3: + sttClickButton(hwndDlg, IDC_REMOVE); + break; + + case 4: + sttClickButton(hwndDlg, IDC_OPTIONS); + break; + + case 5: + sttClickButton(hwndDlg, IDC_UPGRADE); + break; + } + DestroyMenu( hMenu ); + } + } + break; + + case WM_COMMAND: + switch( LOWORD(wParam)) { + case IDC_ACCLIST: + { + HWND hwndList = GetDlgItem(hwndDlg, IDC_ACCLIST); + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + sttUpdateAccountInfo(hwndDlg, dat); + sttSelectItem(dat, hwndList, ListBox_GetCurSel(hwndList)); + SetFocus(hwndList); + break; + + case LBN_DBLCLK: + PostMessage(hwndList, WM_MY_RENAME, 0, 0); + break; + + case LBN_MY_CHECK: + { + PROTOACCOUNT *pa = (PROTOACCOUNT *)ListBox_GetItemData(hwndList, lParam); + if ( pa ) { + if ( pa->bOldProto || pa->bDynDisabled) + break; + + pa->bIsEnabled = !pa->bIsEnabled; + if ( pa->bIsEnabled ) { + if ( ActivateAccount( pa )) { + pa->ppro->OnEvent( EV_PROTO_ONLOAD, 0, 0 ); + if (!DBGetContactSettingByte(NULL, "CList", "MoveProtoMenus", FALSE)) + pa->ppro->OnEvent( EV_PROTO_ONMENU, 0, 0 ); + } + else pa->type = PROTOTYPE_DISPROTO; + } + else { + DWORD dwStatus = CallProtoService(pa->szModuleName, PS_GETSTATUS, 0, 0); + if (dwStatus >= ID_STATUS_ONLINE) { + if (IDCANCEL == ::MessageBox(hwndDlg, + TranslateT("Account is online. Disable account?"), + TranslateT("Accounts"), MB_OKCANCEL)) { + pa->bIsEnabled = 1; //stay enabled + } + } + + if ( !pa->bIsEnabled ) + DeactivateAccount( pa, true, false ); + } + + WriteDbAccounts(); + NotifyEventHooks( hAccListChanged, PRAC_CHECKED, ( LPARAM )pa ); + sttUpdateAccountInfo(hwndDlg, dat); + RedrawWindow(hwndList, NULL, NULL, RDW_INVALIDATE); + } } + break; + + case LBN_MY_RENAME: + { + int iItem = ListBox_GetCurSel(hwndList); + PROTOACCOUNT *pa = (PROTOACCOUNT *)ListBox_GetItemData(hwndList, iItem); + if ( pa ) { + mir_free(pa->tszAccountName); + pa->tszAccountName = (TCHAR*)lParam; + WriteDbAccounts(); + NotifyEventHooks(hAccListChanged, PRAC_CHANGED, (LPARAM)pa); + + ListBox_DeleteString(hwndList, iItem); + iItem = ListBox_AddString(hwndList, pa->tszAccountName); + ListBox_SetItemData(hwndList, iItem, (LPARAM)pa); + ListBox_SetCurSel(hwndList, iItem); + + sttSelectItem(dat, hwndList, iItem); + + RedrawWindow(hwndList, NULL, NULL, RDW_INVALIDATE); + } + else mir_free((TCHAR*)lParam); + } + break; + } } + break; + + case IDC_ADD: + { + AccFormDlgParam param = { PRAC_ADDED, NULL }; + if ( IDOK == DialogBoxParam( hMirandaInst, MAKEINTRESOURCE(IDD_ACCFORM), hwndDlg, AccFormDlgProc, (LPARAM)¶m )) + SendMessage( hwndDlg, WM_MY_REFRESH, 0, 0 ); + } + break; + + case IDC_EDIT: + { + HWND hList = GetDlgItem( hwndDlg, IDC_ACCLIST ); + int idx = ListBox_GetCurSel( hList ); + if ( idx != -1 ) + PostMessage(hList, WM_MY_RENAME, 0, 0); + } + break; + + case IDC_REMOVE: + { + HWND hList = GetDlgItem( hwndDlg, IDC_ACCLIST ); + int idx = ListBox_GetCurSel( hList ); + if ( idx != -1 ) { + PROTOACCOUNT* pa = ( PROTOACCOUNT* )ListBox_GetItemData( hList, idx ); + TCHAR buf[ 200 ]; + mir_sntprintf( buf, SIZEOF(buf), TranslateT( "Account %s is being deleted" ), pa->tszAccountName ); + if (pa->bOldProto) { + MessageBox( NULL, TranslateT( "You need to disable plugin to delete this account" ), buf, + MB_ICONERROR | MB_OK ); + break; + } + if ( IDYES == MessageBox( NULL, TranslateT( errMsg ), buf, MB_ICONSTOP | MB_DEFBUTTON2 | MB_YESNO )) { + // lock controls to avoid changes during remove process + ListBox_SetCurSel( hList, -1 ); + sttUpdateAccountInfo( hwndDlg, dat ); + EnableWindow( hList, FALSE ); + EnableWindow( GetDlgItem(hwndDlg, IDC_ADD), FALSE ); + + ListBox_SetItemData( hList, idx, 0 ); + + accounts.remove( pa ); + + CheckProtocolOrder(); + + WriteDbAccounts(); + NotifyEventHooks( hAccListChanged, PRAC_REMOVED, ( LPARAM )pa ); + + UnloadAccount( pa, true, true ); + SendMessage( hwndDlg, WM_MY_REFRESH, 0, 0 ); + + EnableWindow( hList, TRUE ); + EnableWindow( GetDlgItem(hwndDlg, IDC_ADD), TRUE ); + } } } + break; + + case IDC_OPTIONS: + { + HWND hList = GetDlgItem( hwndDlg, IDC_ACCLIST ); + int idx = ListBox_GetCurSel( hList ); + if ( idx != -1 ) { + PROTOACCOUNT* pa = ( PROTOACCOUNT* )ListBox_GetItemData( hList, idx ); + if ( pa->bOldProto ) { + OPENOPTIONSDIALOG ood; + ood.cbSize = sizeof(ood); + ood.pszGroup = "Network"; + ood.pszPage = pa->szModuleName; + ood.pszTab = NULL; + CallService( MS_OPT_OPENOPTIONS, 0, (LPARAM)&ood ); + } + else OpenAccountOptions( pa ); + } } + break; + + case IDC_UPGRADE: + { + HWND hList = GetDlgItem( hwndDlg, IDC_ACCLIST ); + int idx = ListBox_GetCurSel( hList ); + if ( idx != -1 ) { + AccFormDlgParam param = { PRAC_UPGRADED, ( PROTOACCOUNT* )ListBox_GetItemData( hList, idx ) }; + DialogBoxParam( hMirandaInst, MAKEINTRESOURCE(IDD_ACCFORM), hwndDlg, AccFormDlgProc, (LPARAM)¶m ); + } } + break; + + case IDC_LNK_NETWORK: + { + PSHNOTIFY pshn = {0}; + pshn.hdr.code = PSN_APPLY; + pshn.hdr.hwndFrom = hwndDlg; + SendMessage(hwndDlg, WM_NOTIFY, 0, (LPARAM)&pshn); + + OPENOPTIONSDIALOG ood = {0}; + ood.cbSize = sizeof(ood); + ood.pszPage = "Network"; + CallService( MS_OPT_OPENOPTIONS, 0, (LPARAM)&ood ); + break; + } + + case IDC_LNK_ADDONS: + CallService(MS_UTILS_OPENURL, TRUE, (LPARAM)"http://addons.miranda-im.org/"); + break; + + case IDOK: + { + PSHNOTIFY pshn = {0}; + pshn.hdr.code = PSN_APPLY; + pshn.hdr.hwndFrom = hwndDlg; + SendMessage(hwndDlg, WM_NOTIFY, 0, (LPARAM)&pshn); + DestroyWindow(hwndDlg); + break; + } + + case IDCANCEL: + { + PSHNOTIFY pshn = {0}; + pshn.hdr.code = PSN_RESET; + pshn.hdr.hwndFrom = hwndDlg; + SendMessage(hwndDlg, WM_NOTIFY, 0, (LPARAM)&pshn); + DestroyWindow(hwndDlg); + break; + } + } + case PSM_CHANGED: + { + HWND hList = GetDlgItem( hwndDlg, IDC_ACCLIST ); + int idx = ListBox_GetCurSel( hList ); + if ( idx != -1 ) { + PROTOACCOUNT *acc = (PROTOACCOUNT *)ListBox_GetItemData(hList, idx); + if (acc) + { + acc->bAccMgrUIChanged = TRUE; + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + } + break; + } + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + { + int i; + PSHNOTIFY pshn = {0}; + pshn.hdr.code = PSN_APPLY; + for (i = 0; i < accounts.getCount(); ++i) { + if ( accounts[i]->hwndAccMgrUI && accounts[i]->bAccMgrUIChanged ) { + pshn.hdr.hwndFrom = accounts[i]->hwndAccMgrUI; + SendMessage(accounts[i]->hwndAccMgrUI, WM_NOTIFY, 0, (LPARAM)&pshn); + accounts[i]->bAccMgrUIChanged = FALSE; + } } + return TRUE; + } + case PSN_RESET: + { + int i; + PSHNOTIFY pshn = {0}; + pshn.hdr.code = PSN_RESET; + for (i = 0; i < accounts.getCount(); ++i) { + if ( accounts[i]->hwndAccMgrUI && accounts[i]->bAccMgrUIChanged ) { + pshn.hdr.hwndFrom = accounts[i]->hwndAccMgrUI; + SendMessage(accounts[i]->hwndAccMgrUI, WM_NOTIFY, 0, (LPARAM)&pshn); + accounts[i]->bAccMgrUIChanged = FALSE; + } } + return TRUE; + } + } + break; + } + break; + case WM_DESTROY: + { + for (int i = 0; i < accounts.getCount(); ++i) { + accounts[i]->bAccMgrUIChanged = FALSE; + if (accounts[i]->hwndAccMgrUI) { + DestroyWindow(accounts[i]->hwndAccMgrUI); + accounts[i]->hwndAccMgrUI = NULL; + } } } + + Window_FreeIcon_IcoLib( hwndDlg ); + Button_FreeIcon_IcoLib( hwndDlg, IDC_ADD ); + Button_FreeIcon_IcoLib( hwndDlg, IDC_EDIT ); + Button_FreeIcon_IcoLib( hwndDlg, IDC_REMOVE ); + Button_FreeIcon_IcoLib( hwndDlg, IDC_OPTIONS ); + Button_FreeIcon_IcoLib( hwndDlg, IDC_UPGRADE ); + Utils_SaveWindowPosition( hwndDlg, NULL, "AccMgr", ""); + sttSubclassAccList(GetDlgItem(hwndDlg, IDC_ACCLIST), FALSE); + DeleteObject(dat->hfntTitle); + DeleteObject(dat->hfntText); + mir_free(dat); + hAccMgr = NULL; + break; + } + + return FALSE; +} + +static INT_PTR OptProtosShow(WPARAM, LPARAM) +{ + if ( !hAccMgr ) + hAccMgr = CreateDialogParam( hMirandaInst, MAKEINTRESOURCE(IDD_ACCMGR), NULL, AccMgrDlgProc, 0 ); + + ShowWindow( hAccMgr, SW_RESTORE ); + SetForegroundWindow( hAccMgr ); + SetActiveWindow( hAccMgr ); + return 0; +} + +int OptProtosLoaded(WPARAM, LPARAM) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_ACCMGR ); + mi.position = 1900000000; + mi.pszName = LPGEN("&Accounts..."); + mi.pszService = MS_PROTO_SHOWACCMGR; + CallService( MS_CLIST_ADDMAINMENUITEM, 0, ( LPARAM )&mi ); + return 0; +} + +static int OnAccListChanged( WPARAM eventCode, LPARAM lParam ) +{ + PROTOACCOUNT* pa = (PROTOACCOUNT*)lParam; + + switch( eventCode ) { + case PRAC_CHANGED: + if ( pa->ppro ) { + mir_free( pa->ppro->m_tszUserName ); + pa->ppro->m_tszUserName = mir_tstrdup( pa->tszAccountName ); + pa->ppro->OnEvent( EV_PROTO_ONRENAME, 0, lParam ); + } + } + + return 0; +} + +static int ShutdownAccMgr(WPARAM, LPARAM) +{ + if ( IsWindow( hAccMgr )) + DestroyWindow( hAccMgr ); + hAccMgr = NULL; + return 0; +} + +int LoadProtoOptions( void ) +{ + CreateServiceFunction( MS_PROTO_SHOWACCMGR, OptProtosShow ); + + HookEvent( ME_SYSTEM_MODULESLOADED, OptProtosLoaded ); + HookEvent( ME_PROTO_ACCLISTCHANGED, OnAccListChanged ); + HookEvent( ME_SYSTEM_PRESHUTDOWN, ShutdownAccMgr ); + return 0; +} diff --git a/src/modules/skin/hotkeys.cpp b/src/modules/skin/hotkeys.cpp new file mode 100644 index 0000000000..cd87fda1a7 --- /dev/null +++ b/src/modules/skin/hotkeys.cpp @@ -0,0 +1,1465 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include + +#define DBMODULENAME "SkinHotKeys" +#define WM_HOTKEYUNREGISTERED (WM_USER+721) + +typedef enum { HKT_GLOBAL, HKT_LOCAL, HKT_MANUAL, HKT_COUNT } THotkeyType; + +typedef struct _THotkeyItem THotkeyItem; +struct _THotkeyItem +{ + THotkeyType type; + char *pszService, *pszName; // pszName is valid _only_ for "root" hotkeys + TCHAR *ptszSection, *ptszDescription; + TCHAR *ptszSection_tr, *ptszDescription_tr; + LPARAM lParam; + WORD DefHotkey, Hotkey; + bool Enabled; + ATOM idHotkey; + + THotkeyItem *rootHotkey; + int nSubHotkeys; + bool allowSubHotkeys; + + bool OptChanged, OptDeleted, OptNew; + WORD OptHotkey; + THotkeyType OptType; + bool OptEnabled; + + bool UnregisterHotkey; // valid only during WM_APP message in options UI, used to remove unregistered hotkeys from options +}; + +static int sttCompareHotkeys(const THotkeyItem *p1, const THotkeyItem *p2) +{ + int res; + if ( res = lstrcmp( p1->ptszSection_tr, p2->ptszSection_tr )) + return res; + if ( res = lstrcmp( p1->ptszDescription_tr, p2->ptszDescription_tr )) + return res; + if (!p1->rootHotkey && p2->rootHotkey) + return -1; + if (p1->rootHotkey && !p2->rootHotkey) + return 1; + return 0; +} + +static LIST hotkeys( 10, sttCompareHotkeys ); + +static void sttFreeHotkey(THotkeyItem *item); + +static BOOL bModuleInitialized = FALSE; +static HWND g_hwndHotkeyHost = NULL; +static HWND g_hwndOptions = NULL; +static DWORD g_pid = 0; +static int g_hotkeyCount = 0; +static HANDLE hEvChanged = 0; + +static LRESULT CALLBACK sttHotkeyHostWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +static TCHAR *sttHokeyVkToName(WORD vkKey); +static void sttHotkeyEditCreate(HWND hwnd); +static void sttHotkeyEditDestroy(HWND hwnd); +static LRESULT CALLBACK sttHotkeyEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); +static void sttRegisterHotkeys(); +static void sttUnregisterHotkeys(); + +static INT_PTR svcHotkeySubclass(WPARAM wParam, LPARAM lParam); +static INT_PTR svcHotkeyUnsubclass(WPARAM wParam, LPARAM lParam); +static INT_PTR svcHotkeyRegister(WPARAM wParam, LPARAM lParam); +static INT_PTR svcHotkeyUnregister(WPARAM wParam, LPARAM lParam); +static INT_PTR svcHotkeyCheck(WPARAM wParam, LPARAM lParam); + +HHOOK hhkKeyboard = NULL; +static LRESULT CALLBACK sttKeyboardProc(int code, WPARAM wParam, LPARAM lParam); + +static void sttWordToModAndVk(WORD w, BYTE *mod, BYTE *vk) +{ + *mod = 0; + if (HIBYTE(w) & HOTKEYF_CONTROL) *mod |= MOD_CONTROL; + if (HIBYTE(w) & HOTKEYF_SHIFT) *mod |= MOD_SHIFT; + if (HIBYTE(w) & HOTKEYF_ALT) *mod |= MOD_ALT; + if (HIBYTE(w) & HOTKEYF_EXT) *mod |= MOD_WIN; + *vk = LOBYTE(w); +} + +static LRESULT CALLBACK sttHotkeyHostWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_HOTKEY ) { + int i; + for (i = 0; i < hotkeys.getCount(); i++) { + THotkeyItem *item = hotkeys[i]; + if (item->type != HKT_GLOBAL) continue; + if (!item->Enabled) continue; + if (item->pszService && (wParam == item->idHotkey)) { + CallService(item->pszService, 0, item->lParam); + break; + } } + + return FALSE; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +static LRESULT CALLBACK sttKeyboardProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (code == HC_ACTION && !(HIWORD(lParam) & KF_UP)) { + int i; + BYTE mod=0, vk=wParam; + + if ( vk ) { + if (GetAsyncKeyState(VK_CONTROL)) mod |= MOD_CONTROL; + if (GetAsyncKeyState(VK_MENU)) mod |= MOD_ALT; + if (GetAsyncKeyState(VK_SHIFT)) mod |= MOD_SHIFT; + if (GetAsyncKeyState(VK_LWIN) || GetAsyncKeyState(VK_RWIN)) mod |= MOD_WIN; + + for ( i = 0; i < hotkeys.getCount(); i++ ) { + THotkeyItem *item = hotkeys[i]; + BYTE hkMod, hkVk; + if (item->type != HKT_LOCAL) continue; + sttWordToModAndVk(item->Hotkey, &hkMod, &hkVk); + if (!hkVk) continue; + if (!item->Enabled) continue; + if (item->pszService && (vk == hkVk) && (mod == hkMod)) { + CallService(item->pszService, 0, item->lParam); + return TRUE; + } } } } + + return CallNextHookEx(hhkKeyboard, code, wParam, lParam); +} + +static INT_PTR svcHotkeySubclass(WPARAM wParam, LPARAM) +{ + sttHotkeyEditCreate((HWND)wParam); + return 0; +} + +static INT_PTR svcHotkeyUnsubclass(WPARAM wParam, LPARAM) +{ + sttHotkeyEditDestroy((HWND)wParam); + return 0; +} + +static INT_PTR svcHotkeyRegister(WPARAM wParam, LPARAM lParam) +{ + HOTKEYDESC *desc = (HOTKEYDESC *)lParam; + if ( desc->cbSize != sizeof(HOTKEYDESC) && desc->cbSize != HOTKEYDESC_SIZE_V1 ) + return 0; + + THotkeyItem *item = ( THotkeyItem* )mir_alloc(sizeof(THotkeyItem)); + #if defined( _UNICODE ) + DWORD dwFlags = ( desc->cbSize >= sizeof(HOTKEYDESC)) ? desc->dwFlags : 0; + if ( dwFlags & HKD_UNICODE ) { + item->ptszSection = mir_tstrdup( desc->ptszSection ); + item->ptszDescription = mir_tstrdup( desc->ptszDescription ); + } + else { + item->ptszSection = mir_a2u( desc->pszSection ); + item->ptszDescription = mir_a2u( desc->pszDescription ); + } + #else + item->ptszSection = mir_tstrdup( desc->pszSection ); + item->ptszDescription = mir_tstrdup( desc->pszDescription ); + #endif + item->ptszSection_tr = TranslateTS(item->ptszSection); + item->ptszDescription_tr = TranslateTS(item->ptszDescription); + item->allowSubHotkeys = TRUE; + item->rootHotkey = NULL; + item->nSubHotkeys = 0; + + if ( item->rootHotkey = hotkeys.find( item )) { + if (item->rootHotkey->allowSubHotkeys) { + char nameBuf[MAXMODULELABELLENGTH]; + mir_snprintf(nameBuf, SIZEOF(nameBuf), "%s$%d", item->rootHotkey->pszName, item->rootHotkey->nSubHotkeys); + item->pszName = mir_strdup(nameBuf); + item->Enabled = TRUE; + + item->rootHotkey->nSubHotkeys++; + } + else { + mir_free(item->ptszSection); + mir_free(item->ptszDescription); + mir_free(item); + return 0; + } + } + else { + item->pszName = mir_strdup(desc->pszName); + item->Enabled = !DBGetContactSettingByte(NULL, DBMODULENAME "Off", item->pszName, 0); + } + + item->pszService = desc->pszService ? mir_strdup(desc->pszService) : 0; + item->DefHotkey = desc->DefHotKey & ~HKF_MIRANDA_LOCAL; + item->Hotkey = DBGetContactSettingWord(NULL, DBMODULENAME, item->pszName, item->DefHotkey); + item->type = item->pszService ? + ( THotkeyType )DBGetContactSettingByte(NULL, DBMODULENAME "Types", item->pszName, + (desc->DefHotKey & HKF_MIRANDA_LOCAL) ? HKT_LOCAL : HKT_GLOBAL) : HKT_MANUAL; + item->lParam = desc->lParam; + + char buf[256]; + mir_snprintf(buf, SIZEOF(buf), "mir_hotkey_%d_%d", g_pid, g_hotkeyCount++); + item->idHotkey = GlobalAddAtomA(buf); + if (item->type == HKT_GLOBAL) { + if (item->Enabled) { + BYTE mod, vk; + sttWordToModAndVk(item->Hotkey, &mod, &vk); + if (vk) RegisterHotKey(g_hwndHotkeyHost, item->idHotkey, mod, vk); + } } + + hotkeys.insert( item ); + + if ( !item->rootHotkey ) { + /* try to load alternatives from db */ + int count, i; + mir_snprintf(buf, SIZEOF(buf), "%s$count", item->pszName); + count = (int)DBGetContactSettingDword(NULL, DBMODULENAME, buf, -1); + for (i = 0; i < count; i++) { + mir_snprintf(buf, SIZEOF(buf), "%s$%d", item->pszName, i); + if (!DBGetContactSettingWord(NULL, DBMODULENAME, buf, 0)) + continue; + + svcHotkeyRegister(wParam, lParam); + } + item->allowSubHotkeys = count < 0; + } + else { + mir_free( item->pszName ); + item->pszName = NULL; + } + + return item->idHotkey; +} + +static INT_PTR svcHotkeyUnregister(WPARAM, LPARAM lParam) +{ + int i; + char *pszName = (char *)lParam; + char pszNamePrefix[MAXMODULELABELLENGTH]; + size_t cbNamePrefix; + mir_snprintf(pszNamePrefix, SIZEOF(pszNamePrefix), "%s$", pszName); + cbNamePrefix = strlen(pszNamePrefix); + + for (i = 0; i < hotkeys.getCount(); ++i) + { + char *pszCurrentName = hotkeys[i]->rootHotkey ? + hotkeys[i]->rootHotkey->pszName : + hotkeys[i]->pszName; + if (!pszCurrentName) continue; + + hotkeys[i]->UnregisterHotkey = + !lstrcmpA(pszCurrentName, pszName) || + !strncmp(pszCurrentName, pszNamePrefix, cbNamePrefix); + } + + if (g_hwndOptions) + SendMessage(g_hwndOptions, WM_HOTKEYUNREGISTERED, 0, 0); + + for (i = 0; i < hotkeys.getCount(); ++i) + if (hotkeys[i]->UnregisterHotkey) { + sttFreeHotkey(hotkeys[i]); + List_Remove((SortedList *)&hotkeys, i); + --i; + } + + return 0; +} + +static INT_PTR svcHotkeyCheck(WPARAM wParam, LPARAM lParam) +{ + MSG *msg = (MSG *)wParam; + TCHAR *pszSection = mir_a2t((char *)lParam); + + if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) { + int i; + BYTE mod=0, vk=msg->wParam; + + if (vk) { + if (GetAsyncKeyState(VK_CONTROL)) mod |= MOD_CONTROL; + if (GetAsyncKeyState(VK_MENU)) mod |= MOD_ALT; + if (GetAsyncKeyState(VK_SHIFT)) mod |= MOD_SHIFT; + if (GetAsyncKeyState(VK_LWIN) || GetAsyncKeyState(VK_RWIN)) mod |= MOD_WIN; + + for ( i = 0; i < hotkeys.getCount(); i++ ) { + THotkeyItem *item = hotkeys[i]; + BYTE hkMod, hkVk; + if ((item->type != HKT_MANUAL) || lstrcmp(pszSection, item->ptszSection)) continue; + sttWordToModAndVk(item->Hotkey, &hkMod, &hkVk); + if (!hkVk) continue; + if (!item->Enabled) continue; + if ((vk == hkVk) && (mod == hkMod)) { + mir_free(pszSection); + return item->lParam; + } } } } + + mir_free(pszSection); + return 0; +} + +static void sttFreeHotkey(THotkeyItem *item) +{ + if ( item->type == HKT_GLOBAL && item->Enabled ) + UnregisterHotKey(g_hwndHotkeyHost, item->idHotkey); + GlobalDeleteAtom(item->idHotkey); + mir_free(item->pszName); + mir_free(item->pszService); + mir_free(item->ptszDescription); + mir_free(item->ptszSection); + mir_free(item); +} + +static void sttRegisterHotkeys() +{ + int i; + for ( i = 0; i < hotkeys.getCount(); i++ ) { + THotkeyItem *item = hotkeys[i]; + UnregisterHotKey(g_hwndHotkeyHost, item->idHotkey); + if (item->type != HKT_GLOBAL) continue; + if (item->Enabled) { + BYTE mod, vk; + sttWordToModAndVk(item->Hotkey, &mod, &vk); + if (vk) RegisterHotKey(g_hwndHotkeyHost, item->idHotkey, mod, vk); +} } } + +static void sttUnregisterHotkeys() +{ + int i; + for (i = 0; i < hotkeys.getCount(); i++) { + THotkeyItem *item = hotkeys[i]; + if ( item->type == HKT_GLOBAL && item->Enabled ) + UnregisterHotKey(g_hwndHotkeyHost, item->idHotkey); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Hotkey control +typedef struct +{ + WNDPROC oldWndProc; + BYTE shift; + BYTE key; +} + THotkeyBoxData; + +static TCHAR *sttHokeyVkToName(WORD vkKey) +{ + static TCHAR buf[256] = {0}; + DWORD code = MapVirtualKey(vkKey, 0) << 16; + + switch (vkKey) + { + case 0: + case VK_CONTROL: + case VK_SHIFT: + case VK_MENU: + case VK_LWIN: + case VK_RWIN: + case VK_PAUSE: + case VK_CANCEL: + case VK_NUMLOCK: + case VK_CAPITAL: + case VK_SCROLL: + return _T(""); + + case VK_DIVIDE: + case VK_INSERT: + case VK_HOME: + case VK_PRIOR: + case VK_DELETE: + case VK_END: + case VK_NEXT: + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + code |= (1UL << 24); + } + + GetKeyNameText(code, buf, 256); + return buf; +} + +void HotkeyToName(TCHAR *buf, int size, BYTE shift, BYTE key) +{ + mir_sntprintf(buf, size, _T("%s%s%s%s%s"), + (shift & HOTKEYF_CONTROL) ? _T("Ctrl + ") : _T(""), + (shift & HOTKEYF_ALT) ? _T("Alt + ") : _T(""), + (shift & HOTKEYF_SHIFT) ? _T("Shift + ") : _T(""), + (shift & HOTKEYF_EXT) ? _T("Win + ") : _T(""), + sttHokeyVkToName(key)); +} + +WORD GetHotkeyValue(INT_PTR idHotkey) +{ + for (int i = 0; i < hotkeys.getCount(); i++) + if (hotkeys[i]->idHotkey == idHotkey) + return hotkeys[i]->Enabled ? hotkeys[i]->Hotkey : 0; + + return 0; +} + +static LRESULT CALLBACK sttHotkeyEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + BOOL bKeyDown = FALSE; + if (!data) return 0; + + switch (msg) { + case HKM_GETHOTKEY: + return data->key ? MAKEWORD(data->key, data->shift) : 0; + + case HKM_SETHOTKEY: + { + TCHAR buf[256] = {0}; + data->key = (BYTE)LOWORD(wParam); + data->shift = (BYTE)HIWORD(wParam); + HotkeyToName(buf, SIZEOF(buf), data->shift, data->key); + SetWindowText(hwnd, buf); + return 0; + } + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS; + + case WM_KILLFOCUS: + break; + + case WM_CHAR: + case WM_SYSCHAR: + case WM_PASTE: + case WM_CONTEXTMENU: + return TRUE; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + bKeyDown = TRUE; + + case WM_KEYUP: + case WM_SYSKEYUP: + { + TCHAR buf[256] = {0}; + + BYTE shift = 0; + BYTE key = wParam; + TCHAR *name = sttHokeyVkToName(key); + if (!*name || !bKeyDown) key = 0; + + if (GetAsyncKeyState(VK_CONTROL)) shift |= HOTKEYF_CONTROL; + if (GetAsyncKeyState(VK_MENU)) shift |= HOTKEYF_ALT; + if (GetAsyncKeyState(VK_SHIFT)) shift |= HOTKEYF_SHIFT; + if (GetAsyncKeyState(VK_LWIN) || GetAsyncKeyState(VK_RWIN)) shift |= HOTKEYF_EXT; + + if (bKeyDown || !data->key) { + data->shift = shift; + data->key = key; + } + + HotkeyToName(buf, SIZEOF(buf), data->shift, data->key); + SetWindowText(hwnd, buf); + + if (bKeyDown && data->key) + SendMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(GetWindowLongPtr(hwnd, GWL_ID), 0), (LPARAM)hwnd); + return TRUE; + } + + case WM_DESTROY: + { + WNDPROC saveOldWndProc = data->oldWndProc; + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)data->oldWndProc); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + mir_free(data); + return CallWindowProc(saveOldWndProc, hwnd, msg, wParam, lParam); + } } + + return CallWindowProc(data->oldWndProc, hwnd, msg, wParam, lParam); +} + +static void sttHotkeyEditCreate(HWND hwnd) +{ + THotkeyBoxData *data = (THotkeyBoxData *)mir_alloc(sizeof(THotkeyBoxData)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (ULONG_PTR)data); + data->oldWndProc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG_PTR)sttHotkeyEditProc); +} + +static void sttHotkeyEditDestroy(HWND hwnd) +{ + THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG_PTR)data->oldWndProc); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + mir_free(data); +} + +/////////////////////////////////////////////////////////////////////////////// +// Options + +enum { COL_NAME, COL_TYPE, COL_KEY, COL_RESET, COL_ADDREMOVE }; + +static void sttOptionsSetupItem(HWND hwndList, int idx, THotkeyItem *item) +{ + TCHAR buf[256]; + LVITEM lvi = {0}; + lvi.iItem = idx; + + if ( !item->rootHotkey ) { + lvi.mask = LVIF_TEXT|LVIF_IMAGE; + lvi.iSubItem = COL_NAME; + lvi.pszText = item->ptszDescription_tr; + lvi.iImage = item->OptType; + ListView_SetItem(hwndList, &lvi); + + ListView_SetCheckState(hwndList, lvi.iItem, item->Enabled); + } + + lvi.mask = LVIF_TEXT; + lvi.iSubItem = COL_KEY; + HotkeyToName(buf, SIZEOF(buf), HIBYTE(item->OptHotkey), LOBYTE(item->OptHotkey)); + lvi.pszText = buf; + ListView_SetItem(hwndList, &lvi); + + if ( item->rootHotkey ) { + lvi.mask = LVIF_IMAGE; + lvi.iSubItem = COL_TYPE; + lvi.iImage = item->OptType; + ListView_SetItem(hwndList, &lvi); + } + + lvi.mask = LVIF_IMAGE; + lvi.iSubItem = COL_RESET; + lvi.iImage = (item->Hotkey != item->OptHotkey) ? 5 : -1; + ListView_SetItem(hwndList, &lvi); + + lvi.mask = LVIF_IMAGE|LVIF_TEXT; + lvi.iSubItem = COL_ADDREMOVE; + if (item->rootHotkey) { + lvi.iImage = 4; + lvi.pszText = TranslateT("Remove shortcut"); + } + else { + lvi.iImage = 3; + lvi.pszText = TranslateT("Add another shortcut"); + } + ListView_SetItem(hwndList, &lvi); +} + +static void sttOptionsDeleteHotkey(HWND hwndList, int idx, THotkeyItem *item) +{ + item->OptDeleted = TRUE; + ListView_DeleteItem(hwndList, idx); + if (item->rootHotkey) + item->rootHotkey->OptChanged = TRUE; +} + +static int CALLBACK sttOptionsSortList(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + TCHAR title1[256] = {0}, title2[256] = {0}; + THotkeyItem *item1 = NULL, *item2 = NULL; + LVITEM lvi = {0}; + int res; + + lvi.mask = LVIF_TEXT|LVIF_PARAM; + lvi.iItem = lParam1; + lvi.pszText = title1; + lvi.cchTextMax = SIZEOF(title1); + if (ListView_GetItem((HWND)lParamSort, &lvi)) + item1 = (THotkeyItem *)lvi.lParam; + + lvi.mask = LVIF_TEXT|LVIF_PARAM; + lvi.iItem = lParam2; + lvi.pszText = title2; + lvi.cchTextMax = SIZEOF(title2); + if (ListView_GetItem((HWND)lParamSort, &lvi)) + item2 = (THotkeyItem *)lvi.lParam; + + if (!item1 && !item2) + return lstrcmp(title1, title2); + + if (!item1) { + if (res = lstrcmp(title1, item2->ptszSection_tr)) + return res; + return -1; + } + + if (!item2) { + if (res = lstrcmp(item1->ptszSection_tr, title2)) + return res; + return 1; + } + return sttCompareHotkeys(item1, item2); +} + +static void sttOptionsAddHotkey(HWND hwndList, THotkeyItem *item) +{ + char buf[256]; + LVITEM lvi = {0}; + + THotkeyItem *newItem = (THotkeyItem *)mir_alloc(sizeof(THotkeyItem)); + newItem->pszName = NULL; + newItem->pszService = item->pszService ? mir_strdup(item->pszService) : NULL; + newItem->ptszSection = mir_tstrdup(item->ptszSection); + newItem->ptszDescription = mir_tstrdup(item->ptszDescription); + newItem->ptszSection_tr = item->ptszSection_tr; + newItem->ptszDescription_tr = item->ptszDescription_tr; + newItem->lParam = item->lParam; + mir_snprintf(buf, SIZEOF(buf), "mir_hotkey_%d_%d", g_pid, g_hotkeyCount++); + newItem->idHotkey = GlobalAddAtomA(buf); + newItem->rootHotkey = item; + newItem->Hotkey = newItem->DefHotkey = newItem->OptHotkey = 0; + newItem->type = newItem->OptType = item->OptType; + newItem->Enabled = newItem->OptEnabled = TRUE; + newItem->OptChanged = newItem->OptDeleted = FALSE; + newItem->OptNew = TRUE; + + hotkeys.insert( newItem ); + + SendMessage(hwndList, WM_SETREDRAW, FALSE, 0); + + lvi.mask |= LVIF_PARAM; + lvi.lParam = (LPARAM)newItem; + sttOptionsSetupItem(hwndList, ListView_InsertItem(hwndList, &lvi), newItem); + ListView_SortItemsEx(hwndList, sttOptionsSortList, (LPARAM)hwndList); + + SendMessage(hwndList, WM_SETREDRAW, TRUE, 0); + RedrawWindow(hwndList, NULL, NULL, RDW_INVALIDATE); + + item->OptChanged = TRUE; +} + +static void sttOptionsSetChanged(THotkeyItem *item) +{ + item->OptChanged = TRUE; + if (item->rootHotkey) + item->rootHotkey->OptChanged = TRUE; +} + +static void sttOptionsSaveItem(THotkeyItem *item) +{ + int i; + char buf[MAXMODULELABELLENGTH]; + + if (item->rootHotkey) return; + if (!item->OptChanged) return; + + item->Hotkey = item->OptHotkey; + item->type = item->OptType; + item->Enabled = item->OptEnabled; + + DBWriteContactSettingWord(NULL, DBMODULENAME, item->pszName, item->Hotkey); + DBWriteContactSettingByte(NULL, DBMODULENAME "Off", item->pszName, (BYTE)!item->Enabled); + if (item->type != HKT_MANUAL) + DBWriteContactSettingByte(NULL, DBMODULENAME "Types", item->pszName, (BYTE)item->type); + + item->nSubHotkeys = 0; + for (i = 0; i < hotkeys.getCount(); i++) { + THotkeyItem *subItem = hotkeys[i]; + if (subItem->rootHotkey == item) { + subItem->Hotkey = subItem->OptHotkey; + subItem->type = subItem->OptType; + + mir_snprintf(buf, SIZEOF(buf), "%s$%d", item->pszName, item->nSubHotkeys); + DBWriteContactSettingWord(NULL, DBMODULENAME, buf, subItem->Hotkey); + if (subItem->type != HKT_MANUAL) + DBWriteContactSettingByte(NULL, DBMODULENAME "Types", buf, (BYTE)subItem->type); + + ++item->nSubHotkeys; + } } + + mir_snprintf(buf, SIZEOF(buf), "%s$count", item->pszName); + DBWriteContactSettingDword(NULL, DBMODULENAME, buf, item->nSubHotkeys); +} + +static void sttBuildHotkeyList(HWND hwndList, TCHAR *section) +{ + int i, nItems=0; + ListView_DeleteAllItems(hwndList); + + for (i = 0; i < hotkeys.getCount(); i++) { + LVITEM lvi = {0}; + THotkeyItem *item = hotkeys[i]; + + if (item->OptDeleted) continue; + if (section && lstrcmp(section, item->ptszSection)) continue; + + if ( !section && (!i || lstrcmp(item->ptszSection, ((THotkeyItem *)hotkeys[i-1])->ptszSection ))) { + lvi.mask = LVIF_TEXT|LVIF_PARAM; + lvi.iItem = nItems++; + lvi.iSubItem = 0; + lvi.lParam = 0; + lvi.pszText = item->ptszSection_tr; + ListView_InsertItem(hwndList, &lvi); + ListView_SetCheckState(hwndList, lvi.iItem, TRUE); + + lvi.mask = LVIF_TEXT; + lvi.iSubItem = 1; + lvi.pszText = item->ptszSection; + ListView_SetItem(hwndList, &lvi); + + lvi.iSubItem = 0; + } + + lvi.mask = LVIF_PARAM; + if (!section) { + lvi.mask |= LVIF_INDENT; + lvi.iIndent = 1; + } + lvi.iItem = nItems++; + lvi.lParam = (LPARAM)item; + ListView_InsertItem(hwndList, &lvi); + sttOptionsSetupItem(hwndList, nItems-1, item); + } +} + +static void sttOptionsStartEdit(HWND hwndDlg, HWND hwndHotkey) +{ + LVITEM lvi; + THotkeyItem *item; + int iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED); + if (iItem < 0) return; + + lvi.mask = LVIF_PARAM; + lvi.iItem = iItem; + ListView_GetItem(hwndHotkey, &lvi); + + if (item = (THotkeyItem *)lvi.lParam) { + RECT rc; + ListView_GetSubItemRect(hwndHotkey, iItem, COL_KEY, LVIR_BOUNDS, &rc); + MapWindowPoints(hwndHotkey, hwndDlg, (LPPOINT)&rc, 2); + SendDlgItemMessage(hwndDlg, IDC_HOTKEY, HKM_SETHOTKEY, MAKELONG(LOBYTE(item->OptHotkey), HIBYTE(item->OptHotkey)), 0); + + SetWindowPos(hwndHotkey, HWND_BOTTOM, 0, 0, 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hwndDlg, IDC_HOTKEY), HWND_TOP, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, SWP_SHOWWINDOW); + RedrawWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), NULL, NULL, RDW_INVALIDATE); + + SetFocus(GetDlgItem(hwndDlg, IDC_HOTKEY)); + RedrawWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), NULL, NULL, RDW_INVALIDATE); + } +} + +static void sttOptionsDrawTextChunk(HDC hdc, TCHAR *text, RECT *rc) +{ + SIZE sz; + DrawText(hdc, text, lstrlen(text), rc, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER|DT_WORD_ELLIPSIS); + GetTextExtentPoint32(hdc, text, lstrlen(text), &sz); + rc->left += sz.cx; +} + +static INT_PTR CALLBACK sttOptionsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static BOOL initialized = FALSE; + static int colWidth = 0; + static WORD currentLanguage = 0; + + HWND hwndHotkey = GetDlgItem(hwndDlg, IDC_LV_HOTKEYS); + + switch (msg) { + case WM_INITDIALOG: + { + int i; + LVCOLUMN lvc; + RECT rc; + HIMAGELIST hIml; + + initialized = FALSE; + + TranslateDialogDefault(hwndDlg); + + sttHotkeyEditCreate(GetDlgItem(hwndDlg, IDC_HOTKEY)); + + hIml = ImageList_Create(16, 16, ILC_MASK + (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16), 3, 1); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_WINDOWS); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_MIRANDA); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_WINDOW); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_ADDCONTACT); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_DELETE); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_UNDO); + + // This is added to use for drawing operation only + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_GROUPOPEN); + ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_GROUPSHUT); + + ListView_SetImageList(hwndHotkey, hIml, LVSIL_SMALL); + + ListView_SetExtendedListViewStyle(hwndHotkey, LVS_EX_CHECKBOXES|LVS_EX_SUBITEMIMAGES|LVS_EX_FULLROWSELECT|LVS_EX_DOUBLEBUFFER|LVS_EX_INFOTIP); + + GetClientRect(hwndHotkey, &rc); + colWidth = rc.right - GetSystemMetrics(SM_CXHTHUMB) - 3*GetSystemMetrics(SM_CXSMICON) - 5; + + lvc.mask = LVCF_WIDTH; + lvc.cx = colWidth * 2 / 3; + ListView_InsertColumn(hwndHotkey, COL_NAME, &lvc); + lvc.cx = GetSystemMetrics(SM_CXSMICON); + ListView_InsertColumn(hwndHotkey, COL_TYPE, &lvc); + lvc.cx = colWidth / 3; + ListView_InsertColumn(hwndHotkey, COL_KEY, &lvc); + lvc.cx = GetSystemMetrics(SM_CXSMICON); + ListView_InsertColumn(hwndHotkey, COL_RESET, &lvc); + lvc.cx = GetSystemMetrics(SM_CXSMICON); + ListView_InsertColumn(hwndHotkey, COL_ADDREMOVE, &lvc); + + for (i = 0; i < hotkeys.getCount(); i++) { + THotkeyItem *item = hotkeys[i]; + + item->OptChanged = FALSE; + item->OptDeleted = item->OptNew = FALSE; + item->OptEnabled = item->Enabled; + item->OptHotkey = item->Hotkey; + item->OptType = item->type; + } + + currentLanguage = LOWORD(GetKeyboardLayout(0)); + sttBuildHotkeyList(hwndHotkey, NULL); + SetTimer(hwndDlg, 1024, 1000, NULL); + + initialized = TRUE; + + { /* load group states */ + int count = ListView_GetItemCount(hwndHotkey); + TCHAR buf[128]; + LVITEM lvi = {0}; + lvi.pszText = buf; + lvi.cchTextMax = SIZEOF(buf); + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + char *szSetting; + + lvi.mask = LVIF_PARAM; + lvi.iSubItem = 0; + ListView_GetItem(hwndHotkey, &lvi); + if (lvi.lParam) continue; + + lvi.mask = LVIF_TEXT; + lvi.iSubItem = 1; + ListView_GetItem(hwndHotkey, &lvi); + + szSetting = mir_t2a(lvi.pszText); + + ListView_SetCheckState(hwndHotkey, lvi.iItem, + DBGetContactSettingByte(NULL, DBMODULENAME "UI", szSetting, TRUE)); + + mir_free(szSetting); + } + } + + g_hwndOptions = hwndDlg; + + break; + } + + case WM_DESTROY: + { + int count = ListView_GetItemCount(hwndHotkey); + TCHAR buf[128]; + LVITEM lvi = {0}; + + g_hwndOptions = NULL; + + KillTimer(hwndDlg, 1024); + + lvi.pszText = buf; + lvi.cchTextMax = SIZEOF(buf); + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + char *szSetting; + + lvi.mask = LVIF_PARAM; + lvi.iSubItem = 0; + ListView_GetItem(hwndHotkey, &lvi); + if (lvi.lParam) continue; + + lvi.mask = LVIF_TEXT; + lvi.iSubItem = 1; + ListView_GetItem(hwndHotkey, &lvi); + + szSetting = mir_t2a(lvi.pszText); + + DBWriteContactSettingByte(NULL, DBMODULENAME "UI", szSetting, + (BYTE) ListView_GetCheckState(hwndHotkey, lvi.iItem)); + + mir_free(szSetting); + } + break; + } + + case WM_TIMER: + { + WORD newLanguage; + int count; + LVITEM lvi = {0}; + + if (!initialized) break; + + newLanguage = LOWORD(GetKeyboardLayout(0)); + if (newLanguage == currentLanguage) break; + + count = ListView_GetItemCount(hwndHotkey); + lvi.mask = LVIF_PARAM; + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + ListView_GetItem(hwndHotkey, &lvi); + if (!lvi.lParam) continue; + + sttOptionsSetupItem(hwndHotkey, lvi.iItem, (THotkeyItem *)lvi.lParam); + } + + currentLanguage = newLanguage; + break; + } + + case WM_HOTKEYUNREGISTERED: + { + int count; + LVITEM lvi = {0}; + + count = ListView_GetItemCount(hwndHotkey); + lvi.mask = LVIF_PARAM; + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + ListView_GetItem(hwndHotkey, &lvi); + if (!lvi.lParam) continue; + + if (((THotkeyItem *)lvi.lParam)->UnregisterHotkey) { + ListView_DeleteItem(hwndHotkey, lvi.iItem); + --lvi.iItem; + --count; + } + } + break; + } + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam; + RECT rc = lpdis->rcItem; + int prefix = 65; + int width = (lpdis->rcItem.right - lpdis->rcItem.left - prefix) / 3; + rc.left += 5; + + HIMAGELIST hIml = ListView_GetImageList(hwndHotkey, LVSIL_SMALL); + + if (lpdis->CtlID == IDC_CANVAS2) { + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Scope:"), &rc); + + rc.left = prefix + width * 0; + ImageList_Draw(hIml, 0, lpdis->hDC, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 20; + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("System"), &rc); + + rc.left = prefix + width * 1; + ImageList_Draw(hIml, 1, lpdis->hDC, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 20; + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Miranda"), &rc); + + rc.left = prefix + width * 2; + ImageList_Draw(hIml, 2, lpdis->hDC, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 20; + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Window"), &rc); + + return TRUE; + } + + if (lpdis->CtlID == IDC_CANVAS) { + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Actions:"), &rc); + rc.left += 10; + + rc.left = prefix + width * 0; + ImageList_Draw(hIml, 5, lpdis->hDC, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 20; + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Undo"), &rc); + + rc.left = prefix + width * 1; + ImageList_Draw(hIml, 3, lpdis->hDC, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 20; + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Add binding"), &rc); + + rc.left = prefix + width * 2; + ImageList_Draw(hIml, 4, lpdis->hDC, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 20; + sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Remove"), &rc); + + return TRUE; + } + + break; + } + + case WM_COMMAND: + if (( LOWORD( wParam ) == IDC_HOTKEY) && (( HIWORD( wParam ) == EN_KILLFOCUS) || (HIWORD(wParam) == 0 ))) { + LVITEM lvi; + THotkeyItem *item; + WORD wHotkey = (WORD)SendDlgItemMessage(hwndDlg, IDC_HOTKEY, HKM_GETHOTKEY, 0, 0); + + ShowWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), SW_HIDE); + SetFocus(hwndHotkey); + if ( !wHotkey || (wHotkey == VK_ESCAPE) || (HIWORD(wParam) != 0 )) + break; + + lvi.mask = LVIF_PARAM; + lvi.iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED); + if (lvi.iItem >= 0) { + ListView_GetItem(hwndHotkey, &lvi); + if (item = (THotkeyItem *)lvi.lParam) { + item->OptHotkey = wHotkey; + + sttOptionsSetupItem(hwndHotkey, lvi.iItem, item); + sttOptionsSetChanged(item); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } } } + break; + + case WM_CONTEXTMENU: + if (GetWindowLongPtr((HWND)wParam, GWL_ID) == IDC_LV_HOTKEYS) + { + HWND hwndList = (HWND)wParam; + POINT pt = { (signed short)LOWORD( lParam ), (signed short)HIWORD( lParam ) }; + LVITEM lvi = {0}; + THotkeyItem *item = NULL; + + lvi.iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED); + if (lvi.iItem < 0) return FALSE; + + lvi.mask = LVIF_PARAM; + ListView_GetItem(hwndList, &lvi); + if (!(item = (THotkeyItem *)lvi.lParam)) return FALSE; + + if (( pt.x == -1 ) && ( pt.y == -1 )) { + RECT rc; + ListView_GetItemRect(hwndList, lvi.iItem, &rc, LVIR_LABEL); + pt.x = rc.left; + pt.y = rc.bottom; + ClientToScreen(hwndList, &pt); + } + + { + enum { MI_CANCEL, MI_CHANGE, MI_SYSTEM, MI_LOCAL, MI_ADD, MI_REMOVE, MI_REVERT }; + + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_DEFAULT; + + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)MI_CHANGE, TranslateT("Modify")); + SetMenuItemInfo(hMenu, (UINT_PTR)MI_CHANGE, FALSE, &mii); + if (item->type != HKT_MANUAL) { + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + AppendMenu(hMenu, MF_STRING| + ((item->OptType == HKT_GLOBAL) ? MF_CHECKED : 0), + (UINT_PTR)MI_SYSTEM, TranslateT("System scope")); + AppendMenu(hMenu, MF_STRING| + ((item->OptType == HKT_LOCAL) ? MF_CHECKED : 0), + (UINT_PTR)MI_LOCAL, TranslateT("Miranda scope")); + } + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + if (!item->rootHotkey) + AppendMenu(hMenu, MF_STRING, (UINT_PTR)MI_ADD, TranslateT("Add binding")); + else + AppendMenu(hMenu, MF_STRING, (UINT_PTR)MI_REMOVE, TranslateT("Remove")); + if (item->Hotkey != item->OptHotkey) { + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + AppendMenu(hMenu, MF_STRING, (UINT_PTR)MI_REVERT, TranslateT("Undo")); + } + + switch (TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL)) { + case MI_CHANGE: + sttOptionsStartEdit(hwndDlg, hwndHotkey); + break; + case MI_SYSTEM: + item->OptType = HKT_GLOBAL; + sttOptionsSetupItem(hwndList, lvi.iItem, item); + sttOptionsSetChanged(item); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case MI_LOCAL: + item->OptType = HKT_LOCAL; + sttOptionsSetupItem(hwndList, lvi.iItem, item); + sttOptionsSetChanged(item); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case MI_ADD: + initialized = FALSE; + sttOptionsAddHotkey(hwndList, item); + initialized = FALSE; + break; + case MI_REMOVE: + sttOptionsDeleteHotkey(hwndList, lvi.iItem, item); + break; + case MI_REVERT: + item->OptHotkey = item->Hotkey; + sttOptionsSetupItem(hwndList, lvi.iItem, item); + break; + } + DestroyMenu( hMenu ); + } + + break; + } + break; + + case WM_NOTIFY: + { + LPNMHDR lpnmhdr = (LPNMHDR)lParam; + switch (lpnmhdr->idFrom) { + case 0: + { + int i; + + if (( lpnmhdr->code != PSN_APPLY) && (lpnmhdr->code != PSN_RESET )) + break; + + sttUnregisterHotkeys(); + + for (i = 0; i < hotkeys.getCount(); i++) { + THotkeyItem *item = hotkeys[i]; + if (item->OptNew && item->OptDeleted || + item->rootHotkey && !item->OptHotkey || + (lpnmhdr->code == PSN_APPLY) && item->OptDeleted || + (lpnmhdr->code == PSN_RESET) && item->OptNew) + { + sttFreeHotkey(item); + hotkeys.remove( i-- ); + } + } + + if (lpnmhdr->code == PSN_APPLY) { + LVITEM lvi = {0}; + int count = ListView_GetItemCount(hwndHotkey); + + for (i = 0; i < hotkeys.getCount(); i++) + sttOptionsSaveItem(hotkeys[i]); + + lvi.mask = LVIF_IMAGE; + lvi.iSubItem = COL_RESET; + lvi.iImage = -1; + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) + ListView_SetItem(hwndHotkey, &lvi); + } + + sttRegisterHotkeys(); + + NotifyEventHooks( hEvChanged, 0, 0 ); + break; + } + case IDC_LV_HOTKEYS: + switch (lpnmhdr->code) { + case NM_CLICK: + { + THotkeyItem *item = NULL; + LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lParam; + LVHITTESTINFO lvhti = {0}; + LVITEM lvi = {0}; + + lvi.mask = LVIF_PARAM|LVIF_IMAGE; + lvi.iItem = lpnmia->iItem; + ListView_GetItem(lpnmia->hdr.hwndFrom, &lvi); + item = (THotkeyItem *)lvi.lParam; + + lvhti.pt = lpnmia->ptAction; + lvhti.iItem = lpnmia->iItem; + lvhti.iSubItem = lpnmia->iSubItem; + ListView_HitTest(lpnmia->hdr.hwndFrom, &lvhti); + + if (item && + (!item->rootHotkey && (lpnmia->iSubItem == COL_NAME) && ((lvhti.flags & LVHT_ONITEM) == LVHT_ONITEMICON) || + item->rootHotkey && (lpnmia->iSubItem == COL_TYPE)) && + ((item->OptType == HKT_GLOBAL) || (item->OptType == HKT_LOCAL))) + { + item->OptType = (item->OptType == HKT_GLOBAL) ? HKT_LOCAL : HKT_GLOBAL; + sttOptionsSetupItem(lpnmia->hdr.hwndFrom, lpnmia->iItem, item); + sttOptionsSetChanged(item); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + else if (item && (lpnmia->iSubItem == COL_RESET)) { + item->OptHotkey = item->Hotkey; + sttOptionsSetupItem(lpnmia->hdr.hwndFrom, lpnmia->iItem, item); + } + else if (item && (lpnmia->iSubItem == COL_ADDREMOVE)) { + if (item->rootHotkey) + sttOptionsDeleteHotkey(lpnmia->hdr.hwndFrom, lpnmia->iItem, item); + else { + initialized = FALSE; + sttOptionsAddHotkey(lpnmia->hdr.hwndFrom, item); + initialized = TRUE; + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + break; + } + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN param = (LPNMLVKEYDOWN)lParam; + if ((param->wVKey == VK_SUBTRACT) || (param->wVKey == VK_LEFT) || + (param->wVKey == VK_ADD) || (param->wVKey == VK_RIGHT)) + { + LVITEM lvi = {0}; + lvi.mask = LVIF_PARAM; + lvi.iItem = ListView_GetNextItem(lpnmhdr->hwndFrom, -1, LVNI_SELECTED); + if (lvi.iItem < 0) break; + ListView_GetItem(lpnmhdr->hwndFrom, &lvi); + if (lvi.lParam) break; + + if ((param->wVKey == VK_ADD) || (param->wVKey == VK_RIGHT)) + { + ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, TRUE); + } else + // if ((param->wVKey == VK_SUBTRACT) || (param->wVKey == VK_LEFT)) + { + ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, FALSE); + } + } + else if (param->wVKey == VK_F2) + sttOptionsStartEdit(hwndDlg, hwndHotkey); + + break; + } + case LVN_ITEMACTIVATE: + { + LVITEM lvi = {0}; + lvi.mask = LVIF_PARAM; + lvi.iItem = ListView_GetNextItem(lpnmhdr->hwndFrom, -1, LVNI_SELECTED); + if (lvi.iItem < 0) break; + ListView_GetItem(lpnmhdr->hwndFrom, &lvi); + + if (lvi.lParam) + sttOptionsStartEdit(hwndDlg, hwndHotkey); + else + ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, !ListView_GetCheckState(lpnmhdr->hwndFrom, lvi.iItem)); + break; + } + case LVN_ITEMCHANGED: + { + LPNMLISTVIEW param = (LPNMLISTVIEW)lParam; + THotkeyItem *item = (THotkeyItem *)param->lParam; + if (!initialized || (param->uNewState>>12 == param->uOldState>>12)) + break; + + if (item && !item->rootHotkey) { + item->OptEnabled = ListView_GetCheckState(lpnmhdr->hwndFrom, param->iItem) ? 1 : 0; + sttOptionsSetChanged(item); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + else if (!item) { + TCHAR buf[256]; + LVITEM lvi = {0}; + lvi.mask = LVIF_TEXT; + lvi.iItem = param->iItem; + lvi.pszText = buf; + lvi.cchTextMax = SIZEOF(buf); + ListView_GetItem(lpnmhdr->hwndFrom, &lvi); + + if (param->uNewState>>12 == 1) { + int count = ListView_GetItemCount(lpnmhdr->hwndFrom); + LVITEM lvi = {0}; + lvi.mask = LVIF_PARAM; + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + THotkeyItem *item; + ListView_GetItem(lpnmhdr->hwndFrom, &lvi); + item = (THotkeyItem *)lvi.lParam; + if (!item) continue; + if (!lstrcmp(item->ptszSection_tr, buf)) { + ListView_DeleteItem(lpnmhdr->hwndFrom, lvi.iItem); + --lvi.iItem; + --count; + } } + } + else if (param->uNewState>>12 == 2) { + int i, nItems = ListView_GetItemCount(lpnmhdr->hwndFrom); + initialized = FALSE; + for (i = 0; i < hotkeys.getCount(); ++i) { + LVITEM lvi = {0}; + THotkeyItem *item = hotkeys[i]; + + if (item->OptDeleted) continue; + if (lstrcmp(buf, item->ptszSection_tr)) continue; + + lvi.mask = LVIF_PARAM|LVIF_INDENT; + lvi.iIndent = 1; + lvi.iItem = nItems++; + lvi.lParam = (LPARAM)item; + ListView_InsertItem(lpnmhdr->hwndFrom, &lvi); + sttOptionsSetupItem(lpnmhdr->hwndFrom, nItems-1, item); + } + ListView_SortItemsEx(lpnmhdr->hwndFrom, sttOptionsSortList, (LPARAM)lpnmhdr->hwndFrom); + initialized = TRUE; + } + } + break; + } + case NM_CUSTOMDRAW: + { + NMLVCUSTOMDRAW *param = (NMLVCUSTOMDRAW *) lParam; + switch (param->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + case CDDS_ITEMPREPAINT: + SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, CDRF_NOTIFYSUBITEMDRAW ); + return TRUE; + + case CDDS_SUBITEM|CDDS_ITEMPREPAINT: + { + THotkeyItem *item; + TCHAR buf[256]; + LVITEM lvi = {0}; + lvi.mask = LVIF_TEXT|LVIF_PARAM; + lvi.iItem = param->nmcd.dwItemSpec; + lvi.pszText = buf; + lvi.cchTextMax = SIZEOF(buf); + ListView_GetItem(lpnmhdr->hwndFrom, &lvi); + item = (THotkeyItem *)lvi.lParam; + + if (!item) { + RECT rc; + HFONT hfnt; + + ListView_GetSubItemRect(lpnmhdr->hwndFrom, param->nmcd.dwItemSpec, param->iSubItem, LVIR_BOUNDS, &rc); + FillRect(param->nmcd.hdc, &rc, GetSysColorBrush(param->nmcd.uItemState&CDIS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW)); + SetTextColor(param->nmcd.hdc, GetSysColor(param->nmcd.uItemState&CDIS_SELECTED ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); + + if (param->iSubItem == 0) { + rc.left += 3; + HIMAGELIST hIml = ListView_GetImageList(hwndHotkey, LVSIL_SMALL); + ImageList_Draw(hIml, + ListView_GetCheckState(hwndHotkey, lvi.iItem) ? 6 : 7, + param->nmcd.hdc, rc.left, (rc.top+rc.bottom-16)/2, ILD_TRANSPARENT); + rc.left += 18; + hfnt = ( HFONT )SelectObject(param->nmcd.hdc, (HFONT)SendMessage(GetParent(hwndDlg), PSM_GETBOLDFONT, 0, 0)); + DrawText(param->nmcd.hdc, buf, -1, &rc, DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER); + SelectObject(param->nmcd.hdc, hfnt); + } + + SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT ); + return TRUE; + } + + if (item->rootHotkey && (param->iSubItem == 0)) { + RECT rc; + ListView_GetSubItemRect(lpnmhdr->hwndFrom, param->nmcd.dwItemSpec, param->iSubItem, LVIR_BOUNDS, &rc); + FillRect(param->nmcd.hdc, &rc, GetSysColorBrush(COLOR_WINDOW)); + SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT ); + return TRUE; + } + break; + } } + break; + } + break; + } } + break; + } /* case WM_NOTIFY */ + } /* switch */ + + return FALSE; +} + +static int sttOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.hInstance = hMirandaInst; + odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR; + odp.position = -180000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_HOTKEYS); + odp.ptszTitle = TranslateT("Hotkeys"); + odp.ptszGroup = TranslateT("Customize"); + odp.pfnDlgProc = sttOptionsDlgProc; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + return 0; +} + +static int sttModulesLoaded(WPARAM, LPARAM) +{ + HookEvent(ME_OPT_INITIALISE, sttOptionsInit); + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Hotkey manager + +static const char* oldSettings[] = { "ShowHide", "ReadMsg", "NetSearch", "ShowOptions" }; +static const char* newSettings[] = { "ShowHide", "ReadMessage", "SearchInWeb", "ShowOptions" }; + +int LoadSkinHotkeys(void) +{ + WNDCLASSEX wcl = {0}; + + bModuleInitialized = TRUE; + + wcl.cbSize = sizeof(wcl); + wcl.lpfnWndProc = sttHotkeyHostWndProc; + wcl.style = 0; + wcl.cbClsExtra = 0; + wcl.cbWndExtra = 0; + wcl.hInstance = hMirandaInst; + wcl.hIcon = NULL; + wcl.hCursor = LoadCursor(NULL, IDC_ARROW); + wcl.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); + wcl.lpszMenuName = NULL; + wcl.lpszClassName = _T("MirandaHotkeyHostWnd"); + wcl.hIconSm = NULL; + RegisterClassEx(&wcl); + + g_pid = GetCurrentProcessId(); + + g_hwndHotkeyHost = CreateWindow(_T("MirandaHotkeyHostWnd"), NULL, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hMirandaInst, NULL); + SetWindowPos(g_hwndHotkeyHost, 0, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_DEFERERASE|SWP_NOSENDCHANGING|SWP_HIDEWINDOW); + + hhkKeyboard = SetWindowsHookEx(WH_KEYBOARD, sttKeyboardProc, NULL, GetCurrentThreadId()); + + hEvChanged = CreateHookableEvent(ME_HOTKEYS_CHANGED); + + CreateServiceFunction(MS_HOTKEY_SUBCLASS, svcHotkeySubclass); + CreateServiceFunction(MS_HOTKEY_UNSUBCLASS, svcHotkeyUnsubclass); + CreateServiceFunction(MS_HOTKEY_REGISTER, svcHotkeyRegister); + CreateServiceFunction(MS_HOTKEY_UNREGISTER, svcHotkeyUnregister); + CreateServiceFunction(MS_HOTKEY_CHECK, svcHotkeyCheck); + + HookEvent(ME_SYSTEM_MODULESLOADED, sttModulesLoaded); + { + WORD key; + int i; + for ( i = 0; i < SIZEOF( oldSettings ); i++ ) { + char szSetting[ 100 ]; + mir_snprintf( szSetting, SIZEOF(szSetting), "HK%s", oldSettings[i] ); + if (( key = DBGetContactSettingWord( NULL, "Clist", szSetting, 0 ))) { + DBDeleteContactSetting( NULL, "Clist", szSetting ); + DBWriteContactSettingWord( NULL, DBMODULENAME, newSettings[i], key ); + } + + mir_snprintf( szSetting, SIZEOF(szSetting), "HKEn%s", oldSettings[i] ); + if (( key = DBGetContactSettingByte( NULL, "Clist", szSetting, 0 ))) { + DBDeleteContactSetting( NULL, "Clist", szSetting ); + DBWriteContactSettingByte( NULL, DBMODULENAME "Off", newSettings[i], (BYTE)(key == 0) ); + } } } + + return 0; +} + +void UnloadSkinHotkeys(void) +{ + int i; + + if ( !bModuleInitialized ) return; + + DestroyHookableEvent(hEvChanged); + UnhookWindowsHookEx(hhkKeyboard); + sttUnregisterHotkeys(); + DestroyWindow(g_hwndHotkeyHost); + for ( i = 0; i < hotkeys.getCount(); i++ ) + sttFreeHotkey(hotkeys[i]); + hotkeys.destroy(); +} diff --git a/src/modules/skin/skinicons.cpp b/src/modules/skin/skinicons.cpp new file mode 100644 index 0000000000..9bfb0cf655 --- /dev/null +++ b/src/modules/skin/skinicons.cpp @@ -0,0 +1,489 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include + +struct StandardIconDescription +{ + int id; + const char* description; + int resource_id; + int pf2; + const char* section; +}; + +static const struct StandardIconDescription mainIcons[] = +{ + { SKINICON_OTHER_MIRANDA, LPGEN("Miranda IM"), -IDI_MIRANDA }, + { SKINICON_EVENT_MESSAGE, LPGEN("Message"), -IDI_RECVMSG }, + { SKINICON_EVENT_URL, LPGEN("URL"), -IDI_URL }, + { SKINICON_EVENT_FILE, LPGEN("File"), -IDI_FILE }, + { SKINICON_OTHER_USERONLINE, LPGEN("User Online"), -IDI_USERONLINE }, + { SKINICON_OTHER_GROUPOPEN, LPGEN("Group (Open)"), -IDI_GROUPOPEN }, + { SKINICON_OTHER_GROUPSHUT, LPGEN("Group (Closed)"), -IDI_GROUPSHUT }, + { SKINICON_OTHER_CONNECTING, LPGEN("Connecting"), -IDI_LOAD }, + { SKINICON_OTHER_ADDCONTACT, LPGEN("Add Contact"), -IDI_ADDCONTACT }, + { SKINICON_OTHER_USERDETAILS, LPGEN("User Details"), -IDI_USERDETAILS }, + { SKINICON_OTHER_HISTORY, LPGEN("History"), -IDI_HISTORY }, + { SKINICON_OTHER_DOWNARROW, LPGEN("Down Arrow"), -IDI_DOWNARROW }, + { SKINICON_OTHER_FINDUSER, LPGEN("Find User"), -IDI_FINDUSER }, + { SKINICON_OTHER_OPTIONS, LPGEN("Options"), -IDI_OPTIONS }, + { SKINICON_OTHER_SENDEMAIL, LPGEN("Send E-mail"), -IDI_SENDEMAIL }, + { SKINICON_OTHER_DELETE, LPGEN("Delete"), -IDI_DELETE }, + { SKINICON_OTHER_RENAME, LPGEN("Rename"), -IDI_RENAME }, + { SKINICON_OTHER_SMS, LPGEN("SMS"), -IDI_SMS }, + { SKINICON_OTHER_SEARCHALL, LPGEN("Search All"), -IDI_SEARCHALL }, + { SKINICON_OTHER_TICK, LPGEN("Tick"), -IDI_TICK }, + { SKINICON_OTHER_NOTICK, LPGEN("No Tick"), -IDI_NOTICK }, + { SKINICON_OTHER_HELP, LPGEN("Help"), -IDI_HELP }, + { SKINICON_OTHER_MIRANDAWEB, LPGEN("Miranda Website"), -IDI_MIRANDAWEBSITE }, + { SKINICON_OTHER_TYPING, LPGEN("Typing"), -IDI_TYPING }, + { SKINICON_OTHER_SMALLDOT, LPGEN("Small Dot"), -IDI_SMALLDOT }, + { SKINICON_OTHER_FILLEDBLOB, LPGEN("Filled Blob"), -IDI_FILLEDBLOB }, + { SKINICON_OTHER_EMPTYBLOB, LPGEN("Empty Blob"), -IDI_EMPTYBLOB }, + { SKINICON_OTHER_UNICODE, LPGEN("Unicode plugin"), -IDI_UNICODE }, + { SKINICON_OTHER_ANSI, LPGEN("ANSI plugin"), -IDI_ANSI }, + { SKINICON_OTHER_LOADED, LPGEN("Running plugin"), -IDI_LOADED }, + { SKINICON_OTHER_NOTLOADED, LPGEN("Unloaded plugin"), -IDI_NOTLOADED }, + { SKINICON_OTHER_UNDO, LPGEN("Undo"), -IDI_UNDO }, + { SKINICON_OTHER_WINDOW, LPGEN("Window"), -IDI_WINDOW }, + { SKINICON_OTHER_WINDOWS, LPGEN("System"), -IDI_WINDOWS }, + { SKINICON_OTHER_ACCMGR, LPGEN("Accounts"), -IDI_ACCMGR }, + { SKINICON_OTHER_SHOWHIDE, LPGEN("ShowHide"), -IDI_SHOWHIDE }, + { SKINICON_OTHER_EXIT, LPGEN("Exit"), -IDI_EXIT }, + { SKINICON_OTHER_MAINMENU, LPGEN("Main Menu"), -IDI_MIRANDA }, + { SKINICON_OTHER_STATUS, LPGEN("Status"), -IDI_ONLINE }, + { SKINICON_CHAT_JOIN, LPGEN("Join chat"), -IDI_JOINCHAT }, + { SKINICON_CHAT_LEAVE, LPGEN("Leave chat"), -IDI_LEAVECHAT }, + { SKINICON_OTHER_GROUP, LPGEN("Move to Group"), -IDI_MOVETOGROUP }, + { SKINICON_OTHER_ON, LPGEN("On"), -IDI_ON }, + { SKINICON_OTHER_OFF, LPGEN("Off"), -IDI_OFF }, + { SKINICON_OTHER_STATUS_LOCKED, LPGEN("Locked status"), -IDI_STATUS_LOCKED, 0, "Status Icons" }, +}; + +HANDLE hMainIcons[SIZEOF(mainIcons)]; + +static const struct StandardIconDescription statusIcons[] = +{ + { ID_STATUS_OFFLINE, LPGEN("Offline"), -IDI_OFFLINE, 0xFFFFFFFF }, + { ID_STATUS_ONLINE, LPGEN("Online"), -IDI_ONLINE, PF2_ONLINE }, + { ID_STATUS_AWAY, LPGEN("Away"), -IDI_AWAY, PF2_SHORTAWAY }, + { ID_STATUS_NA, LPGEN("NA"), -IDI_NA, PF2_LONGAWAY }, + { ID_STATUS_OCCUPIED, LPGEN("Occupied"), -IDI_OCCUPIED, PF2_LIGHTDND }, + { ID_STATUS_DND, LPGEN("DND"), -IDI_DND, PF2_HEAVYDND }, + { ID_STATUS_FREECHAT, LPGEN("Free for chat"), -IDI_FREE4CHAT, PF2_FREECHAT }, + { ID_STATUS_INVISIBLE, LPGEN("Invisible"), -IDI_INVISIBLE, PF2_INVISIBLE }, + { ID_STATUS_ONTHEPHONE, LPGEN("On the phone"), -IDI_ONTHEPHONE, PF2_ONTHEPHONE }, + { ID_STATUS_OUTTOLUNCH, LPGEN("Out to lunch"), -IDI_OUTTOLUNCH, PF2_OUTTOLUNCH } +}; + +HANDLE hStatusIcons[SIZEOF(statusIcons)]; + +const char* mainIconsFmt = "core_main_"; +const char* statusIconsFmt = "core_status_"; +const char* protoIconsFmt = LPGEN("%s Icons"); + +#define PROTOCOLS_PREFIX "Status Icons/" +#define GLOBAL_PROTO_NAME "*" + + + + +// load small icon (shared) it's not need to be destroyed + +static HICON LoadSmallIconShared(HINSTANCE hInstance, LPCTSTR lpIconName) +{ + int cx = GetSystemMetrics(SM_CXSMICON); + return ( HICON )LoadImage( hInstance, lpIconName, IMAGE_ICON,cx, cx, LR_DEFAULTCOLOR | LR_SHARED ); +} + +// load small icon (not shared) it IS NEED to be destroyed +static HICON LoadSmallIcon(HINSTANCE hInstance, LPCTSTR lpIconName) +{ + HICON hIcon = NULL; // icon handle + int index = -( int )lpIconName; + TCHAR filename[MAX_PATH] = {0}; + GetModuleFileName( hInstance, filename, MAX_PATH ); + ExtractIconEx( filename, index, NULL, &hIcon, 1 ); + return hIcon; +} + +// load small icon from hInstance +HICON LoadIconEx(HINSTANCE hInstance, LPCTSTR lpIconName, BOOL bShared) +{ + HICON hResIcon = bShared ? LoadSmallIcon(hInstance,lpIconName) : LoadSmallIconShared(hInstance,lpIconName); + if ( !hResIcon ) { //Icon not found in hInstance lets try to load it from core + HINSTANCE hCoreInstance=hMirandaInst; + if ( hCoreInstance != hInstance ) + hResIcon = bShared ? LoadSmallIcon(hCoreInstance,lpIconName) : LoadSmallIconShared(hCoreInstance,lpIconName); + } + return hResIcon; +} + +int ImageList_AddIcon_NotShared(HIMAGELIST hIml, LPCTSTR szResource) +{ + HICON hTempIcon=LoadIconEx( hMirandaInst, szResource, 0); + int res = ImageList_AddIcon(hIml, hTempIcon); + Safe_DestroyIcon(hTempIcon); + return res; +} + +int ImageList_AddIcon_IconLibLoaded(HIMAGELIST hIml, int iconId) +{ + HICON hIcon = LoadSkinIcon( iconId ); + int res = ImageList_AddIcon(hIml, hIcon); + IconLib_ReleaseIcon(hIcon,0); + return res; +} + +int ImageList_AddIcon_ProtoIconLibLoaded(HIMAGELIST hIml, const char* szProto, int iconId) +{ + HICON hIcon = LoadSkinProtoIcon( szProto, iconId ); + int res = ImageList_AddIcon(hIml, hIcon); + IconLib_ReleaseIcon(hIcon,0); + return res; +} + +int ImageList_ReplaceIcon_NotShared(HIMAGELIST hIml, int iIndex, HINSTANCE hInstance, LPCTSTR szResource) +{ + HICON hTempIcon = LoadIconEx(hInstance, szResource, 0); + int res = ImageList_ReplaceIcon(hIml, iIndex, hTempIcon); + Safe_DestroyIcon(hTempIcon); + return res; +} + +int ImageList_ReplaceIcon_IconLibLoaded(HIMAGELIST hIml, int nIndex, HICON hIcon) +{ + int res = ImageList_ReplaceIcon(hIml,nIndex, hIcon); + IconLib_ReleaseIcon(hIcon,0); + return res; +} + +void Window_SetIcon_IcoLib(HWND hWnd, int iconId) +{ + SendMessage(hWnd, WM_SETICON, ICON_BIG, ( LPARAM )LoadSkinIcon( iconId, true )); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, ( LPARAM )LoadSkinIcon( iconId )); +} + +void Window_SetProtoIcon_IcoLib(HWND hWnd, const char* szProto, int iconId) +{ + SendMessage(hWnd, WM_SETICON, ICON_BIG, ( LPARAM )LoadSkinProtoIcon( szProto, iconId, true )); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, ( LPARAM )LoadSkinProtoIcon( szProto, iconId )); +} + +void Window_FreeIcon_IcoLib(HWND hWnd) +{ + IconLib_ReleaseIcon(( HICON )SendMessage(hWnd, WM_SETICON, ICON_BIG, 0), NULL); + IconLib_ReleaseIcon(( HICON )SendMessage(hWnd, WM_SETICON, ICON_SMALL, 0), NULL); +} + +void Button_SetIcon_IcoLib(HWND hwndDlg, int itemId, int iconId, const char* tooltip) +{ + HWND hWnd = GetDlgItem( hwndDlg, itemId ); + SendMessage( hWnd, BM_SETIMAGE, IMAGE_ICON, ( LPARAM )LoadSkinIcon( iconId )); + SendMessage( hWnd, BUTTONSETASFLATBTN, 0, 0 ); + SendMessage( hWnd, BUTTONADDTOOLTIP, (WPARAM)tooltip, 0); +} + +void Button_FreeIcon_IcoLib(HWND hwndDlg, int itemId) +{ + HICON hIcon = ( HICON )SendDlgItemMessage(hwndDlg, itemId, BM_SETIMAGE, IMAGE_ICON, 0 ); + IconLib_ReleaseIcon(hIcon,0); +} + +// +// wParam = szProto +// lParam = status +// +HICON LoadSkinProtoIcon( const char* szProto, int status, bool big ) +{ + int i, statusIndx = -1; + char iconName[MAX_PATH]; + HICON hIcon; + DWORD caps2 = ( szProto == NULL ) ? ( DWORD )-1 : CallProtoService(szProto,PS_GETCAPS,PFLAGNUM_2,0); + + if ( status >= ID_STATUS_CONNECTING && status < ID_STATUS_CONNECTING+MAX_CONNECT_RETRIES ) { + mir_snprintf( iconName, SIZEOF(iconName), "%s%d", mainIconsFmt, 7 ); + return IcoLib_GetIcon( iconName, big ); + } + + for ( i = 0; i < SIZEOF(statusIcons); i++ ) { + if ( statusIcons[i].id == status ) { + statusIndx = i; + break; + } } + + if ( statusIndx == -1 ) + return NULL; + + if ( !szProto ) { + // Only return a protocol specific icon if there is only one protocol + // Otherwise return the global icon. This affects the global status menu mainly. + if ( accounts.getCount() == 1 ) { + HICON hIcon; + + // format: core_status_%proto%statusindex + mir_snprintf(iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, szProto, statusIndx); + + hIcon = IcoLib_GetIcon( iconName, big ); + if ( hIcon ) + return hIcon; + } + + // format: core_status_%s%d + mir_snprintf(iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, GLOBAL_PROTO_NAME, statusIndx); + return IcoLib_GetIcon( iconName, big ); + } + + // format: core_status_%s%d + mir_snprintf(iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, szProto, statusIndx); + hIcon = IcoLib_GetIcon( iconName, big ); + if ( hIcon == NULL && ( caps2 == 0 || ( caps2 & statusIcons[statusIndx].pf2 ))) { + PROTOACCOUNT* pa = Proto_GetAccount( szProto ); + if ( pa ) { + TCHAR szPath[MAX_PATH], szFullPath[MAX_PATH], *str; + SKINICONDESC sid = { 0 }; + + // + // Queried protocol isn't in list, adding + // + TCHAR tszSection[MAX_PATH]; + mir_sntprintf( tszSection, SIZEOF(tszSection), _T("%s%s"), _T(PROTOCOLS_PREFIX), pa->tszAccountName ); + sid.ptszSection = tszSection; + + sid.cbSize = sizeof(sid); + sid.flags = SIDF_ALL_TCHAR; + + GetModuleFileName( hMirandaInst, szPath, MAX_PATH ); + str = _tcsrchr( szPath, '\\' ); + if ( str != NULL ) + *str = 0; + mir_sntprintf( szFullPath, SIZEOF(szFullPath), _T("%s\\Icons\\proto_") _T(TCHAR_STR_PARAM) _T(".dll"), szPath, pa->szProtoName ); + if ( GetFileAttributes( szFullPath ) != INVALID_FILE_ATTRIBUTES ) + sid.ptszDefaultFile = szFullPath; + else { + mir_sntprintf( szFullPath, SIZEOF(szFullPath), _T("%s\\Plugins\\") _T(TCHAR_STR_PARAM) _T(".dll"), szPath, szProto ); + if (( int )ExtractIconEx( szFullPath, statusIcons[i].resource_id, NULL, &hIcon, 1 ) > 0 ) { + DestroyIcon( hIcon ); + sid.ptszDefaultFile = szFullPath; + hIcon = NULL; + } + + if ( sid.pszDefaultFile == NULL ) { + if ( str != NULL ) + *str = '\\'; + sid.ptszDefaultFile = szPath; + } } + + // + // Add global icons to list + // + { + int lowidx, highidx; + if ( caps2 == 0 ) + lowidx = statusIndx, highidx = statusIndx+1; + else + lowidx = 0, highidx = SIZEOF(statusIcons); + + for ( i = lowidx; i < highidx; i++ ) { + if ( caps2 == 0 || ( caps2 & statusIcons[i].pf2 )) { + // format: core_%s%d + mir_snprintf( iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, szProto, i ); + sid.pszName = iconName; + sid.ptszDescription = cli.pfnGetStatusModeDescription( statusIcons[i].id, 0 ); + sid.iDefaultIndex = statusIcons[i].resource_id; + IcoLib_AddNewIcon( &sid ); + } } } } + + // format: core_status_%s%d + mir_snprintf( iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, szProto, statusIndx ); + hIcon = IcoLib_GetIcon( iconName, big ); + if ( hIcon ) + return hIcon; + } + + if ( hIcon == NULL ) { + mir_snprintf( iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, GLOBAL_PROTO_NAME, statusIndx ); + hIcon = IcoLib_GetIcon( iconName, big ); + } + + return hIcon; +} + +HANDLE GetSkinIconHandle( int idx ) +{ + int i; + for ( i = 0; i < SIZEOF(mainIcons); i++ ) + if ( idx == mainIcons[i].id ) + return hMainIcons[i]; + + return NULL; +} + +HICON LoadSkinIcon( int idx, bool big ) +{ + // + // Query for global status icons + // + if ( idx < SKINICON_EVENT_MESSAGE ) { + if ( idx >= SIZEOF( statusIcons )) + return NULL; + + return LoadSkinProtoIcon( NULL, statusIcons[ idx ].id, big ); + } + + return IcoLib_GetIconByHandle( GetSkinIconHandle( idx ), big ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Initializes the icon skin module + +static void convertOneProtocol( char* moduleName, char* iconName ) +{ + char* pm = moduleName + strlen( moduleName ); + char* pi = iconName + strlen( iconName ); + DBVARIANT dbv; + int i; + + for ( i = 0; i < SIZEOF(statusIcons); i++ ) { + _itoa( statusIcons[i].id, pm, 10 ); + + if ( !DBGetContactSettingTString( NULL, "Icons", moduleName, &dbv ) ) { + _itoa( i, pi, 10 ); + + DBWriteContactSettingTString( NULL, "SkinIcons", iconName, dbv.ptszVal ); + DBFreeVariant( &dbv ); + + DBDeleteContactSetting( NULL, "Icons", moduleName ); +} } } + +static INT_PTR sttLoadSkinIcon( WPARAM wParam, LPARAM lParam ) +{ + switch (lParam) + { + case 0: + return (INT_PTR)LoadSkinIcon( wParam ); + + case 1: + return (INT_PTR)GetSkinIconHandle( wParam ); + + case 2: + return (INT_PTR)LoadSkinIcon( wParam, true ); + } + + return 0; +} + +static INT_PTR sttLoadSkinProtoIcon( WPARAM wParam, LPARAM lParam ) +{ + return (INT_PTR)LoadSkinProtoIcon( (char*)wParam, (int)lParam, false ); +} + +static INT_PTR sttLoadSkinProtoIconBig( WPARAM wParam, LPARAM lParam ) +{ + return (INT_PTR)LoadSkinProtoIcon( (char*)wParam, (int)lParam, true ); +} + +int LoadSkinIcons(void) +{ + SKINICONDESC sid; + int i, j = 0; + char iconName[MAX_PATH], moduleName[MAX_PATH]; + TCHAR modulePath[MAX_PATH]; + DBVARIANT dbv; + + // + // Perform "1st-time running import" + + for ( i = 0; i < SIZEOF(mainIcons); i++ ) { + _itoa( mainIcons[i].id, moduleName, 10 ); + if ( DBGetContactSettingTString( NULL, "Icons", moduleName, &dbv )) + break; + + mir_snprintf( iconName, SIZEOF(iconName), "%s%d", mainIconsFmt, i ); + + DBWriteContactSettingTString( NULL, "SkinIcons", iconName, dbv.ptszVal ); + DBFreeVariant( &dbv ); + + DBDeleteContactSetting( NULL, "Icons", moduleName ); + } + + for ( ;; ) { + // get the next protocol name + moduleName[0] = 'p'; + moduleName[1] = 0; + _itoa( j++, moduleName+1, 100 ); + if ( DBGetContactSettingTString( NULL, "Icons", moduleName, &dbv )) + break; + + DBDeleteContactSetting( NULL, "Icons", moduleName ); + + // make old skinicons' prefix + mir_snprintf( moduleName, SIZEOF(moduleName), TCHAR_STR_PARAM, dbv.ptszVal ); + // make IcoLib's prefix + mir_snprintf( iconName, SIZEOF(iconName), "%s" TCHAR_STR_PARAM, statusIconsFmt, dbv.ptszVal ); + + convertOneProtocol( moduleName, iconName ); + DBFreeVariant( &dbv ); + } + moduleName[0] = 0; + strcpy(iconName, "core_status_" GLOBAL_PROTO_NAME); + convertOneProtocol( moduleName, iconName ); + + CreateServiceFunction( MS_SKIN_LOADICON, sttLoadSkinIcon ); + CreateServiceFunction( MS_SKIN_LOADPROTOICON, sttLoadSkinProtoIcon ); + CreateServiceFunction( MS_SKIN_LOADPROTOICONBIG, sttLoadSkinProtoIconBig ); + + ZeroMemory( &sid, sizeof(sid) ); + sid.cbSize = sizeof(sid); + GetModuleFileName(NULL, modulePath, SIZEOF(modulePath)); + sid.ptszDefaultFile = modulePath; + sid.flags = SIDF_PATH_TCHAR; + sid.pszName = iconName; + + // + // Add main icons to list + // + for ( i = 0; i < SIZEOF(mainIcons); i++ ) { + mir_snprintf( iconName, SIZEOF(iconName), "%s%d", mainIconsFmt, i ); + sid.pszSection = mainIcons[i].section == NULL ? "Main Icons" : (char*)mainIcons[i].section; + sid.pszDescription = (char*)mainIcons[i].description; + sid.iDefaultIndex = mainIcons[i].resource_id; + hMainIcons[i] = IcoLib_AddNewIcon( &sid ); + } + // + // Add global icons to list + // + sid.pszSection = PROTOCOLS_PREFIX "Global"; + // + // Asterisk is used, to avoid conflict with proto-plugins + // 'coz users can't rename it to name with '*' + for ( i = 0; i < SIZEOF(statusIcons); i++ ) { + mir_snprintf( iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, GLOBAL_PROTO_NAME, i ); + sid.pszName = iconName; + sid.pszDescription = (char*)statusIcons[i].description; + sid.iDefaultIndex = statusIcons[i].resource_id; + hStatusIcons[i] = IcoLib_AddNewIcon( &sid ); + } + return 0; +} diff --git a/src/modules/skin/sounds.cpp b/src/modules/skin/sounds.cpp new file mode 100644 index 0000000000..734c319ac9 --- /dev/null +++ b/src/modules/skin/sounds.cpp @@ -0,0 +1,469 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +struct SoundItem { + char* name; + TCHAR* section; + TCHAR* description; + char* tempFile; +}; + +static BOOL bModuleInitialized = FALSE; +static struct SoundItem *soundList = NULL; +static int soundCount; +static HANDLE hPlayEvent = NULL; + +static INT_PTR ServiceSkinAddNewSound(WPARAM, LPARAM lParam) +{ + SKINSOUNDDESCEX *ssd = ( SKINSOUNDDESCEX* )lParam; + switch( ssd->cbSize ) { + case sizeof( SKINSOUNDDESCEX ): + case SKINSOUNDDESC_SIZE_V1: + case SKINSOUNDDESC_SIZE_V2: + break; + + default: + return 1; + } + + if ( ssd->pszName == NULL || ssd->pszDescription == NULL) + return 1; + + DBVARIANT dbv; + DWORD dwFlags = ( ssd->cbSize == sizeof(SKINSOUNDDESCEX)) ? ssd->dwFlags : 0; + + soundList=(struct SoundItem*)mir_realloc(soundList,sizeof(struct SoundItem)*(soundCount+1)); + SoundItem* item = &soundList[soundCount++]; + item->name = mir_strdup( ssd->pszName ); + item->tempFile = NULL; + #if defined( _UNICODE ) + TCHAR* ptszDefaultFile; + if ( dwFlags & SSDF_UNICODE ) { + item->description = mir_tstrdup( TranslateTS( ssd->ptszDescription )); + item->section = mir_tstrdup( TranslateTS( ssd->cbSize != SKINSOUNDDESC_SIZE_V1 && ssd->pszSection != NULL ? ssd->ptszSection : _T("Other"))); + ptszDefaultFile = mir_tstrdup( ssd->ptszDefaultFile ); + } + else { + item->description = LangPackPcharToTchar( ssd->pszDescription ); + item->section = LangPackPcharToTchar( ssd->cbSize != SKINSOUNDDESC_SIZE_V1 && ssd->pszSection != NULL ? ssd->pszSection : "Other" ); + ptszDefaultFile = mir_a2t( ssd->pszDefaultFile ); + } + + if ( ptszDefaultFile ) { + if ( DBGetContactSettingString(NULL, "SkinSounds", item->name, &dbv)) + DBWriteContactSettingTString(NULL, "SkinSounds", item->name, ptszDefaultFile); + else + DBFreeVariant(&dbv); + mir_free( ptszDefaultFile ); + } + #else + item->description = mir_tstrdup( TranslateTS( ssd->pszDescription )); + item->section = mir_tstrdup( TranslateTS( ssd->cbSize != SKINSOUNDDESC_SIZE_V1 && ssd->pszSection != NULL ? ssd->pszSection : "Other" )); + if ( ssd->pszDefaultFile ) { + if ( DBGetContactSettingString(NULL, "SkinSounds", item->name, &dbv)) + DBWriteContactSettingString(NULL, "SkinSounds", item->name, ssd->pszDefaultFile); + else + DBFreeVariant(&dbv); + } + #endif + return 0; +} + +static int SkinPlaySoundDefault(WPARAM wParam, LPARAM lParam) +{ + char * pszFile = (char *) lParam; + if ( pszFile && (DBGetContactSettingByte(NULL,"Skin","UseSound",0) || (int)wParam==1)) + PlaySoundA(pszFile, NULL, SND_ASYNC | SND_FILENAME | SND_NOWAIT); + + return 0; +} + +static INT_PTR ServiceSkinPlaySound(WPARAM, LPARAM lParam) +{ + char* pszSoundName = ( char* )lParam; + int j; + + for (j=0; jidFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_APPLY) + { + int i; + + DBWriteContactSettingByte(NULL, "Skin", "UseSound", (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ENABLESOUNDS)); + for ( i=0; i < soundCount; i++ ) + if ( soundList[i].tempFile ) + DBWriteContactSettingString(NULL,"SkinSounds",soundList[i].name,soundList[i].tempFile); + { + TVITEM tvi,tvic; + tvi.hItem = TreeView_GetRoot(hwndTree); + while ( tvi.hItem != NULL ) { + tvi.mask = TVIF_PARAM | TVIF_HANDLE | TVIF_STATE; + TreeView_GetItem(hwndTree, &tvi); + if ( tvi.lParam == -1 ) { + tvic.hItem = TreeView_GetChild(hwndTree, tvi.hItem); + while ( tvic.hItem != NULL ) { + tvic.mask = TVIF_PARAM | TVIF_HANDLE | TVIF_STATE; + TreeView_GetItem(hwndTree, &tvic); + if ((( tvic.state & TVIS_STATEIMAGEMASK ) >> 12 == 2 )) { + DBCONTACTGETSETTING cgs; + cgs.szModule = "SkinSoundsOff"; + cgs.szSetting = soundList[tvic.lParam].name; + CallService(MS_DB_CONTACT_DELETESETTING,(WPARAM)(HANDLE)NULL,(LPARAM)&cgs); + } + else DBWriteContactSettingByte(NULL,"SkinSoundsOff",soundList[tvic.lParam].name,1); + tvic.hItem=TreeView_GetNextSibling(hwndTree,tvic.hItem); + } } + + tvi.hItem=TreeView_GetNextSibling(hwndTree,tvi.hItem); + } } + return TRUE; + } + break; + case IDC_SOUNDTREE: + switch(((NMHDR*)lParam)->code) { + case TVN_SELCHANGEDA: + { + NMTREEVIEW *pnmtv = (NMTREEVIEW*)lParam; + TVITEM tvi = pnmtv->itemNew; + + if (tvi.lParam==-1) { + SendMessage(hwndDlg, DM_HIDEPANE, 0, 0); + } + else { + TCHAR buf[256]; + DBVARIANT dbv; + + mir_sntprintf(buf, SIZEOF(buf), _T("%s: %s"), soundList[tvi.lParam].section, soundList[tvi.lParam].description); + SetDlgItemText(hwndDlg, IDC_NAMEVAL, buf); + if (soundList[tvi.lParam].tempFile) + SetDlgItemTextA(hwndDlg, IDC_LOCATION, soundList[tvi.lParam].tempFile); + else if(!DBGetContactSettingString(NULL,"SkinSounds",soundList[tvi.lParam].name,&dbv)) { + SetDlgItemTextA(hwndDlg, IDC_LOCATION, dbv.pszVal); + DBFreeVariant(&dbv); + } + else SetDlgItemText(hwndDlg, IDC_LOCATION, TranslateT("")); + SendMessage(hwndDlg, DM_SHOWPANE, 0, 0); + } + } + break; + case TVN_KEYDOWN: + { + NMTVKEYDOWN* ptkd = (NMTVKEYDOWN*)lParam; + + if (ptkd&&ptkd->wVKey==VK_SPACE&&TreeView_GetSelection(ptkd->hdr.hwndFrom)) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + break; + case NM_CLICK: + { + TVHITTESTINFO hti; + hti.pt.x=(short)LOWORD(GetMessagePos()); + hti.pt.y=(short)HIWORD(GetMessagePos()); + ScreenToClient(((LPNMHDR)lParam)->hwndFrom,&hti.pt); + if(TreeView_HitTest(((LPNMHDR)lParam)->hwndFrom,&hti)) + if (hti.flags&TVHT_ONITEM) + if(hti.flags&TVHT_ONITEMSTATEICON) + if (TreeView_GetParent(hwndTree, hti.hItem)!=NULL) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } } + break; + } + break; + } + return FALSE; +} + +static UINT iconsExpertOnlyControls[]={IDC_IMPORT}; + +static int SkinOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.position = -200000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_SOUND); + odp.pszGroup = LPGEN("Customize"); + odp.pszTitle = LPGEN("Sounds"); + odp.pfnDlgProc = DlgProcSoundOpts; + odp.flags = ODPF_BOLDGROUPS; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} + +static int SkinSystemModulesLoaded(WPARAM, LPARAM) +{ + HookEvent(ME_OPT_INITIALISE,SkinOptionsInit); + return 0; +} + +int LoadSkinSounds(void) +{ + bModuleInitialized = TRUE; + + soundList=NULL; + soundCount=0; + CreateServiceFunction(MS_SKIN_ADDNEWSOUND,ServiceSkinAddNewSound); + CreateServiceFunction(MS_SKIN_PLAYSOUND,ServiceSkinPlaySound); + HookEvent(ME_SYSTEM_MODULESLOADED,SkinSystemModulesLoaded); + hPlayEvent=CreateHookableEvent(ME_SKIN_PLAYINGSOUND); + SetHookDefaultForHookableEvent(hPlayEvent, SkinPlaySoundDefault); + return 0; +} + +void UnloadSkinSounds(void) +{ + int i; + + if ( !bModuleInitialized ) return; + + for(i=0;ihDbEvent); + return 0; +} + +INT_PTR ShowAddedWindow(WPARAM, LPARAM lParam) +{ + CreateDialogParam(hMirandaInst, MAKEINTRESOURCE(IDD_ADDED), NULL, DlgProcAdded, + (LPARAM)((CLISTEVENT *)lParam)->hDbEvent); + return 0; +} + +static int AuthEventAdded(WPARAM, LPARAM lParam) +{ + TCHAR szUid[128] = _T(""); + TCHAR szTooltip[256]; + const HANDLE hDbEvent = (HANDLE)lParam; + + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + CallService(MS_DB_EVENT_GET,(WPARAM)lParam,(LPARAM)&dbei); + if (dbei.flags & (DBEF_SENT | DBEF_READ) || + (dbei.eventType != EVENTTYPE_AUTHREQUEST && dbei.eventType != EVENTTYPE_ADDED)) + return 0; + + dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, lParam, 0); + dbei.pBlob = (PBYTE)alloca(dbei.cbBlob); + CallService(MS_DB_EVENT_GET, lParam, (LPARAM)&dbei); + + HANDLE hContact = *(PHANDLE)(dbei.pBlob + sizeof(DWORD)); + + CLISTEVENT cli ={0}; + cli.cbSize = sizeof(cli); + cli.hContact = hContact; + cli.ptszTooltip = szTooltip; + cli.flags = CLEF_TCHAR; + cli.lParam = lParam; + cli.hDbEvent = hDbEvent; + + CONTACTINFO ci = {0}; + ci.cbSize = sizeof(ci); + ci.hContact = hContact; + ci.szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + ci.dwFlag = CNF_UNIQUEID | CNF_TCHAR; + if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci)) + { + switch (ci.type) + { + case CNFT_ASCIIZ: + mir_sntprintf(szUid, SIZEOF(szUid), _T("%s"), ci.pszVal); + mir_free(ci.pszVal); + break; + + case CNFT_DWORD: + mir_sntprintf(szUid, SIZEOF(szUid), _T("%u"), ci.dVal); + break; + } + } + + if (dbei.eventType == EVENTTYPE_AUTHREQUEST) + { + SkinPlaySound("AuthRequest"); + if (szUid[0]) + mir_sntprintf(szTooltip, SIZEOF(szTooltip), TranslateT("%s requests authorization"), szUid); + else + mir_sntprintf(szTooltip, SIZEOF(szTooltip), TranslateT("%u requests authorization"), *((PDWORD)dbei.pBlob)); + + cli.hIcon = LoadSkinIcon(SKINICON_OTHER_MIRANDA); + cli.pszService = MS_AUTH_SHOWREQUEST; + CallService(MS_CLIST_ADDEVENT, 0, (LPARAM)&cli); + } + else if (dbei.eventType == EVENTTYPE_ADDED) + { + SkinPlaySound("AddedEvent"); + if (szUid[0]) + mir_sntprintf(szTooltip, SIZEOF(szTooltip), TranslateT("%s added you to their contact list"), szUid); + else + mir_sntprintf(szTooltip, SIZEOF(szTooltip), TranslateT("%u added you to their contact list"), *((PDWORD)dbei.pBlob)); + + cli.hIcon = LoadSkinIcon(SKINICON_OTHER_MIRANDA); + cli.pszService = MS_AUTH_SHOWADDED; + CallService(MS_CLIST_ADDEVENT, 0, (LPARAM)&cli); + } + return 0; +} + +int LoadSendRecvAuthModule(void) +{ + CreateServiceFunction(MS_AUTH_SHOWREQUEST, ShowReqWindow); + CreateServiceFunction(MS_AUTH_SHOWADDED, ShowAddedWindow); + HookEvent(ME_DB_EVENT_ADDED, AuthEventAdded); + + SkinAddNewSoundEx("AuthRequest", "Alerts", "Authorization request"); + SkinAddNewSoundEx("AddedEvent", "Alerts", "Added event"); + + return 0; +} diff --git a/src/modules/srauth/authdialogs.cpp b/src/modules/srauth/authdialogs.cpp new file mode 100644 index 0000000000..07651da4ce --- /dev/null +++ b/src/modules/srauth/authdialogs.cpp @@ -0,0 +1,312 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +INT_PTR CALLBACK DlgProcAdded(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HANDLE hDbEvent = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) + { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwndDlg); + Button_SetIcon_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View User's Details")); + Button_SetIcon_IcoLib(hwndDlg, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add Contact Permanently to List")); + + hDbEvent = (HANDLE)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + + //blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)hDbEvent,0); + dbei.pBlob = (PBYTE)alloca(dbei.cbBlob); + CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei); + + DWORD uin = *(PDWORD)dbei.pBlob; + HANDLE hContact = *(HANDLE*)(dbei.pBlob + sizeof(DWORD)); + char* nick = (char *)(dbei.pBlob + sizeof(DWORD) + sizeof(HANDLE)); + char* first = nick + strlen(nick) + 1; + char* last = first + strlen(first) + 1; + char* email = last + strlen(last) + 1; + + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0)); + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_LARGE, 0)); + + PROTOACCOUNT* acc = Proto_GetAccount(dbei.szModule); + + TCHAR* lastT = dbei.flags & DBEF_UTF ? Utf8DecodeT(last) : mir_a2t(last); + TCHAR* firstT = dbei.flags & DBEF_UTF ? Utf8DecodeT(first) : mir_a2t(first); + TCHAR* nickT = dbei.flags & DBEF_UTF ? Utf8DecodeT(nick) : mir_a2t(nick); + TCHAR* emailT = dbei.flags & DBEF_UTF ? Utf8DecodeT(email) : mir_a2t(email); + + TCHAR name[128] = _T(""); + int off = 0; + if (firstT[0] && lastT[0]) + off = mir_sntprintf(name, SIZEOF(name), _T("%s %s"), firstT, lastT); + else if (firstT[0]) + off = mir_sntprintf(name, SIZEOF(name), _T("%s"), firstT); + else if (lastT[0]) + off = mir_sntprintf(name, SIZEOF(name), _T("%s"), lastT); + if (nickT[0]) + { + if (off) + mir_sntprintf(name + off, SIZEOF(name) - off, _T(" (%s)"), nickT); + else + mir_sntprintf(name, SIZEOF(name), _T("%s"), nickT); + } + if (!name[0]) + _tcscpy(name, TranslateT("")); + + TCHAR hdr[256]; + if (uin && emailT[0]) + mir_sntprintf(hdr, SIZEOF(hdr), TranslateT("%s added you to the contact list\n%u (%s) on %s"), name, uin, emailT, acc->tszAccountName); + else if (uin) + mir_sntprintf(hdr, SIZEOF(hdr), TranslateT("%s added you to the contact list\n%u on %s"), name, uin, acc->tszAccountName); + else + mir_sntprintf(hdr, SIZEOF(hdr), TranslateT("%s added you to the contact list\n%s on %s"), name, emailT[0] ? emailT : TranslateT("(Unknown)"), acc->tszAccountName); + + SetDlgItemText(hwndDlg, IDC_HEADERBAR, hdr); + + mir_free(lastT); + mir_free(firstT); + mir_free(nickT); + mir_free(emailT); + + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_DETAILS), GWLP_USERDATA, (LONG_PTR)hContact); + + if (hContact == INVALID_HANDLE_VALUE || !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + ShowWindow(GetDlgItem(hwndDlg, IDC_ADD), FALSE); + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_ADD: + { + ADDCONTACTSTRUCT acs = {0}; + acs.handle = hDbEvent; + acs.handleType = HANDLE_EVENT; + acs.szProto = ""; + CallService(MS_ADDCONTACT_SHOW, (WPARAM)hwndDlg, (LPARAM)&acs); + + HANDLE hContact = (HANDLE)GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_DETAILS), GWLP_USERDATA); + if ((hContact == INVALID_HANDLE_VALUE) || !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + ShowWindow(GetDlgItem(hwndDlg,IDC_ADD),FALSE); + break; + } + case IDC_DETAILS: + { + HANDLE hContact = (HANDLE)GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_DETAILS), GWLP_USERDATA); + CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)hContact, 0); + break; + } + + case IDOK: + { + ADDCONTACTSTRUCT acs = {0}; + acs.handle = hDbEvent; + acs.handleType = HANDLE_EVENT; + acs.szProto = ""; + CallService(MS_ADDCONTACT_SHOW, (WPARAM)hwndDlg, (LPARAM)&acs); + } + //fall through + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + } + break; + + case WM_DESTROY: + Button_FreeIcon_IcoLib(hwndDlg,IDC_ADD); + Button_FreeIcon_IcoLib(hwndDlg,IDC_DETAILS); + DestroyIcon((HICON)SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 0)); + DestroyIcon((HICON)SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, 0)); + break; + } + return FALSE; +} + +INT_PTR CALLBACK DlgProcAuthReq(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HANDLE hDbEvent = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + Button_SetIcon_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View User Details")); + Button_SetIcon_IcoLib(hwndDlg, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add Contact Permanently to List")); + + { + DBEVENTINFO dbei = {0}; + DWORD uin; + char *nick,*first,*last,*email,*reason; + HANDLE hContact; + + hDbEvent = (HANDLE)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + + //blob is: uin(DWORD),hcontact(HANDLE),nick(ASCIIZ),first(ASCIIZ),last(ASCIIZ),email(ASCIIZ),reason(ASCIIZ) + dbei.cbSize = sizeof(dbei); + dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM)hDbEvent, 0); + dbei.pBlob = (PBYTE)alloca(dbei.cbBlob); + CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei); + + uin = *(PDWORD)dbei.pBlob; + hContact = *(HANDLE*)(dbei.pBlob + sizeof(DWORD)); + nick = (char *)(dbei.pBlob + sizeof(DWORD) + sizeof(HANDLE)); + first = nick + strlen(nick) + 1; + last = first + strlen(first) + 1; + email = last + strlen(last) + 1; + reason = email + strlen(email) + 1; + + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0)); + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_LARGE, 0)); + + PROTOACCOUNT* acc = Proto_GetAccount(dbei.szModule); + + TCHAR* lastT = dbei.flags & DBEF_UTF ? Utf8DecodeT(last) : mir_a2t(last); + TCHAR* firstT = dbei.flags & DBEF_UTF ? Utf8DecodeT(first) : mir_a2t(first); + TCHAR* nickT = dbei.flags & DBEF_UTF ? Utf8DecodeT(nick) : mir_a2t(nick); + TCHAR* emailT = dbei.flags & DBEF_UTF ? Utf8DecodeT(email) : mir_a2t(email); + TCHAR* reasonT = dbei.flags & DBEF_UTF ? Utf8DecodeT(reason) : mir_a2t(reason); + + TCHAR name[128] =_T(""); + int off = 0; + if (firstT[0] && lastT[0]) + off = mir_sntprintf(name, SIZEOF(name), _T("%s %s"), firstT, lastT); + else if (firstT[0]) + off = mir_sntprintf(name, SIZEOF(name), _T("%s"), firstT); + else if (lastT[0]) + off = mir_sntprintf(name, SIZEOF(name), _T("%s"), lastT); + if (nickT[0]) + { + if (off) + mir_sntprintf(name + off, SIZEOF(name) - off, _T(" (%s)"), nickT); + else + mir_sntprintf(name, SIZEOF(name), _T("%s"), nickT); + } + if (!name[0]) + _tcscpy(name, TranslateT("")); + + TCHAR hdr[256]; + if (uin && emailT[0]) + mir_sntprintf(hdr, SIZEOF(hdr), TranslateT("%s requested authorization\n%u (%s) on %s"), name, uin, emailT, acc->tszAccountName); + else if (uin) + mir_sntprintf(hdr, SIZEOF(hdr), TranslateT("%s requested authorization\n%u on %s"), name, uin, acc->tszAccountName); + else + mir_sntprintf(hdr, SIZEOF(hdr), TranslateT("%s requested authorization\n%s on %s"), name, emailT[0] ? emailT : TranslateT("(Unknown)"), acc->tszAccountName); + + SetDlgItemText(hwndDlg, IDC_HEADERBAR, hdr); + SetDlgItemText(hwndDlg, IDC_REASON, reasonT); + + mir_free(lastT); + mir_free(firstT); + mir_free(nickT); + mir_free(emailT); + mir_free(reasonT); + + if (hContact == INVALID_HANDLE_VALUE || !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + ShowWindow(GetDlgItem(hwndDlg, IDC_ADD), FALSE); + + SendDlgItemMessage(hwndDlg, IDC_DENYREASON, EM_LIMITTEXT, 255, 0); + if (CallProtoService(dbei.szModule, PS_GETCAPS,PFLAGNUM_4, 0) & PF4_NOAUTHDENYREASON) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_DENYREASON), FALSE); + SetDlgItemText(hwndDlg, IDC_DENYREASON, TranslateT("Feature is not supported by protocol")); + } + if (!DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_ADDCHECK), FALSE); + CheckDlgButton(hwndDlg, IDC_ADDCHECK, BST_UNCHECKED); + } + else + CheckDlgButton(hwndDlg, IDC_ADDCHECK, BST_CHECKED); + + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_DETAILS), GWLP_USERDATA, (LONG_PTR)hContact); + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_DETAILS: + CallService(MS_USERINFO_SHOWDIALOG, GetWindowLongPtr((HWND)lParam, GWLP_USERDATA), 0); + break; + + case IDC_DECIDELATER: + DestroyWindow(hwndDlg); + break; + + case IDOK: + { + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei); + CallProtoService(dbei.szModule, PS_AUTHALLOW, (WPARAM)hDbEvent,0); + + if (IsDlgButtonChecked(hwndDlg, IDC_ADDCHECK)) + { + ADDCONTACTSTRUCT acs = {0}; + acs.handle = hDbEvent; + acs.handleType = HANDLE_EVENT; + acs.szProto = ""; + CallService(MS_ADDCONTACT_SHOW, (WPARAM)hwndDlg, (LPARAM)&acs); + } + } + DestroyWindow(hwndDlg); + break; + + case IDCANCEL: + { + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei); + + if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_DENYREASON))) + { + TCHAR szReason[256]; + GetDlgItemText(hwndDlg, IDC_DENYREASON, szReason, SIZEOF(szReason)); + CallProtoService(dbei.szModule, PS_AUTHDENYT, (WPARAM)hDbEvent, (LPARAM)szReason); + } + else + CallProtoService(dbei.szModule, PS_AUTHDENYT, (WPARAM)hDbEvent, 0); + } + DestroyWindow(hwndDlg); + break;; + } + break; + + case WM_DESTROY: + Button_FreeIcon_IcoLib(hwndDlg,IDC_ADD); + Button_FreeIcon_IcoLib(hwndDlg,IDC_DETAILS); + DestroyIcon((HICON)SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 0)); + DestroyIcon((HICON)SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, 0)); + break; + } + return FALSE; +} diff --git a/src/modules/srawaymsg/awaymsg.cpp b/src/modules/srawaymsg/awaymsg.cpp new file mode 100644 index 0000000000..39383e5367 --- /dev/null +++ b/src/modules/srawaymsg/awaymsg.cpp @@ -0,0 +1,194 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +int LoadAwayMessageSending(void); + +static HANDLE hAwayMsgMenuItem; +static HANDLE hWindowList; + +struct AwayMsgDlgData { + HANDLE hContact; + HANDLE hSeq; + HANDLE hAwayMsgEvent; +}; +#define HM_AWAYMSG (WM_USER+10) +static INT_PTR CALLBACK ReadAwayMsgDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + AwayMsgDlgData *dat = (AwayMsgDlgData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(message) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + dat = (AwayMsgDlgData*)mir_alloc(sizeof(AwayMsgDlgData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + dat->hContact = (HANDLE)lParam; + dat->hAwayMsgEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_AWAYMSG); + dat->hSeq = (HANDLE)CallContactService(dat->hContact, PSS_GETAWAYMSG, 0, 0); + WindowList_Add(hWindowList, hwndDlg, dat->hContact); + + { + TCHAR str[256], format[128]; + TCHAR* contactName = cli.pfnGetContactDisplayName(dat->hContact, 0); + char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)dat->hContact, 0); + WORD dwStatus = DBGetContactSettingWord(dat->hContact, szProto, "Status", ID_STATUS_OFFLINE); + TCHAR* status = cli.pfnGetStatusModeDescription(dwStatus, 0); + + GetWindowText(hwndDlg, format, SIZEOF(format)); + mir_sntprintf(str, SIZEOF(str), format, status, contactName); + SetWindowText(hwndDlg, str); + + GetDlgItemText(hwndDlg, IDC_RETRIEVING, format, SIZEOF(format)); + mir_sntprintf(str, SIZEOF(str), format, status); + SetDlgItemText(hwndDlg, IDC_RETRIEVING, str); + + Window_SetProtoIcon_IcoLib(hwndDlg, szProto, dwStatus); + } + if (dat->hSeq == NULL) + { + ACKDATA ack = {0}; + ack.cbSize = sizeof(ack); + ack.hContact = dat->hContact; + ack.type = ACKTYPE_AWAYMSG; + ack.result = ACKRESULT_SUCCESS; + SendMessage(hwndDlg, HM_AWAYMSG, 0, (LPARAM)&ack); + } + Utils_RestoreWindowPosition(hwndDlg, (HANDLE)lParam, "SRAway", "AwayMsgDlg"); + return TRUE; + + case HM_AWAYMSG: + { + ACKDATA *ack = (ACKDATA*)lParam; + if (ack->hContact != dat->hContact || ack->type != ACKTYPE_AWAYMSG) break; + if (ack->result != ACKRESULT_SUCCESS) break; + if (dat->hAwayMsgEvent && ack->hProcess == dat->hSeq) { UnhookEvent(dat->hAwayMsgEvent); dat->hAwayMsgEvent = NULL; } + +#ifdef _UNICODE + DBVARIANT dbv; + bool unicode = !DBGetContactSetting(dat->hContact, "CList", "StatusMsg", &dbv) && + (dbv.type == DBVT_UTF8 || dbv.type == DBVT_WCHAR); + DBFreeVariant(&dbv); + if (unicode) + { + DBGetContactSettingWString(dat->hContact, "CList", "StatusMsg", &dbv); + SetDlgItemText(hwndDlg, IDC_MSG, dbv.pwszVal); + } + else +#endif + SetDlgItemTextA(hwndDlg, IDC_MSG, (const char*)ack->lParam); + + ShowWindow(GetDlgItem(hwndDlg,IDC_RETRIEVING), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg,IDC_MSG), SW_SHOW); + SetDlgItemText(hwndDlg, IDOK, TranslateT("&Close")); + break; + } + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + if (dat->hAwayMsgEvent) UnhookEvent(dat->hAwayMsgEvent); + Utils_SaveWindowPosition(hwndDlg,dat->hContact,"SRAway","AwayMsgDlg"); + WindowList_Remove(hWindowList,hwndDlg); + Window_FreeIcon_IcoLib(hwndDlg); + mir_free(dat); + break; + } + return FALSE; +} + +static INT_PTR GetMessageCommand(WPARAM wParam, LPARAM) +{ + HWND hwnd; + if(hwnd=WindowList_Find(hWindowList,(HANDLE)wParam)) { + SetForegroundWindow(hwnd); + SetFocus(hwnd); + } + else CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_READAWAYMSG),NULL,ReadAwayMsgDlgProc,wParam); + return 0; +} + +static int AwayMsgPreBuildMenu(WPARAM wParam, LPARAM) +{ + CLISTMENUITEM clmi; + TCHAR str[128]; + char *szProto; + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,wParam,0); + ZeroMemory(&clmi,sizeof(clmi)); + clmi.cbSize = sizeof(clmi); + clmi.flags = CMIM_FLAGS | CMIF_NOTOFFLINE | CMIF_HIDDEN | CMIF_TCHAR; + + if ( szProto != NULL ) { + int chatRoom = szProto ? DBGetContactSettingByte((HANDLE)wParam, szProto, "ChatRoom", 0) : 0; + if ( !chatRoom ) { + int status = DBGetContactSettingWord((HANDLE)wParam,szProto,"Status",ID_STATUS_OFFLINE); + mir_sntprintf( str, SIZEOF(str), TranslateT("Re&ad %s Message"), cli.pfnGetStatusModeDescription( status, 0 )); + clmi.ptszName = str; + if ( CallProtoService( szProto, PS_GETCAPS, PFLAGNUM_1, 0 ) & PF1_MODEMSGRECV ) { + if ( CallProtoService( szProto, PS_GETCAPS, PFLAGNUM_3, 0 ) & Proto_Status2Flag( status )) { + clmi.flags = CMIM_FLAGS | CMIM_NAME | CMIF_NOTOFFLINE | CMIM_ICON | CMIF_TCHAR; + clmi.hIcon = LoadSkinProtoIcon(szProto, status); + } } } } + + CallService( MS_CLIST_MODIFYMENUITEM, ( WPARAM )hAwayMsgMenuItem, ( LPARAM )&clmi ); + IconLib_ReleaseIcon(clmi.hIcon,0); + return 0; +} + +static int AwayMsgPreShutdown(WPARAM, LPARAM) +{ + if (hWindowList) WindowList_BroadcastAsync(hWindowList,WM_CLOSE,0,0); + return 0; +} + +int LoadAwayMsgModule(void) +{ + CLISTMENUITEM mi = { 0 }; + + hWindowList = (HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST,0,0); + CreateServiceFunction(MS_AWAYMSG_SHOWAWAYMSG,GetMessageCommand); + + mi.cbSize = sizeof(mi); + mi.position = -2000005000; + mi.flags = CMIF_NOTOFFLINE; + mi.pszName = LPGEN("Re&ad Status Message"); + mi.pszService = MS_AWAYMSG_SHOWAWAYMSG; + hAwayMsgMenuItem = ( HANDLE )CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU,AwayMsgPreBuildMenu); + HookEvent(ME_SYSTEM_PRESHUTDOWN,AwayMsgPreShutdown); + return LoadAwayMessageSending(); +} diff --git a/src/modules/srawaymsg/sendmsg.cpp b/src/modules/srawaymsg/sendmsg.cpp new file mode 100644 index 0000000000..09cfcc77ad --- /dev/null +++ b/src/modules/srawaymsg/sendmsg.cpp @@ -0,0 +1,637 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +extern bool prochotkey; + +static DWORD protoModeMsgFlags; +static HWND hwndStatusMsg; + +static const TCHAR *GetDefaultMessage(int status) +{ + switch(status) { + case ID_STATUS_AWAY: return TranslateT("I've been away since %time%."); + case ID_STATUS_NA: return TranslateT("Give it up, I'm not in!"); + case ID_STATUS_OCCUPIED: return TranslateT("Not right now."); + case ID_STATUS_DND: return TranslateT("Give a guy some peace, would ya?"); + case ID_STATUS_FREECHAT: return TranslateT("I'm a chatbot!"); + case ID_STATUS_ONLINE: return TranslateT("Yep, I'm here."); + case ID_STATUS_OFFLINE: return TranslateT("Nope, not here."); + case ID_STATUS_INVISIBLE: return TranslateT("I'm hiding from the mafia."); + case ID_STATUS_ONTHEPHONE: return TranslateT("That'll be the phone."); + case ID_STATUS_OUTTOLUNCH: return TranslateT("Mmm...food."); + case ID_STATUS_IDLE: return TranslateT("idleeeeeeee"); + } + return NULL; +} + +static const char *StatusModeToDbSetting(int status, const char *suffix) +{ + const char *prefix; + static char str[64]; + + switch(status) + { + case ID_STATUS_AWAY: prefix = "Away"; break; + case ID_STATUS_NA: prefix = "Na"; break; + case ID_STATUS_DND: prefix = "Dnd"; break; + case ID_STATUS_OCCUPIED: prefix = "Occupied"; break; + case ID_STATUS_FREECHAT: prefix = "FreeChat"; break; + case ID_STATUS_ONLINE: prefix = "On"; break; + case ID_STATUS_OFFLINE: prefix = "Off"; break; + case ID_STATUS_INVISIBLE: prefix = "Inv"; break; + case ID_STATUS_ONTHEPHONE: prefix = "Otp"; break; + case ID_STATUS_OUTTOLUNCH: prefix = "Otl"; break; + case ID_STATUS_IDLE: prefix = "Idl"; break; + default: return NULL; + } + mir_snprintf(str, SIZEOF(str), "%s%s", prefix, suffix); + return str; +} + +static TCHAR* GetAwayMessage(int statusMode, char *szProto) +{ + DBVARIANT dbv; + + if (szProto && !(CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_3, 0) & Proto_Status2Flag(statusMode))) + return NULL; + + if (DBGetContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(statusMode, "Ignore"), 0)) + return NULL; + + if (DBGetContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(statusMode, "UsePrev"),0)) + { + if (DBGetContactSettingTString(NULL, "SRAway", StatusModeToDbSetting(statusMode, "Msg"), &dbv)) + dbv.ptszVal = mir_tstrdup(GetDefaultMessage(statusMode)); + } + else { + int i; + TCHAR substituteStr[128]; + if (DBGetContactSettingTString(NULL, "SRAway", StatusModeToDbSetting(statusMode,"Default"), &dbv)) + dbv.ptszVal = mir_tstrdup(GetDefaultMessage(statusMode)); + + for (i=0; dbv.ptszVal[i]; i++) + { + if (dbv.ptszVal[i] != '%') continue; + if (!_tcsnicmp(dbv.ptszVal + i, _T("%time%"), 6)) + { + MIRANDA_IDLE_INFO mii = {0}; + mii.cbSize = sizeof(mii); + CallService(MS_IDLE_GETIDLEINFO, 0, (LPARAM)&mii); + + if (mii.idleType == 1) + { + int mm; + SYSTEMTIME t; + GetLocalTime(&t); + mm = t.wMinute + t.wHour * 60 - mii.idleTime; + if (mm < 0) mm += 60 * 24; + t.wMinute = mm % 60; + t.wHour = mm / 60; + GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &t, NULL, substituteStr, SIZEOF(substituteStr)); + } + else GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, NULL, substituteStr, SIZEOF(substituteStr)); + } + else if (!_tcsnicmp(dbv.ptszVal + i, _T("%date%"), 6)) + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, substituteStr, SIZEOF(substituteStr)); + else continue; + if (lstrlen(substituteStr) > 6) + dbv.ptszVal = (TCHAR*)mir_realloc(dbv.ptszVal, (lstrlen(dbv.ptszVal) + 1 + lstrlen(substituteStr) - 6) * sizeof(TCHAR)); + MoveMemory(dbv.ptszVal + i + lstrlen(substituteStr), dbv.ptszVal + i + 6, (lstrlen(dbv.ptszVal) - i - 5) * sizeof(TCHAR)); + CopyMemory(dbv.ptszVal+i, substituteStr, lstrlen(substituteStr) * sizeof(TCHAR)); + } + } + return dbv.ptszVal; +} + +static WNDPROC OldMessageEditProc; + +static LRESULT CALLBACK MessageEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) + { + case WM_CHAR: + if(wParam == '\n' && GetKeyState(VK_CONTROL) & 0x8000) + { + PostMessage(GetParent(hwnd), WM_COMMAND, IDOK, 0); + return 0; + } + if (wParam == 1 && GetKeyState(VK_CONTROL) & 0x8000) //ctrl-a + { + SendMessage(hwnd, EM_SETSEL, 0, -1); + return 0; + } + if (wParam == 23 && GetKeyState(VK_CONTROL) & 0x8000) // ctrl-w + { + SendMessage(GetParent(hwnd), WM_CLOSE, 0, 0); + return 0; + } + if (wParam == 127 && GetKeyState(VK_CONTROL) & 0x8000) //ctrl-backspace + { + DWORD start, end; + TCHAR *text; + int textLen; + SendMessage(hwnd, EM_GETSEL, (WPARAM)&end, 0); + SendMessage(hwnd, WM_KEYDOWN, VK_LEFT, 0); + SendMessage(hwnd, EM_GETSEL, (WPARAM)&start, 0); + textLen = GetWindowTextLength(hwnd); + text = (TCHAR *)alloca(sizeof(TCHAR) * (textLen + 1)); + GetWindowText(hwnd, text, textLen + 1); + memmove(text + start, text + end, sizeof(TCHAR) * (textLen + 1 - end)); + SetWindowText(hwnd, text); + SendMessage(hwnd, EM_SETSEL, start, start); + SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwnd), EN_CHANGE), (LPARAM)hwnd); + return 0; + } + break; + } + return CallWindowProc(OldMessageEditProc, hwnd, msg, wParam, lParam); +} + +void ChangeAllProtoMessages(char *szProto, int statusMode, TCHAR *msg) +{ + if (szProto == NULL) + { + for (int i=0; i < accounts.getCount(); i++) + { + PROTOACCOUNT* pa = accounts[i]; + if (!Proto_IsAccountEnabled(pa)) continue; + if ((CallProtoService(pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND) && + !Proto_IsAccountLocked(pa)) + CallProtoService(pa->szModuleName, PS_SETAWAYMSGT, statusMode, (LPARAM)msg); + } + } + else + CallProtoService(szProto, PS_SETAWAYMSGT, statusMode,(LPARAM)msg); +} + +struct SetAwayMsgData +{ + int statusMode; + int countdown; + TCHAR okButtonFormat[64]; + char *szProto; + HANDLE hPreshutdown; +}; +struct SetAwasMsgNewData +{ + char *szProto; + int statusMode; +}; + +#define DM_SRAWAY_SHUTDOWN WM_USER+10 + +static INT_PTR CALLBACK SetAwayMsgDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + SetAwayMsgData* dat = (SetAwayMsgData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(message) + { + case WM_INITDIALOG: + { + SetAwasMsgNewData *newdat = (SetAwasMsgNewData*)lParam; + TranslateDialogDefault(hwndDlg); + dat = (SetAwayMsgData*)mir_alloc(sizeof(SetAwayMsgData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->statusMode = newdat->statusMode; + dat->szProto = newdat->szProto; + mir_free(newdat); + SendDlgItemMessage(hwndDlg, IDC_MSG, EM_LIMITTEXT, 1024, 0); + OldMessageEditProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MSG), GWLP_WNDPROC, (LONG_PTR)MessageEditSubclassProc); + { + TCHAR str[256], format[128]; + GetWindowText(hwndDlg, format, SIZEOF(format)); + mir_sntprintf(str, SIZEOF(str), format, cli.pfnGetStatusModeDescription(dat->statusMode, 0)); + SetWindowText(hwndDlg, str ); + } + GetDlgItemText(hwndDlg, IDOK, dat->okButtonFormat, SIZEOF(dat->okButtonFormat)); + { + TCHAR *msg = GetAwayMessage(dat->statusMode, dat->szProto); + SetDlgItemText(hwndDlg, IDC_MSG, msg); + mir_free(msg); + } + dat->countdown = 6; + SendMessage(hwndDlg, WM_TIMER, 0, 0); + Window_SetProtoIcon_IcoLib(hwndDlg, dat->szProto, dat->statusMode); + Utils_RestoreWindowPosition(hwndDlg,NULL,"SRAway","AwayMsgDlg"); + SetTimer(hwndDlg, 1, 1000, 0); + dat->hPreshutdown = HookEventMessage(ME_SYSTEM_PRESHUTDOWN, hwndDlg, DM_SRAWAY_SHUTDOWN); + } + return TRUE; + + case WM_TIMER: + if (--dat->countdown >= 0) + { + TCHAR str[64]; + mir_sntprintf(str, SIZEOF(str), dat->okButtonFormat, dat->countdown); + SetDlgItemText(hwndDlg, IDOK, str); + } + else + { + KillTimer(hwndDlg, 1); + PostMessage(hwndDlg, WM_CLOSE, 0, 0); + } + break; + + case WM_CLOSE: + { + TCHAR *msg = GetAwayMessage(dat->statusMode, dat->szProto); + ChangeAllProtoMessages(dat->szProto, dat->statusMode, msg); + mir_free(msg); + } + DestroyWindow(hwndDlg); + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDOK: + if (dat->countdown < 0) + { + TCHAR str[1024]; + GetDlgItemText(hwndDlg, IDC_MSG, str, SIZEOF(str)); + ChangeAllProtoMessages(dat->szProto, dat->statusMode, str); + DBWriteContactSettingTString(NULL, "SRAway", StatusModeToDbSetting(dat->statusMode, "Msg"), str); + DestroyWindow(hwndDlg); + } + else + PostMessage(hwndDlg, WM_CLOSE, 0, 0); + break; + + case IDC_MSG: + if (dat->countdown >= 0) + { + KillTimer(hwndDlg, 1); + SetDlgItemText(hwndDlg, IDOK, TranslateT("OK")); + dat->countdown = -1; + } + break; + } + break; + + case DM_SRAWAY_SHUTDOWN: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + Utils_SaveWindowPosition(hwndDlg,NULL,"SRAway","AwayMsgDlg"); + KillTimer(hwndDlg, 1); + UnhookEvent(dat->hPreshutdown); + Window_FreeIcon_IcoLib(hwndDlg); + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MSG), GWLP_WNDPROC, (LONG_PTR)OldMessageEditProc); + mir_free(dat); + hwndStatusMsg = NULL; + break; + } + return FALSE; +} + +static int StatusModeChange(WPARAM wParam, LPARAM lParam) +{ + BOOL bScreenSaverRunning = FALSE; + int statusMode = (int)wParam; + char *szProto = (char*)lParam; + + if (protoModeMsgFlags == 0) return 0; + + // If its a global change check the complete PFLAGNUM_3 flags to see if a popup might be needed + if (!szProto) + { + if (!(protoModeMsgFlags & Proto_Status2Flag(statusMode))) + return 0; + } + else + { + // If its a single protocol check the PFLAGNUM_3 for the single protocol + if (!(CallProtoService(szProto, PS_GETCAPS,PFLAGNUM_1, 0) & PF1_MODEMSGSEND) || + !(CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_3, 0) & Proto_Status2Flag(statusMode))) + return 0; + } + + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &bScreenSaverRunning, FALSE); + if (DBGetContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(statusMode, "Ignore"), 0)) + { + ChangeAllProtoMessages(szProto, statusMode, NULL); + } + else if (bScreenSaverRunning || ((!GetAsyncKeyState(VK_CONTROL) || prochotkey) && + DBGetContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(statusMode, "NoDlg"), 0))) + { + TCHAR *msg = GetAwayMessage(statusMode, szProto); + ChangeAllProtoMessages(szProto, statusMode, msg); + mir_free(msg); + } + else { + SetAwasMsgNewData *newdat = (SetAwasMsgNewData*)mir_alloc(sizeof(SetAwasMsgNewData)); + newdat->szProto = szProto; + newdat->statusMode = statusMode; + if (hwndStatusMsg) + DestroyWindow(hwndStatusMsg); + hwndStatusMsg = CreateDialogParam(hMirandaInst, MAKEINTRESOURCE(IDD_SETAWAYMSG), + NULL, SetAwayMsgDlgProc, (LPARAM)newdat); + } + return 0; +} + +static const int statusModes[] = +{ + ID_STATUS_OFFLINE, ID_STATUS_ONLINE, ID_STATUS_AWAY, ID_STATUS_NA, ID_STATUS_OCCUPIED, ID_STATUS_DND, + ID_STATUS_FREECHAT,ID_STATUS_INVISIBLE,ID_STATUS_OUTTOLUNCH,ID_STATUS_ONTHEPHONE, ID_STATUS_IDLE +}; + +struct AwayMsgInfo +{ + int ignore; + int noDialog; + int usePrevious; + TCHAR msg[1024]; +}; +struct AwayMsgDlgData +{ + struct AwayMsgInfo info[ SIZEOF(statusModes) ]; + int oldPage; +}; + +static INT_PTR CALLBACK DlgProcAwayMsgOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct AwayMsgDlgData *dat; + + HWND hLst = GetDlgItem(hwndDlg, IDC_LST_STATUS); + + dat=(struct AwayMsgDlgData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) + { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwndDlg); + dat = (AwayMsgDlgData*)mir_alloc(sizeof(AwayMsgDlgData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->oldPage = -1; + for (int i = 0; i < SIZEOF(statusModes); i++) + { + int j; + if (!(protoModeMsgFlags & Proto_Status2Flag(statusModes[i]))) + continue; + + if (hLst) + { + j = SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_ADDSTRING, 0, (LPARAM)cli.pfnGetStatusModeDescription(statusModes[i], 0)); + SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_SETITEMDATA, j, statusModes[i]); + } + else + { + j = SendDlgItemMessage(hwndDlg, IDC_STATUS, CB_ADDSTRING, 0, (LPARAM)cli.pfnGetStatusModeDescription(statusModes[i], 0)); + SendDlgItemMessage(hwndDlg, IDC_STATUS, CB_SETITEMDATA, j, statusModes[i]); + } + + dat->info[j].ignore = DBGetContactSettingByte(NULL, "SRAway", + StatusModeToDbSetting(statusModes[i], "Ignore"), 0); + dat->info[j].noDialog = DBGetContactSettingByte(NULL, "SRAway", + StatusModeToDbSetting(statusModes[i], "NoDlg"), 0); + dat->info[j].usePrevious = DBGetContactSettingByte(NULL, "SRAway", + StatusModeToDbSetting(statusModes[i], "UsePrev"), 0); + + DBVARIANT dbv; + if (DBGetContactSettingTString(NULL, "SRAway", StatusModeToDbSetting(statusModes[i], "Default"), &dbv)) + if (DBGetContactSettingTString(NULL, "SRAway", StatusModeToDbSetting(statusModes[i], "Msg"), &dbv)) + dbv.ptszVal = mir_tstrdup(GetDefaultMessage(statusModes[i])); + lstrcpy(dat->info[j].msg, dbv.ptszVal); + mir_free(dbv.ptszVal); + } + if (hLst) + SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_SETCURSEL, 0, 0); + else + SendDlgItemMessage(hwndDlg, IDC_STATUS, CB_SETCURSEL, 0, 0); + SendMessage(hwndDlg, WM_COMMAND, hLst ? MAKEWPARAM(IDC_LST_STATUS, LBN_SELCHANGE) : MAKEWPARAM(IDC_STATUS, CBN_SELCHANGE), 0); + return TRUE; + } + case WM_MEASUREITEM: + { + LPMEASUREITEMSTRUCT mis = (LPMEASUREITEMSTRUCT)lParam; + if (mis->CtlID == IDC_LST_STATUS) + mis->itemHeight = 20; + break; + } + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT)lParam; + if (dis->CtlID != IDC_LST_STATUS) break; + if (dis->itemID < 0) break; + + TCHAR buf[128]; + SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_GETTEXT, dis->itemID, (LPARAM)buf); + + if (dis->itemState & (ODS_SELECTED|ODS_FOCUS)) + { + FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_HIGHLIGHT)); + SetTextColor(dis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + else + { + FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_WINDOW)); + SetTextColor(dis->hDC, GetSysColor(COLOR_WINDOWTEXT)); + } + + RECT rc = dis->rcItem; + DrawIconEx(dis->hDC, 3, (rc.top + rc.bottom - 16) / 2, LoadSkinnedProtoIcon(NULL, dis->itemData), 16, 16, 0, NULL, DI_NORMAL); + rc.left += 25; + SetBkMode(dis->hDC, TRANSPARENT); + DrawText(dis->hDC, buf, -1, &rc, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX); + break; + } + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_LST_STATUS: + case IDC_STATUS: + if ((HIWORD(wParam) == CBN_SELCHANGE) || (HIWORD(wParam) == LBN_SELCHANGE)) + { + int i = hLst ? + SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_GETCURSEL, 0, 0) : + SendDlgItemMessage(hwndDlg, IDC_STATUS, CB_GETCURSEL, 0, 0); + if (dat->oldPage != -1) + { + dat->info[dat->oldPage].ignore = IsDlgButtonChecked(hwndDlg, IDC_DONTREPLY); + dat->info[dat->oldPage].noDialog = IsDlgButtonChecked(hwndDlg, IDC_NODIALOG); + dat->info[dat->oldPage].usePrevious = IsDlgButtonChecked(hwndDlg, IDC_USEPREVIOUS); + GetDlgItemText(hwndDlg, IDC_MSG, dat->info[dat->oldPage].msg, SIZEOF(dat->info[dat->oldPage].msg)); + } + CheckDlgButton(hwndDlg, IDC_DONTREPLY, i < 0 ? 0 : dat->info[i].ignore); + CheckDlgButton(hwndDlg, IDC_NODIALOG, i < 0 ? 0 : dat->info[i].noDialog); + CheckDlgButton(hwndDlg, IDC_USEPREVIOUS, i < 0 ? 0 : dat->info[i].usePrevious); + CheckDlgButton(hwndDlg, IDC_USESPECIFIC, i < 0 ? 0 : !dat->info[i].usePrevious); + + SetDlgItemText(hwndDlg,IDC_MSG, i < 0 ? _T("") : dat->info[i].msg); + + EnableWindow(GetDlgItem(hwndDlg, IDC_NODIALOG), i < 0 ? 0 : !dat->info[i].ignore); + EnableWindow(GetDlgItem(hwndDlg, IDC_USEPREVIOUS), i < 0 ? 0 : !dat->info[i].ignore); + EnableWindow(GetDlgItem(hwndDlg, IDC_USESPECIFIC), i < 0 ? 0 : !dat->info[i].ignore); + EnableWindow(GetDlgItem(hwndDlg, IDC_MSG), i < 0 ? 0 : !(dat->info[i].ignore || dat->info[i].usePrevious)); + dat->oldPage = i; + } + return 0; + + case IDC_DONTREPLY: + case IDC_USEPREVIOUS: + case IDC_USESPECIFIC: + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_STATUS,CBN_SELCHANGE),0); + break; + + case IDC_MSG: + if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; + break; + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) + { + case 0: + switch(((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { int i, status; + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_STATUS,CBN_SELCHANGE), 0); + i = hLst ? + (SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_GETCOUNT, 0, 0) - 1) : + (SendDlgItemMessage(hwndDlg, IDC_STATUS, CB_GETCOUNT, 0, 0) - 1); + for (; i>=0; i--) + { + status = hLst ? + SendDlgItemMessage(hwndDlg, IDC_LST_STATUS, LB_GETITEMDATA, i, 0): + SendDlgItemMessage(hwndDlg, IDC_STATUS, CB_GETITEMDATA, i, 0); + DBWriteContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(status,"Ignore"), (BYTE)dat->info[i].ignore); + DBWriteContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(status,"NoDlg"), (BYTE)dat->info[i].noDialog); + DBWriteContactSettingByte(NULL, "SRAway", StatusModeToDbSetting(status,"UsePrev"),(BYTE)dat->info[i].usePrevious); + DBWriteContactSettingTString(NULL, "SRAway", StatusModeToDbSetting(status,"Default"), dat->info[i].msg); + } + return TRUE; + } + } + break; + } + break; + + case WM_DESTROY: + mir_free(dat); + break; + } + return FALSE; +} + +static int AwayMsgOptInitialise(WPARAM wParam, LPARAM) +{ + if (protoModeMsgFlags == 0) + return 0; + + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.position = 870000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_AWAYMSG); + odp.pszTitle = LPGEN("Status Messages"); + odp.pszGroup = LPGEN("Status"); + odp.pfnDlgProc = DlgProcAwayMsgOpts; + odp.flags = ODPF_BOLDGROUPS; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + return 0; +} + +static int AwayMsgSendModernOptInit(WPARAM wParam, LPARAM) +{ + if (protoModeMsgFlags == 0) + return 0; + + static const int iBoldControls[] = + { + IDC_TXT_TITLE1, IDC_TXT_TITLE2, IDC_TXT_TITLE3, + MODERNOPT_CTRL_LAST + }; + + MODERNOPTOBJECT obj = {0}; + obj.cbSize = sizeof(obj); + obj.hInstance = hMirandaInst; + obj.dwFlags = MODEROPT_FLG_TCHAR | MODEROPT_FLG_NORESIZE; + obj.iSection = MODERNOPT_PAGE_STATUS; + obj.iType = MODERNOPT_TYPE_SECTIONPAGE; + obj.iBoldControls = (int*)iBoldControls; + obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_STATUS); + obj.pfnDlgProc = DlgProcAwayMsgOpts; +// obj.lpzClassicGroup = "Status"; +// obj.lpzClassicPage = "Messages"; + obj.lpzHelpUrl = "http://wiki.miranda-im.org/"; + CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj); + return 0; +} + +static int AwayMsgSendAccountsChanged(WPARAM, LPARAM) +{ + protoModeMsgFlags = 0; + for (int i=0; i < accounts.getCount(); i++) + { + if (!Proto_IsAccountEnabled(accounts[i])) continue; + protoModeMsgFlags |= CallProtoService(accounts[i]->szModuleName, PS_GETCAPS, PFLAGNUM_3, 0); + } + + return 0; +} + +static int AwayMsgSendModulesLoaded(WPARAM, LPARAM) +{ + AwayMsgSendAccountsChanged(0, 0); + + HookEvent(ME_CLIST_STATUSMODECHANGE, StatusModeChange); + HookEvent(ME_MODERNOPT_INITIALIZE, AwayMsgSendModernOptInit); + HookEvent(ME_OPT_INITIALISE, AwayMsgOptInitialise); + return 0; +} + +//remember to mir_free() the return value +static INT_PTR sttGetAwayMessageT(WPARAM wParam, LPARAM lParam) +{ + return (INT_PTR)GetAwayMessage((int)wParam, (char*)lParam); +} + +#ifdef UNICODE +static INT_PTR sttGetAwayMessage(WPARAM wParam, LPARAM lParam) +{ + TCHAR* msg = GetAwayMessage((int)wParam, (char*)lParam); + char* res = mir_t2a(msg); + mir_free(msg); + return (INT_PTR)res; +} +#endif + +int LoadAwayMessageSending(void) +{ + HookEvent(ME_SYSTEM_MODULESLOADED,AwayMsgSendModulesLoaded); + HookEvent(ME_PROTO_ACCLISTCHANGED, AwayMsgSendAccountsChanged); + +#ifdef UNICODE + CreateServiceFunction(MS_AWAYMSG_GETSTATUSMSG, sttGetAwayMessage); + CreateServiceFunction(MS_AWAYMSG_GETSTATUSMSGW, sttGetAwayMessageT); +#else + CreateServiceFunction(MS_AWAYMSG_GETSTATUSMSG, sttGetAwayMessageT); +#endif + return 0; +} diff --git a/src/modules/sremail/email.cpp b/src/modules/sremail/email.cpp new file mode 100644 index 0000000000..251963a406 --- /dev/null +++ b/src/modules/sremail/email.cpp @@ -0,0 +1,89 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +static HANDLE hEMailMenuItem; + +void SendEmailThread(void *szUrl) +{ + ShellExecuteA(NULL,"open",( char* )szUrl,"","",SW_SHOW); + mir_free(szUrl); + return; +} + +static INT_PTR SendEMailCommand(WPARAM wParam,LPARAM lParam) +{ + DBVARIANT dbv; + char *szUrl; + char *szProto; + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,wParam,0); + if(szProto==NULL || DBGetContactSettingString((HANDLE)wParam,szProto,"e-mail",&dbv)) { + if(DBGetContactSettingString((HANDLE)wParam,"UserInfo","Mye-mail0",&dbv)) { + MessageBox((HWND)lParam,TranslateT("User has not registered an e-mail address"),TranslateT("Send e-mail"),MB_OK); + return 1; + } + } + szUrl=(char*)mir_alloc(lstrlenA(dbv.pszVal)+8); + lstrcpyA(szUrl,"mailto:"); + lstrcatA(szUrl,dbv.pszVal); + mir_free(dbv.pszVal); + forkthread(SendEmailThread,0,szUrl); + return 0; +} + +static int EMailPreBuildMenu(WPARAM wParam, LPARAM) +{ + CLISTMENUITEM mi; + DBVARIANT dbv = { 0 }; + char *szProto; + + ZeroMemory(&mi,sizeof(mi)); + mi.cbSize = sizeof(mi); + mi.flags = CMIM_FLAGS; + + szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto == NULL || DBGetContactSettingString((HANDLE)wParam, szProto, "e-mail",& dbv)) + if (DBGetContactSettingString((HANDLE)wParam, "UserInfo", "Mye-mail0", &dbv)) + mi.flags = CMIM_FLAGS | CMIF_HIDDEN; + + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hEMailMenuItem, (LPARAM)&mi); + if (dbv.pszVal) DBFreeVariant(&dbv); + return 0; +} + +int LoadSendRecvEMailModule(void) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.position = -2000010000; + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_SENDEMAIL ); + mi.pszName = LPGEN("&E-mail"); + mi.pszService = MS_EMAIL_SENDEMAIL; + hEMailMenuItem = (HANDLE)CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM)&mi); + + CreateServiceFunction(MS_EMAIL_SENDEMAIL, SendEMailCommand); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, EMailPreBuildMenu); + return 0; +} diff --git a/src/modules/srfile/file.cpp b/src/modules/srfile/file.cpp new file mode 100644 index 0000000000..85eb99a7bc --- /dev/null +++ b/src/modules/srfile/file.cpp @@ -0,0 +1,392 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "file.h" + +TCHAR* PFTS_StringToTchar( int flags, const PROTOCHAR* s ); +int PFTS_CompareWithTchar( PROTOFILETRANSFERSTATUS* ft, const PROTOCHAR* s, TCHAR* r ); + +static HANDLE hSRFileMenuItem; + +static INT_PTR SendFileCommand(WPARAM wParam, LPARAM) +{ + struct FileSendData fsd; + fsd.hContact=(HANDLE)wParam; + fsd.ppFiles=NULL; + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_FILESEND),NULL,DlgProcSendFile,(LPARAM)&fsd); + return 0; +} + +static INT_PTR SendSpecificFiles(WPARAM wParam,LPARAM lParam) +{ + FileSendData fsd; + fsd.hContact=(HANDLE)wParam; + #if defined( _UNICODE ) + char** ppFiles = ( char** )lParam; + int count = 0; + while ( ppFiles[count] != NULL ) + count++; + + fsd.ppFiles = (const TCHAR**)alloca(( count+1 ) * sizeof( void* )); + for ( int i=0; i < count; i++ ) + fsd.ppFiles[i] = ( const TCHAR* )mir_a2t( ppFiles[i] ); + fsd.ppFiles[ count ] = NULL; + #else + fsd.ppFiles=(const char**)lParam; + #endif + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_FILESEND),NULL,DlgProcSendFile,(LPARAM)&fsd); + #if defined( _UNICODE ) + for ( int j=0; j < count; j++ ) + mir_free(( void* )fsd.ppFiles[j] ); + #endif + return 0; +} + +static INT_PTR SendSpecificFilesT(WPARAM wParam,LPARAM lParam) +{ + FileSendData fsd; + fsd.hContact=(HANDLE)wParam; + fsd.ppFiles=(const TCHAR**)lParam; + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_FILESEND),NULL,DlgProcSendFile,(LPARAM)&fsd); + return 0; +} + +static INT_PTR GetReceivedFilesFolder(WPARAM wParam,LPARAM lParam) +{ + TCHAR buf[MAX_PATH]; + GetContactReceivedFilesDir((HANDLE)wParam,buf,MAX_PATH,TRUE); + char* dir = mir_t2a(buf); + lstrcpynA((char*)lParam,dir,MAX_PATH); + mir_free(dir); + return 0; +} + +static INT_PTR RecvFileCommand(WPARAM, LPARAM lParam) +{ + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_FILERECV),NULL,DlgProcRecvFile,lParam); + return 0; +} + +void PushFileEvent( HANDLE hContact, HANDLE hdbe, LPARAM lParam ) +{ + CLISTEVENT cle={0}; + cle.cbSize = sizeof(cle); + cle.hContact = hContact; + cle.hDbEvent = hdbe; + cle.lParam = lParam; + if ( DBGetContactSettingByte(NULL,"SRFile","AutoAccept",0) && !DBGetContactSettingByte(hContact,"CList","NotOnList",0)) { + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_FILERECV),NULL,DlgProcRecvFile,(LPARAM)&cle); + } + else { + SkinPlaySound("RecvFile"); + + TCHAR szTooltip[256]; + mir_sntprintf(szTooltip,SIZEOF(szTooltip),TranslateT("File from %s"), cli.pfnGetContactDisplayName( hContact, 0 )); + cle.ptszTooltip = szTooltip; + + cle.flags |= CLEF_TCHAR; + cle.hIcon = LoadSkinIcon( SKINICON_EVENT_FILE ); + cle.pszService = "SRFile/RecvFile"; + CallService(MS_CLIST_ADDEVENT,0,(LPARAM)&cle); +} } + +static int FileEventAdded(WPARAM wParam,LPARAM lParam) +{ + DWORD dwSignature; + + DBEVENTINFO dbei={0}; + dbei.cbSize = sizeof(dbei); + dbei.cbBlob = sizeof( DWORD ); + dbei.pBlob = ( PBYTE )&dwSignature; + CallService( MS_DB_EVENT_GET, lParam, ( LPARAM )&dbei ); + if ( dbei.flags&(DBEF_SENT|DBEF_READ) || dbei.eventType != EVENTTYPE_FILE || dwSignature == 0 ) + return 0; + + PushFileEvent(( HANDLE )wParam, ( HANDLE )lParam, 0 ); + return 0; +} + +int SRFile_GetRegValue(HKEY hKeyBase,const TCHAR *szSubKey,const TCHAR *szValue,TCHAR *szOutput,int cbOutput) +{ + HKEY hKey; + DWORD cbOut=cbOutput; + + if ( RegOpenKeyEx( hKeyBase,szSubKey,0,KEY_QUERY_VALUE,&hKey ) != ERROR_SUCCESS) + return 0; + + if ( RegQueryValueEx( hKey,szValue,NULL,NULL,(PBYTE)szOutput, &cbOut ) != ERROR_SUCCESS ) { + RegCloseKey(hKey); + return 0; + } + + RegCloseKey(hKey); + return 1; +} + +void GetSensiblyFormattedSize(__int64 size,TCHAR *szOut,int cchOut,int unitsOverride,int appendUnits,int *unitsUsed) +{ + if(!unitsOverride) { + if(size<1000) unitsOverride=UNITS_BYTES; + else if(size<100*1024) unitsOverride=UNITS_KBPOINT1; + else if(size<1024*1024) unitsOverride=UNITS_KBPOINT0; + else if(size<1024*1024*1024) unitsOverride=UNITS_MBPOINT2; + else unitsOverride=UNITS_GBPOINT3; + } + if(unitsUsed) *unitsUsed=unitsOverride; + switch(unitsOverride) { + case UNITS_BYTES: mir_sntprintf(szOut,cchOut,_T("%u%s%s"),(int)size,appendUnits?_T(" "):_T(""),appendUnits?TranslateT("bytes"):_T("")); break; + case UNITS_KBPOINT1: mir_sntprintf(szOut,cchOut,_T("%.1lf%s"),size/1024.0,appendUnits?_T(" KB"):_T("")); break; + case UNITS_KBPOINT0: mir_sntprintf(szOut,cchOut,_T("%u%s"),(int)(size/1024),appendUnits?_T(" KB"):_T("")); break; + case UNITS_GBPOINT3: mir_sntprintf(szOut,cchOut,_T("%.3f%s"),(size >> 20)/1024.0,appendUnits?_T(" GB"):_T("")); break; + default: mir_sntprintf(szOut,cchOut,_T("%.2lf%s"),size/1048576.0,appendUnits?_T(" MB"):_T("")); break; + } +} + +// Tripple redirection sucks but is needed to nullify the array pointer +void FreeFilesMatrix(TCHAR ***files) +{ + if (*files == NULL) + return; + + // Free each filename in the pointer array + TCHAR **pFile = *files; + while (*pFile != NULL) + { + mir_free(*pFile); + *pFile = NULL; + pFile++; + } + + // Free the array itself + mir_free(*files); + *files = NULL; +} + +void FreeProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *fts) +{ + mir_free(fts->tszCurrentFile); + if(fts->ptszFiles) { + for( int i=0;itotalFiles;i++) mir_free(fts->ptszFiles[i]); + mir_free(fts->ptszFiles); + } + mir_free(fts->tszWorkingDir); +} + +void CopyProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest, PROTOFILETRANSFERSTATUS *src) +{ + *dest=*src; + if ( src->tszCurrentFile ) dest->tszCurrentFile = PFTS_StringToTchar(src->flags, src->tszCurrentFile); + if ( src->ptszFiles ) { + dest->ptszFiles = (TCHAR**)mir_alloc(sizeof(TCHAR*)*src->totalFiles); + for( int i=0; i < src->totalFiles; i++ ) + dest->ptszFiles[i] = PFTS_StringToTchar(src->flags, src->ptszFiles[i] ); + } + if ( src->tszWorkingDir ) dest->tszWorkingDir = PFTS_StringToTchar(src->flags, src->tszWorkingDir ); + dest->flags &= ~PFTS_UTF; + dest->flags |= PFTS_TCHAR; +} + +void UpdateProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest, PROTOFILETRANSFERSTATUS *src) +{ + if (src->cbSize == sizeof(PROTOFILETRANSFERSTATUS_V1)) + { + PROTOFILETRANSFERSTATUS_V1 *src1 = (PROTOFILETRANSFERSTATUS_V1*)src; + src = (PROTOFILETRANSFERSTATUS*)alloca(sizeof(PROTOFILETRANSFERSTATUS)); + + src->cbSize = sizeof(PROTOFILETRANSFERSTATUS); + src->hContact = src1->hContact; + src->flags = src1->sending ? PFTS_SENDING : 0; + src->pszFiles = src1->files; + src->totalFiles = src1->totalFiles; + src->currentFileNumber = src1->currentFileNumber; + src->totalBytes = src1->totalBytes; + src->totalProgress = src1->totalProgress; + src->szWorkingDir = src1->workingDir; + src->szCurrentFile = src1->currentFile; + src->currentFileSize = src1->currentFileSize; + src->currentFileProgress = src1->currentFileProgress; + src->currentFileTime = src1->currentFileTime; + } + + dest->hContact = src->hContact; + dest->flags = src->flags; + if ( dest->totalFiles != src->totalFiles ) { + for( int i=0;itotalFiles;i++) mir_free(dest->ptszFiles[i]); + mir_free(dest->ptszFiles); + dest->ptszFiles = NULL; + dest->totalFiles = src->totalFiles; + } + if ( src->ptszFiles ) { + if ( !dest->ptszFiles ) + dest->ptszFiles = ( TCHAR** )mir_calloc( sizeof(TCHAR*)*src->totalFiles); + for ( int i=0; i < src->totalFiles; i++ ) + if ( !dest->ptszFiles[i] || !src->ptszFiles[i] || PFTS_CompareWithTchar( src, src->ptszFiles[i], dest->ptszFiles[i] )) { + mir_free( dest->ptszFiles[i] ); + if ( src->ptszFiles[i] ) + dest->ptszFiles[i] = PFTS_StringToTchar( src->flags, src->ptszFiles[i] ); + else + dest->ptszFiles[i] = NULL; + } + } + else if (dest->ptszFiles) { + for( int i=0; i < dest->totalFiles; i++ ) + mir_free(dest->ptszFiles[i]); + mir_free( dest->ptszFiles ); + dest->ptszFiles = NULL; + } + + dest->currentFileNumber = src->currentFileNumber; + dest->totalBytes = src->totalBytes; + dest->totalProgress = src->totalProgress; + if (src->tszWorkingDir && (!dest->tszWorkingDir || PFTS_CompareWithTchar( src, src->tszWorkingDir, dest->tszWorkingDir))) { + mir_free( dest->tszWorkingDir ); + if ( src->tszWorkingDir ) + dest->tszWorkingDir = PFTS_StringToTchar( src->flags, src->tszWorkingDir ); + else + dest->tszWorkingDir = NULL; + } + + if ( !dest->tszCurrentFile || !src->tszCurrentFile || PFTS_CompareWithTchar( src, src->tszCurrentFile, dest->tszCurrentFile )) { + mir_free( dest->tszCurrentFile ); + if ( src->tszCurrentFile ) + dest->tszCurrentFile = PFTS_StringToTchar( src->flags, src->tszCurrentFile ); + else + dest->tszCurrentFile = NULL; + } + dest->currentFileSize = src->currentFileSize; + dest->currentFileProgress = src->currentFileProgress; + dest->currentFileTime = src->currentFileTime; + dest->flags &= ~PFTS_UTF; + dest->flags |= PFTS_TCHAR; +} + +static void RemoveUnreadFileEvents(void) +{ + DBEVENTINFO dbei={0}; + HANDLE hDbEvent,hContact; + + dbei.cbSize=sizeof(dbei); + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST,0,0); + while(hContact) { + hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDFIRSTUNREAD,(WPARAM)hContact,0); + while(hDbEvent) { + dbei.cbBlob=0; + CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei); + if(!(dbei.flags&(DBEF_SENT|DBEF_READ)) && dbei.eventType==EVENTTYPE_FILE) + CallService(MS_DB_EVENT_MARKREAD,(WPARAM)hContact,(LPARAM)hDbEvent); + hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDNEXT,(WPARAM)hDbEvent,0); + } + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDNEXT,(WPARAM)hContact,0); + } +} + +static int SRFilePreBuildMenu(WPARAM wParam, LPARAM) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.flags = CMIM_FLAGS | CMIF_HIDDEN; + + char *szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if (szProto != NULL) { + if ( CallProtoService(szProto, PS_GETCAPS,PFLAGNUM_1, 0 ) & PF1_FILESEND) { + if ( CallProtoService(szProto, PS_GETCAPS,PFLAGNUM_4, 0 ) & PF4_OFFLINEFILES ) + mi.flags = CMIM_FLAGS; + else if ( DBGetContactSettingWord(( HANDLE )wParam, szProto, "Status", ID_STATUS_OFFLINE ) != ID_STATUS_OFFLINE ) + mi.flags = CMIM_FLAGS; + } } + + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hSRFileMenuItem, (LPARAM)&mi); + return 0; +} + +static int SRFileModulesLoaded(WPARAM, LPARAM) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.position = -2000020000; + mi.icolibItem = GetSkinIconHandle( SKINICON_EVENT_FILE ); + mi.pszName = LPGEN("&File"); + mi.pszService = MS_FILE_SENDFILE; + mi.flags = CMIF_ICONFROMICOLIB; + hSRFileMenuItem = ( HANDLE )CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi); + + RemoveUnreadFileEvents(); + return 0; +} + +INT_PTR FtMgrShowCommand(WPARAM, LPARAM) +{ + FtMgr_Show(true, true); + return 0; +} + +INT_PTR openContRecDir(WPARAM wparam, LPARAM) +{ + TCHAR szContRecDir[MAX_PATH]; + HANDLE hContact = (HANDLE)wparam; + GetContactReceivedFilesDir(hContact, szContRecDir, SIZEOF(szContRecDir),TRUE); + ShellExecute(0, _T("open"), szContRecDir, 0, 0, SW_SHOW); + return 0; +} + +INT_PTR openRecDir(WPARAM, LPARAM) +{ + TCHAR szContRecDir[MAX_PATH]; + GetReceivedFilesDir(szContRecDir, SIZEOF(szContRecDir)); + ShellExecute(0, _T("open"), szContRecDir, 0, 0, SW_SHOW); + return 0; +} + +int LoadSendRecvFileModule(void) +{ + CreateServiceFunction("FtMgr/Show", FtMgrShowCommand); + + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_EVENT_FILE ); + mi.position = 1900000000; + mi.pszName = LPGEN("File &Transfers..."); + mi.pszService = "FtMgr/Show"; //MS_PROTO_SHOWFTMGR; + CallService( MS_CLIST_ADDMAINMENUITEM, 0, ( LPARAM )&mi ); + + HookEvent(ME_SYSTEM_MODULESLOADED,SRFileModulesLoaded); + HookEvent(ME_DB_EVENT_ADDED,FileEventAdded); + HookEvent(ME_OPT_INITIALISE,FileOptInitialise); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, SRFilePreBuildMenu); + + CreateServiceFunction(MS_FILE_SENDFILE,SendFileCommand); + CreateServiceFunction(MS_FILE_SENDSPECIFICFILES,SendSpecificFiles); + CreateServiceFunction(MS_FILE_SENDSPECIFICFILEST,SendSpecificFilesT); + CreateServiceFunction(MS_FILE_GETRECEIVEDFILESFOLDER,GetReceivedFilesFolder); + CreateServiceFunction("SRFile/RecvFile",RecvFileCommand); + + CreateServiceFunction("SRFile/OpenContRecDir",openContRecDir); + CreateServiceFunction("SRFile/OpenRecDir",openRecDir); + + SkinAddNewSoundEx("RecvFile", "File", "Incoming"); + SkinAddNewSoundEx("FileDone", "File", "Complete"); + SkinAddNewSoundEx("FileFailed", "File", "Error"); + SkinAddNewSoundEx("FileDenied", "File", "Denied"); + return 0; +} diff --git a/src/modules/srfile/file.h b/src/modules/srfile/file.h new file mode 100644 index 0000000000..8ee9048eb9 --- /dev/null +++ b/src/modules/srfile/file.h @@ -0,0 +1,115 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +#define VIRUSSCAN_DISABLE 0 +#define VIRUSSCAN_AFTERDL 1 +#define VIRUSSCAN_DURINGDL 2 + +#define FILERESUME_ASK 0 +//1,2,3,4: resume, overwrite, rename, skip: from proto library +#define FILERESUMEF_ALL 0x80 +#define FILERESUME_RESUMEALL (FILERESUME_RESUME|FILERESUMEF_ALL) +#define FILERESUME_OVERWRITEALL (FILERESUME_OVERWRITE|FILERESUMEF_ALL) +#define FILERESUME_RENAMEALL (FILERESUME_RENAME|FILERESUMEF_ALL) +#define FILERESUME_CANCEL 0xFFFFFFFF + +#define M_FILEEXISTSDLGREPLY (WM_USER+200) +#define M_PRESHUTDOWN (WM_USER+201) + +struct FileSendData { + HANDLE hContact; + const TCHAR **ppFiles; +}; + +#define BYTESRECVEDHISTORYCOUNT 10 //the number of bytes recved is sampled once a second and the last 10 are used to get the transfer speed +struct FileDlgData { + HWND hwndTransfer; + HANDLE fs; + HANDLE hContact; + HANDLE hDbEvent; + HANDLE hNotifyEvent; + TCHAR **files; + int send; + int closeIfFileChooseCancelled; + int resumeBehaviour; + int bytesRecvedHistory[BYTESRECVEDHISTORYCOUNT]; + int bytesRecvedHistorySize; + int waitingForAcceptance; + PROTOFILETRANSFERSTATUS transferStatus; + int *fileVirusScanned; + HANDLE hPreshutdownEvent; + DWORD dwTicks; + + TCHAR szSavePath[MAX_PATH]; + TCHAR szMsg[450], szFilenames[1024]; + HICON hIcon, hIconFolder; +}; + +//file.c +#define UNITS_BYTES 1 // 0<=size<1000: "%d bytes" +#define UNITS_KBPOINT1 2 // 1000<=size<100*1024: "%.1f KB" +#define UNITS_KBPOINT0 3 // 100*1024<=size<1024*1024: "%d KB" +#define UNITS_MBPOINT2 4 // 1024*1024<=size: "%.2f MB" +#define UNITS_GBPOINT3 5 // 1024*1024*1024<=size: "%.3f GB" + +void GetSensiblyFormattedSize(__int64 size,TCHAR *szOut,int cchOut,int unitsOverride,int appendUnits,int *unitsUsed); +void FreeFilesMatrix(TCHAR ***files); //loving that triple indirection +void FreeProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *fts); +void CopyProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest,PROTOFILETRANSFERSTATUS *src); +void UpdateProtoFileTransferStatus(PROTOFILETRANSFERSTATUS *dest,PROTOFILETRANSFERSTATUS *src); +int SRFile_GetRegValue(HKEY hKeyBase,const TCHAR *szSubKey,const TCHAR *szValue,TCHAR *szOutput,int cbOutput); +//filesenddlg.c +INT_PTR CALLBACK DlgProcSendFile(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +//filerecv.c +INT_PTR CALLBACK DlgProcRecvFile(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +void RemoveInvalidFilenameChars(TCHAR *tszString); +void RemoveInvalidPathChars(TCHAR *tszString); +void GetContactReceivedFilesDir(HANDLE hContact,TCHAR *szDir,int cchDir,BOOL substVars); +void GetReceivedFilesDir(TCHAR *szDir,int cchDir); +int BrowseForFolder(HWND hwnd,TCHAR *szPath); +//fileexistsdlg.c +struct TDlgProcFileExistsParam +{ + HWND hwndParent; + PROTOFILETRANSFERSTATUS *fts; +}; +INT_PTR CALLBACK DlgProcFileExists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +//filexferdlg.c +INT_PTR CALLBACK DlgProcFileTransfer(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +//fileopts.c +int FileOptInitialise(WPARAM wParam,LPARAM lParam); +//ftmanager.c +#define WM_FT_ADD (WM_USER+701) +#define WM_FT_RESIZE (WM_USER+702) +#define WM_FT_REMOVE (WM_USER+703) +#define WM_FT_SELECTPAGE (WM_USER+704) +#define WM_FT_CLEANUP (WM_USER+705) +#define WM_FT_COMPLETED (WM_USER+706) + +HWND FtMgr_Show(bool bForceActivate, bool bFromMenu); +void FtMgr_Destroy(); +HWND FtMgr_AddTransfer(struct FileDlgData *dat); + +void FreeFileDlgData( FileDlgData* dat ); + +TCHAR *GetContactID(HANDLE hContact); diff --git a/src/modules/srfile/fileexistsdlg.cpp b/src/modules/srfile/fileexistsdlg.cpp new file mode 100644 index 0000000000..d97d6d8da2 --- /dev/null +++ b/src/modules/srfile/fileexistsdlg.cpp @@ -0,0 +1,354 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include +#include +#include "file.h" + +static void SetControlToUnixTime(HWND hwndDlg, UINT idCtrl, time_t unixTime) +{ + LARGE_INTEGER liFiletime; + FILETIME filetime; + SYSTEMTIME st; + char szTime[64],szDate[64],szOutput[128]; + + liFiletime.QuadPart=(BIGI(11644473600)+(__int64)unixTime)*10000000; + filetime.dwHighDateTime=liFiletime.HighPart; + filetime.dwLowDateTime=liFiletime.LowPart; + FileTimeToSystemTime(&filetime,&st); + GetTimeFormatA(LOCALE_USER_DEFAULT,0,&st,NULL,szTime,SIZEOF(szTime)); + GetDateFormatA(LOCALE_USER_DEFAULT,DATE_SHORTDATE,&st,NULL,szDate,SIZEOF(szDate)); + mir_snprintf(szOutput, SIZEOF(szOutput), "%s %s",szDate,szTime); + SetDlgItemTextA(hwndDlg,idCtrl,szOutput); +} + +#define C_CONTEXTMENU 0 +#define C_PROPERTIES 1 +// not defined in VC++ 6.0 SE +#ifndef CMF_EXTENDEDVERBS +#define CMF_EXTENDEDVERBS 0x00000100 +#endif +static void DoAnnoyingShellCommand(HWND hwnd,const TCHAR *szFilename,int cmd,POINT *ptCursor) +{ + IShellFolder *pDesktopFolder; + if(SHGetDesktopFolder(&pDesktopFolder)==NOERROR) { + ITEMIDLIST *pCurrentIdl; + #if defined( _UNICODE ) + WCHAR* wszFilename = ( LPWSTR )szFilename; + #else + WCHAR wszFilename[MAX_PATH]; + MultiByteToWideChar(CP_ACP,0,szFilename,-1,wszFilename,SIZEOF(wszFilename)); + #endif + if(pDesktopFolder->ParseDisplayName(NULL,NULL,wszFilename,NULL,&pCurrentIdl,NULL)==NOERROR) { + if(pCurrentIdl->mkid.cb) { + ITEMIDLIST *pidl,*pidlNext,*pidlFilename; + IShellFolder *pFileFolder; + + for(pidl=pCurrentIdl;;) { + pidlNext=(ITEMIDLIST*)((PBYTE)pidl+pidl->mkid.cb); + if(pidlNext->mkid.cb==0) { + pidlFilename = (ITEMIDLIST*)CoTaskMemAlloc(pidl->mkid.cb+sizeof(pidl->mkid.cb)); + CopyMemory(pidlFilename,pidl,pidl->mkid.cb+sizeof(pidl->mkid.cb)); + pidl->mkid.cb=0; + break; + } + pidl=pidlNext; + } + if(pDesktopFolder->BindToObject(pCurrentIdl,NULL,IID_IShellFolder,(void**)&pFileFolder)==NOERROR) { + IContextMenu *pContextMenu; + if(pFileFolder->GetUIObjectOf(NULL,1,(LPCITEMIDLIST*)&pidlFilename,IID_IContextMenu,NULL,(void**)&pContextMenu)==NOERROR) { + switch(cmd) { + case C_PROPERTIES: + { CMINVOKECOMMANDINFO ici={0}; + ici.cbSize=sizeof(ici); + ici.hwnd=hwnd; + ici.lpVerb="properties"; + ici.nShow=SW_SHOW; + pContextMenu->InvokeCommand(&ici); + break; + } + case C_CONTEXTMENU: + { HMENU hMenu; + hMenu=CreatePopupMenu(); + if(SUCCEEDED(pContextMenu->QueryContextMenu(hMenu,0,1000,65535,(GetKeyState(VK_SHIFT)&0x8000?CMF_EXTENDEDVERBS:0)|CMF_NORMAL))) { + int cmd; + cmd=TrackPopupMenu(hMenu,TPM_RETURNCMD,ptCursor->x,ptCursor->y,0,hwnd,NULL); + if(cmd) { + CMINVOKECOMMANDINFO ici={0}; + ici.cbSize=sizeof(ici); + ici.hwnd=hwnd; + ici.lpVerb=MAKEINTRESOURCEA(cmd-1000); + ici.nShow=SW_SHOW; + pContextMenu->InvokeCommand(&ici); + } + } + DestroyMenu(hMenu); + break; + } + } + pContextMenu->Release(); + } + pFileFolder->Release(); + } + CoTaskMemFree(pidlFilename); + } + CoTaskMemFree(pCurrentIdl); + } + pDesktopFolder->Release(); + } +} + +static WNDPROC pfnIconWindowProc; +static LRESULT CALLBACK IconCtrlSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PROTOFILETRANSFERSTATUS* pft = (PROTOFILETRANSFERSTATUS*)GetWindowLongPtr(GetParent(hwnd),GWLP_USERDATA); + + switch(msg) { + case WM_LBUTTONDBLCLK: + ShellExecute(hwnd,NULL,pft->tszCurrentFile,NULL,NULL,SW_SHOW); + break; + case WM_RBUTTONUP: + { POINT pt; + pt.x=(short)LOWORD(lParam); pt.y=(short)HIWORD(lParam); + ClientToScreen( hwnd, &pt ); + DoAnnoyingShellCommand( hwnd, pft->tszCurrentFile, C_CONTEXTMENU, &pt ); + return 0; + } + } + return CallWindowProc(pfnIconWindowProc,hwnd,msg,wParam,lParam); +} + +struct loadiconsstartinfo { + HWND hwndDlg; + TCHAR *szFilename; +}; +void __cdecl LoadIconsAndTypesThread(void* param) +{ + loadiconsstartinfo *info = ( loadiconsstartinfo* )param; + SHFILEINFO fileInfo; + + if ( SHGetFileInfo( info->szFilename, 0, &fileInfo, sizeof(fileInfo),SHGFI_TYPENAME|SHGFI_ICON|SHGFI_LARGEICON)) { + TCHAR *pszExtension,*pszFilename; + TCHAR szExtension[64]; + TCHAR szIconFile[MAX_PATH]; + + pszFilename = _tcsrchr(info->szFilename,'\\'); + if ( pszFilename == NULL ) + pszFilename = info->szFilename; + + pszExtension = _tcsrchr( pszFilename, '.' ); + if ( pszExtension ) + lstrcpyn( szExtension, pszExtension+1, SIZEOF( szExtension )); + else { + pszExtension = _T("."); + szExtension[0]='\0'; + } + CharUpper(szExtension); + if ( fileInfo.szTypeName[0]=='\0' ) + mir_sntprintf( fileInfo.szTypeName, SIZEOF(fileInfo.szTypeName), TranslateT("%s File"),szExtension); + SetDlgItemText(info->hwndDlg,IDC_EXISTINGTYPE,fileInfo.szTypeName); + SetDlgItemText(info->hwndDlg,IDC_NEWTYPE,fileInfo.szTypeName); + SendDlgItemMessage(info->hwndDlg,IDC_EXISTINGICON,STM_SETICON,(WPARAM)fileInfo.hIcon,0); + szIconFile[0]='\0'; + if ( !lstrcmp( szExtension, _T("EXE"))) { + SRFile_GetRegValue(HKEY_LOCAL_MACHINE,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Icons"),_T("2"),szIconFile,SIZEOF(szIconFile)); + } + else { + TCHAR szTypeName[MAX_PATH]; + if(SRFile_GetRegValue(HKEY_CLASSES_ROOT,pszExtension,NULL,szTypeName,SIZEOF(szTypeName))) { + lstrcat(szTypeName,_T("\\DefaultIcon")); + if(SRFile_GetRegValue(HKEY_CLASSES_ROOT,szTypeName,NULL,szIconFile,SIZEOF(szIconFile))) { + if ( _tcsstr( szIconFile, _T("%1"))) + SRFile_GetRegValue(HKEY_LOCAL_MACHINE,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Icons"),_T("0"),szIconFile,SIZEOF(szIconFile)); + else szIconFile[0]='\0'; + } } } + + if ( szIconFile[0]) { + int iconIndex; + HICON hIcon; + TCHAR *pszComma = _tcsrchr(szIconFile,','); + if ( pszComma == NULL ) + iconIndex=0; + else { + iconIndex = _ttoi(pszComma+1); *pszComma='\0'; + } + hIcon = ExtractIcon( hMirandaInst, szIconFile, iconIndex ); + if ( hIcon ) + fileInfo.hIcon = hIcon; + } + SendDlgItemMessage(info->hwndDlg,IDC_NEWICON,STM_SETICON,(WPARAM)fileInfo.hIcon,0); + } + mir_free(info->szFilename); + mir_free(info); +} + +INT_PTR CALLBACK DlgProcFileExists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PROTOFILETRANSFERSTATUS *fts; + + fts=(PROTOFILETRANSFERSTATUS*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch(msg) { + case WM_INITDIALOG: + { + TCHAR szSize[64]; + struct _stati64 statbuf; + HWND hwndFocus; + struct TDlgProcFileExistsParam *dat = (struct TDlgProcFileExistsParam *)lParam; + + SetPropA(hwndDlg,"Miranda.Preshutdown",HookEventMessage(ME_SYSTEM_PRESHUTDOWN,hwndDlg,M_PRESHUTDOWN)); + SetPropA(hwndDlg,"Miranda.ParentWnd",dat->hwndParent); + + TranslateDialogDefault(hwndDlg); + fts=(PROTOFILETRANSFERSTATUS*)mir_alloc(sizeof(PROTOFILETRANSFERSTATUS)); + CopyProtoFileTransferStatus(fts,dat->fts); + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)fts); + SetDlgItemText(hwndDlg,IDC_FILENAME,fts->tszCurrentFile); + SetControlToUnixTime(hwndDlg,IDC_NEWDATE,fts->currentFileTime); + GetSensiblyFormattedSize(fts->currentFileSize,szSize,SIZEOF(szSize),0,1,NULL); + SetDlgItemText(hwndDlg,IDC_NEWSIZE,szSize); + + pfnIconWindowProc=(WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_EXISTINGICON),GWLP_WNDPROC,(LONG_PTR)IconCtrlSubclassProc); + + hwndFocus=GetDlgItem(hwndDlg,IDC_RESUME); + if ( _tstati64(fts->tszCurrentFile,&statbuf)==0) { + SetControlToUnixTime(hwndDlg,IDC_EXISTINGDATE,statbuf.st_mtime); + GetSensiblyFormattedSize(statbuf.st_size,szSize,SIZEOF(szSize),0,1,NULL); + SetDlgItemText(hwndDlg,IDC_EXISTINGSIZE,szSize); + if(statbuf.st_size>(int)fts->currentFileSize) { + EnableWindow(GetDlgItem(hwndDlg,IDC_RESUME),FALSE); + hwndFocus=GetDlgItem(hwndDlg,IDC_OVERWRITE); + } } + + loadiconsstartinfo *lisi = ( loadiconsstartinfo* )mir_alloc(sizeof(loadiconsstartinfo)); + lisi->hwndDlg=hwndDlg; + lisi->szFilename = mir_tstrdup(fts->tszCurrentFile); + //can be a little slow, so why not? + forkthread(LoadIconsAndTypesThread,0,lisi); + SetFocus(hwndFocus); + SetWindowLongPtr(hwndFocus,GWL_STYLE,GetWindowLongPtr(hwndFocus,GWL_STYLE)|BS_DEFPUSHBUTTON); + return FALSE; + } + case WM_COMMAND: + { + PROTOFILERESUME pfr={0}; + switch(LOWORD(wParam)) { + case IDC_OPENFILE: + ShellExecute( hwndDlg, NULL, fts->tszCurrentFile, NULL, NULL, SW_SHOW ); + return FALSE; + + case IDC_OPENFOLDER: + { + TCHAR szFile[MAX_PATH]; + lstrcpyn( szFile, fts->tszCurrentFile, SIZEOF(szFile)); + TCHAR* pszLastBackslash = _tcsrchr( szFile, '\\' ); + if ( pszLastBackslash ) + *pszLastBackslash = '\0'; + ShellExecute(hwndDlg,NULL,szFile,NULL,NULL,SW_SHOW); + return FALSE; + } + case IDC_PROPERTIES: + DoAnnoyingShellCommand(hwndDlg,fts->tszCurrentFile,C_PROPERTIES,NULL); + return FALSE; + case IDC_RESUME: + pfr.action=FILERESUME_RESUME; + break; + case IDC_RESUMEALL: + pfr.action=FILERESUME_RESUMEALL; + break; + case IDC_OVERWRITE: + pfr.action=FILERESUME_OVERWRITE; + break; + case IDC_OVERWRITEALL: + pfr.action=FILERESUME_OVERWRITEALL; + break; + + case IDC_AUTORENAME: + pfr.action = FILERESUME_RENAMEALL; + break; + + case IDC_SAVEAS: + { + OPENFILENAME ofn={0}; + TCHAR filter[512],*pfilter; + TCHAR str[MAX_PATH]; + + lstrcpyn( str, fts->tszCurrentFile, SIZEOF(str)); + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY; + _tcscpy( filter, TranslateT("All Files")); + _tcscat( filter, _T(" (*)")); + pfilter = filter + _tcslen(filter) + 1; + _tcscpy( pfilter, _T("*")); + pfilter = pfilter + _tcslen(pfilter) + 1; + *pfilter='\0'; + ofn.lpstrFilter = filter; + ofn.lpstrFile = str; + ofn.nMaxFile = SIZEOF(str); + ofn.nMaxFileTitle = MAX_PATH; + if(!GetSaveFileName(&ofn)) + return FALSE; + + pfr.szFilename = mir_tstrdup(str); + pfr.action = FILERESUME_RENAME; + break; + } + case IDC_SKIP: + pfr.action=FILERESUME_SKIP; + break; + case IDCANCEL: + pfr.action=FILERESUME_CANCEL; + break; + default: + return FALSE; + } + { PROTOFILERESUME *pfrCopy; + pfrCopy=(PROTOFILERESUME*)mir_alloc(sizeof(pfr)); + CopyMemory(pfrCopy,&pfr,sizeof(pfr)); + PostMessage((HWND)GetPropA(hwndDlg,"Miranda.ParentWnd"),M_FILEEXISTSDLGREPLY,(WPARAM)mir_tstrdup(fts->tszCurrentFile),(LPARAM)pfrCopy); + DestroyWindow(hwndDlg); + } + break; + } + + case WM_CLOSE: + PostMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDCANCEL,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDCANCEL)); + break; + + case M_PRESHUTDOWN: + PostMessage(hwndDlg,WM_CLOSE,0,0); + break; + + case WM_DESTROY: + UnhookEvent(GetPropA(hwndDlg,"Miranda.Preshutdown")); // GetProp() will return NULL if it couldnt find anything + RemovePropA(hwndDlg,"Miranda.Preshutdown"); + RemovePropA(hwndDlg,"Miranda.ParentWnd"); + DestroyIcon((HICON)SendDlgItemMessage(hwndDlg,IDC_EXISTINGICON,STM_GETICON,0,0)); + DestroyIcon((HICON)SendDlgItemMessage(hwndDlg,IDC_NEWICON,STM_GETICON,0,0)); + FreeProtoFileTransferStatus(fts); + mir_free(fts); + break; + } + return FALSE; +} diff --git a/src/modules/srfile/fileopts.cpp b/src/modules/srfile/fileopts.cpp new file mode 100644 index 0000000000..3107f109a3 --- /dev/null +++ b/src/modules/srfile/fileopts.cpp @@ -0,0 +1,247 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "file.h" + +#define VSCAN_MCAFEE 1 +#define VSCAN_DRSOLOMON 2 +#define VSCAN_NORTON 3 +#define VSCAN_CA 4 + +struct virusscannerinfo { + const TCHAR *szProductName; + const TCHAR *szExeRegPath; + const TCHAR *szExeRegValue; + const TCHAR *szCommandLine; +}; + +static const struct virusscannerinfo virusScanners[]={ + {_T("Network Associates/McAfee VirusScan"),_T("SOFTWARE\\McAfee\\VirusScan"),_T("Scan32EXE"),_T("\"%s\" %%f /nosplash /comp /autoscan /autoexit /noboot")}, + {_T("Dr Solomon's VirusScan (Network Associates)"),_T("SOFTWARE\\Network Associates\\TVD\\VirusScan\\AVConsol\\General"),_T("szScannerExe"),_T("\"%s\" %%f /uinone /noboot /comp /prompt /autoexit")}, + {_T("Norton AntiVirus"),_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Navw32.exe"),NULL,_T("\"%s\" %%f /b- /m- /s+ /noresults")}, + {_T("Computer Associates/Inoculate IT"),_T("Software\\Antivirus"),_T("ImageFilename"),_T("\"%s\" %%f /display=progress /exit")}, + {_T("Computer Associates eTrust"),_T("SOFTWARE\\ComputerAssociates\\Anti-Virus\\Resident"),_T("VetPath"),_T("\"%s\" %%f /display=progress /exit")}, + {_T("Kaspersky Anti-Virus"),_T("SOFTWARE\\KasperskyLab\\Components\\101"),_T("EXEName"),_T("\"%s\" /S /Q %%f")}, + {_T("Kaspersky Anti-Virus"),_T("SOFTWARE\\KasperskyLab\\SetupFolders"),_T("KAV8"),_T("\"%savp.exe\" SCAN %%f")}, + {_T("Kaspersky Anti-Virus"),_T("SOFTWARE\\KasperskyLab\\SetupFolders"),_T("KAV9"),_T("\"%savp.exe\" SCAN %%f")}, + {_T("AntiVir PersonalEdition Classic"),_T("SOFTWARE\\Avira\\AntiVir PersonalEdition Classic"),_T("Path"),_T("\"%savscan.exe\" /GUIMODE=2 /PATH=\"%%f\"")}, + {_T("ESET NOD32 Antivirus"),_T("SOFTWARE\\ESET\\ESET Security\\CurrentVersion\\Info"),_T("InstallDir"),_T("\"%secls.exe\" /log-all /aind /no-boots /adware /sfx /unsafe /unwanted /heur /adv-heur /action=clean \"%%f\"")}, +}; + +#define M_UPDATEENABLING (WM_USER+100) +#define M_SCANCMDLINESELCHANGE (WM_USER+101) + +#ifndef SHACF_FILESYS_DIRS + #define SHACF_FILESYS_DIRS 0x00000020 +#endif + +static INT_PTR CALLBACK DlgProcFileOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwndDlg); + + if (shAutoComplete) + shAutoComplete(GetDlgItem(hwndDlg, IDC_FILEDIR), SHACF_FILESYS_DIRS); + + { + TCHAR str[MAX_PATH]; + GetContactReceivedFilesDir(NULL,str,SIZEOF(str),FALSE); + SetDlgItemText(hwndDlg,IDC_FILEDIR,str); + } + + CheckDlgButton(hwndDlg, IDC_AUTOACCEPT, DBGetContactSettingByte(NULL,"SRFile","AutoAccept",0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_AUTOMIN, DBGetContactSettingByte(NULL,"SRFile","AutoMin",0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_AUTOCLOSE, DBGetContactSettingByte(NULL,"SRFile","AutoClose",0) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_AUTOCLEAR, DBGetContactSettingByte(NULL,"SRFile","AutoClear",1) ? BST_CHECKED : BST_UNCHECKED); + switch(DBGetContactSettingByte(NULL,"SRFile","UseScanner",VIRUSSCAN_DISABLE)) { + case VIRUSSCAN_AFTERDL: CheckDlgButton(hwndDlg, IDC_SCANAFTERDL, BST_CHECKED); break; + case VIRUSSCAN_DURINGDL: CheckDlgButton(hwndDlg, IDC_SCANDURINGDL, BST_CHECKED); break; + default: CheckDlgButton(hwndDlg, IDC_NOSCANNER, BST_CHECKED); break; + } + CheckDlgButton(hwndDlg, IDC_WARNBEFOREOPENING, DBGetContactSettingByte(NULL,"SRFile","WarnBeforeOpening",1) ? BST_CHECKED : BST_UNCHECKED); + + { TCHAR szScanExe[MAX_PATH]; + int i,iItem; + for( i=0; i < SIZEOF(virusScanners); i++ ) { + if(SRFile_GetRegValue(HKEY_LOCAL_MACHINE,virusScanners[i].szExeRegPath,virusScanners[i].szExeRegValue,szScanExe,SIZEOF(szScanExe))) { + iItem=SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_ADDSTRING,0,(LPARAM)virusScanners[i].szProductName); + SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_SETITEMDATA,iItem,i); + } + } + if ( SendDlgItemMessageA(hwndDlg,IDC_SCANCMDLINE,CB_GETCOUNT,0,0) == 0 ) + { + iItem = SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_ADDSTRING,0,(LPARAM)_T("") ); + SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_SETITEMDATA,iItem, (LPARAM)-1); + } + } + + DBVARIANT dbv; + if(DBGetContactSettingTString(NULL,"SRFile","ScanCmdLine",&dbv)==0) { + SetDlgItemText(hwndDlg,IDC_SCANCMDLINE,dbv.ptszVal); + DBFreeVariant(&dbv); + } + else { + if(SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_GETCOUNT,0,0)) { + SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_SETCURSEL,0,0); + PostMessage(hwndDlg,M_SCANCMDLINESELCHANGE,0,0); + } + } + switch(DBGetContactSettingByte(NULL,"SRFile","IfExists",FILERESUME_ASK)) { + case FILERESUME_RESUMEALL: CheckDlgButton(hwndDlg, IDC_RESUME, BST_CHECKED); break; + case FILERESUME_OVERWRITEALL: CheckDlgButton(hwndDlg, IDC_OVERWRITE, BST_CHECKED); break; + case FILERESUME_RENAMEALL: CheckDlgButton(hwndDlg, IDC_RENAME, BST_CHECKED); break; + default: CheckDlgButton(hwndDlg, IDC_ASK, BST_CHECKED); break; + } + SendMessage(hwndDlg,M_UPDATEENABLING,0,0); + return TRUE; + } + case M_UPDATEENABLING: + { int on=!IsDlgButtonChecked(hwndDlg,IDC_NOSCANNER); + EnableWindow(GetDlgItem(hwndDlg,IDC_ST_CMDLINE),on); + EnableWindow(GetDlgItem(hwndDlg,IDC_SCANCMDLINE),on); + EnableWindow(GetDlgItem(hwndDlg,IDC_SCANCMDLINEBROWSE),on); + EnableWindow(GetDlgItem(hwndDlg,IDC_ST_CMDLINEHELP),on); + EnableWindow(GetDlgItem(hwndDlg,IDC_AUTOMIN),IsDlgButtonChecked(hwndDlg,IDC_AUTOACCEPT)); + break; + } + case M_SCANCMDLINESELCHANGE: + { TCHAR str[512]; + TCHAR szScanExe[MAX_PATH]; + int iScanner=SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_GETITEMDATA,SendDlgItemMessage(hwndDlg,IDC_SCANCMDLINE,CB_GETCURSEL,0,0),0); + if(iScanner >= SIZEOF(virusScanners) || iScanner<0) break; + str[0]='\0'; + if(SRFile_GetRegValue(HKEY_LOCAL_MACHINE,virusScanners[iScanner].szExeRegPath,virusScanners[iScanner].szExeRegValue,szScanExe,SIZEOF(szScanExe))) + mir_sntprintf(str, SIZEOF(str), virusScanners[iScanner].szCommandLine,szScanExe); + SetDlgItemText(hwndDlg,IDC_SCANCMDLINE,str); + break; + } + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_FILEDIR: + if((HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus())) return 0; + break; + case IDC_FILEDIRBROWSE: + { TCHAR str[MAX_PATH]; + GetDlgItemText(hwndDlg,IDC_FILEDIR,str,SIZEOF(str)); + if(BrowseForFolder(hwndDlg,str)) + SetDlgItemText(hwndDlg,IDC_FILEDIR,str); + break; + } + case IDC_AUTOACCEPT: + case IDC_NOSCANNER: + case IDC_SCANAFTERDL: + case IDC_SCANDURINGDL: + SendMessage(hwndDlg,M_UPDATEENABLING,0,0); + break; + case IDC_SCANCMDLINE: + if(HIWORD(wParam)==CBN_SELCHANGE) PostMessage(hwndDlg,M_SCANCMDLINESELCHANGE,0,0); + else if(HIWORD(wParam)!=CBN_EDITCHANGE) return 0; + break; + case IDC_SCANCMDLINEBROWSE: + { TCHAR str[MAX_PATH+2]; + OPENFILENAME ofn = {0}; + TCHAR filter[512], *pfilter; + + GetDlgItemText(hwndDlg, IDC_SCANCMDLINE, str, SIZEOF(str)); + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + _tcscpy(filter,TranslateT("Executable Files")); + _tcscat(filter, _T(" (*.exe)")); + pfilter = filter + _tcslen(filter) + 1; + _tcscpy(pfilter, _T("*.exe")); + pfilter = pfilter + _tcslen(pfilter)+1; + _tcscpy(pfilter, TranslateT("All Files")); + _tcscat(pfilter, _T(" (*)")); + pfilter = pfilter + _tcslen(pfilter) + 1; + _tcscpy(pfilter, _T("*")); + pfilter = pfilter + _tcslen(pfilter) + 1; + *pfilter = 0; + ofn.lpstrFilter = filter; + ofn.lpstrFile = str; + ofn.nMaxFile = SIZEOF(str)-2; + if(str[0]=='"') { + TCHAR *pszQuote = _tcschr(str + 1, '"'); + if (pszQuote) *pszQuote = 0; + MoveMemory(str, str + 1, _tcslen(str) * sizeof(TCHAR)); + } + else { + TCHAR *pszSpace = _tcschr(str, ' '); + if (pszSpace) *pszSpace = 0; + } + ofn.nMaxFileTitle = MAX_PATH; + if (!GetOpenFileName(&ofn)) break; + if (_tcschr(str, ' ') != NULL) { + MoveMemory(str+1, str, SIZEOF(str) - 2 * sizeof(TCHAR)); + str[0] = '"'; + _tcscat(str, _T("\"")); + } + SetDlgItemText(hwndDlg, IDC_SCANCMDLINE, str); + break; + } + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { TCHAR str[512]; + GetDlgItemText(hwndDlg, IDC_FILEDIR, str, SIZEOF(str)); + RemoveInvalidPathChars(str); + DBWriteContactSettingTString(NULL,"SRFile","RecvFilesDirAdv",str); + DBWriteContactSettingByte(NULL,"SRFile","AutoAccept",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_AUTOACCEPT)); + DBWriteContactSettingByte(NULL,"SRFile","AutoMin",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_AUTOMIN)); + DBWriteContactSettingByte(NULL,"SRFile","AutoClose",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_AUTOCLOSE)); + DBWriteContactSettingByte(NULL,"SRFile","AutoClear",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_AUTOCLEAR)); + DBWriteContactSettingByte(NULL,"SRFile","UseScanner",(BYTE)(IsDlgButtonChecked(hwndDlg,IDC_SCANAFTERDL)?VIRUSSCAN_AFTERDL:(IsDlgButtonChecked(hwndDlg,IDC_SCANDURINGDL)?VIRUSSCAN_DURINGDL:VIRUSSCAN_DISABLE))); + GetDlgItemText(hwndDlg, IDC_SCANCMDLINE, str, SIZEOF(str)); + DBWriteContactSettingTString(NULL,"SRFile","ScanCmdLine",str); + DBWriteContactSettingByte(NULL,"SRFile","WarnBeforeOpening",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_WARNBEFOREOPENING)); + DBWriteContactSettingByte(NULL,"SRFile","IfExists",(BYTE)(IsDlgButtonChecked(hwndDlg,IDC_ASK)?FILERESUME_ASK:(IsDlgButtonChecked(hwndDlg,IDC_RESUME)?FILERESUME_RESUMEALL:(IsDlgButtonChecked(hwndDlg,IDC_OVERWRITE)?FILERESUME_OVERWRITEALL:FILERESUME_RENAMEALL)))); + return TRUE; + } + } + break; + } + return FALSE; +} + +int FileOptInitialise(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp={0}; + odp.cbSize = sizeof(odp); + odp.position = 900000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_FILETRANSFER); + odp.pszTitle = LPGEN("File Transfers"); + odp.pszGroup = LPGEN("Events"); + odp.pfnDlgProc = DlgProcFileOpts; + odp.flags = ODPF_BOLDGROUPS; + odp.nIDBottomSimpleControl = IDC_VIRUSSCANNERGROUP; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} + diff --git a/src/modules/srfile/filerecvdlg.cpp b/src/modules/srfile/filerecvdlg.cpp new file mode 100644 index 0000000000..451a27e8bc --- /dev/null +++ b/src/modules/srfile/filerecvdlg.cpp @@ -0,0 +1,446 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "file.h" + +#define MAX_MRU_DIRS 5 + +static BOOL CALLBACK ClipSiblingsChildEnumProc(HWND hwnd, LPARAM) +{ + SetWindowLongPtr(hwnd,GWL_STYLE,GetWindowLongPtr(hwnd,GWL_STYLE)|WS_CLIPSIBLINGS); + return TRUE; +} + +static void GetLowestExistingDirName(const TCHAR *szTestDir,TCHAR *szExistingDir,int cchExistingDir) +{ + DWORD dwAttributes; + TCHAR *pszLastBackslash; + + lstrcpyn(szExistingDir,szTestDir,cchExistingDir); + while((dwAttributes=GetFileAttributes(szExistingDir))!=INVALID_FILE_ATTRIBUTES && !(dwAttributes&FILE_ATTRIBUTE_DIRECTORY)) { + pszLastBackslash=_tcsrchr(szExistingDir,'\\'); + if(pszLastBackslash==NULL) {*szExistingDir='\0'; break;} + *pszLastBackslash='\0'; + } + if(szExistingDir[0]=='\0') GetCurrentDirectory(cchExistingDir,szExistingDir); +} + +static const TCHAR InvalidFilenameChars[] = _T("\\/:*?\"<>|"); +void RemoveInvalidFilenameChars(TCHAR *tszString) +{ + size_t i; + if (tszString) { + for(i=_tcscspn(tszString,InvalidFilenameChars); tszString[i]; i+=_tcscspn(tszString+i+1,InvalidFilenameChars)+1) + if(tszString[i] >= 0) + tszString[i] = _T('_'); + } +} + +static const TCHAR InvalidPathChars[] = _T("*?\"<>|"); // "\/:" are excluded as they are allowed in file path +void RemoveInvalidPathChars(TCHAR *tszString) +{ + size_t i; + if (tszString) { + for(i=_tcscspn(tszString,InvalidPathChars); tszString[i]; i+=_tcscspn(tszString+i+1,InvalidPathChars)+1) + if(tszString[i] >= 0) + tszString[i] = _T('_'); + } +} + +static INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) +{ + TCHAR szDir[MAX_PATH]; + switch(uMsg) { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTION, TRUE, pData); + break; + case BFFM_SELCHANGED: + if (SHGetPathFromIDList((LPITEMIDLIST) lp ,szDir)) + SendMessage(hwnd,BFFM_SETSTATUSTEXT,0,(LPARAM)szDir); + break; + } + return 0; +} + +int BrowseForFolder(HWND hwnd,TCHAR *szPath) +{ + BROWSEINFO bi={0}; + LPITEMIDLIST pidlResult; + + bi.hwndOwner=hwnd; + bi.pszDisplayName=szPath; + bi.lpszTitle=TranslateT("Select Folder"); + bi.ulFlags=BIF_NEWDIALOGSTYLE|BIF_EDITBOX|BIF_RETURNONLYFSDIRS; // Use this combo instead of BIF_USENEWUI + bi.lpfn=BrowseCallbackProc; + bi.lParam=(LPARAM)szPath; + + pidlResult=SHBrowseForFolder(&bi); + if(pidlResult) { + SHGetPathFromIDList(pidlResult,szPath); + lstrcat(szPath,_T("\\")); + CoTaskMemFree(pidlResult); + } + return pidlResult != NULL; +} + +static REPLACEVARSARRAY sttVarsToReplace[] = +{ + { ( TCHAR* )"///", ( TCHAR* )"//" }, + { ( TCHAR* )"//", ( TCHAR* )"/" }, + { ( TCHAR* )"()", ( TCHAR* )"" }, + { NULL, NULL } +}; + +static void patchDir( TCHAR* str, size_t strSize ) +{ + REPLACEVARSDATA dat = { 0 }; + dat.cbSize = sizeof( dat ); + dat.dwFlags = RVF_TCHAR; + dat.variables = sttVarsToReplace; + + TCHAR* result = ( TCHAR* )CallService( MS_UTILS_REPLACEVARS, (WPARAM)str, (LPARAM)&dat ); + if ( result ) { + _tcsncpy( str, result, strSize ); + mir_free( result ); + } + + size_t len = lstrlen( str ); + if ( len+1 < strSize && str[len-1] != '\\' ) + lstrcpy( str+len, _T("\\") ); +} + +void GetContactReceivedFilesDir(HANDLE hContact, TCHAR *szDir, int cchDir, BOOL patchVars) +{ + DBVARIANT dbv; + TCHAR szTemp[MAX_PATH]; + szTemp[0] = 0; + + if ( !DBGetContactSettingTString( NULL, "SRFile", "RecvFilesDirAdv", &dbv)) { + if ( lstrlen( dbv.ptszVal ) > 0 ) + lstrcpyn( szTemp, dbv.ptszVal, SIZEOF( szTemp )); + DBFreeVariant( &dbv ); + } + + if ( !szTemp[0] ) +#ifdef _UNICODE + mir_sntprintf( szTemp, SIZEOF(szTemp), _T("%%mydocuments%%\\%s\\%%userid%%"), TranslateT("My Received Files")); +#else + mir_sntprintf( szTemp, SIZEOF(szTemp), _T("%%mydocuments%%\\%s\\%%userid%%"), "My Received Files"); +#endif + + if ( hContact ) { + REPLACEVARSDATA dat = { 0 }; + REPLACEVARSARRAY rvaVarsToReplace[4]; + rvaVarsToReplace[0].lptzKey = _T("nick"); + rvaVarsToReplace[0].lptzValue = mir_tstrdup((TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR)); + rvaVarsToReplace[1].lptzKey = _T("userid"); + rvaVarsToReplace[1].lptzValue = GetContactID(hContact); + rvaVarsToReplace[2].lptzKey = _T("proto"); + rvaVarsToReplace[2].lptzValue = mir_a2t((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact,0)); + rvaVarsToReplace[3].lptzKey = NULL; + rvaVarsToReplace[3].lptzValue = NULL; + for (int i=0; i < (SIZEOF(rvaVarsToReplace)-1);i++) + RemoveInvalidFilenameChars(rvaVarsToReplace[i].lptzValue); + + dat.cbSize = sizeof( dat ); + dat.dwFlags = RVF_TCHAR; + dat.variables = rvaVarsToReplace; + dat.hContact = hContact; + TCHAR* result = ( TCHAR* )CallService( MS_UTILS_REPLACEVARS, (WPARAM)szTemp, (LPARAM)&dat ); + if ( result ) { + _tcsncpy( szTemp, result, SIZEOF(szTemp)); + mir_free( result ); + for (int i=0; i < (SIZEOF(rvaVarsToReplace)-1);i++) + mir_free(rvaVarsToReplace[i].lptzValue); + } } + + if (patchVars) + patchDir( szTemp, SIZEOF(szTemp)); + RemoveInvalidPathChars(szTemp); + lstrcpyn( szDir, szTemp, cchDir ); +} + +void GetReceivedFilesDir(TCHAR *szDir, int cchDir) +{ + DBVARIANT dbv; + TCHAR szTemp[MAX_PATH]; + szTemp[0] = 0; + + if ( !DBGetContactSettingTString( NULL, "SRFile", "RecvFilesDirAdv", &dbv )) { + if ( lstrlen( dbv.ptszVal ) > 0 ) + lstrcpyn( szTemp, dbv.ptszVal, SIZEOF( szTemp )); + DBFreeVariant(&dbv); + } + + if ( !szTemp[0] ) +#ifdef _UNICODE + mir_sntprintf( szTemp, SIZEOF(szTemp), _T("%%mydocuments%%\\%s"), TranslateT("My Received Files")); +#else + mir_sntprintf( szTemp, SIZEOF(szTemp), _T("%%mydocuments%%\\%s"), "My Received Files"); +#endif + + patchDir( szTemp, SIZEOF(szTemp)); + RemoveInvalidPathChars(szTemp); + lstrcpyn( szDir, szTemp, cchDir ); +} + +INT_PTR CALLBACK DlgProcRecvFile(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct FileDlgData *dat; + + dat=(struct FileDlgData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: { + TCHAR *contactName; + TCHAR szPath[450]; + CLISTEVENT* cle = (CLISTEVENT*)lParam; + + TranslateDialogDefault(hwndDlg); + + dat=(struct FileDlgData*)mir_calloc(sizeof(struct FileDlgData)); + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)dat); + dat->hContact = cle->hContact; + dat->hDbEvent = cle->hDbEvent; + dat->hPreshutdownEvent = HookEventMessage(ME_SYSTEM_PRESHUTDOWN,hwndDlg,M_PRESHUTDOWN); + dat->dwTicks = GetTickCount(); + + EnumChildWindows(hwndDlg,ClipSiblingsChildEnumProc,0); + + Window_SetIcon_IcoLib(hwndDlg, SKINICON_EVENT_FILE); + Button_SetIcon_IcoLib(hwndDlg, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add Contact Permanently to List")); + Button_SetIcon_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View User's Details")); + Button_SetIcon_IcoLib(hwndDlg, IDC_HISTORY, SKINICON_OTHER_HISTORY, LPGEN("View User's History")); + Button_SetIcon_IcoLib(hwndDlg, IDC_USERMENU, SKINICON_OTHER_DOWNARROW, LPGEN("User Menu")); + + contactName = cli.pfnGetContactDisplayName( dat->hContact, 0 ); + SetDlgItemText(hwndDlg,IDC_FROM,contactName); + GetContactReceivedFilesDir(dat->hContact,szPath,SIZEOF(szPath),TRUE); + SetDlgItemText(hwndDlg,IDC_FILEDIR,szPath); + { + int i; + char idstr[32]; + DBVARIANT dbv; + + if (shAutoComplete) + shAutoComplete(GetWindow(GetDlgItem(hwndDlg,IDC_FILEDIR),GW_CHILD),1); + + for(i=0;ihContact,(LPARAM)dat->hDbEvent); + { + DBEVENTINFO dbei={0}; + TCHAR datetimestr[64]; + char buf[540]; + + dbei.cbSize=sizeof(dbei); + dbei.cbBlob=CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)dat->hDbEvent,0); + dbei.pBlob=(PBYTE)mir_alloc(dbei.cbBlob); + CallService(MS_DB_EVENT_GET,(WPARAM)dat->hDbEvent,(LPARAM)&dbei); + dat->fs = cle->lParam ? (HANDLE)cle->lParam : (HANDLE)*(PDWORD)dbei.pBlob; + lstrcpynA(buf, (char*)dbei.pBlob+4, min(dbei.cbBlob+1,SIZEOF(buf))); + TCHAR* ptszFileName = DbGetEventStringT( &dbei, buf ); + SetDlgItemText(hwndDlg,IDC_FILENAMES,ptszFileName); + mir_free(ptszFileName); + lstrcpynA(buf, (char*)dbei.pBlob+4+strlen((char*)dbei.pBlob+4)+1, min((int)(dbei.cbBlob-4-strlen((char*)dbei.pBlob+4)),SIZEOF(buf))); + TCHAR* ptszDescription = DbGetEventStringT( &dbei, buf ); + SetDlgItemText(hwndDlg,IDC_MSG,ptszDescription); + mir_free(ptszDescription); + mir_free(dbei.pBlob); + + tmi.printTimeStamp(NULL, dbei.timestamp, _T("t d"), datetimestr, SIZEOF(datetimestr), 0); + SetDlgItemText(hwndDlg, IDC_DATE, datetimestr); + } + { + char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)dat->hContact, 0); + if (szProto) { + CONTACTINFO ci; + int hasName = 0; + char buf[128]; + ZeroMemory(&ci,sizeof(ci)); + + ci.cbSize = sizeof(ci); + ci.hContact = dat->hContact; + ci.szProto = szProto; + ci.dwFlag = CNF_UNIQUEID; + if (!CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) { + switch(ci.type) { + case CNFT_ASCIIZ: + hasName = 1; + mir_snprintf(buf, SIZEOF(buf), "%s", ci.pszVal); + mir_free(ci.pszVal); + break; + case CNFT_DWORD: + hasName = 1; + mir_snprintf(buf, SIZEOF(buf),"%u",ci.dVal); + break; + } } + if (hasName) + SetDlgItemTextA(hwndDlg, IDC_NAME, buf ); + else + SetDlgItemText(hwndDlg, IDC_NAME, contactName); + } } + + if(DBGetContactSettingByte(dat->hContact,"CList","NotOnList",0)) { + RECT rcBtn1,rcBtn2,rcDateCtrl; + GetWindowRect(GetDlgItem(hwndDlg,IDC_ADD),&rcBtn1); + GetWindowRect(GetDlgItem(hwndDlg,IDC_USERMENU),&rcBtn2); + GetWindowRect(GetDlgItem(hwndDlg,IDC_DATE),&rcDateCtrl); + SetWindowPos(GetDlgItem(hwndDlg,IDC_DATE),0,0,0,rcDateCtrl.right-rcDateCtrl.left-(rcBtn2.left-rcBtn1.left),rcDateCtrl.bottom-rcDateCtrl.top,SWP_NOZORDER|SWP_NOMOVE); + } + else if(DBGetContactSettingByte(NULL,"SRFile","AutoAccept",0)) { + //don't check auto-min here to fix BUG#647620 + PostMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDOK,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDOK)); + } + if(!DBGetContactSettingByte(dat->hContact,"CList","NotOnList",0)) + ShowWindow(GetDlgItem(hwndDlg, IDC_ADD),SW_HIDE); + return TRUE; + } + + case WM_MEASUREITEM: + return CallService(MS_CLIST_MENUMEASUREITEM,wParam,lParam); + + case WM_DRAWITEM: + { LPDRAWITEMSTRUCT dis=(LPDRAWITEMSTRUCT)lParam; + if(dis->hwndItem==GetDlgItem(hwndDlg, IDC_PROTOCOL)) { + char *szProto; + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + if (szProto) { + HICON hIcon; + + hIcon=(HICON)CallProtoService(szProto,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0); + if (hIcon) { + DrawIconEx(dis->hDC,dis->rcItem.left,dis->rcItem.top,hIcon,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0,NULL,DI_NORMAL); + DestroyIcon(hIcon); + } } } } + return CallService(MS_CLIST_MENUDRAWITEM,wParam,lParam); + + case WM_COMMAND: + if ( CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam),MPCF_CONTACTMENU), (LPARAM)dat->hContact )) + break; + + switch ( LOWORD( wParam )) { + case IDC_FILEDIRBROWSE: + { + TCHAR szDirName[MAX_PATH],szExistingDirName[MAX_PATH]; + + GetDlgItemText(hwndDlg,IDC_FILEDIR,szDirName,SIZEOF(szDirName)); + GetLowestExistingDirName(szDirName,szExistingDirName,SIZEOF(szExistingDirName)); + if(BrowseForFolder(hwndDlg,szExistingDirName)) + SetDlgItemText(hwndDlg,IDC_FILEDIR,szExistingDirName); + } + break; + + case IDOK: + { //most recently used directories + TCHAR szRecvDir[MAX_PATH],szDefaultRecvDir[MAX_PATH]; + GetDlgItemText(hwndDlg,IDC_FILEDIR,szRecvDir,SIZEOF(szRecvDir)); + RemoveInvalidPathChars(szRecvDir); + GetContactReceivedFilesDir(NULL,szDefaultRecvDir,SIZEOF(szDefaultRecvDir),TRUE); + if(_tcsnicmp(szRecvDir,szDefaultRecvDir,lstrlen(szDefaultRecvDir))) { + char idstr[32]; + int i; + DBVARIANT dbv; + for(i=MAX_MRU_DIRS-2;i>=0;i--) { + mir_snprintf(idstr, SIZEOF(idstr), "MruDir%d",i); + if(DBGetContactSettingTString(NULL,"SRFile",idstr,&dbv)) continue; + mir_snprintf(idstr, SIZEOF(idstr), "MruDir%d",i+1); + DBWriteContactSettingTString(NULL,"SRFile",idstr,dbv.ptszVal); + DBFreeVariant(&dbv); + } + DBWriteContactSettingTString(NULL,"SRFile",idstr,szRecvDir); + } + } + EnableWindow(GetDlgItem(hwndDlg,IDC_FILENAMES),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_MSG),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_FILEDIR),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_FILEDIRBROWSE),FALSE); + + GetDlgItemText(hwndDlg,IDC_FILEDIR,dat->szSavePath,SIZEOF(dat->szSavePath)); + GetDlgItemText(hwndDlg,IDC_FILE,dat->szFilenames,SIZEOF(dat->szFilenames)); + GetDlgItemText(hwndDlg,IDC_MSG,dat->szMsg,SIZEOF(dat->szMsg)); + dat->hwndTransfer = FtMgr_AddTransfer(dat); + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, 0); + //check for auto-minimize here to fix BUG#647620 + if(DBGetContactSettingByte(NULL,"SRFile","AutoAccept",0) && DBGetContactSettingByte(NULL,"SRFile","AutoMin",0)) { + ShowWindow(hwndDlg,SW_HIDE); + ShowWindow(hwndDlg,SW_SHOWMINNOACTIVE); + } + DestroyWindow(hwndDlg); + break; + + case IDCANCEL: + if (dat->fs) CallContactService(dat->hContact,PSS_FILEDENYT,(WPARAM)dat->fs,(LPARAM)TranslateT("Cancelled")); + dat->fs=NULL; /* the protocol will free the handle */ + DestroyWindow(hwndDlg); + break; + + case IDC_ADD: + { ADDCONTACTSTRUCT acs={0}; + + acs.handle=dat->hContact; + acs.handleType=HANDLE_CONTACT; + acs.szProto=""; + CallService(MS_ADDCONTACT_SHOW,(WPARAM)hwndDlg,(LPARAM)&acs); + if(!DBGetContactSettingByte(dat->hContact,"CList","NotOnList",0)) + ShowWindow(GetDlgItem(hwndDlg,IDC_ADD), SW_HIDE); + } + break; + + case IDC_USERMENU: + { RECT rc; + HMENU hMenu=(HMENU)CallService(MS_CLIST_MENUBUILDCONTACT,(WPARAM)dat->hContact,0); + GetWindowRect((HWND)lParam,&rc); + TrackPopupMenu(hMenu,0,rc.left,rc.bottom,0,hwndDlg,NULL); + DestroyMenu(hMenu); + } + break; + + case IDC_DETAILS: + CallService(MS_USERINFO_SHOWDIALOG,(WPARAM)dat->hContact,0); + break; + + case IDC_HISTORY: + CallService(MS_HISTORY_SHOWCONTACTHISTORY,(WPARAM)dat->hContact,0); + break; + } + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Button_FreeIcon_IcoLib(hwndDlg,IDC_ADD); + Button_FreeIcon_IcoLib(hwndDlg,IDC_DETAILS); + Button_FreeIcon_IcoLib(hwndDlg,IDC_HISTORY); + Button_FreeIcon_IcoLib(hwndDlg,IDC_USERMENU); + + if ( dat ) FreeFileDlgData( dat ); + break; + } + return FALSE; +} diff --git a/src/modules/srfile/filesenddlg.cpp b/src/modules/srfile/filesenddlg.cpp new file mode 100644 index 0000000000..94c24a2479 --- /dev/null +++ b/src/modules/srfile/filesenddlg.cpp @@ -0,0 +1,363 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include +#include "file.h" + +static void SetFileListAndSizeControls(HWND hwndDlg,struct FileDlgData *dat) +{ + int fileCount=0,dirCount=0,totalSize=0,i; + struct _stat statbuf; + TCHAR str[64]; + + for ( i=0; dat->files[i]; i++ ) { + if ( _tstat( dat->files[i], &statbuf ) == 0 ) { + if ( statbuf.st_mode & _S_IFDIR) + dirCount++; + else + fileCount++; + totalSize += statbuf.st_size; + } } + + GetSensiblyFormattedSize(totalSize,str,SIZEOF(str),0,1,NULL); + SetDlgItemText(hwndDlg,IDC_TOTALSIZE,str); + if(i>1) { + TCHAR szFormat[32]; + if(fileCount && dirCount) { + mir_sntprintf(szFormat,SIZEOF(szFormat),_T("%s, %s"),TranslateTS(fileCount==1?_T("%d file"):_T("%d files")),TranslateTS(dirCount==1?_T("%d directory"):_T("%d directories"))); + mir_sntprintf(str,SIZEOF(str),szFormat,fileCount,dirCount); + } + else if(fileCount) { + lstrcpy(szFormat,TranslateT("%d files")); + mir_sntprintf(str,SIZEOF(str),szFormat,fileCount); + } + else { + lstrcpy(szFormat,TranslateT("%d directories")); + mir_sntprintf(str,SIZEOF(str),szFormat,dirCount); + } + SetDlgItemText(hwndDlg,IDC_FILE,str); + } + else SetDlgItemText(hwndDlg,IDC_FILE,dat->files[0]); + + EnableWindow(GetDlgItem(hwndDlg, IDOK), fileCount || dirCount); +} + +static void FilenameToFileList(HWND hwndDlg, struct FileDlgData* dat, const TCHAR* buf) +{ + DWORD dwFileAttributes; + + // Make sure that the file matrix is empty (the user may select files several times) + FreeFilesMatrix(&dat->files); + + // Get the file attributes of selection + dwFileAttributes = GetFileAttributes( buf ); + if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) + return; + + // Check if the selection is a directory or a file + if ( GetFileAttributes( buf ) & FILE_ATTRIBUTE_DIRECTORY ) { + const TCHAR* pBuf; + int nNumberOfFiles = 0; + int nTemp; + int fileOffset; + + // :NOTE: The first string in the buffer is the directory, followed by a + // NULL separated list of all files + + // fileOffset is the offset to the first file. + fileOffset = lstrlen(buf) + 1; + + // Count number of files + pBuf = buf + fileOffset; + while ( *pBuf ) { + pBuf += lstrlen(pBuf) + 1; + nNumberOfFiles++; + } + + // Allocate memory for a pointer array + if (( dat->files = ( TCHAR* *)mir_alloc((nNumberOfFiles + 1) * sizeof(TCHAR*))) == NULL ) + return; + + // Fill the array + pBuf = buf + fileOffset; + nTemp = 0; + while(*pBuf) + { + // Allocate space for path+filename + int cbFileNameLen = lstrlen( pBuf ); + dat->files[nTemp] = ( TCHAR* )mir_alloc( sizeof(TCHAR)*(fileOffset + cbFileNameLen + 1)); + + // Add path to filename and copy into array + #if defined( _UNICODE ) + CopyMemory(dat->files[nTemp], buf, (fileOffset-1)*sizeof( TCHAR )); + dat->files[nTemp][fileOffset-1] = '\\'; + _tcscpy(dat->files[nTemp] + fileOffset - (buf[fileOffset-2]=='\\'?1:0), pBuf); + #else + CopyMemory(dat->files[nTemp], buf, fileOffset-1 ); + dat->files[nTemp][fileOffset-1] = '\\'; + strcpy(dat->files[nTemp] + fileOffset - (buf[fileOffset-2]=='\\'?1:0), pBuf); + #endif + // Move pointers to next file... + pBuf += cbFileNameLen + 1; + nTemp++; + } + // Terminate array + dat->files[nNumberOfFiles] = NULL; + } + // ...the selection is a single file + else + { + if (( dat->files = ( TCHAR **)mir_alloc(2 * sizeof( TCHAR*))) == NULL ) // Leaks when aborted + return; + + dat->files[0] = mir_tstrdup(buf); + dat->files[1] = NULL; + } + + // Update dialog text with new file selection + SetFileListAndSizeControls(hwndDlg, dat); +} + +#define M_FILECHOOSEDONE (WM_USER+100) +void __cdecl ChooseFilesThread(void* param) +{ + HWND hwndDlg = ( HWND )param; + TCHAR filter[128], *pfilter; + TCHAR* buf = ( TCHAR* )mir_alloc( sizeof(TCHAR)*32767 ); + if ( buf == NULL ) + PostMessage( hwndDlg, M_FILECHOOSEDONE, 0, ( LPARAM )( TCHAR* )NULL ); + else { + OPENFILENAME ofn = {0}; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + lstrcpy( filter, TranslateT( "All Files" )); + lstrcat( filter, _T(" (*)" )); + pfilter = filter + lstrlen( filter )+1; + lstrcpy( pfilter, _T( "*" )); + pfilter = filter + lstrlen( filter )+1; + pfilter[ 0 ] = '\0'; + ofn.lpstrFilter = filter; + ofn.lpstrFile = buf; *buf = 0; + ofn.nMaxFile = 32767; + ofn.Flags = OFN_NOCHANGEDIR | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_DONTADDTORECENT; + if ( GetOpenFileName( &ofn )) + PostMessage( hwndDlg, M_FILECHOOSEDONE, 0, ( LPARAM )buf ); + else { + mir_free( buf ); + PostMessage( hwndDlg, M_FILECHOOSEDONE, 0, ( LPARAM )( TCHAR* )NULL ); +} } } + +static BOOL CALLBACK ClipSiblingsChildEnumProc(HWND hwnd,LPARAM) +{ + SetWindowLongPtr(hwnd,GWL_STYLE,GetWindowLongPtr(hwnd,GWL_STYLE)|WS_CLIPSIBLINGS); + return TRUE; +} + +static WNDPROC OldSendEditProc; +static LRESULT CALLBACK SendEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) { + case WM_CHAR: + if(wParam=='\n' && GetKeyState(VK_CONTROL)&0x8000) { + PostMessage(GetParent(hwnd),WM_COMMAND,IDOK,0); + return 0; + } + break; + case WM_SYSCHAR: + if((wParam=='s' || wParam=='S') && GetKeyState(VK_MENU)&0x8000) { + PostMessage(GetParent(hwnd),WM_COMMAND,IDOK,0); + return 0; + } + break; + } + return CallWindowProc(OldSendEditProc,hwnd,msg,wParam,lParam); +} + +INT_PTR CALLBACK DlgProcSendFile(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct FileDlgData *dat; + + dat=(struct FileDlgData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + { + struct FileSendData *fsd=(struct FileSendData*)lParam; + + dat=(struct FileDlgData*)mir_calloc(sizeof(struct FileDlgData)); + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)dat); + dat->hContact=fsd->hContact; + dat->send=1; + dat->hPreshutdownEvent=HookEventMessage(ME_SYSTEM_PRESHUTDOWN,hwndDlg,M_PRESHUTDOWN); + dat->fs=NULL; + dat->dwTicks=GetTickCount(); + + TranslateDialogDefault(hwndDlg); + EnumChildWindows(hwndDlg,ClipSiblingsChildEnumProc,0); + OldSendEditProc=(WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MSG),GWLP_WNDPROC,(LONG_PTR)SendEditSubclassProc); + + Window_SetIcon_IcoLib(hwndDlg, SKINICON_EVENT_FILE); + Button_SetIcon_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View User's Details")); + Button_SetIcon_IcoLib(hwndDlg, IDC_HISTORY, SKINICON_OTHER_HISTORY, LPGEN("View User's History")); + Button_SetIcon_IcoLib(hwndDlg, IDC_USERMENU, SKINICON_OTHER_DOWNARROW, LPGEN("User Menu")); + + EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); + + if(fsd->ppFiles!=NULL && fsd->ppFiles[0]!=NULL) { + int totalCount,i; + for(totalCount=0;fsd->ppFiles[totalCount];totalCount++); + dat->files = ( TCHAR** )mir_alloc( sizeof(TCHAR*)*(totalCount+1)); // Leaks + for(i=0;ifiles[i] = mir_tstrdup( fsd->ppFiles[i] ); + dat->files[totalCount]=NULL; + SetFileListAndSizeControls(hwndDlg,dat); + } + { + char *szProto; + TCHAR* contactName = cli.pfnGetContactDisplayName( dat->hContact, 0 ); + SetDlgItemText(hwndDlg,IDC_TO,contactName); + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + if (szProto) { + CONTACTINFO ci; + int hasName = 0; + char buf[128]; + ZeroMemory(&ci,sizeof(ci)); + + ci.cbSize = sizeof(ci); + ci.hContact = dat->hContact; + ci.szProto = szProto; + ci.dwFlag = CNF_UNIQUEID; + if (!CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) { + switch(ci.type) { + case CNFT_ASCIIZ: + hasName = 1; + mir_snprintf(buf, SIZEOF(buf), "%s", ci.pszVal); + mir_free(ci.pszVal); + break; + case CNFT_DWORD: + hasName = 1; + mir_snprintf(buf, SIZEOF(buf),"%u",ci.dVal); + break; + } } + + if ( hasName ) + SetDlgItemTextA(hwndDlg,IDC_NAME,buf); + else + SetDlgItemText(hwndDlg,IDC_NAME,contactName); + } } + + if ( fsd->ppFiles == NULL ) { + EnableWindow(hwndDlg, FALSE); + dat->closeIfFileChooseCancelled=1; + PostMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_CHOOSE,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDC_CHOOSE)); + } + return TRUE; + } + + case WM_MEASUREITEM: + return CallService(MS_CLIST_MENUMEASUREITEM,wParam,lParam); + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis=(LPDRAWITEMSTRUCT)lParam; + if(dis->hwndItem==GetDlgItem(hwndDlg, IDC_PROTOCOL)) { + char *szProto; + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + if (szProto) { + HICON hIcon = (HICON)CallProtoService(szProto,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0); + if (hIcon) { + DrawIconEx(dis->hDC,dis->rcItem.left,dis->rcItem.top,hIcon,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0,NULL,DI_NORMAL); + DestroyIcon(hIcon); + } } } } + return CallService(MS_CLIST_MENUDRAWITEM,wParam,lParam); + + case M_FILECHOOSEDONE: + if( lParam != 0 ) { + FilenameToFileList( hwndDlg, dat, ( TCHAR* )lParam ); + mir_free(( TCHAR* )lParam ); + dat->closeIfFileChooseCancelled = 0; + } + else if(dat->closeIfFileChooseCancelled) DestroyWindow(hwndDlg); + EnableWindow(hwndDlg,TRUE); + break; + + case WM_COMMAND: + if(CallService(MS_CLIST_MENUPROCESSCOMMAND,MAKEWPARAM(LOWORD(wParam),MPCF_CONTACTMENU),(LPARAM)dat->hContact)) + break; + switch (LOWORD(wParam)) + { + case IDC_CHOOSE: + EnableWindow(hwndDlg,FALSE); + //GetOpenFileName() creates its own message queue which prevents any incoming events being processed + forkthread(ChooseFilesThread,0,hwndDlg); + break; + case IDOK: + EnableWindow(GetDlgItem(hwndDlg,IDC_FILENAME),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_MSG),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_CHOOSE),FALSE); + + GetDlgItemText(hwndDlg,IDC_FILEDIR,dat->szSavePath,SIZEOF(dat->szSavePath)); + GetDlgItemText(hwndDlg,IDC_FILE,dat->szFilenames,SIZEOF(dat->szFilenames)); + GetDlgItemText(hwndDlg,IDC_MSG,dat->szMsg,SIZEOF(dat->szMsg)); + dat->hwndTransfer = FtMgr_AddTransfer(dat); + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, 0); + DestroyWindow(hwndDlg); + return TRUE; + + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + + case IDC_USERMENU: + { RECT rc; + HMENU hMenu=(HMENU)CallService(MS_CLIST_MENUBUILDCONTACT,(WPARAM)dat->hContact,0); + GetWindowRect((HWND)lParam,&rc); + TrackPopupMenu(hMenu,0,rc.left,rc.bottom,0,hwndDlg,NULL); + DestroyMenu(hMenu); + break; + } + case IDC_DETAILS: + CallService(MS_USERINFO_SHOWDIALOG,(WPARAM)dat->hContact,0); + return TRUE; + case IDC_HISTORY: + CallService(MS_HISTORY_SHOWCONTACTHISTORY,(WPARAM)dat->hContact,0); + return TRUE; + } + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Button_FreeIcon_IcoLib(hwndDlg,IDC_DETAILS); + Button_FreeIcon_IcoLib(hwndDlg,IDC_HISTORY); + Button_FreeIcon_IcoLib(hwndDlg,IDC_USERMENU); + + if ( dat ) + FreeFileDlgData( dat ); + + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MSG),GWLP_WNDPROC,(LONG_PTR)OldSendEditProc); + return TRUE; + } + return FALSE; +} diff --git a/src/modules/srfile/filexferdlg.cpp b/src/modules/srfile/filexferdlg.cpp new file mode 100644 index 0000000000..0a6246dea0 --- /dev/null +++ b/src/modules/srfile/filexferdlg.cpp @@ -0,0 +1,799 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include "file.h" + +#define HM_RECVEVENT (WM_USER+10) + +static int CheckVirusScanned(HWND hwnd,struct FileDlgData *dat,int i) +{ + if(dat->send) return 1; + if(dat->fileVirusScanned == NULL) return 0; + if(dat->fileVirusScanned[i]) return 1; + if(DBGetContactSettingByte(NULL,"SRFile","WarnBeforeOpening",1)==0) return 1; + return IDYES==MessageBox(hwnd,TranslateT("This file has not yet been scanned for viruses. Are you certain you want to open it?"),TranslateT("File Received"),MB_YESNO|MB_DEFBUTTON2); +} + +#define M_VIRUSSCANDONE (WM_USER+100) +struct virusscanthreadstartinfo { + TCHAR *szFile; + int returnCode; + HWND hwndReply; +}; + +TCHAR* PFTS_StringToTchar( int flags, const PROTOCHAR* s ) +{ +#ifdef _UNICODE + if ( flags & PFTS_UTF ) + return Utf8DecodeUcs2(( char* )s ); + else if ( flags & PFTS_UNICODE ) + return mir_tstrdup( s ); + else + return mir_a2t(( char* )s ); +#else + if ( flags & PFTS_UTF ) { + char *szAnsi = mir_strdup(( char* )s ); + return Utf8Decode(szAnsi, NULL); + } + else + return mir_strdup( s ); +#endif +} + +int PFTS_CompareWithTchar( PROTOFILETRANSFERSTATUS* ft, const PROTOCHAR* s, TCHAR* r ) +{ +#ifdef _UNICODE + if ( ft->flags & PFTS_UTF ) { + TCHAR* ts = Utf8DecodeUcs2(( char* )s ); + int res = _tcscmp( ts, r ); + mir_free( ts ); + return res; + } + else if ( ft->flags & PFTS_UNICODE ) + return _tcscmp( s, r ); + else { + TCHAR* ts = mir_a2t(( char* )s ); + int res = _tcscmp( ts, r ); + mir_free( ts ); + return res; + } +#else + if ( ft->flags & PFTS_UTF ) { + char *ts = NEWSTR_ALLOCA(( char* )s ); + return _tcscmp( Utf8Decode(( char* )ts, NULL), r ); + } + else + return _tcscmp( s, r ); +#endif +} + +static void SetOpenFileButtonStyle(HWND hwndButton,int enabled) +{ + EnableWindow(hwndButton,enabled); +} + +void FillSendData( FileDlgData* dat, DBEVENTINFO& dbei ) +{ + dbei.cbSize = sizeof(dbei); + dbei.szModule = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + dbei.eventType = EVENTTYPE_FILE; + dbei.flags = DBEF_SENT; + dbei.timestamp = time(NULL); + #if defined( _UNICODE ) + char *szFileNames = Utf8EncodeT(dat->szFilenames), *szMsg = Utf8EncodeT(dat->szMsg); + dbei.flags |= DBEF_UTF; + #else + char *szFileNames = dat->szFilenames, *szMsg = dat->szMsg; + #endif + + dbei.cbBlob = sizeof(DWORD) + lstrlenA(szFileNames)+lstrlenA(szMsg)+2; + dbei.pBlob=(PBYTE)mir_alloc(dbei.cbBlob); + *(PDWORD)dbei.pBlob=0; + lstrcpyA((char*)dbei.pBlob+sizeof(DWORD),szFileNames); + lstrcpyA((char*)dbei.pBlob+sizeof(DWORD)+lstrlenA(szFileNames)+1,szMsg); + + #if defined( _UNICODE ) + mir_free( szFileNames ), mir_free( szMsg ); + #endif +} + +static void __cdecl RunVirusScannerThread(struct virusscanthreadstartinfo *info) +{ + PROCESS_INFORMATION pi; + STARTUPINFO si={0}; + DBVARIANT dbv; + TCHAR szCmdLine[768]; + + if (!DBGetContactSettingTString(NULL,"SRFile", "ScanCmdLine", &dbv)) + { + if(dbv.ptszVal[0]) + { + TCHAR *pszReplace; + si.cb=sizeof(si); + pszReplace = _tcsstr(dbv.ptszVal, _T("%f")); + if (pszReplace) + { + if ( info->szFile[_tcslen(info->szFile) - 1] == '\\') + info->szFile[_tcslen(info->szFile) - 1] = '\0'; + *pszReplace = 0; + mir_sntprintf(szCmdLine, SIZEOF(szCmdLine), _T("%s\"%s\"%s"), dbv.ptszVal, info->szFile, pszReplace+2); + } + else lstrcpyn(szCmdLine, dbv.ptszVal, SIZEOF(szCmdLine)); + if(CreateProcess(NULL,szCmdLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)) { + if(WaitForSingleObject(pi.hProcess,3600*1000)==WAIT_OBJECT_0) + PostMessage(info->hwndReply,M_VIRUSSCANDONE,info->returnCode,0); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + } + DBFreeVariant(&dbv); + } + mir_free(info->szFile); + mir_free(info); +} + +static void SetFilenameControls(HWND hwndDlg, struct FileDlgData *dat, PROTOFILETRANSFERSTATUS *fts) +{ + TCHAR msg[MAX_PATH]; + TCHAR *fnbuf = NULL, *fn = NULL; + SHFILEINFO shfi = {0}; + + if ( fts->tszCurrentFile ) { + fnbuf = mir_tstrdup( fts->tszCurrentFile ); + if (( fn = _tcsrchr( fnbuf, '\\' )) == NULL ) + fn = fnbuf; + else fn++; + } + + if (dat->hIcon) DestroyIcon(dat->hIcon); dat->hIcon = NULL; + + if (fn && (fts->totalFiles > 1)) { + mir_sntprintf(msg, SIZEOF(msg), _T("%s: %s (%d %s %d)"), + cli.pfnGetContactDisplayName( fts->hContact, 0 ), + fn, fts->currentFileNumber+1, TranslateT("of"), fts->totalFiles); + + SHGetFileInfo(fn, FILE_ATTRIBUTE_DIRECTORY, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SMALLICON); + dat->hIcon = shfi.hIcon; + } + else if (fn) { + mir_sntprintf(msg, SIZEOF(msg), _T("%s: %s"), cli.pfnGetContactDisplayName( fts->hContact, 0 ), fn); + + SHGetFileInfo(fn, FILE_ATTRIBUTE_NORMAL, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SMALLICON); + dat->hIcon = shfi.hIcon; + } + else { + lstrcpyn(msg, cli.pfnGetContactDisplayName( fts->hContact, 0 ), SIZEOF(msg)); + HICON hIcon = LoadSkinIcon(SKINICON_OTHER_DOWNARROW); + dat->hIcon = CopyIcon(hIcon); + IconLib_ReleaseIcon(hIcon, NULL); + } + + mir_free( fnbuf ); + + SendDlgItemMessage(hwndDlg, IDC_FILEICON, STM_SETIMAGE, IMAGE_ICON, (LPARAM)dat->hIcon); + SetDlgItemText(hwndDlg, IDC_CONTACTNAME, msg); +} + +enum { FTS_TEXT, FTS_PROGRESS, FTS_OPEN }; +static void SetFtStatus(HWND hwndDlg, TCHAR *text, int mode) +{ + SetDlgItemText(hwndDlg,IDC_STATUS,TranslateTS(text)); + SetDlgItemText(hwndDlg,IDC_TRANSFERCOMPLETED,TranslateTS(text)); + + ShowWindow(GetDlgItem(hwndDlg,IDC_STATUS), (mode == FTS_TEXT)?SW_SHOW:SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg,IDC_ALLFILESPROGRESS), (mode == FTS_PROGRESS)?SW_SHOW:SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg,IDC_TRANSFERCOMPLETED), (mode == FTS_OPEN)?SW_SHOW:SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg,IDC_FILEICON), (mode == FTS_OPEN)?SW_SHOW:SW_HIDE); +} + +static void HideProgressControls(HWND hwndDlg) +{ + RECT rc; + char buf[64]; + + GetWindowRect(GetDlgItem(hwndDlg, IDC_ALLPRECENTS), &rc); + MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc, 2); + SetWindowPos(hwndDlg, NULL, 0, 0, 100, rc.bottom+3, SWP_NOMOVE|SWP_NOZORDER); + ShowWindow(GetDlgItem(hwndDlg, IDC_ALLTRANSFERRED), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_ALLSPEED), SW_HIDE); + + _strtime(buf); + SetDlgItemTextA(hwndDlg, IDC_ALLPRECENTS, buf); + + PostMessage(GetParent(hwndDlg), WM_FT_RESIZE, 0, (LPARAM)hwndDlg); +} + +static int FileTransferDlgResizer(HWND, LPARAM, UTILRESIZECONTROL *urc) +{ + switch(urc->wId) { + case IDC_CONTACTNAME: + case IDC_STATUS: + case IDC_ALLFILESPROGRESS: + case IDC_TRANSFERCOMPLETED: + return RD_ANCHORX_WIDTH|RD_ANCHORY_TOP; + case IDC_FRAME: + return RD_ANCHORX_WIDTH|RD_ANCHORY_BOTTOM; + case IDC_ALLPRECENTS: + case IDCANCEL: + case IDC_OPENFILE: + case IDC_OPENFOLDER: + return RD_ANCHORX_RIGHT|RD_ANCHORY_TOP; + + case IDC_ALLTRANSFERRED: + urc->rcItem.right = urc->rcItem.left + (urc->rcItem.right - urc->rcItem.left - urc->dlgOriginalSize.cx + urc->dlgNewSize.cx) / 3; + return RD_ANCHORX_CUSTOM|RD_ANCHORY_CUSTOM; + + case IDC_ALLSPEED: + urc->rcItem.right = urc->rcItem.right - urc->dlgOriginalSize.cx + urc->dlgNewSize.cx; + urc->rcItem.left = urc->rcItem.left + (urc->rcItem.right - urc->rcItem.left) / 3; + return RD_ANCHORX_CUSTOM|RD_ANCHORY_CUSTOM; + } + return RD_ANCHORX_LEFT|RD_ANCHORY_TOP; +} + +INT_PTR CALLBACK DlgProcFileTransfer(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + FileDlgData *dat = (FileDlgData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + dat = (FileDlgData*)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->hNotifyEvent=HookEventMessage(ME_PROTO_ACK,hwndDlg,HM_RECVEVENT); + dat->transferStatus.currentFileNumber = -1; + if(dat->send) { + dat->fs=(HANDLE)CallContactService(dat->hContact,PSS_FILET,(WPARAM)dat->szMsg,(LPARAM)dat->files); + SetFtStatus(hwndDlg, LPGENT("Request sent, waiting for acceptance..."), FTS_TEXT); + SetOpenFileButtonStyle(GetDlgItem(hwndDlg,IDC_OPENFILE),1); + dat->waitingForAcceptance=1; + // hide "open" button since it may cause potential access violations... + ShowWindow(GetDlgItem(hwndDlg, IDC_OPENFILE), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_OPENFOLDER), SW_HIDE); + } + else { //recv + CreateDirectoryTreeT(dat->szSavePath); + dat->fs=(HANDLE)CallContactService(dat->hContact,PSS_FILEALLOWT,(WPARAM)dat->fs,(LPARAM)dat->szSavePath); + dat->transferStatus.tszWorkingDir = mir_tstrdup(dat->szSavePath); + if(DBGetContactSettingByte(dat->hContact,"CList","NotOnList",0)) dat->resumeBehaviour=FILERESUME_ASK; + else dat->resumeBehaviour=DBGetContactSettingByte(NULL,"SRFile","IfExists",FILERESUME_ASK); + SetFtStatus(hwndDlg, LPGENT("Waiting for connection..."), FTS_TEXT); + } + { + /* check we actually got an fs handle back from the protocol */ + if (!dat->fs) { + SetFtStatus(hwndDlg, LPGENT("Unable to initiate transfer."), FTS_TEXT); + dat->waitingForAcceptance=0; + } + } + { LOGFONT lf; + HFONT hFont; + hFont=(HFONT)SendDlgItemMessage(hwndDlg,IDC_CONTACTNAME,WM_GETFONT,0,0); + GetObject(hFont,sizeof(lf),&lf); + lf.lfWeight=FW_BOLD; + hFont=CreateFontIndirect(&lf); + SendDlgItemMessage(hwndDlg,IDC_CONTACTNAME,WM_SETFONT,(WPARAM)hFont,0); + } + + { SHFILEINFO shfi = {0}; + SHGetFileInfo(_T(""), FILE_ATTRIBUTE_DIRECTORY, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SMALLICON); + dat->hIconFolder = shfi.hIcon; + } + + dat->hIcon = NULL; + + SendDlgItemMessage(hwndDlg, IDC_CONTACT, BM_SETIMAGE, IMAGE_ICON, + (LPARAM)LoadSkinnedProtoIcon((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)dat->hContact, 0), ID_STATUS_ONLINE)); + SendDlgItemMessage(hwndDlg, IDC_CONTACT, BUTTONADDTOOLTIP, (WPARAM)LPGEN("Contact menu"), 0); + SendDlgItemMessage(hwndDlg, IDC_CONTACT, BUTTONSETASFLATBTN, 0, 0); + + Button_SetIcon_IcoLib(hwndDlg, IDC_OPENFILE, SKINICON_OTHER_DOWNARROW, LPGEN("Open...")); + SendDlgItemMessage(hwndDlg, IDC_OPENFILE, BUTTONSETASPUSHBTN, 0, 0); + + SendDlgItemMessage(hwndDlg, IDC_OPENFOLDER, BM_SETIMAGE, IMAGE_ICON, (LPARAM)dat->hIconFolder); + SendDlgItemMessage(hwndDlg, IDC_OPENFOLDER, BUTTONADDTOOLTIP, (WPARAM)LPGEN("Open folder"), 0); + SendDlgItemMessage(hwndDlg, IDC_OPENFOLDER, BUTTONSETASFLATBTN, 0, 0); + + Button_SetIcon_IcoLib(hwndDlg, IDCANCEL, SKINICON_OTHER_DELETE, LPGEN("Cancel")); + + SetDlgItemText(hwndDlg, IDC_CONTACTNAME, cli.pfnGetContactDisplayName( dat->hContact, 0 )); + + if(!dat->waitingForAcceptance) SetTimer(hwndDlg,1,1000,NULL); + return TRUE; + case WM_TIMER: + MoveMemory(dat->bytesRecvedHistory+1,dat->bytesRecvedHistory,sizeof(dat->bytesRecvedHistory)-sizeof(dat->bytesRecvedHistory[0])); + dat->bytesRecvedHistory[0]=dat->transferStatus.totalProgress; + if ( dat->bytesRecvedHistorySize < SIZEOF(dat->bytesRecvedHistory)) + dat->bytesRecvedHistorySize++; + + { TCHAR szSpeed[32], szTime[32], szDisplay[96]; + SYSTEMTIME st; + ULARGE_INTEGER li; + FILETIME ft; + + GetSensiblyFormattedSize((dat->bytesRecvedHistory[0]-dat->bytesRecvedHistory[dat->bytesRecvedHistorySize-1])/dat->bytesRecvedHistorySize,szSpeed,SIZEOF(szSpeed),0,1,NULL); + if(dat->bytesRecvedHistory[0]==dat->bytesRecvedHistory[dat->bytesRecvedHistorySize-1]) + lstrcpy(szTime,_T("??:??:??")); + else { + li.QuadPart=BIGI(10000000)*(dat->transferStatus.currentFileSize-dat->transferStatus.currentFileProgress)*dat->bytesRecvedHistorySize/(dat->bytesRecvedHistory[0]-dat->bytesRecvedHistory[dat->bytesRecvedHistorySize-1]); + ft.dwHighDateTime=li.HighPart; ft.dwLowDateTime=li.LowPart; + FileTimeToSystemTime(&ft,&st); + GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT|TIME_NOTIMEMARKER,&st,NULL,szTime,SIZEOF(szTime)); + } + if(dat->bytesRecvedHistory[0]!=dat->bytesRecvedHistory[dat->bytesRecvedHistorySize-1]) { + li.QuadPart=BIGI(10000000)*(dat->transferStatus.totalBytes-dat->transferStatus.totalProgress)*dat->bytesRecvedHistorySize/(dat->bytesRecvedHistory[0]-dat->bytesRecvedHistory[dat->bytesRecvedHistorySize-1]); + ft.dwHighDateTime=li.HighPart; ft.dwLowDateTime=li.LowPart; + FileTimeToSystemTime(&ft,&st); + GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT|TIME_NOTIMEMARKER,&st,NULL,szTime,SIZEOF(szTime)); + } + + mir_sntprintf(szDisplay,SIZEOF(szDisplay),_T("%s/%s (%s %s)"),szSpeed,TranslateT("sec"),szTime,TranslateT("remaining")); + SetDlgItemText(hwndDlg,IDC_ALLSPEED,szDisplay); + } + break; + + case WM_MEASUREITEM: + return CallService(MS_CLIST_MENUMEASUREITEM,wParam,lParam); + + case WM_DRAWITEM: + return CallService(MS_CLIST_MENUDRAWITEM,wParam,lParam); + + case WM_FT_CLEANUP: + if (!dat->fs) + { + PostMessage(GetParent(hwndDlg), WM_FT_REMOVE, 0, (LPARAM)hwndDlg); + DestroyWindow(hwndDlg); + } + break; + + case WM_COMMAND: + if ( CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam),MPCF_CONTACTMENU), (LPARAM)dat->hContact )) + break; + + switch (LOWORD(wParam)) + { + case IDOK: + case IDCANCEL: + PostMessage(GetParent(hwndDlg), WM_FT_REMOVE, 0, (LPARAM)hwndDlg); + DestroyWindow(hwndDlg); + break; + + case IDC_CONTACT: + { RECT rc; + HMENU hMenu=(HMENU)CallService(MS_CLIST_MENUBUILDCONTACT,(WPARAM)dat->hContact,0); + GetWindowRect((HWND)lParam,&rc); + TrackPopupMenu(hMenu,0,rc.left,rc.bottom,0,hwndDlg,NULL); + DestroyMenu(hMenu); + break; + } + + case IDC_TRANSFERCOMPLETED: + if (dat->transferStatus.currentFileNumber <= 1 && CheckVirusScanned(hwndDlg, dat, 0)) + { + ShellExecute(NULL, NULL, dat->files[0], NULL, NULL, SW_SHOW); + break; + } + + case IDC_OPENFOLDER: + if ( dat ) + { + TCHAR* path = dat->transferStatus.tszWorkingDir; + if (!path || !path[0]) + { + path = NEWTSTR_ALLOCA(dat->transferStatus.tszCurrentFile); + TCHAR* p = _tcsrchr(path, '\\'); if (p) *p = 0; + } + + if (path) ShellExecute(NULL, _T("open"), path, NULL, NULL, SW_SHOW); + } + break; + + case IDC_OPENFILE: + { + TCHAR **files; + HMENU hMenu; + RECT rc; + int ret; + + if (dat->send) + if (dat->files == NULL) + files = dat->transferStatus.ptszFiles; + else + files = dat->files; + else + files=dat->files; + + hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, 1, TranslateT("Open folder")); + AppendMenu(hMenu, MF_SEPARATOR, 0, 0); + + if (files && *files) + { + int i, limit; + TCHAR *pszFilename,*pszNewFileName; + + if (dat->send) + limit = dat->transferStatus.totalFiles; + else + limit = dat->transferStatus.currentFileNumber; + + // Loop over all transfered files and add them to the menu + for (i = 0; i < limit; i++) { + pszFilename = _tcsrchr(files[i], '\\'); + if (pszFilename == NULL) + pszFilename = files[i]; + else + pszFilename++; + { + if (pszFilename) { + size_t cbFileNameLen = _tcslen(pszFilename); + + pszNewFileName = (TCHAR*)mir_alloc( cbFileNameLen*2*sizeof( TCHAR )); + TCHAR *p = pszNewFileName; + for (size_t pszlen=0; pszlen < cbFileNameLen; pszlen++) { + *p++ = pszFilename[pszlen]; + if (pszFilename[pszlen]=='&') + *p++ = '&'; + } + *p = '\0'; + AppendMenu(hMenu, MF_STRING, i+10, pszNewFileName); + mir_free(pszNewFileName); + } + } + } + } + + GetWindowRect((HWND)lParam, &rc); + CheckDlgButton(hwndDlg, IDC_OPENFILE, BST_CHECKED); + ret = TrackPopupMenu(hMenu, TPM_RETURNCMD|TPM_RIGHTALIGN, rc.right, rc.bottom, 0, hwndDlg, NULL); + CheckDlgButton(hwndDlg, IDC_OPENFILE, BST_UNCHECKED); + DestroyMenu(hMenu); + + if (ret == 1) + { + TCHAR* path = dat->transferStatus.tszWorkingDir; + if (!path || !path[0]) + { + path = NEWTSTR_ALLOCA(dat->transferStatus.tszCurrentFile); + TCHAR* p = _tcsrchr(path, '\\'); if (p) *p = 0; + } + + if (path) ShellExecute(NULL, _T("open"), path, NULL, NULL, SW_SHOW); + } + else if (ret && CheckVirusScanned(hwndDlg, dat, ret)) + ShellExecute(NULL, NULL, files[ret-10], NULL, NULL, SW_SHOW); + + break; + } + } + break; + case M_FILEEXISTSDLGREPLY: + { PROTOFILERESUME *pfr=(PROTOFILERESUME*)lParam; + TCHAR *szOriginalFilename=(TCHAR*)wParam; + char *szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + + EnableWindow(hwndDlg,TRUE); + switch(pfr->action) { + case FILERESUME_CANCEL: + if (dat->fs) CallContactService(dat->hContact,PSS_FILECANCEL,(WPARAM)dat->fs,0); + dat->fs=NULL; + mir_free(szOriginalFilename); + if(pfr->szFilename) mir_free((char*)pfr->szFilename); + mir_free(pfr); + return 0; + case FILERESUME_RESUMEALL: + case FILERESUME_OVERWRITEALL: + dat->resumeBehaviour=pfr->action; + pfr->action&=~FILERESUMEF_ALL; + break; + case FILERESUME_RENAMEALL: + pfr->action=FILERESUME_RENAME; + { TCHAR *pszExtension,*pszFilename; + int i; + if((pszFilename = _tcsrchr(szOriginalFilename,'\\'))==NULL) pszFilename=szOriginalFilename; + if((pszExtension = _tcsrchr(pszFilename+1,'.'))==NULL) pszExtension=pszFilename+lstrlen(pszFilename); + if(pfr->szFilename) mir_free((TCHAR*)pfr->szFilename); + pfr->szFilename = (TCHAR*)mir_alloc(sizeof(TCHAR)*((pszExtension-szOriginalFilename)+21+lstrlen(pszExtension))); + for(i=1;;i++) { + _stprintf((TCHAR*)pfr->szFilename,_T("%.*s (%u)%s"),pszExtension-szOriginalFilename,szOriginalFilename,i,pszExtension); + if(_taccess(pfr->szFilename,0)!=0) + break; + } + } + break; + } + mir_free(szOriginalFilename); + CallProtoService(szProto,PS_FILERESUMET,(WPARAM)dat->fs,(LPARAM)pfr); + if(pfr->szFilename) mir_free((char*)pfr->szFilename); + mir_free(pfr); + break; + } + case HM_RECVEVENT: + { ACKDATA *ack=(ACKDATA*)lParam; + if (ack->hProcess!=dat->fs) break; /* icq abuses this sometimes */ + if(ack->hContact!=dat->hContact) break; + if(ack->type!=ACKTYPE_FILE) break; + + if(dat->waitingForAcceptance) { + SetTimer(hwndDlg,1,1000,NULL); + dat->waitingForAcceptance=0; + } + switch(ack->result) { + case ACKRESULT_SENTREQUEST: SetFtStatus(hwndDlg, LPGENT("Decision sent"), FTS_TEXT); break; + case ACKRESULT_CONNECTING: SetFtStatus(hwndDlg, LPGENT("Connecting..."), FTS_TEXT); break; + case ACKRESULT_CONNECTPROXY: SetFtStatus(hwndDlg, LPGENT("Connecting to proxy..."), FTS_TEXT); break; + case ACKRESULT_CONNECTED: SetFtStatus(hwndDlg, LPGENT("Connected"), FTS_TEXT); break; + case ACKRESULT_LISTENING: SetFtStatus(hwndDlg, LPGENT("Waiting for connection..."), FTS_TEXT); break; + case ACKRESULT_INITIALISING: SetFtStatus(hwndDlg, LPGENT("Initialising..."), FTS_TEXT); break; + case ACKRESULT_NEXTFILE: + SetFtStatus(hwndDlg, LPGENT("Moving to next file..."), FTS_TEXT); + SetDlgItemTextA(hwndDlg,IDC_FILENAME,""); + if(dat->transferStatus.currentFileNumber==1 && dat->transferStatus.totalFiles>1 && !dat->send) + SetOpenFileButtonStyle(GetDlgItem(hwndDlg,IDC_OPENFILE),1); + if(dat->transferStatus.currentFileNumber!=-1 && dat->files && !dat->send && DBGetContactSettingByte(NULL,"SRFile","UseScanner",VIRUSSCAN_DISABLE)==VIRUSSCAN_DURINGDL) { + if(GetFileAttributes(dat->files[dat->transferStatus.currentFileNumber])&FILE_ATTRIBUTE_DIRECTORY) + PostMessage(hwndDlg,M_VIRUSSCANDONE,dat->transferStatus.currentFileNumber,0); + else { + virusscanthreadstartinfo *vstsi; + vstsi=(struct virusscanthreadstartinfo*)mir_alloc(sizeof(struct virusscanthreadstartinfo)); + vstsi->hwndReply = hwndDlg; + vstsi->szFile = mir_tstrdup(dat->files[dat->transferStatus.currentFileNumber]); + vstsi->returnCode = dat->transferStatus.currentFileNumber; + forkthread((void (*)(void*))RunVirusScannerThread,0,vstsi); + } + } + break; + case ACKRESULT_FILERESUME: + { + UpdateProtoFileTransferStatus(&dat->transferStatus, (PROTOFILETRANSFERSTATUS*)ack->lParam); + PROTOFILETRANSFERSTATUS *fts = &dat->transferStatus; + + SetFilenameControls( hwndDlg, dat, fts ); + int res = _taccess( fts->tszCurrentFile, 0 ); + if ( res ) + break; + + SetFtStatus(hwndDlg, LPGENT("File already exists"), FTS_TEXT); + if(dat->resumeBehaviour==FILERESUME_ASK) { + TDlgProcFileExistsParam param = { hwndDlg, fts }; + ShowWindow(hwndDlg,SW_SHOWNORMAL); + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_FILEEXISTS),hwndDlg,DlgProcFileExists,(LPARAM)¶m); + EnableWindow(hwndDlg,FALSE); + } + else { + PROTOFILERESUME *pfr; + pfr=(PROTOFILERESUME*)mir_alloc(sizeof(PROTOFILERESUME)); + pfr->action = dat->resumeBehaviour; + pfr->szFilename = NULL; + PostMessage(hwndDlg,M_FILEEXISTSDLGREPLY,(WPARAM)mir_tstrdup(fts->tszCurrentFile),(LPARAM)pfr); + } + SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,1); + return TRUE; + } + case ACKRESULT_DATA: + { + PROTOFILETRANSFERSTATUS *fts=(PROTOFILETRANSFERSTATUS*)ack->lParam; + TCHAR str[64], str2[64], szSizeDone[32], szSizeTotal[32];//,*contactName; + int units; + + if ( dat->fileVirusScanned==NULL ) + dat->fileVirusScanned=(int*)mir_calloc(sizeof(int) * fts->totalFiles); + + // This needs to be here - otherwise we get holes in the files array + if (!dat->send) + { + if (dat->files == NULL) + dat->files = (TCHAR**)mir_calloc((fts->totalFiles + 1) * sizeof(TCHAR*)); + if (fts->currentFileNumber < fts->totalFiles && dat->files[fts->currentFileNumber] == NULL) + { + if (fts->cbSize == sizeof(PROTOFILETRANSFERSTATUS_V1)) + { + PROTOFILETRANSFERSTATUS_V1 *fts1 = (PROTOFILETRANSFERSTATUS_V1*)fts; + dat->files[fts->currentFileNumber] = PFTS_StringToTchar(0, (PROTOCHAR*)fts1->currentFile); + } + else + dat->files[fts->currentFileNumber] = PFTS_StringToTchar(fts->flags, fts->tszCurrentFile); + } + } + + /* HACK: for 0.3.3, limit updates to around 1.1 ack per second */ + if (fts->totalProgress != fts->totalBytes && GetTickCount() < (dat->dwTicks + 650)) break; // the last update was less than a second ago! + dat->dwTicks = GetTickCount(); + + // Update local transfer status with data from protocol + UpdateProtoFileTransferStatus(&dat->transferStatus, fts); + fts = &dat->transferStatus; + + bool firstTime = false; + if ((GetWindowLong(GetDlgItem(hwndDlg, IDC_ALLFILESPROGRESS), GWL_STYLE) & WS_VISIBLE) == 0) + { + SetFtStatus(hwndDlg, ( fts->flags & PFTS_SENDING ) ? LPGENT("Sending...") : LPGENT("Receiving..."), FTS_PROGRESS); + SetFilenameControls(hwndDlg, dat, fts); + firstTime = true; + } + + const unsigned long lastPos = SendDlgItemMessage(hwndDlg, IDC_ALLFILESPROGRESS, PBM_GETPOS, 0, 0); + const unsigned long nextPos = fts->totalBytes ? (BIGI(100) * fts->totalProgress / fts->totalBytes) : 0; + if (lastPos != nextPos || firstTime) + { + SendDlgItemMessage(hwndDlg, IDC_ALLFILESPROGRESS, PBM_SETPOS, nextPos, 0); + mir_sntprintf(str, SIZEOF(str), _T("%u%%"), nextPos); + SetDlgItemText(hwndDlg, IDC_ALLPRECENTS, str); + } + + GetSensiblyFormattedSize(fts->totalBytes, szSizeTotal, SIZEOF(szSizeTotal), 0, 1, &units); + GetSensiblyFormattedSize(fts->totalProgress, szSizeDone, SIZEOF(szSizeDone), units, 0, NULL); + mir_sntprintf(str, SIZEOF(str), _T("%s/%s"), szSizeDone, szSizeTotal); + str2[0] = 0; + GetDlgItemText(hwndDlg, IDC_ALLTRANSFERRED, str2, SIZEOF(str2)); + if (_tcscmp(str, str2)) SetDlgItemText(hwndDlg, IDC_ALLTRANSFERRED, str); + break; + } + + case ACKRESULT_SUCCESS: + case ACKRESULT_FAILED: + case ACKRESULT_DENIED: + { + + HideProgressControls(hwndDlg); + KillTimer(hwndDlg,1); + if (!dat->send) + SetOpenFileButtonStyle(GetDlgItem(hwndDlg,IDC_OPENFILE),1); + SetDlgItemText(hwndDlg,IDCANCEL,TranslateT("Close")); + if (dat->hNotifyEvent) + UnhookEvent(dat->hNotifyEvent); + dat->hNotifyEvent=NULL; + + if (ack->result == ACKRESULT_DENIED) + { + dat->fs=NULL; /* protocol will free structure */ + SkinPlaySound("FileDenied"); + SetFtStatus(hwndDlg, LPGENT("File transfer denied"), FTS_TEXT); + } else if (ack->result == ACKRESULT_FAILED) + { + dat->fs=NULL; /* protocol will free structure */ + SkinPlaySound("FileFailed"); + SetFtStatus(hwndDlg, LPGENT("File transfer failed"), FTS_TEXT); + } else { + SkinPlaySound("FileDone"); + if (dat->send) + { + dat->fs=NULL; /* protocol will free structure */ + SetFtStatus(hwndDlg, LPGENT("Transfer completed."), FTS_TEXT); + + DBEVENTINFO dbei={0}; + FillSendData( dat, dbei ); + CallService(MS_DB_EVENT_ADD,(WPARAM)dat->hContact,(LPARAM)&dbei); + if (dbei.pBlob) + mir_free(dbei.pBlob); + dat->files=NULL; //protocol library frees this + + } else { + SetFtStatus(hwndDlg, + (dat->transferStatus.totalFiles == 1) ? + LPGENT("Transfer completed, open file.") : + LPGENT("Transfer completed, open folder."), + FTS_OPEN); + + int useScanner=DBGetContactSettingByte(NULL,"SRFile","UseScanner",VIRUSSCAN_DISABLE); + if (useScanner!=VIRUSSCAN_DISABLE) { + struct virusscanthreadstartinfo *vstsi; + vstsi=(struct virusscanthreadstartinfo*)mir_alloc(sizeof(struct virusscanthreadstartinfo)); + vstsi->hwndReply=hwndDlg; + if(useScanner==VIRUSSCAN_DURINGDL) { + vstsi->returnCode=dat->transferStatus.currentFileNumber; + if ( GetFileAttributes(dat->files[dat->transferStatus.currentFileNumber])&FILE_ATTRIBUTE_DIRECTORY) { + PostMessage(hwndDlg,M_VIRUSSCANDONE,vstsi->returnCode,0); + mir_free(vstsi); + vstsi=NULL; + } + else vstsi->szFile = mir_tstrdup(dat->files[dat->transferStatus.currentFileNumber]); + } + else { + vstsi->szFile = mir_tstrdup(dat->transferStatus.tszWorkingDir); + vstsi->returnCode = -1; + } + SetFtStatus(hwndDlg, LPGENT("Scanning for viruses..."), FTS_TEXT); + if(vstsi) forkthread((void (*)(void*))RunVirusScannerThread,0,vstsi); + } else { + dat->fs=NULL; /* protocol will free structure */ + } + dat->transferStatus.currentFileNumber=dat->transferStatus.totalFiles; + } // else dat->send + + } // else ack->result + + PostMessage(GetParent(hwndDlg), WM_FT_COMPLETED, ack->result, (LPARAM)hwndDlg); + break; + } + break; + } // switch ack->result + } break; // case HM_RECVEVENT + case M_VIRUSSCANDONE: + { int done=1,i; + if((int)wParam==-1) { + for(i=0;itransferStatus.totalFiles;i++) dat->fileVirusScanned[i]=1; + } + else { + dat->fileVirusScanned[wParam]=1; + for(i=0;itransferStatus.totalFiles;i++) if(!dat->fileVirusScanned[i]) {done=0; break;} + } + if (done) + { + dat->fs=NULL; /* protocol will free structure */ + SetFtStatus(hwndDlg, LPGENT("Transfer and virus scan complete"), FTS_TEXT); + } + break; + } + case WM_SIZE: + { + UTILRESIZEDIALOG urd={0}; + urd.cbSize=sizeof(urd); + urd.hwndDlg=hwndDlg; + urd.hInstance=hMirandaInst; + urd.lpTemplate=MAKEINTRESOURCEA(IDD_FILETRANSFERINFO); + urd.pfnResizer=FileTransferDlgResizer; + CallService(MS_UTILS_RESIZEDIALOG,0,(LPARAM)&urd); + + RedrawWindow(GetDlgItem(hwndDlg, IDC_ALLTRANSFERRED), NULL, NULL, RDW_INVALIDATE|RDW_NOERASE); + RedrawWindow(GetDlgItem(hwndDlg, IDC_ALLSPEED), NULL, NULL, RDW_INVALIDATE|RDW_NOERASE); + RedrawWindow(GetDlgItem(hwndDlg, IDC_CONTACTNAME), NULL, NULL, RDW_INVALIDATE|RDW_NOERASE); + RedrawWindow(GetDlgItem(hwndDlg, IDC_STATUS), NULL, NULL, RDW_INVALIDATE|RDW_NOERASE); + break; + } + case WM_DESTROY: + KillTimer(hwndDlg, 1); + + HFONT hFont = (HFONT)SendDlgItemMessage(hwndDlg,IDC_CONTACTNAME,WM_GETFONT,0,0); + DeleteObject(hFont); + + Button_FreeIcon_IcoLib(hwndDlg, IDC_CONTACT); + Button_FreeIcon_IcoLib(hwndDlg, IDC_OPENFILE); + Button_FreeIcon_IcoLib(hwndDlg, IDCANCEL); + + FreeFileDlgData(dat); + break; + } + return FALSE; +} + +void FreeFileDlgData( FileDlgData* dat ) +{ + if(dat->fs) + CallContactService(dat->hContact,PSS_FILECANCEL,(WPARAM)dat->fs,0); + dat->fs = NULL; + + if (dat->hPreshutdownEvent) UnhookEvent(dat->hPreshutdownEvent); + if (dat->hNotifyEvent) UnhookEvent(dat->hNotifyEvent); + dat->hNotifyEvent = NULL; + + FreeProtoFileTransferStatus(&dat->transferStatus); + FreeFilesMatrix(&dat->files); + + mir_free(dat->fileVirusScanned); + Safe_DestroyIcon(dat->hIcon); + Safe_DestroyIcon(dat->hIconFolder); + mir_free(dat); +} diff --git a/src/modules/srfile/ftmanager.cpp b/src/modules/srfile/ftmanager.cpp new file mode 100644 index 0000000000..a219d2188a --- /dev/null +++ b/src/modules/srfile/ftmanager.cpp @@ -0,0 +1,592 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "file.h" + +static HWND hwndFtMgr = NULL; + +struct TFtMgrData +{ + HWND hwndIncoming; + HWND hwndOutgoing; + + HANDLE hhkPreshutdown; + TBPFLAG errorState; +}; + + +#define M_CALCPROGRESS (WM_USER + 200) +struct TFtProgressData +{ + unsigned int init, run, scan; + unsigned __int64 totalBytes, totalProgress; +}; + +struct TLayoutWindowInfo +{ + HWND hwnd; + RECT rc; +}; + +struct TLayoutWindowList +{ + struct TLayoutWindowInfo **items; + int realCount, limit, increment; + FSortFunc sortFunc; +}; + +struct TFtPageData +{ + struct TLayoutWindowList *wnds; + int runningCount; + int height, dataHeight, scrollPos; +}; + +static void LayoutTransfers(HWND hwnd, struct TFtPageData *dat) +{ + int top = 0; + RECT rc; + GetClientRect(hwnd, &rc); + + dat->scrollPos = GetScrollPos(hwnd, SB_VERT); + dat->height = rc.bottom - rc.top; + + if (dat->wnds->realCount) + { + int i; + HDWP hdwp; + + hdwp = BeginDeferWindowPos(dat->wnds->realCount); + top -= dat->scrollPos; + for (i = 0; i < dat->wnds->realCount; ++i) + { + int height = dat->wnds->items[i]->rc.bottom - dat->wnds->items[i]->rc.top; + hdwp = DeferWindowPos(hdwp, dat->wnds->items[i]->hwnd, NULL, 0, top, rc.right, height, SWP_NOZORDER); + top += height; + } + top += dat->scrollPos; + EndDeferWindowPos(hdwp); + } + + dat->dataHeight = top; + + { + SCROLLINFO si = {0}; + si.cbSize = sizeof(si); + si.fMask = SIF_DISABLENOSCROLL|SIF_PAGE|SIF_RANGE; + si.nPage = dat->height; + si.nMin = 0; + si.nMax = dat->dataHeight; + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + } +} + +static INT_PTR CALLBACK FtMgrPageDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct TFtPageData *dat = (struct TFtPageData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (msg) + { + case WM_INITDIALOG: + { + // Force scrollbar visibility + SCROLLINFO si = {0}; + si.cbSize = sizeof(si); + si.fMask = SIF_DISABLENOSCROLL; + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + + dat = (struct TFtPageData *)mir_alloc(sizeof(struct TFtPageData)); + dat->wnds = (struct TLayoutWindowList *)List_Create(0, 1); + dat->scrollPos = 0; + dat->runningCount = 0; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)dat); + break; + } + + case WM_FT_ADD: + { + struct TLayoutWindowInfo *wnd = (struct TLayoutWindowInfo *)mir_alloc(sizeof(struct TLayoutWindowInfo)); + wnd->hwnd = (HWND)lParam; + GetWindowRect(wnd->hwnd, &wnd->rc); + List_Insert((SortedList *)dat->wnds, wnd, dat->wnds->realCount); + LayoutTransfers(hwnd, dat); + dat->runningCount++; + PostMessage(GetParent(hwnd), WM_TIMER, 1, NULL); + break; + } + + case WM_FT_RESIZE: + { + int i; + for (i = 0; i < dat->wnds->realCount; ++i) + if (dat->wnds->items[i]->hwnd == (HWND)lParam) + { + GetWindowRect(dat->wnds->items[i]->hwnd, &dat->wnds->items[i]->rc); + break; + } + LayoutTransfers(hwnd, dat); + break; + } + + case WM_FT_REMOVE: + { + int i; + for (i = 0; i < dat->wnds->realCount; ++i) + if (dat->wnds->items[i]->hwnd == (HWND)lParam) + { + mir_free(dat->wnds->items[i]); + List_Remove((SortedList *)dat->wnds, i); + break; + } + LayoutTransfers(hwnd, dat); + break; + } + + case WM_FT_COMPLETED: + { //wParam: { ACKRESULT_SUCCESS | ACKRESULT_FAILED | ACKRESULT_DENIED } + dat->runningCount--; + int i = 0; + while (i < dat->wnds->realCount) + { + // no error when canceling (WM_FT_REMOVE is send first, check if hwnd is still registered) + if (dat->wnds->items[i]->hwnd == (HWND)lParam) + { + SendMessage(GetParent(hwnd), WM_TIMER, 1, (LPARAM)wParam); + break; + } + ++i; + } + if (i == dat->wnds->realCount) + PostMessage(GetParent(hwnd), WM_TIMER, 1, NULL); + + if(dat->runningCount == 0 && (int)wParam == ACKRESULT_SUCCESS && DBGetContactSettingByte(NULL,"SRFile","AutoClose",0)) + ShowWindow(hwndFtMgr, SW_HIDE); + break; + } + + case WM_FT_CLEANUP: + { + int i; + for (i = 0; i < dat->wnds->realCount; ++i) + SendMessage(dat->wnds->items[i]->hwnd, WM_FT_CLEANUP, wParam, lParam); + break; + } + + case WM_SIZE: + { + LayoutTransfers(hwnd, dat); + break; + } + + case WM_MOUSEWHEEL: + { + int zDelta = GET_WHEEL_DELTA_WPARAM(wParam); + if (zDelta) + { + int i, nScrollLines = 0; + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, (void*)&nScrollLines, 0); + for (i = 0; i < (nScrollLines + 1) / 2; i++ ) + SendMessage(hwnd, WM_VSCROLL, (zDelta < 0) ? SB_LINEDOWN : SB_LINEUP, 0); + } + + SetWindowLongPtr(hwnd, DWLP_MSGRESULT, 0); + return TRUE; + } + + case WM_VSCROLL: + { + int pos = dat->scrollPos; + switch (LOWORD(wParam)) + { + case SB_LINEDOWN: + pos += 15; + break; + case SB_LINEUP: + pos -= 15; + break; + case SB_PAGEDOWN: + pos += dat->height - 10; + break; + case SB_PAGEUP: + pos -= dat->height - 10; + break; + case SB_THUMBTRACK: + pos = HIWORD(wParam); + break; + } + + if (pos > dat->dataHeight - dat->height) pos = dat->dataHeight - dat->height; + if (pos < 0) pos = 0; + + if (dat->scrollPos != pos) + { + ScrollWindow(hwnd, 0, dat->scrollPos - pos, NULL, NULL); + SetScrollPos(hwnd, SB_VERT, pos, TRUE); + dat->scrollPos = pos; + } + break; + } + + case M_PRESHUTDOWN: + { + int i; + for (i = 0; i < dat->wnds->realCount; ++i) + PostMessage(dat->wnds->items[i]->hwnd, WM_COMMAND, MAKEWPARAM(IDCANCEL, BN_CLICKED), 0); + break; + } + + case WM_DESTROY: + { + int i; + for (i = 0; i < dat->wnds->realCount; ++i) + mir_free(dat->wnds->items[i]); + List_Destroy((SortedList *)dat->wnds); + mir_free(dat->wnds); + mir_free(dat); + break; + } + + case M_CALCPROGRESS: + { + int i; + TFtProgressData * prg = (TFtProgressData *)wParam; + for (i = 0; i < dat->wnds->realCount; ++i) + { + struct FileDlgData *trdat = (struct FileDlgData *)GetWindowLongPtr(dat->wnds->items[i]->hwnd, GWLP_USERDATA); + if (trdat->transferStatus.totalBytes && trdat->fs && !trdat->send && (trdat->transferStatus.totalBytes == trdat->transferStatus.totalProgress)) + { + prg->scan++; + } else if (trdat->transferStatus.totalBytes && trdat->fs) + { // in progress + prg->run++; + prg->totalBytes += trdat->transferStatus.totalBytes; + prg->totalProgress += trdat->transferStatus.totalProgress; + } else if (trdat->fs) + { // starting + prg->init++; + } + + } + } + } + + return FALSE; +} + +static INT_PTR CALLBACK FtMgrDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct TFtMgrData *dat = (struct TFtMgrData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (msg) + { + case WM_INITDIALOG: + { + TCITEM tci = {0}; + HWND hwndTab = GetDlgItem(hwnd, IDC_TABS); + + TranslateDialogDefault(hwnd); + Window_SetIcon_IcoLib(hwnd, SKINICON_EVENT_FILE); + + dat = (struct TFtMgrData *)mir_calloc(sizeof(struct TFtMgrData)); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)dat); + + dat->hhkPreshutdown = HookEventMessage(ME_SYSTEM_PRESHUTDOWN, hwnd, M_PRESHUTDOWN); + + dat->hwndIncoming = CreateDialog(hMirandaInst, MAKEINTRESOURCE(IDD_FTPAGE), hwnd, FtMgrPageDlgProc); + dat->hwndOutgoing = CreateDialog(hMirandaInst, MAKEINTRESOURCE(IDD_FTPAGE), hwnd, FtMgrPageDlgProc); + ShowWindow(dat->hwndIncoming, SW_SHOW); + + tci.mask = TCIF_PARAM|TCIF_TEXT; + tci.pszText = TranslateT("Incoming"); + tci.lParam = (LPARAM)dat->hwndIncoming; + TabCtrl_InsertItem(hwndTab, 0, &tci); + tci.pszText = TranslateT("Outgoing"); + tci.lParam = (LPARAM)dat->hwndOutgoing; + TabCtrl_InsertItem(hwndTab, 1, &tci); + + // Utils_RestoreWindowPosition(hwnd, NULL, "SRFile", "FtMgrDlg_"); + SAVEWINDOWPOS swp; + swp.hwnd=hwnd; swp.hContact=NULL; swp.szModule="SRFile"; swp.szNamePrefix="FtMgrDlg_"; + CallService(MS_UTILS_RESTOREWINDOWPOSITION, RWPF_NOACTIVATE, (LPARAM)&swp); + + // Fall through to setup initial placement + } + case WM_SIZE: + { + RECT rc, rcButton; + HDWP hdwp; + HWND hwndTab = GetDlgItem(hwnd, IDC_TABS); + + GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rcButton); + OffsetRect(&rcButton, -rcButton.left, -rcButton.top); + + GetClientRect(hwnd, &rc); + InflateRect(&rc, -6, -6); + + hdwp = BeginDeferWindowPos(3); + + hdwp = DeferWindowPos(hdwp, GetDlgItem(hwnd, IDC_CLEAR), NULL, rc.left, rc.bottom-rcButton.bottom, 0, 0, SWP_NOZORDER|SWP_NOSIZE); + hdwp = DeferWindowPos(hdwp, GetDlgItem(hwnd, IDCANCEL), NULL, rc.right-rcButton.right, rc.bottom-rcButton.bottom, 0, 0, SWP_NOZORDER|SWP_NOSIZE); + + rc.bottom -= rcButton.bottom + 5; + + hdwp = DeferWindowPos(hdwp, hwndTab, NULL, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER); + + EndDeferWindowPos(hdwp); + + GetWindowRect(hwndTab, &rc); + MapWindowPoints(NULL, hwnd, (LPPOINT)&rc, 2); + TabCtrl_AdjustRect(hwndTab, FALSE, &rc); + InflateRect(&rc, -5, -5); + + hdwp = BeginDeferWindowPos(2); + + hdwp = DeferWindowPos(hdwp, dat->hwndIncoming, HWND_TOP, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, 0); + hdwp = DeferWindowPos(hdwp, dat->hwndOutgoing, HWND_TOP, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, 0); + + EndDeferWindowPos(hdwp); + + break; + } + + case WM_MOUSEWHEEL: + { + if (IsWindowVisible(dat->hwndIncoming)) SendMessage(dat->hwndIncoming, msg, wParam, lParam); + if (IsWindowVisible(dat->hwndOutgoing)) SendMessage(dat->hwndOutgoing, msg, wParam, lParam); + break; + } + + case WM_FT_SELECTPAGE: + { + TCITEM tci = {0}; + HWND hwndTab = GetDlgItem(hwnd, IDC_TABS); + + if (TabCtrl_GetCurSel(hwndTab) == (int)wParam) break; + + tci.mask = TCIF_PARAM; + + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_HIDE); + + TabCtrl_SetCurSel(hwndTab, wParam); + + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_SHOW); + + break; + } + + case WM_GETMINMAXINFO: + { + LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam; + lpmmi->ptMinTrackSize.x = 300; + lpmmi->ptMinTrackSize.y = 400; + return 0; + } + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDCANCEL: + PostMessage(hwnd, WM_CLOSE , 0, 0); + break; + + case IDC_CLEAR: + PostMessage(dat->hwndIncoming, WM_FT_CLEANUP, 0, 0); + PostMessage(dat->hwndOutgoing, WM_FT_CLEANUP, 0, 0); + break; + } + break; + + case WM_NOTIFY: + { + switch (((LPNMHDR)lParam)->idFrom) + { + case IDC_TABS: + { + HWND hwndTab = GetDlgItem(hwnd, IDC_TABS); + switch (((LPNMHDR)lParam)->code) + { + case TCN_SELCHANGING: + { + TCITEM tci = {0}; + tci.mask = TCIF_PARAM; + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_HIDE); + break; + } + + case TCN_SELCHANGE: + { + TCITEM tci = {0}; + tci.mask = TCIF_PARAM; + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tci); + ShowWindow((HWND)tci.lParam, SW_SHOW); + break; + } + } + break; + } + } + break; + } + + case M_PRESHUTDOWN: + SendMessage(dat->hwndIncoming, M_PRESHUTDOWN, 0, 0); + SendMessage(dat->hwndOutgoing, M_PRESHUTDOWN, 0, 0); + DestroyWindow(hwnd); + break; + + case WM_CLOSE: + ShowWindow(hwnd, SW_HIDE); + if (DBGetContactSettingByte(NULL, "SRFile", "AutoClear", 1)) { + PostMessage(dat->hwndIncoming, WM_FT_CLEANUP, 0, 0); + PostMessage(dat->hwndOutgoing, WM_FT_CLEANUP, 0, 0); + } + return TRUE; /* Disable default IDCANCEL notification */ + + case WM_DESTROY: + UnhookEvent(dat->hhkPreshutdown); + Window_FreeIcon_IcoLib(hwnd); + DestroyWindow(dat->hwndIncoming); + DestroyWindow(dat->hwndOutgoing); + mir_free(dat); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + Utils_SaveWindowPosition(hwnd, NULL, "SRFile", "FtMgrDlg_"); + break; + + case WM_ACTIVATE: + { + dat->errorState = TBPF_NOPROGRESS; + wParam = 1; + } break; + case WM_SHOWWINDOW: + { + if (!wParam) // hiding + { + KillTimer(hwnd, 1); + break; + } + lParam = 0; + } + case WM_TIMER: + { + if (pTaskbarInterface) + { + SetTimer(hwnd, 1, 400, NULL); + if ((lParam == ACKRESULT_FAILED) || (lParam == ACKRESULT_DENIED)) + dat->errorState = TBPF_ERROR; + + TFtProgressData prg = {0}; + SendMessage(dat->hwndIncoming, M_CALCPROGRESS, (WPARAM)&prg, 0); + SendMessage(dat->hwndOutgoing, M_CALCPROGRESS, (WPARAM)&prg, 0); + if (dat->errorState) + { + pTaskbarInterface->SetProgressState(hwnd, dat->errorState); + if (!prg.run) + pTaskbarInterface->SetProgressValue(hwnd, 1, 1); + } else if (prg.run) + { + pTaskbarInterface->SetProgressState(hwnd, TBPF_NORMAL); + } else if (prg.init || prg.scan) + { + pTaskbarInterface->SetProgressState(hwnd, TBPF_INDETERMINATE); + } else { + pTaskbarInterface->SetProgressState(hwnd, TBPF_NOPROGRESS); + KillTimer(hwnd, 1); + } + + if (prg.run) + { + pTaskbarInterface->SetProgressValue(hwnd, prg.totalProgress, prg.totalBytes); + } + + } + } break; + + } + + return FALSE; +} + +HWND FtMgr_Show(bool bForceActivate, bool bFromMenu) +{ + bool bAutoMin = DBGetContactSettingByte(NULL,"SRFile","AutoMin",0) != 0; /* lqbe */ + + bool bJustCreated = (hwndFtMgr == NULL); + if (bJustCreated) + { + hwndFtMgr = CreateDialog(hMirandaInst, MAKEINTRESOURCE(IDD_FTMGR), NULL, FtMgrDlgProc); + } + if (bFromMenu) /* lqbe */ + { + ShowWindow(hwndFtMgr, SW_RESTORE); + ShowWindow(hwndFtMgr, SW_SHOW); + SetForegroundWindow(hwndFtMgr); + return hwndFtMgr; + } + else if (bAutoMin && bJustCreated) /* lqbe */ + { + ShowWindow(hwndFtMgr, SW_HIDE); + ShowWindow(hwndFtMgr, SW_MINIMIZE); + return hwndFtMgr; + } + else if (bForceActivate) /* lqbe */ + { + ShowWindow(hwndFtMgr, SW_RESTORE); + ShowWindow(hwndFtMgr, SW_SHOWNOACTIVATE); + SetForegroundWindow(hwndFtMgr); + return hwndFtMgr; + } + if (!bJustCreated && IsWindowVisible(hwndFtMgr)) + return hwndFtMgr; + + ShowWindow(hwndFtMgr, bAutoMin ? SW_SHOWMINNOACTIVE : SW_SHOWNOACTIVATE); + return hwndFtMgr; + } + +void FtMgr_Destroy() +{ + if (hwndFtMgr) + DestroyWindow(hwndFtMgr); +} + +void FtMgr_ShowPage(int page) +{ + if (hwndFtMgr) + SendMessage(hwndFtMgr, WM_FT_SELECTPAGE, page, 0); +} + +HWND FtMgr_AddTransfer(FileDlgData *fdd) +{ + bool bForceActivate = fdd->send || !DBGetContactSettingByte(NULL, "SRFile", "AutoAccept", 0); + TFtMgrData *dat = (TFtMgrData*)GetWindowLongPtr(FtMgr_Show(bForceActivate, false), GWLP_USERDATA); + if (dat == NULL) return NULL; + HWND hwndBox = fdd->send ? dat->hwndOutgoing : dat->hwndIncoming; + HWND hwndFt = CreateDialogParam(hMirandaInst, MAKEINTRESOURCE(IDD_FILETRANSFERINFO), hwndBox, DlgProcFileTransfer, (LPARAM)fdd); + ShowWindow(hwndFt, SW_SHOWNA); + SendMessage(hwndBox, WM_FT_ADD, 0, (LPARAM)hwndFt); + FtMgr_ShowPage(fdd->send ? 1 : 0); + return hwndFt; +} \ No newline at end of file diff --git a/src/modules/srurl/url.cpp b/src/modules/srurl/url.cpp new file mode 100644 index 0000000000..a6dfb9141f --- /dev/null +++ b/src/modules/srurl/url.cpp @@ -0,0 +1,182 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include +#include "url.h" + +HANDLE hUrlWindowList = NULL; +static HANDLE hEventContactSettingChange = NULL; +static HANDLE hContactDeleted = NULL; +static HANDLE hSRUrlMenuItem = NULL; + +INT_PTR CALLBACK DlgProcUrlSend(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK DlgProcUrlRecv(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +static INT_PTR ReadUrlCommand(WPARAM, LPARAM lParam) +{ + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_URLRECV),NULL,DlgProcUrlRecv,lParam); + return 0; +} + +static int UrlEventAdded(WPARAM wParam,LPARAM lParam) +{ + CLISTEVENT cle; + DBEVENTINFO dbei; + TCHAR szTooltip[256]; + + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + dbei.cbBlob=0; + CallService(MS_DB_EVENT_GET,lParam,(LPARAM)&dbei); + if(dbei.flags&(DBEF_SENT|DBEF_READ) || dbei.eventType!=EVENTTYPE_URL) return 0; + + SkinPlaySound("RecvUrl"); + ZeroMemory(&cle,sizeof(cle)); + cle.cbSize=sizeof(cle); + cle.flags = CLEF_TCHAR; + cle.hContact=(HANDLE)wParam; + cle.hDbEvent=(HANDLE)lParam; + cle.hIcon = LoadSkinIcon( SKINICON_EVENT_URL ); + cle.pszService="SRUrl/ReadUrl"; + mir_sntprintf(szTooltip,SIZEOF(szTooltip),TranslateT("URL from %s"), cli.pfnGetContactDisplayName(( HANDLE )wParam, 0 )); + cle.ptszTooltip=szTooltip; + CallService(MS_CLIST_ADDEVENT,0,(LPARAM)&cle); + return 0; +} + +static INT_PTR SendUrlCommand(WPARAM wParam, LPARAM) +{ + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_URLSEND),NULL,DlgProcUrlSend,wParam); + return 0; +} + +static void RestoreUnreadUrlAlerts(void) +{ + CLISTEVENT cle={0}; + DBEVENTINFO dbei={0}; + TCHAR toolTip[256]; + HANDLE hDbEvent,hContact; + + dbei.cbSize=sizeof(dbei); + cle.cbSize=sizeof(cle); + cle.hIcon = LoadSkinIcon( SKINICON_EVENT_URL ); + cle.pszService="SRUrl/ReadUrl"; + + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST,0,0); + while(hContact) { + hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDFIRSTUNREAD,(WPARAM)hContact,0); + while(hDbEvent) { + dbei.cbBlob=0; + CallService(MS_DB_EVENT_GET,(WPARAM)hDbEvent,(LPARAM)&dbei); + if(!(dbei.flags&(DBEF_SENT|DBEF_READ)) && dbei.eventType==EVENTTYPE_URL) { + cle.hContact=hContact; + cle.hDbEvent=hDbEvent; + cle.flags = CLEF_TCHAR; + mir_sntprintf(toolTip,SIZEOF(toolTip),TranslateT("URL from %s"), cli.pfnGetContactDisplayName( hContact, 0 )); + cle.ptszTooltip=toolTip; + CallService(MS_CLIST_ADDEVENT,0,(LPARAM)&cle); + } + hDbEvent=(HANDLE)CallService(MS_DB_EVENT_FINDNEXT,(WPARAM)hDbEvent,0); + } + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDNEXT,(WPARAM)hContact,0); + } +} + +static int ContactSettingChanged(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTWRITESETTING *cws=(DBCONTACTWRITESETTING*)lParam; + char *szProto; + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,wParam,0); + if(lstrcmpA(cws->szModule,"CList") && (szProto==NULL || lstrcmpA(cws->szModule,szProto))) return 0; + WindowList_Broadcast(hUrlWindowList,DM_UPDATETITLE,0,0); + return 0; +} + +static int SRUrlPreBuildMenu(WPARAM wParam, LPARAM) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.flags = CMIM_FLAGS | CMIF_HIDDEN; + + char *szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0); + if ( szProto != NULL ) + if ( CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_URLSEND ) + mi.flags = CMIM_FLAGS; + + CallService( MS_CLIST_MODIFYMENUITEM, (WPARAM)hSRUrlMenuItem, (LPARAM)&mi ); + return 0; +} + +static int SRUrlModulesLoaded(WPARAM, LPARAM) +{ + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.position = -2000040000; + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle( SKINICON_EVENT_URL ); + mi.pszName = LPGEN("Web Page Address (&URL)"); + mi.pszService = MS_URL_SENDURL; + hSRUrlMenuItem = (HANDLE)CallService( MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM)&mi ); + + RestoreUnreadUrlAlerts(); + return 0; +} + +static int SRUrlShutdown(WPARAM, LPARAM) +{ + if ( hEventContactSettingChange ) + UnhookEvent( hEventContactSettingChange ); + + if ( hContactDeleted ) + UnhookEvent( hContactDeleted ); + + if ( hUrlWindowList ) + WindowList_BroadcastAsync( hUrlWindowList, WM_CLOSE, 0, 0 ); + + return 0; +} + +int UrlContactDeleted(WPARAM wParam, LPARAM) +{ + HWND h = WindowList_Find(hUrlWindowList,(HANDLE)wParam); + if ( h ) + SendMessage(h,WM_CLOSE,0,0); + + return 0; +} + +int LoadSendRecvUrlModule(void) +{ + hUrlWindowList=(HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST,0,0); + HookEvent(ME_SYSTEM_MODULESLOADED,SRUrlModulesLoaded); + HookEvent(ME_DB_EVENT_ADDED,UrlEventAdded); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU,SRUrlPreBuildMenu); + hEventContactSettingChange = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChanged); + hContactDeleted = HookEvent(ME_DB_CONTACT_DELETED, UrlContactDeleted); + HookEvent(ME_SYSTEM_PRESHUTDOWN,SRUrlShutdown); + CreateServiceFunction(MS_URL_SENDURL,SendUrlCommand); + CreateServiceFunction("SRUrl/ReadUrl",ReadUrlCommand); + SkinAddNewSoundEx("RecvUrl","URL","Incoming"); + return 0; +} diff --git a/src/modules/srurl/url.h b/src/modules/srurl/url.h new file mode 100644 index 0000000000..98c5566f53 --- /dev/null +++ b/src/modules/srurl/url.h @@ -0,0 +1,41 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. +*/ + +#define TIMEOUT_URLSEND 9000 +#define HM_EVENTSENT (WM_USER+10) +#define DM_UPDATETITLE (WM_USER+11) + +#define DDEMESSAGETIMEOUT 1000 + + +struct UrlRcvData { + HANDLE hContact; + HANDLE hDbEvent; +}; + +struct UrlSendData { + HANDLE hContact; + HANDLE hSendId; + HANDLE hAckEvent; + char *sendBuffer; +}; diff --git a/src/modules/srurl/urldialogs.cpp b/src/modules/srurl/urldialogs.cpp new file mode 100644 index 0000000000..294e3be4da --- /dev/null +++ b/src/modules/srurl/urldialogs.cpp @@ -0,0 +1,664 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "url.h" + +INT_PTR CALLBACK DlgProcUrlSend(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +extern HANDLE hUrlWindowList; + +static void sttUpdateTitle( HWND hwndDlg, HANDLE hContact ) +{ + TCHAR newtitle[256],oldtitle[256]; + TCHAR *szStatus, *contactName, *pszNewTitleStart = TranslateT("Send URL to"); + char *szProto; + + if ( hContact ) { + szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if ( szProto ) { + CONTACTINFO ci; + int hasName = 0; + char buf[128]; + ZeroMemory(&ci,sizeof(ci)); + + ci.cbSize = sizeof(ci); + ci.hContact = hContact; + ci.szProto = szProto; + ci.dwFlag = CNF_UNIQUEID; + if (!CallService(MS_CONTACT_GETCONTACTINFO,0,(LPARAM)&ci)) { + switch(ci.type) { + case CNFT_ASCIIZ: + hasName = 1; + mir_snprintf(buf, SIZEOF(buf), "%s", ci.pszVal); + mir_free(ci.pszVal); + break; + case CNFT_DWORD: + hasName = 1; + mir_snprintf(buf, SIZEOF(buf),"%u",ci.dVal); + break; + } } + + contactName = cli.pfnGetContactDisplayName( hContact, 0 ); + if ( hasName ) + SetDlgItemTextA( hwndDlg, IDC_NAME, buf ); + else + SetDlgItemText( hwndDlg, IDC_NAME, contactName ); + + szStatus = cli.pfnGetStatusModeDescription( szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord( hContact, szProto, "Status", ID_STATUS_OFFLINE ), 0 ); + mir_sntprintf( newtitle, SIZEOF(newtitle), _T("%s %s (%s)"), pszNewTitleStart, contactName, szStatus); + } + } + else lstrcpyn( newtitle, pszNewTitleStart, SIZEOF(newtitle)); + + GetWindowText( hwndDlg, oldtitle, SIZEOF(oldtitle)); + + if ( lstrcmp( newtitle, oldtitle )) //swt() flickers even if the title hasn't actually changed + SetWindowText( hwndDlg, newtitle ); +} + +INT_PTR CALLBACK DlgProcUrlRecv(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct UrlRcvData *dat = (struct UrlRcvData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + Window_SetIcon_IcoLib(hwndDlg, SKINICON_EVENT_URL); + Button_SetIcon_IcoLib(hwndDlg, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add Contact Permanently to List")); + Button_SetIcon_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View User's Details")); + Button_SetIcon_IcoLib(hwndDlg, IDC_HISTORY, SKINICON_OTHER_HISTORY, LPGEN("View User's History")); + Button_SetIcon_IcoLib(hwndDlg, IDC_USERMENU, SKINICON_OTHER_DOWNARROW, LPGEN("User Menu")); + + dat=(struct UrlRcvData*)mir_alloc(sizeof(struct UrlRcvData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + dat->hContact = ((CLISTEVENT*)lParam)->hContact; + dat->hDbEvent = ((CLISTEVENT*)lParam)->hDbEvent; + + WindowList_Add(hUrlWindowList, hwndDlg, dat->hContact); + { + DBEVENTINFO dbei; + TCHAR* contactName; + TCHAR msg[128]; + + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + dbei.cbBlob=CallService(MS_DB_EVENT_GETBLOBSIZE,(WPARAM)dat->hDbEvent,0); + dbei.pBlob=(PBYTE)mir_alloc(dbei.cbBlob); + CallService(MS_DB_EVENT_GET,(WPARAM)dat->hDbEvent,(LPARAM)&dbei); + SetDlgItemTextA(hwndDlg,IDC_URL,(char*)dbei.pBlob); + SetDlgItemTextA(hwndDlg,IDC_MSG,(char*)dbei.pBlob+lstrlenA((char*)dbei.pBlob)+1); + mir_free(dbei.pBlob); + + CallService(MS_DB_EVENT_MARKREAD,(WPARAM)dat->hContact,(LPARAM)dat->hDbEvent); + + contactName = cli.pfnGetContactDisplayName( dat->hContact, 0 ); + mir_sntprintf(msg,SIZEOF(msg),TranslateT("URL from %s"),contactName); + SetWindowText(hwndDlg,msg); + SetDlgItemText(hwndDlg,IDC_FROM,contactName); + SendDlgItemMessage(hwndDlg,IDOK,BUTTONSETARROW,1,0); + { TCHAR str[128]; + tmi.printTimeStamp(NULL, dbei.timestamp, _T("t d"), str, SIZEOF(str), 0); + SetDlgItemText(hwndDlg, IDC_DATE, str); + } } + + // From message dlg + if (!DBGetContactSettingByte(dat->hContact, "CList", "NotOnList", 0)) + ShowWindow(GetDlgItem(hwndDlg, IDC_ADD), SW_HIDE); + + SendMessage(hwndDlg,DM_UPDATETITLE,0,0); + // From message dlg end + + Utils_RestoreWindowPositionNoSize(hwndDlg,NULL,"SRUrl","recv"); + return TRUE; + + case WM_MEASUREITEM: + return CallService(MS_CLIST_MENUMEASUREITEM,wParam,lParam); + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis=(LPDRAWITEMSTRUCT)lParam; + if(dis->hwndItem==GetDlgItem(hwndDlg, IDC_PROTOCOL)) { + char *szProto; + + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + if (szProto) { + HICON hIcon; + + hIcon = ( HICON )CallProtoService(szProto,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0); + if (hIcon) { + DrawIconEx(dis->hDC,dis->rcItem.left,dis->rcItem.top,hIcon,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0,NULL,DI_NORMAL); + DestroyIcon( hIcon ); + } } } } + return CallService(MS_CLIST_MENUDRAWITEM,wParam,lParam); + + case DM_UPDATETITLE: + sttUpdateTitle( hwndDlg, dat->hContact ); + break; + + case WM_COMMAND: + if (dat) + if(CallService(MS_CLIST_MENUPROCESSCOMMAND,MAKEWPARAM(LOWORD(wParam),MPCF_CONTACTMENU),(LPARAM)dat->hContact)) + break; + switch(LOWORD(wParam)) { + case IDOK: + { HMENU hMenu,hSubMenu; + RECT rc; + char url[256]; + + hMenu=LoadMenu(hMirandaInst,MAKEINTRESOURCE(IDR_CONTEXT)); + hSubMenu=GetSubMenu(hMenu,6); + CallService(MS_LANGPACK_TRANSLATEMENU,(WPARAM)hSubMenu,0); + GetWindowRect((HWND)lParam, &rc); + GetDlgItemTextA(hwndDlg, IDC_URL, url, SIZEOF(url)); + switch(TrackPopupMenu(hSubMenu,TPM_RETURNCMD,rc.left,rc.bottom,0,hwndDlg,NULL)) { + case IDM_OPENNEW: + CallService(MS_UTILS_OPENURL,1,(LPARAM)url); + break; + case IDM_OPENEXISTING: + CallService(MS_UTILS_OPENURL,0,(LPARAM)url); + break; + case IDM_COPYLINK: + { HGLOBAL hData; + if(!OpenClipboard(hwndDlg)) break; + EmptyClipboard(); + hData=GlobalAlloc(GMEM_MOVEABLE,lstrlenA(url)+1); + lstrcpyA((char*)GlobalLock(hData),url); + GlobalUnlock(hData); + SetClipboardData(CF_TEXT,hData); + CloseClipboard(); + break; + } + } + DestroyMenu(hMenu); + } + return TRUE; + + case IDC_USERMENU: + { + RECT rc; + HMENU hMenu=(HMENU)CallService(MS_CLIST_MENUBUILDCONTACT,(WPARAM)dat->hContact,0); + GetWindowRect(GetDlgItem(hwndDlg,IDC_USERMENU),&rc); + TrackPopupMenu(hMenu,0,rc.left,rc.bottom,0,hwndDlg,NULL); + DestroyMenu(hMenu); + } + break; + + case IDC_HISTORY: + CallService(MS_HISTORY_SHOWCONTACTHISTORY,(WPARAM)dat->hContact,0); + break; + + case IDC_DETAILS: + CallService(MS_USERINFO_SHOWDIALOG,(WPARAM)dat->hContact,0); + break; + + case IDC_ADD: + { + ADDCONTACTSTRUCT acs={0}; + + acs.handle=dat->hContact; + acs.handleType=HANDLE_CONTACT; + acs.szProto=0; + CallService(MS_ADDCONTACT_SHOW,(WPARAM)hwndDlg,(LPARAM)&acs); + } + if (!DBGetContactSettingByte(dat->hContact,"CList","NotOnList",0)) { + ShowWindow(GetDlgItem(hwndDlg,IDC_ADD),FALSE); + } + break; + + case IDC_REPLY: + CallService(MS_MSG_SENDMESSAGE,(WPARAM)dat->hContact,0); + //fall through + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + } + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Button_FreeIcon_IcoLib(hwndDlg,IDC_ADD); + Button_FreeIcon_IcoLib(hwndDlg,IDC_DETAILS); + Button_FreeIcon_IcoLib(hwndDlg,IDC_HISTORY); + Button_FreeIcon_IcoLib(hwndDlg,IDC_USERMENU); + + WindowList_Remove(hUrlWindowList, hwndDlg); + mir_free(dat); + Utils_SaveWindowPosition(hwndDlg,NULL,"SRUrl","recv"); + break; + } + return FALSE; +} + +static int ddeAcked,ddeData; +static ATOM hSzDdeData; +static HWND hwndDde; +static HGLOBAL hGlobalDdeData; +static LRESULT DdeMessage(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam) +{ + UINT_PTR hSzItem; + + switch(msg) { + case WM_DDE_ACK: + ddeAcked=1; + hwndDde=(HWND)wParam; + break; + + case WM_DDE_DATA: + UnpackDDElParam(msg,lParam,(PUINT_PTR)&hGlobalDdeData,(PUINT_PTR)&hSzItem); + ddeData = 1; + if( hGlobalDdeData ) { + DDEDATA* data = ( DDEDATA* )GlobalLock( hGlobalDdeData ); + if ( data->fAckReq ) { + DDEACK ack = {0}; + PostMessage(( HWND )wParam, WM_DDE_ACK, ( WPARAM )hwndDlg, PackDDElParam( WM_DDE_ACK, *(PUINT)&ack, hSzItem )); + } + else GlobalDeleteAtom(( ATOM )hSzItem ); + GlobalUnlock( hGlobalDdeData ); + } + else GlobalDeleteAtom(( ATOM )hSzItem ); + break; + } + return 0; +} + +static HGLOBAL DoDdeRequest(const char *szItemName,HWND hwndDlg) +{ + ATOM hSzItemName; + DWORD timeoutTick,thisTick; + MSG msg; + + hSzItemName=GlobalAddAtomA(szItemName); + if(!PostMessage(hwndDde,WM_DDE_REQUEST,(WPARAM)hwndDlg,MAKELPARAM(CF_TEXT,hSzItemName))) { + GlobalDeleteAtom(hSzItemName); + return NULL; + } + timeoutTick=GetTickCount()+5000; + ddeData=0; ddeAcked=0; + do { + if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if(ddeData || ddeAcked) break; + thisTick=GetTickCount(); + if(thisTick>timeoutTick) break; + } + while(MsgWaitForMultipleObjects(0,NULL,FALSE,timeoutTick-thisTick,QS_ALLINPUT)==WAIT_OBJECT_0); + + if(!ddeData) { + GlobalDeleteAtom(hSzItemName); + return NULL; + } + + return hGlobalDdeData; +} + +static void FreeDdeRequestData(HGLOBAL hData) +{ + DDEDATA *data; + data=(DDEDATA*)GlobalLock(hData); + if(data->fRelease) { + GlobalUnlock(hData); + GlobalFree(hData); + } + else GlobalUnlock(hData); +} + +static void AddBrowserPageToCombo(char *url,HWND hwndCombo) +{ + char *title,*frame,*end; + + if(url[0]!='"') return; + url++; + title=strchr(url,'"'); + if(title==NULL) return; + *title='\0'; title++; + if(*title) { + title+=2; + frame=strchr(title,'"'); + if(frame==NULL) return; + *frame='\0'; frame++; + if(*frame) { + frame+=2; + end=strchr(frame,'"'); + if(end==NULL) return; + *end='\0'; + } + else frame=NULL; + } + else title=frame=NULL; + if(frame==NULL || *frame==0) { + char *szItemData; + int i; + char szExistingUrl[1024]; + + for(i=SendMessage(hwndCombo,CB_GETCOUNT,0,0)-1;i>=0;i--) { + if(SendMessage(hwndCombo,CB_GETLBTEXTLEN,i,0) >= SIZEOF(szExistingUrl)) + continue; + SendMessageA(hwndCombo,CB_GETLBTEXT,i,(LPARAM)szExistingUrl); + if(!lstrcmpA(szExistingUrl,url)) return; + } + i=SendMessageA(hwndCombo,CB_ADDSTRING,0,(LPARAM)url); + szItemData=mir_strdup(title); + SendMessage(hwndCombo,CB_SETITEMDATA,i,(LPARAM)szItemData); + } +} + +//see Q160957 and http://developer.netscape.com/docs/manuals/communicator/DDE/index.htm +static void GetOpenBrowserUrlsForBrowser(const char *szBrowser,HWND hwndDlg,HWND hwndCombo) +{ + ATOM hSzBrowser,hSzTopic; + int windowCount,i; + DWORD *windowId; + DWORD dwResult; + HGLOBAL hData; + DDEDATA *data; + int dataLength; + + hSzBrowser=GlobalAddAtomA(szBrowser); + + hSzTopic=GlobalAddAtomA("WWW_ListWindows"); + ddeAcked=0; + if(!SendMessageTimeout(HWND_BROADCAST,WM_DDE_INITIATE,(WPARAM)hwndDlg,MAKELPARAM(hSzBrowser,hSzTopic),SMTO_ABORTIFHUNG|SMTO_NORMAL,DDEMESSAGETIMEOUT,(PDWORD_PTR)&dwResult) + || !ddeAcked) { + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + return; + } + hData=DoDdeRequest("WWW_ListWindows",hwndDlg); + if(hData==NULL) { + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + return; + } + dataLength=GlobalSize(hData)-offsetof(DDEDATA,Value); + data=(DDEDATA*)GlobalLock(hData); + windowCount=dataLength/sizeof(DWORD); + windowId=(PDWORD)mir_alloc(sizeof(DWORD)*windowCount); + memcpy(windowId,data->Value,windowCount*sizeof(DWORD)); + GlobalUnlock(hData); + FreeDdeRequestData(hData); + PostMessage(hwndDde,WM_DDE_TERMINATE,(WPARAM)hwndDlg,0); + GlobalDeleteAtom(hSzTopic); + + hSzTopic=GlobalAddAtomA("WWW_GetWindowInfo"); + ddeAcked=0; + if(!SendMessageTimeout(HWND_BROADCAST,WM_DDE_INITIATE,(WPARAM)hwndDlg,MAKELPARAM(hSzBrowser,hSzTopic),SMTO_ABORTIFHUNG|SMTO_NORMAL,DDEMESSAGETIMEOUT,(PDWORD_PTR)&dwResult) + || !ddeAcked) { + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + mir_free(windowId); + return; + } + for(i=0;iValue,hwndCombo); + GlobalUnlock(hData); + FreeDdeRequestData(hData); + } + } + PostMessage(hwndDde,WM_DDE_TERMINATE,(WPARAM)hwndDlg,0); + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + mir_free(windowId); +} + +static void GetOpenBrowserUrls(HWND hwndDlg,HWND hwndCombo) +{ + GetOpenBrowserUrlsForBrowser("opera",hwndDlg,hwndCombo); + GetOpenBrowserUrlsForBrowser("netscape",hwndDlg,hwndCombo); + GetOpenBrowserUrlsForBrowser("iexplore",hwndDlg,hwndCombo); +} + +static WNDPROC OldSendEditProc; +static LRESULT CALLBACK SendEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) { + case WM_CHAR: + if(wParam=='\n' && GetKeyState(VK_CONTROL)&0x8000) { + PostMessage(GetParent(hwnd),WM_COMMAND,IDOK,0); + return 0; + } + break; + case WM_SYSCHAR: + if((wParam=='s' || wParam=='S') && GetKeyState(VK_MENU)&0x8000) { + PostMessage(GetParent(hwnd),WM_COMMAND,IDOK,0); + return 0; + } + break; + } + return CallWindowProc(OldSendEditProc,hwnd,msg,wParam,lParam); +} + +INT_PTR CALLBACK DlgProcUrlSend(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct UrlSendData* dat = (struct UrlSendData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + Window_SetIcon_IcoLib(hwndDlg, SKINICON_EVENT_URL); + Button_SetIcon_IcoLib(hwndDlg, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add Contact Permanently to List")); + Button_SetIcon_IcoLib(hwndDlg, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View User's Details")); + Button_SetIcon_IcoLib(hwndDlg, IDC_HISTORY, SKINICON_OTHER_HISTORY, LPGEN("View User's History")); + Button_SetIcon_IcoLib(hwndDlg, IDC_USERMENU, SKINICON_OTHER_DOWNARROW, LPGEN("User Menu")); + + SendDlgItemMessage(hwndDlg, IDC_MESSAGE, EM_LIMITTEXT, 450, 0); + dat=(struct UrlSendData*)mir_alloc(sizeof(struct UrlSendData)); + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)dat); + dat->hContact=(HANDLE)lParam; + dat->hAckEvent=NULL; + dat->hSendId=NULL; + dat->sendBuffer=NULL; + + WindowList_Add(hUrlWindowList, hwndDlg, dat->hContact); + { + TCHAR *str = cli.pfnGetContactDisplayName( dat->hContact, 0 ); + SetDlgItemText(hwndDlg,IDC_NAME,str); + } + + GetOpenBrowserUrls(hwndDlg,GetDlgItem(hwndDlg,IDC_URLS)); + SendDlgItemMessage(hwndDlg, IDC_URLS, CB_SETCURSEL, 0, 0); + if (SendDlgItemMessage(hwndDlg,IDC_URLS,CB_GETCOUNT,0,0))SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_URLS,CBN_SELCHANGE),0); + EnableWindow(GetDlgItem(hwndDlg, IDOK), (SendDlgItemMessage(hwndDlg, IDC_URLS, CB_GETCURSEL, 0, 0) == CB_ERR)?FALSE:TRUE); + Utils_RestoreWindowPositionNoSize(hwndDlg,NULL,"SRUrl","send"); + OldSendEditProc=(WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MESSAGE),GWLP_WNDPROC,(LONG_PTR)SendEditSubclassProc); + OldSendEditProc=(WNDPROC)SetWindowLongPtr(GetWindow(GetDlgItem(hwndDlg,IDC_URLS),GW_CHILD),GWLP_WNDPROC,(LONG_PTR)SendEditSubclassProc); + + // From message dlg + if (!DBGetContactSettingByte(dat->hContact, "CList", "NotOnList", 0)) + ShowWindow(GetDlgItem(hwndDlg, IDC_ADD), SW_HIDE); + + SendMessage(hwndDlg,DM_UPDATETITLE,0,0); + // From message dlg end + return TRUE; + + case WM_DDE_DATA: + case WM_DDE_ACK: + return DdeMessage(hwndDlg,msg,wParam,lParam); + + case WM_TIMER: + if ( wParam == 0 ) { + //ICQ sendurl timed out + KillTimer(hwndDlg,0); + MessageBox(hwndDlg,TranslateT("Send timed out"),_T(""),MB_OK); + EnableWindow(GetDlgItem(hwndDlg,IDOK),TRUE); + EnableWindow(GetDlgItem(hwndDlg,IDC_URLS),TRUE); + SendDlgItemMessage(hwndDlg,IDC_MESSAGE,EM_SETREADONLY,FALSE,0); + } + break; + + case WM_MEASUREITEM: + return CallService(MS_CLIST_MENUMEASUREITEM,wParam,lParam); + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT dis = ( LPDRAWITEMSTRUCT )lParam; + if ( dis->hwndItem == GetDlgItem(hwndDlg, IDC_PROTOCOL)) { + char *szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + if (szProto) { + HICON hIcon = (HICON)CallProtoService(szProto,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0); + if ( hIcon ) { + DrawIconEx(dis->hDC,dis->rcItem.left,dis->rcItem.top,hIcon,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0,NULL,DI_NORMAL); + DestroyIcon(hIcon); + } } } } + return CallService(MS_CLIST_MENUDRAWITEM,wParam,lParam); + + case DM_UPDATETITLE: + sttUpdateTitle( hwndDlg, dat->hContact ); + break; + + case WM_COMMAND: + if(CallService(MS_CLIST_MENUPROCESSCOMMAND,MAKEWPARAM(LOWORD(wParam),MPCF_CONTACTMENU),(LPARAM)dat->hContact)) + break; + switch (LOWORD(wParam)) + { + case IDOK: + { + char *body,*url; + int bodySize,urlSize; + + urlSize=GetWindowTextLength(GetDlgItem(hwndDlg,IDC_URLS))+1; + url=(char*)mir_alloc(urlSize); + GetDlgItemTextA(hwndDlg,IDC_URLS,url,urlSize); + if (url[0] == 0) { + mir_free(url); + break; + } + bodySize=GetWindowTextLength(GetDlgItem(hwndDlg,IDC_MESSAGE))+1; + body=(char*)mir_alloc(bodySize); + GetDlgItemTextA(hwndDlg,IDC_MESSAGE,body,bodySize); + + dat->sendBuffer=(char*)mir_realloc(dat->sendBuffer,lstrlenA(url)+lstrlenA(body)+2); + lstrcpyA(dat->sendBuffer,url); + lstrcpyA(dat->sendBuffer+lstrlenA(url)+1,body); + dat->hAckEvent=HookEventMessage(ME_PROTO_ACK,hwndDlg,HM_EVENTSENT); + dat->hSendId=(HANDLE)CallContactService(dat->hContact,PSS_URL,0,(LPARAM)dat->sendBuffer); + mir_free(url); + mir_free(body); + + //create a timeout timer + SetTimer(hwndDlg,0,TIMEOUT_URLSEND,NULL); + EnableWindow(GetDlgItem(hwndDlg,IDOK),FALSE); + EnableWindow(GetDlgItem(hwndDlg,IDC_URLS),FALSE); + SendDlgItemMessage(hwndDlg,IDC_MESSAGE,EM_SETREADONLY,TRUE,0); + + return TRUE; + } + + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + case IDC_URLS: + if(HIWORD(wParam)==CBN_SELCHANGE) { + int i, urlSize; + char *title; + i=SendDlgItemMessage(hwndDlg,IDC_URLS,CB_GETCURSEL,0,0); + title=(char*)SendDlgItemMessage(hwndDlg,IDC_URLS,CB_GETITEMDATA,(WPARAM)i,0); + SetDlgItemTextA(hwndDlg,IDC_MESSAGE,title); + urlSize=SendDlgItemMessage(hwndDlg,IDC_URLS,CB_GETLBTEXTLEN,(WPARAM)i,0); + EnableWindow(GetDlgItem(hwndDlg,IDOK), (urlSize>0)); + } + else if(HIWORD(wParam)==CBN_EDITCHANGE) { + int urlSize = GetWindowTextLength(GetDlgItem(hwndDlg,IDC_URLS)); + EnableWindow(GetDlgItem(hwndDlg,IDOK), (urlSize>0)); + } + break; + case IDC_USERMENU: + { RECT rc; + HMENU hMenu=(HMENU)CallService(MS_CLIST_MENUBUILDCONTACT,(WPARAM)dat->hContact,0); + GetWindowRect(GetDlgItem(hwndDlg,IDC_USERMENU),&rc); + TrackPopupMenu(hMenu,0,rc.left,rc.bottom,0,hwndDlg,NULL); + DestroyMenu(hMenu); + } + break; + case IDC_HISTORY: + CallService(MS_HISTORY_SHOWCONTACTHISTORY,(WPARAM)dat->hContact,0); + break; + case IDC_DETAILS: + CallService(MS_USERINFO_SHOWDIALOG,(WPARAM)dat->hContact,0); + break; + case IDC_ADD: + { ADDCONTACTSTRUCT acs={0}; + + acs.handle=dat->hContact; + acs.handleType=HANDLE_CONTACT; + acs.szProto=0; + CallService(MS_ADDCONTACT_SHOW,(WPARAM)hwndDlg,(LPARAM)&acs); + } + if (!DBGetContactSettingByte(dat->hContact,"CList","NotOnList",0)) { + ShowWindow(GetDlgItem(hwndDlg,IDC_ADD),FALSE); + } + break; + } + break; + case HM_EVENTSENT: + { ACKDATA *ack=(ACKDATA*)lParam; + DBEVENTINFO dbei; + if(ack->hProcess!=dat->hSendId) break; + if(ack->hContact!=dat->hContact) break; + if(ack->type!=ACKTYPE_URL || ack->result!=ACKRESULT_SUCCESS) break; + + ZeroMemory(&dbei,sizeof(dbei)); + dbei.cbSize=sizeof(dbei); + dbei.eventType=EVENTTYPE_URL; + dbei.flags=DBEF_SENT; + dbei.szModule=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + dbei.timestamp=time(NULL); + dbei.cbBlob=(DWORD)(strlen(dat->sendBuffer)+strlen(dat->sendBuffer+strlen(dat->sendBuffer)+1)+2); + dbei.pBlob=(PBYTE)dat->sendBuffer; + CallService(MS_DB_EVENT_ADD,(WPARAM)dat->hContact,(LPARAM)&dbei); + KillTimer(hwndDlg,0); + DestroyWindow(hwndDlg); + break; + } + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Button_FreeIcon_IcoLib(hwndDlg,IDC_ADD); + Button_FreeIcon_IcoLib(hwndDlg,IDC_DETAILS); + Button_FreeIcon_IcoLib(hwndDlg,IDC_HISTORY); + Button_FreeIcon_IcoLib(hwndDlg,IDC_USERMENU); + + WindowList_Remove(hUrlWindowList, hwndDlg); + SetWindowLongPtr(GetWindow(GetDlgItem(hwndDlg,IDC_URLS),GW_CHILD),GWLP_WNDPROC,(LONG_PTR)OldSendEditProc); + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MESSAGE),GWLP_WNDPROC,(LONG_PTR)OldSendEditProc); + if(dat->hAckEvent) UnhookEvent(dat->hAckEvent); + if(dat->sendBuffer!=NULL) mir_free(dat->sendBuffer); + mir_free(dat); + Utils_SaveWindowPosition(hwndDlg,NULL,"SRUrl","send"); + { int i; + for(i=SendDlgItemMessage(hwndDlg,IDC_URLS,CB_GETCOUNT,0,0)-1;i>=0;i--) + mir_free((char*)SendDlgItemMessage(hwndDlg,IDC_URLS,CB_GETITEMDATA,i,0)); + } + break; + } + + return FALSE; +} diff --git a/src/modules/updatenotify/updatenotify.cpp b/src/modules/updatenotify/updatenotify.cpp new file mode 100644 index 0000000000..ef955ba3c5 --- /dev/null +++ b/src/modules/updatenotify/updatenotify.cpp @@ -0,0 +1,680 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +#define UN_MOD "UpdateNotify" +#define UN_ENABLE "UpdateNotifyEnable" +#define UN_ENABLE_DEF 1 +#define UN_LASTCHECK "UpdateNotifyLastCheck" +#define UN_SERVERPERIOD "UpdateNotifyPingDelayPeriod" +#define UN_CURRENTVERSION "UpdateNotifyCurrentVersion" +#define UN_CURRENTVERSIONFND "UpdateNotifyCurrentVersionFound" +#define UN_NOTIFYTYPE "UpdateNotifyType" +#define UN_NOTIFYTYPE_STABLE 1 +#define UN_NOTIFYTYPE_BETA 2 +#define UN_NOTIFYTYPE_ALPHA 3 +#define UN_NOTIFYTYPE_DEF UN_NOTIFYTYPE_STABLE +#define UN_CUSTOMXMLURL "UpdateNotifyCustomXMLURL" +#define UN_URLXML "http://update.miranda-im.org/update.xml" +#define UN_MINCHECKTIME 60*60 /* Check no more than once an hour */ +#define UN_DEFAULTCHECKTIME 60*48*60 /* Default to check once every 48 hours */ +#define UN_FIRSTCHECK 15 /* First check 15 seconds after startup */ +#define UN_REPEATNOTIFYDLY 24*60*60 /* 24 hours before showing release notification again */ + +typedef struct { + int isNew; + int isManual; + char version[64]; + char versionReal[16]; + char notesUrl[256]; + char downloadUrl[256]; + DWORD reqTime; +} UpdateNotifyData; + +typedef struct { + DWORD dwVersion; + char *szVersionPublic; + char *szVersion; + char *szDownload; + char *szNotes; +} UpdateNotifyReleaseData; + +static BOOL bModuleInitialized = FALSE; +static HANDLE hNetlibUser = 0, hHookModules, hHookPreShutdown; +static UINT_PTR updateTimerId; +static HANDLE dwUpdateThreadID = 0; +static HWND hwndUpdateDlg = 0, hwndManualUpdateDlg = 0; +static XML_API xun; + +static int UpdateNotifyOptInit(WPARAM wParam, LPARAM lParam); +static INT_PTR UpdateNotifyMenuCommand(WPARAM wParam, LPARAM lParam); +static VOID CALLBACK UpdateNotifyTimerCheck(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); +static int UpdateNotifyMakeRequest(UpdateNotifyData *und); +static void UpdateNotifyPerform(void *m); +static INT_PTR CALLBACK UpdateNotifyProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK UpdateNotifyOptsProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +static int UpdateNotifyModulesLoaded(WPARAM, LPARAM) { + NETLIBUSER nlu; + + ZeroMemory(&nlu, sizeof(nlu)); + nlu.cbSize = sizeof(nlu); + nlu.flags = NUF_OUTGOING|NUF_HTTPCONNS; + nlu.szSettingsModule = UN_MOD; + nlu.szDescriptiveName = Translate("Update notification"); + hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + return 0; +} + +static int UpdateNotifyPreShutdown(WPARAM, LPARAM) { + if (IsWindow(hwndUpdateDlg)) { + SendMessage(hwndUpdateDlg, WM_COMMAND, MAKELONG(IDOK, 0), 0); + } + if (IsWindow(hwndManualUpdateDlg)) { + SendMessage(hwndManualUpdateDlg, WM_COMMAND, MAKELONG(IDOK, 0), 0); + } + return 0; +} + +int LoadUpdateNotifyModule(void) { + CLISTMENUITEM mi = { 0 }; + + bModuleInitialized = TRUE; + + // Upgrade Routine + if (DBGetContactSettingByte(NULL, UN_MOD, "UpdateNotifyNotifyAlpha", 0)) { + DBDeleteContactSetting(NULL, UN_MOD, "UpdateNotifyNotifyAlpha"); + DBWriteContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_ALPHA); + } + // Ene Upgrade Routine + + CreateServiceFunction("UpdateNotify/UpdateCommand", UpdateNotifyMenuCommand); + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_EMPTYBLOB); + mi.pszPopupName = LPGEN("&Help"); + mi.position = 2000030000; + mi.pszName = LPGEN("Check for Update"); + mi.pszService = "UpdateNotify/UpdateCommand"; + CallService(MS_CLIST_ADDMAINMENUITEM,0,(LPARAM)&mi); + + hHookModules = HookEvent(ME_SYSTEM_MODULESLOADED, UpdateNotifyModulesLoaded); + hHookPreShutdown = HookEvent(ME_SYSTEM_PRESHUTDOWN, UpdateNotifyPreShutdown); + HookEvent(ME_OPT_INITIALISE, UpdateNotifyOptInit); + updateTimerId = SetTimer(NULL, 0, 1000*UN_FIRSTCHECK, UpdateNotifyTimerCheck); + mir_getXI(&xun); + return 0; +} + +void UnloadUpdateNotifyModule() +{ + if (!bModuleInitialized) return; + UnhookEvent(hHookModules); + UnhookEvent(hHookPreShutdown); +} + +static int UpdateNotifyOptInit(WPARAM wParam, LPARAM) { + OPTIONSDIALOGPAGE odp; + + ZeroMemory(&odp, sizeof(odp)); + odp.cbSize = sizeof(odp); + odp.position = 100000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_UPDATENOTIFY); + odp.pszGroup = LPGEN("Events"); + odp.pszTitle = LPGEN("Update Notify"); + odp.pfnDlgProc = UpdateNotifyOptsProc; + odp.flags = ODPF_BOLDGROUPS; + CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); + return 0; +} + +static INT_PTR UpdateNotifyMenuCommand(WPARAM, LPARAM) { + UpdateNotifyData und; + + if (IsWindow(hwndManualUpdateDlg)) { + SetForegroundWindow(hwndManualUpdateDlg); + return 0; + } + ZeroMemory(&und, sizeof(und)); + UpdateNotifyMakeRequest(&und); + if (und.isNew) { + DBWriteContactSettingString(NULL, UN_MOD, UN_CURRENTVERSION, und.versionReal); + DBWriteContactSettingDword(NULL, UN_MOD, UN_CURRENTVERSIONFND, und.reqTime); + } + und.isManual = 1; + DialogBoxParam(hMirandaInst, MAKEINTRESOURCE(IDD_UPDATE_NOTIFY), 0, UpdateNotifyProc,(LPARAM)&und); + hwndManualUpdateDlg = 0; + return 0; +} + +static VOID CALLBACK UpdateNotifyTimerCheck(HWND, UINT, UINT_PTR, DWORD) +{ + KillTimer(NULL, updateTimerId); + if (!DBGetContactSettingByte(NULL, UN_MOD, UN_ENABLE, UN_ENABLE_DEF)) + return; + if (dwUpdateThreadID!=0) { + Netlib_Logf(hNetlibUser, "Update notification already running, ignoring attempt"); + return; + } + { + DWORD lastCheck = 0; + + if (!hNetlibUser) + return; + lastCheck = DBGetContactSettingDword(NULL, UN_MOD, UN_LASTCHECK, 0); + if (!lastCheck) { // never checked for update before + Netlib_Logf(hNetlibUser, "Running update notify check for the first time."); + dwUpdateThreadID = mir_forkthread(UpdateNotifyPerform, 0); + } + else { + DWORD dwNow = time(NULL), dwTimeDiff; + DWORD dwServerPing = DBGetContactSettingDword(NULL, UN_MOD, UN_SERVERPERIOD, UN_DEFAULTCHECKTIME); + + if (lastCheck>dwNow) { + // time for last check is after the current date so reset lastcheck and quit + DBWriteContactSettingDword(NULL, UN_MOD, UN_LASTCHECK, dwNow); + return; + } + dwTimeDiff = dwNow - lastCheck; + if (dwServerPingdwServerPing) + dwUpdateThreadID = mir_forkthread(UpdateNotifyPerform, 0); + } + updateTimerId = SetTimer(NULL, 0, 1000*UN_MINCHECKTIME, UpdateNotifyTimerCheck); + } +} + +static DWORD UpdateNotifyMakeVersion(char *str) { + DWORD a1,a2,a3,a4; + if (!str) + return 0; + sscanf(str, "%u.%u.%u.%u", &a1, &a2, &a3, &a4); + return PLUGIN_MAKE_VERSION(a1, a2, a3, a4); +} + +static int UpdateNotifyIsNewer(DWORD dwCurrent, DWORD dwTest) { + if (dwTest>dwCurrent) + return 1; + return 0; +} + +static int UpdateNotifyReleaseDataValid(UpdateNotifyReleaseData *d) { + if (d&&d->szVersionPublic&&d->szVersion&&d->szDownload&&d->szNotes) + return 1; + return 0; +} + +static void UpdateNotifyFreeReleaseData(UpdateNotifyReleaseData *d) { + if (!d) + return; + if (d->szVersionPublic) mir_free(d->szVersionPublic); + if (d->szVersion) mir_free(d->szVersion); + if (d->szDownload) mir_free(d->szDownload); + if (d->szNotes) mir_free(d->szNotes); +} + +static void UpdateNotifyReleaseLogUpdate(UpdateNotifyReleaseData *d) { + if (!UpdateNotifyReleaseDataValid(d)) + return; + #ifdef _WIN64 + Netlib_Logf(hNetlibUser, "Update server version: %s [%s] [64-bit]", d->szVersionPublic, d->szVersion); + #elif defined(_UNICODE) + Netlib_Logf(hNetlibUser, "Update server version: %s [%s] [Unicode]", d->szVersionPublic, d->szVersion); + #else + Netlib_Logf(hNetlibUser, "Update server version: %s [%s] [ANSI]", d->szVersionPublic, d->szVersion); + #endif + +} + +static void UpdateNotifyReleaseCopyData(UpdateNotifyReleaseData *d, UpdateNotifyData *und) { + if (!UpdateNotifyReleaseDataValid(d)||!und) + return; + mir_snprintf(und->version, sizeof(und->version), "%s", d->szVersionPublic); + mir_snprintf(und->versionReal, sizeof(und->versionReal), "%s", d->szVersion); + mir_snprintf(und->notesUrl, sizeof(und->notesUrl), "%s", d->szNotes); + mir_snprintf(und->downloadUrl, sizeof(und->downloadUrl), "%s", d->szDownload); +} + +static int UpdateNotifyMakeRequest(UpdateNotifyData *und) { + NETLIBHTTPREQUEST req; + NETLIBHTTPREQUEST *resp; + NETLIBHTTPHEADER headers[1]; + DWORD dwVersion; + char szVersion[32], szUrl[256], szVersionText[128], szUserAgent[64]; + int isUnicode, isAlphaCheck, isBetaCheck; + DBVARIANT dbv; + + if (!und) + return 0; + und->version[0] = 0; + und->versionReal[0] = 0; + und->notesUrl[0] = 0; + und->downloadUrl[0] = 0; + und->reqTime = time(NULL); + + DBWriteContactSettingDword(NULL, UN_MOD, UN_LASTCHECK, und->reqTime); + CallService(MS_SYSTEM_GETVERSIONTEXT, sizeof(szVersionText), (LPARAM)szVersionText); + isUnicode = strstr(szVersionText, "Unicode") != NULL ? 1 : 0; + isBetaCheck = DBGetContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_DEF)==UN_NOTIFYTYPE_BETA?1:0; + isAlphaCheck = DBGetContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_DEF)==UN_NOTIFYTYPE_ALPHA?1:0; + dwVersion = CallService(MS_SYSTEM_GETVERSION, 0, 0); + mir_snprintf(szVersion, sizeof(szVersion), "%d.%d.%d.%d", + HIBYTE(HIWORD(dwVersion)), LOBYTE(HIWORD(dwVersion)), + HIBYTE(LOWORD(dwVersion)), LOBYTE(LOWORD(dwVersion))); + if (!DBGetContactSettingString(NULL, UN_MOD, UN_CUSTOMXMLURL, &dbv)) { + mir_snprintf(szUrl, sizeof(szUrl), "%s", dbv.pszVal?dbv.pszVal:UN_URLXML); + DBFreeVariant(&dbv); + } + else mir_snprintf(szUrl, sizeof(szUrl), "%s", UN_URLXML); + ZeroMemory(&req, sizeof(req)); + req.cbSize = sizeof(req); + req.requestType = REQUEST_GET; + req.szUrl = szUrl; + req.flags = 0; + headers[0].szName = "User-Agent"; + headers[0].szValue = szUserAgent; + #ifdef _WIN64 + mir_snprintf(szUserAgent, sizeof(szUserAgent), "Miranda/%s (x64)", szVersion); + #elif defined(_UNICODE) + mir_snprintf(szUserAgent, sizeof(szUserAgent), "Miranda/%s (Unicode)", szVersion); + #else + mir_snprintf(szUserAgent, sizeof(szUserAgent), "Miranda/%s (ANSI)", szVersion); + #endif + req.headersCount = 1; + req.headers = headers; + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)hNetlibUser, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0) { + //int i; + int resUpdate = 0; + TCHAR *tXml; + char *tmp; + HXML nodeDoc, n; + + tXml = mir_a2t(resp->pData); + nodeDoc = xun.parseString(tXml, 0, _T("miranda")); + if (nodeDoc) { + int rdStableValid = 0, rdBetaValid = 0, rdAlphaValid = 0; + // stable release + UpdateNotifyReleaseData rdStable; + ZeroMemory(&rdStable, sizeof(rdStable)); + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasestable/versionpublic"), 0)) != NULL && xun.getText(n)) { + rdStable.szVersionPublic = mir_t2a(xun.getText(n)); + } + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasestable/versionreal"), 0)) != NULL && xun.getText(n)) { + rdStable.szVersion = mir_t2a(xun.getText(n)); + if (rdStable.szVersion) + rdStable.dwVersion = UpdateNotifyMakeVersion(rdStable.szVersion); + } + #ifdef _WIN64 + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasestable/downloadx64exe"), 0)) != NULL && xun.getText(n)) { + rdStable.szDownload = mir_t2a(xun.getText(n)); + } + #elif defined(_UNICODE) + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasestable/downloadunicodeexe"), 0)) != NULL && xun.getText(n)) { + rdStable.szDownload = mir_t2a(xun.getText(n)); + } + #else + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasestable/downloadansiexe"), 0)) != NULL && xun.getText(n)) { + rdStable.szDownload = mir_t2a(xun.getText(n)); + } + #endif + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasestable/notesurl"), 0)) != NULL && xun.getText(n)) { + rdStable.szNotes = mir_t2a(xun.getText(n)); + } + rdStableValid = UpdateNotifyReleaseDataValid(&rdStable); + + // beta release + UpdateNotifyReleaseData rdBeta; + ZeroMemory(&rdBeta, sizeof(rdBeta)); + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasebeta/versionpublic"), 0)) != NULL && xun.getText(n)) { + rdBeta.szVersionPublic = mir_t2a(xun.getText(n)); + } + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasebeta/versionreal"), 0)) != NULL && xun.getText(n)) { + rdBeta.szVersion = mir_t2a(xun.getText(n)); + if (rdBeta.szVersion) + rdBeta.dwVersion = UpdateNotifyMakeVersion(rdBeta.szVersion); + } + #ifdef _WIN64 + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasebeta/downloadx64zip"), 0)) != NULL && xun.getText(n)) { + rdBeta.szDownload = mir_t2a(xun.getText(n)); + } + #elif defined(_UNICODE) + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasebeta/downloadunicodeexe"), 0)) != NULL && xun.getText(n)) { + rdBeta.szDownload = mir_t2a(xun.getText(n)); + } + #else + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasebeta/downloadansiexe"), 0)) != NULL && xun.getText(n)) { + rdBeta.szDownload = mir_t2a(xun.getText(n)); + } + #endif + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasebeta/notesurl"), 0)) != NULL && xun.getText(n)) { + rdBeta.szNotes = mir_t2a(xun.getText(n)); + } + rdBetaValid = UpdateNotifyReleaseDataValid(&rdBeta); + + // alpha release + UpdateNotifyReleaseData rdAlpha; + ZeroMemory(&rdAlpha, sizeof(rdAlpha)); + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasealpha/versionpublic"), 0)) != NULL && xun.getText(n)) { + rdAlpha.szVersionPublic = mir_t2a(xun.getText(n)); + } + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasealpha/versionreal"), 0)) != NULL && xun.getText(n)) { + rdAlpha.szVersion = mir_t2a(xun.getText(n)); + if (rdAlpha.szVersion) + rdAlpha.dwVersion = UpdateNotifyMakeVersion(rdAlpha.szVersion); + } + #ifdef _WIN64 + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasealpha/downloadx64zip"), 0)) != NULL && xun.getText(n)) { + rdAlpha.szDownload = mir_t2a(xun.getText(n)); + } + #elif defined(_UNICODE) + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasealpha/downloadunicodezip"), 0)) != NULL && xun.getText(n)) { + rdAlpha.szDownload = mir_t2a(xun.getText(n)); + } + #else + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasealpha/downloadansizip"), 0)) != NULL && xun.getText(n)) { + rdAlpha.szDownload = mir_t2a(xun.getText(n)); + } + #endif + if ((n = xun.getChildByPath(nodeDoc, _T("releases/releasealpha/notesurl"), 0)) != NULL && xun.getText(n)) { + rdAlpha.szNotes = mir_t2a(xun.getText(n)); + } + rdAlphaValid = UpdateNotifyReleaseDataValid(&rdAlpha); + + if (isBetaCheck) { + if (!rdBetaValid&&rdStableValid) { + UpdateNotifyReleaseLogUpdate(&rdStable); + if (UpdateNotifyIsNewer(dwVersion, rdStable.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdStable, und); + } + else if (rdBetaValid&&rdStableValid&&UpdateNotifyIsNewer(rdBeta.dwVersion, rdStable.dwVersion)) { + UpdateNotifyReleaseLogUpdate(&rdStable); + if (UpdateNotifyIsNewer(dwVersion, UpdateNotifyMakeVersion(rdStable.szVersion))) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdStable, und); + } + else if (rdBetaValid) { + UpdateNotifyReleaseLogUpdate(&rdBeta); + if (UpdateNotifyIsNewer(dwVersion, rdBeta.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdBeta, und); + } + } + else if (isAlphaCheck) { + if (!rdAlphaValid&&rdStableValid) { + if (UpdateNotifyIsNewer(rdStable.dwVersion, rdAlpha.dwVersion)) { + UpdateNotifyReleaseLogUpdate(&rdAlpha); + if (UpdateNotifyIsNewer(dwVersion, rdAlpha.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdAlpha, und); + } + else { + UpdateNotifyReleaseLogUpdate(&rdStable); + if (UpdateNotifyIsNewer(dwVersion, rdStable.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdStable, und); + } + } + else if (rdAlphaValid&&rdStableValid&&UpdateNotifyIsNewer(rdAlpha.dwVersion, rdStable.dwVersion)) { + UpdateNotifyReleaseLogUpdate(&rdStable); + if (UpdateNotifyIsNewer(dwVersion, rdStable.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdStable, und); + } + else if (!rdAlphaValid&&rdBetaValid&&rdStableValid) { + if (UpdateNotifyIsNewer(rdStable.dwVersion, rdBeta.dwVersion)) { + UpdateNotifyReleaseLogUpdate(&rdBeta); + if (UpdateNotifyIsNewer(dwVersion, rdBeta.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdBeta, und); + } + else { + UpdateNotifyReleaseLogUpdate(&rdStable); + if (UpdateNotifyIsNewer(dwVersion, rdStable.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdStable, und); + } + } + else if (rdAlphaValid) { + UpdateNotifyReleaseLogUpdate(&rdAlpha); + if (UpdateNotifyIsNewer(dwVersion, rdAlpha.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdAlpha, und); + } + } + else { + if (rdStableValid) { + UpdateNotifyReleaseLogUpdate(&rdStable); + if (UpdateNotifyIsNewer(dwVersion, rdStable.dwVersion)) + resUpdate = 1; + UpdateNotifyReleaseCopyData(&rdStable, und); + } + } + + UpdateNotifyFreeReleaseData(&rdStable); + UpdateNotifyFreeReleaseData(&rdBeta); + UpdateNotifyFreeReleaseData(&rdAlpha); + // settings + if ((n = xun.getChildByPath(nodeDoc, _T("settings/ping"), 0)) != NULL && xun.getText(n)) { + tmp = mir_t2a(xun.getText(n)); + if (tmp) { + int pingval = atoi(tmp); + if ((pingval*60*60)>UN_MINCHECKTIME) { + Netlib_Logf(hNetlibUser, "Next update check in %d hours", pingval); + DBWriteContactSettingDword(NULL, UN_MOD, UN_SERVERPERIOD, pingval*60*60); + } + mir_free(tmp); + } + } + if ((n = xun.getChildByPath(nodeDoc, _T("settings/updateurl"), 0)) != NULL && xun.getText(n)) { + tmp = mir_t2a(xun.getText(n)); + if (tmp) { + Netlib_Logf(hNetlibUser, "Update URL has changed (%s)", tmp); + DBWriteContactSettingString(NULL, UN_MOD, UN_CUSTOMXMLURL, tmp); + mir_free(tmp); + } + } + if (resUpdate&&und->version&&und->versionReal&&und->notesUrl&&und->downloadUrl) { + Netlib_Logf(hNetlibUser, "A new version of Miranda IM is available: %s", und->version); + und->isNew = 1; + } + xun.destroyNode(nodeDoc); + } + mir_free(tXml); + } + else Netlib_Logf(hNetlibUser, "Invalid response code from HTTP request"); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else Netlib_Logf(hNetlibUser, "No response from HTTP request"); + return und->isNew; +} + +static void UpdateNotifyPerform(void *) +{ + UpdateNotifyData und; + DBVARIANT dbv; + + ZeroMemory(&und, sizeof(und)); + UpdateNotifyMakeRequest(&und); + if (und.isNew) { + int notify = 1; + + if (!DBGetContactSettingString(NULL, UN_MOD, UN_CURRENTVERSION, &dbv)) { + if (!strcmp(dbv.pszVal, und.versionReal)) { // already notified of this version + + DWORD dwNotifyLast = DBGetContactSettingDword(NULL, UN_MOD, UN_CURRENTVERSIONFND, 0); + + if (dwNotifyLast>und.reqTime) { // fix last check date if time has changed + DBWriteContactSettingDword(NULL, UN_MOD, UN_CURRENTVERSIONFND, und.reqTime); + notify = 0; + } + else if (und.reqTime-dwNotifyLastisManual) + hwndManualUpdateDlg = hwndDlg; + else hwndUpdateDlg = hwndDlg; + if (und->isNew) { + TCHAR* ptszVer = mir_a2t( und->version ); + mir_sntprintf(szTmp, SIZEOF(szTmp), TranslateT("Miranda IM %s Now Available"), ptszVer); + mir_free(ptszVer); + ShowWindow(GetDlgItem(hwndDlg, IDC_UPDATE), SW_HIDE); + } + else { + mir_sntprintf(szTmp, SIZEOF(szTmp), TranslateT("No Update Available")); + SetDlgItemText(hwndDlg, IDC_MESSAGE, TranslateT("You are running the latest version of Miranda IM. No update is available at this time.")); + EnableWindow(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); + ShowWindow(GetDlgItem(hwndDlg, IDC_VERSION), SW_HIDE); + } + SetWindowText(hwndDlg, szTmp); + CallService(MS_SYSTEM_GETVERSIONTEXT, sizeof(szVersion), (LPARAM)szVersion); + p = strstr(szVersion, "x64 Unicode"); + if (p) + *p = '\0'; + p = strstr(szVersion, " Unicode"); + if (p) + *p = '\0'; + SetDlgItemTextA(hwndDlg, IDC_CURRENTVERSION, szVersion); + mir_snprintf(szVersionTmp, SIZEOF(szVersionTmp), "%s", und->version?und->version:szVersion); + SetDlgItemTextA(hwndDlg, und->isNew?IDC_VERSION:IDC_UPDATE, szVersionTmp); + if (und->isNew) { + HFONT hFont; + LOGFONT lf; + + hFont = (HFONT)SendDlgItemMessage(hwndDlg, IDC_VERSION, WM_GETFONT, 0, 0); + GetObject(hFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + hFont = CreateFontIndirect(&lf); + SendDlgItemMessage(hwndDlg, IDC_VERSION, WM_SETFONT, (WPARAM)hFont, 0); + hFont = (HFONT)SendDlgItemMessage(hwndDlg, IDC_NEWVERSIONLABEL, WM_GETFONT, 0, 0); + GetObject(hFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + hFont = CreateFontIndirect(&lf); + SendDlgItemMessage(hwndDlg, IDC_NEWVERSIONLABEL, WM_SETFONT, (WPARAM)hFont, 0); + } + SetFocus(GetDlgItem(hwndDlg, IDOK)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_VERSION: + { + UpdateNotifyData *und = (UpdateNotifyData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (und&&und->notesUrl) + CallService(MS_UTILS_OPENURL, 1, (LPARAM)und->notesUrl); + break; + } + case IDC_DOWNLOAD: + { + UpdateNotifyData *und = (UpdateNotifyData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (und&&und->downloadUrl) { + CallService(MS_UTILS_OPENURL, 1, (LPARAM)und->downloadUrl); + DestroyWindow(hwndDlg); + } + break; + } + case IDOK: + case IDCANCEL: + DestroyWindow(hwndDlg); + return TRUE; + } + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib( hwndDlg ); + break; + } + return FALSE; +} + +static INT_PTR CALLBACK UpdateNotifyOptsProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + CheckDlgButton(hwndDlg, IDC_ENABLEUPDATES, DBGetContactSettingByte(NULL, UN_MOD, UN_ENABLE, UN_ENABLE_DEF) ? BST_CHECKED : BST_UNCHECKED); + switch (DBGetContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_STABLE)) { + case UN_NOTIFYTYPE_BETA: CheckDlgButton(hwndDlg, IDC_ENABLEBETA, BST_CHECKED); break; + case UN_NOTIFYTYPE_ALPHA: CheckDlgButton(hwndDlg, IDC_ENABLEALPHA, BST_CHECKED); break; + default: CheckDlgButton(hwndDlg, IDC_ENABLESTABLE, BST_CHECKED); break; + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_ENABLEUPDATES: + case IDC_ENABLEALPHA: + case IDC_ENABLEBETA: + case IDC_ENABLESTABLE: + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + break; + + case WM_NOTIFY: + { + NMHDR *hdr = (NMHDR *)lParam; + if (hdr&&hdr->code==PSN_APPLY) { + DBWriteContactSettingByte(NULL, UN_MOD, UN_ENABLE, (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_ENABLEUPDATES))); + if (IsDlgButtonChecked(hwndDlg, IDC_ENABLESTABLE)) + DBWriteContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_STABLE); + if (IsDlgButtonChecked(hwndDlg, IDC_ENABLEBETA)) + DBWriteContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_BETA); + if (IsDlgButtonChecked(hwndDlg, IDC_ENABLEALPHA)) + DBWriteContactSettingByte(NULL, UN_MOD, UN_NOTIFYTYPE, UN_NOTIFYTYPE_ALPHA); + } + break; + } + } + return FALSE; +} diff --git a/src/modules/userinfo/contactinfo.cpp b/src/modules/userinfo/contactinfo.cpp new file mode 100644 index 0000000000..dc66067bbf --- /dev/null +++ b/src/modules/userinfo/contactinfo.cpp @@ -0,0 +1,515 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +static HFONT hEmailFont=NULL; +static HCURSOR hHandCursor=NULL; + +static INT_PTR CALLBACK EditUserEmailDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)lParam); + if(*(char*)lParam) SetWindowText(hwndDlg,TranslateT("Edit E-Mail Address")); + TranslateDialogDefault(hwndDlg); + SetDlgItemTextA(hwndDlg,IDC_EMAIL,(char*)lParam); + EnableWindow(GetDlgItem(hwndDlg,IDOK),*(char*)lParam); + return TRUE; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + GetDlgItemTextA(hwndDlg,IDC_EMAIL,(char*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA),256); + //fall through + case IDCANCEL: + EndDialog(hwndDlg,wParam); + case IDC_EMAIL: + if(HIWORD(wParam)==EN_CHANGE) + EnableWindow(GetDlgItem(hwndDlg,IDOK),GetWindowTextLength(GetDlgItem(hwndDlg,IDC_EMAIL))); + break; + } + break; + } + return FALSE; +} + +static INT_PTR CALLBACK EditUserPhoneDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static int noRecursion=0; + + switch(msg) { + case WM_INITDIALOG: + { char *szText=(char*)lParam; + int i,item,countryCount; + struct CountryListEntry *countries; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)lParam); + if(szText[0]) SetWindowText(hwndDlg,TranslateT("Edit Phone Number")); + TranslateDialogDefault(hwndDlg); + if(lstrlenA(szText)>4 && !lstrcmpA(szText+lstrlenA(szText)-4," SMS")) { + CheckDlgButton(hwndDlg,IDC_SMS,BST_CHECKED); + szText[lstrlenA(szText)-4]='\0'; + } + EnableWindow(GetDlgItem(hwndDlg,IDOK),szText[0]); + SendDlgItemMessage(hwndDlg,IDC_AREA,EM_LIMITTEXT,31,0); + SendDlgItemMessage(hwndDlg,IDC_NUMBER,EM_LIMITTEXT,63,0); + CallService(MS_UTILS_GETCOUNTRYLIST,(WPARAM)&countryCount,(LPARAM)&countries); + for(i=0;i 4 ) + isValid = 0; + else { + for ( i = SendDlgItemMessage( hwndDlg, IDC_COUNTRY, CB_GETCOUNT, 0, 0 )-1; i >= 0; i-- ) + if ( country == SendDlgItemMessage(hwndDlg,IDC_COUNTRY,CB_GETITEMDATA,i,0)) { + SendDlgItemMessage(hwndDlg,IDC_COUNTRY,CB_SETCURSEL,i,0); + break; + } + if ( i < 0 ) + isValid = 0; + } + } + if ( isValid ) { + pArea = pText+strcspn(pText,"0123456789"); + pText = pArea+strspn(pArea,"0123456789"); + if(*pText) { + *pText='\0'; + pNumber = pText+1+strcspn(pText+1,"0123456789"); + SetDlgItemTextA(hwndDlg,IDC_NUMBER,pNumber); + } + SetDlgItemTextA(hwndDlg,IDC_AREA,pArea); + } + if ( !isValid ) { + SendDlgItemMessage(hwndDlg,IDC_COUNTRY,CB_SETCURSEL,-1,0); + SetDlgItemTextA(hwndDlg,IDC_AREA,""); + SetDlgItemTextA(hwndDlg,IDC_NUMBER,""); + } + } + noRecursion=0; + EnableWindow(GetDlgItem(hwndDlg,IDOK),GetWindowTextLength(GetDlgItem(hwndDlg,IDC_PHONE))); + break; + } + break; + } + return FALSE; +} + +static int IsOverEmail(HWND hwndDlg,TCHAR* szEmail,int cchEmail) +{ + RECT rc; + HWND hwndEmails; + TCHAR szText[256]; + HDC hdc; + SIZE textSize; + LVHITTESTINFO hti; + + hwndEmails=GetDlgItem(hwndDlg,IDC_EMAILS); + GetCursorPos(&hti.pt); + ScreenToClient(hwndEmails,&hti.pt); + GetClientRect(hwndEmails,&rc); + if(!PtInRect(&rc,hti.pt)) return 0; + if(ListView_SubItemHitTest(hwndEmails,&hti)==-1) return 0; + if(hti.iSubItem!=1) return 0; + if(!(hti.flags&LVHT_ONITEMLABEL)) return 0; + ListView_GetSubItemRect(hwndEmails,hti.iItem,1,LVIR_LABEL,&rc); + szText[0] = 0; + ListView_GetItemText(hwndEmails,hti.iItem,1,szText,SIZEOF(szText)); + hdc=GetDC(hwndEmails); + SelectObject(hdc,hEmailFont); + GetTextExtentPoint32(hdc,szText,lstrlen(szText),&textSize); + ReleaseDC(hwndEmails,hdc); + if(hti.pt.x4 && !lstrcmpA(dbv.pszVal+lstrlenA(dbv.pszVal)-4," SMS")) { + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PHONES),lvi.iItem,2,_T("y")); + dbv.ptszVal[lstrlen(dbv.ptszVal)-4]='\0'; + } + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PHONES),lvi.iItem,1,dbv.ptszVal); + DBFreeVariant(&dbv); + lvi.iItem++; + } + if(!DBGetContactSettingTString(hContact,szProto,"CompanyPhone",&dbv)) { + lvi.pszText=TranslateT("Work Phone"); + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_PHONES),&lvi); + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PHONES),lvi.iItem,1,dbv.ptszVal); + DBFreeVariant(&dbv); + lvi.iItem++; + } + if(!DBGetContactSettingTString(hContact,szProto,"CompanyFax",&dbv)) { + lvi.pszText=TranslateT("Work Fax"); + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_PHONES),&lvi); + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PHONES),lvi.iItem,1,dbv.ptszVal); + DBFreeVariant(&dbv); + lvi.iItem++; + } + lvi.iSubItem=0; + for(i=0;;i++) { + lvi.lParam=i; + mir_snprintf(idstr, SIZEOF(idstr), "MyPhone%d",i); + if(DBGetContactSettingTString(hContact,"UserInfo",idstr,&dbv)) + break; + lvi.pszText=idstr2; + mir_sntprintf(idstr2, SIZEOF(idstr2), TranslateT("Custom %d"),i+1); + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_PHONES),&lvi); + if(lstrlen(dbv.ptszVal)>4 && !lstrcmp(dbv.ptszVal+lstrlen(dbv.ptszVal)-4,_T(" SMS"))) { + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PHONES),lvi.iItem,2,_T("y")); + dbv.ptszVal[lstrlen(dbv.ptszVal)-4]='\0'; + } + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PHONES),lvi.iItem,1,dbv.ptszVal); + DBFreeVariant(&dbv); + lvi.iItem++; + } + lvi.mask=LVIF_PARAM; + lvi.lParam=(LPARAM)(-2); + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_PHONES),&lvi); + } + break; + } + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_INFOCHANGED: + SendMessage(hwndDlg,M_REMAKELISTS,0,0); + break; + } + break; + case IDC_EMAILS: + case IDC_PHONES: + switch (((LPNMHDR)lParam)->code) { + case NM_CUSTOMDRAW: + { NMLVCUSTOMDRAW *nm=(NMLVCUSTOMDRAW*)lParam; + switch(nm->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + case CDDS_ITEMPREPAINT: + SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,CDRF_NOTIFYSUBITEMDRAW); + return TRUE; + case CDDS_SUBITEM|CDDS_ITEMPREPAINT: + { + RECT rc; + ListView_GetSubItemRect(nm->nmcd.hdr.hwndFrom,nm->nmcd.dwItemSpec,nm->iSubItem,LVIR_LABEL,&rc); + if(nm->iSubItem==1 && nm->nmcd.hdr.idFrom==IDC_EMAILS) { + HFONT hoFont; + TCHAR szText[256] = {0}; + ListView_GetItemText(nm->nmcd.hdr.hwndFrom,nm->nmcd.dwItemSpec,nm->iSubItem,szText,SIZEOF(szText)); + hoFont=(HFONT)SelectObject(nm->nmcd.hdc,hEmailFont); + SetTextColor(nm->nmcd.hdc,RGB(0,0,255)); + DrawText(nm->nmcd.hdc,szText,-1,&rc,DT_END_ELLIPSIS|DT_LEFT|DT_NOPREFIX|DT_SINGLELINE|DT_TOP); + SelectObject(nm->nmcd.hdc,hoFont); + SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,CDRF_SKIPDEFAULT); + return TRUE; + } + + HICON hIcon = NULL; + if(nm->nmcd.lItemlParam==(LPARAM)(-2) && nm->iSubItem-3==(nm->nmcd.hdr.idFrom==IDC_PHONES)) + hIcon = LoadSkinIcon( SKINICON_OTHER_ADDCONTACT ); + else if(nm->iSubItem>1 && nm->nmcd.lItemlParam!=(LPARAM)(-1) && nm->nmcd.lItemlParam!=(LPARAM)(-2)) { + static int iconResources[3]={SKINICON_OTHER_RENAME,SKINICON_OTHER_DELETE}; + if(nm->iSubItem==2 && nm->nmcd.hdr.idFrom==IDC_PHONES) { + TCHAR szText[2]; + ListView_GetItemText(nm->nmcd.hdr.hwndFrom,nm->nmcd.dwItemSpec,nm->iSubItem,szText,SIZEOF(szText)); + if(szText[0]) hIcon = LoadSkinIcon( SKINICON_OTHER_SMS ); + } + else hIcon = LoadSkinIcon( iconResources[nm->iSubItem-3+(nm->nmcd.hdr.idFrom==IDC_EMAILS)] ); + } + else break; + DrawIconEx(nm->nmcd.hdc,(rc.left+rc.right-GetSystemMetrics(SM_CXSMICON))/2,(rc.top+rc.bottom-GetSystemMetrics(SM_CYSMICON))/2,hIcon,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0,NULL,DI_NORMAL); + IconLib_ReleaseIcon(hIcon, 0); + SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,CDRF_SKIPDEFAULT); + return TRUE; + } + } + break; + } + case NM_CLICK: + { NMLISTVIEW *nm=(NMLISTVIEW*)lParam; + LVITEM lvi; + TCHAR szEmail[256]; + HANDLE hContact=(HANDLE)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + char *szIdTemplate=nm->hdr.idFrom==IDC_PHONES?"MyPhone%d":"Mye-mail%d"; + LVHITTESTINFO hti; + + if(IsOverEmail(hwndDlg,szEmail,SIZEOF(szEmail))) { + TCHAR szExec[264]; + mir_sntprintf(szExec, SIZEOF(szExec), _T("mailto:%s"), szEmail); + ShellExecute(hwndDlg,_T("open"),szExec,NULL,NULL,SW_SHOW); + break; + } + if(nm->iSubItem<2) break; + hti.pt.x=(short)LOWORD(GetMessagePos()); + hti.pt.y=(short)HIWORD(GetMessagePos()); + ScreenToClient(nm->hdr.hwndFrom,&hti.pt); + if(ListView_SubItemHitTest(nm->hdr.hwndFrom,&hti)==-1) break; + lvi.mask=LVIF_PARAM; + lvi.iItem=hti.iItem; + lvi.iSubItem=0; + ListView_GetItem(nm->hdr.hwndFrom,&lvi); + if(lvi.lParam==(LPARAM)(-1)) break; + if(lvi.lParam==(LPARAM)(-2)) { + if(hti.iSubItem-3==(nm->hdr.idFrom==IDC_PHONES)) { + //add + char szNewData[256]="",idstr[33]; + int i; + DBVARIANT dbv; + if(IDOK!=DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(nm->hdr.idFrom==IDC_PHONES?IDD_ADDPHONE:IDD_ADDEMAIL),hwndDlg,nm->hdr.idFrom==IDC_PHONES?EditUserPhoneDlgProc:EditUserEmailDlgProc,(LPARAM)szNewData)) + break; + for(i=0;;i++) { + mir_snprintf(idstr, SIZEOF(idstr), szIdTemplate,i); + if(DBGetContactSettingString(hContact,"UserInfo",idstr,&dbv)) break; + DBFreeVariant(&dbv); + } + DBWriteContactSettingString(hContact,"UserInfo",idstr,szNewData); + SendMessage(hwndDlg,M_REMAKELISTS,0,0); + } + } + else { + if(hti.iSubItem-3==(nm->hdr.idFrom==IDC_PHONES)) { + //delete + int i; + char idstr[33]; + DBVARIANT dbv; + for(i=lvi.lParam;;i++) { + mir_snprintf(idstr, SIZEOF(idstr), szIdTemplate,i+1); + if(DBGetContactSettingString(hContact,"UserInfo",idstr,&dbv)) break; + mir_snprintf(idstr, SIZEOF(idstr), szIdTemplate,i); + DBWriteContactSettingString(hContact,"UserInfo",idstr,dbv.pszVal); + DBFreeVariant(&dbv); + } + mir_snprintf(idstr, SIZEOF(idstr), szIdTemplate,i); + DBDeleteContactSetting(hContact,"UserInfo",idstr); + SendMessage(hwndDlg,M_REMAKELISTS,0,0); + } + else if(hti.iSubItem-2==(nm->hdr.idFrom==IDC_PHONES)) { + //edit + char szText[256],idstr[33]; + DBVARIANT dbv; + mir_snprintf(idstr, SIZEOF(idstr), szIdTemplate,lvi.lParam); + if(DBGetContactSettingString(hContact,"UserInfo",idstr,&dbv)) break; + lstrcpynA(szText,dbv.pszVal,SIZEOF(szText)); + DBFreeVariant(&dbv); + if(IDOK!=DialogBoxParam(hMirandaInst,MAKEINTRESOURCE(nm->hdr.idFrom==IDC_PHONES?IDD_ADDPHONE:IDD_ADDEMAIL),hwndDlg,nm->hdr.idFrom==IDC_PHONES?EditUserPhoneDlgProc:EditUserEmailDlgProc,(LPARAM)szText)) + break; + DBWriteContactSettingString(hContact,"UserInfo",idstr,szText); + SendMessage(hwndDlg,M_REMAKELISTS,0,0); + } + } + break; + } + } + break; + } + break; + case WM_SETCURSOR: + if(LOWORD(lParam)!=HTCLIENT) break; + if(GetForegroundWindow()==GetParent(hwndDlg)) { + POINT pt; + GetCursorPos(&pt); + ScreenToClient(hwndDlg,&pt); +// SetFocus(ChildWindowFromPoint(hwndDlg,pt)); //ugly hack because listviews ignore their first click + } + if(IsOverEmail(hwndDlg,NULL,0)) { + SetCursor(hHandCursor); + SetWindowLongPtr(hwndDlg,DWLP_MSGRESULT,TRUE); + return TRUE; + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + } + break; + } + return FALSE; +} diff --git a/src/modules/userinfo/stdinfo.cpp b/src/modules/userinfo/stdinfo.cpp new file mode 100644 index 0000000000..89f6ab0f45 --- /dev/null +++ b/src/modules/userinfo/stdinfo.cpp @@ -0,0 +1,613 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +INT_PTR CALLBACK ContactDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +#define SVS_NORMAL 0 +#define SVS_GENDER 1 +#define SVS_ZEROISUNSPEC 2 +#define SVS_IP 3 +#define SVS_COUNTRY 4 +#define SVS_MONTH 5 +#define SVS_SIGNED 6 +#define SVS_TIMEZONE 7 + +static int Proto_GetContactInfoSetting(HANDLE hContact,const char *szProto,const char *szModule,const char *szSetting,DBVARIANT *dbv, const int nType) +{ + DBCONTACTGETSETTING cgs={szModule,szSetting,dbv}; + dbv->type=(BYTE)nType; + + return CallProtoService(szProto,PS_GETINFOSETTING,(WPARAM)hContact,(LPARAM)&cgs); +} + +static void Proto_FreeInfoVariant(DBVARIANT *dbv) +{ + switch ( dbv->type ) { + case DBVT_ASCIIZ: + case DBVT_UTF8: + case DBVT_WCHAR: + { + mir_free(dbv->pszVal); + dbv->pszVal=0; + break; + } + case DBVT_BLOB: + { + mir_free(dbv->pbVal); + dbv->pbVal=0; + break; + } + } + dbv->type=0; +} + +static void SetValue(HWND hwndDlg,int idCtrl,HANDLE hContact,char *szModule,char *szSetting,int special) +{ + DBVARIANT dbv = { 0 }; + char str[80],*pstr = NULL; + TCHAR* ptstr = NULL; + int unspecified=0; + char* szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + bool proto_service = szProto && (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_INFOSETTINGSVC); + + dbv.type=DBVT_DELETED; + if(szModule==NULL) unspecified=1; + else if (proto_service) unspecified=Proto_GetContactInfoSetting(hContact,szProto,szModule,szSetting,&dbv,0); + else unspecified=DBGetContactSettingW(hContact,szModule,szSetting,&dbv); + if(!unspecified) { + switch(dbv.type) { + case DBVT_BYTE: + if(special==SVS_GENDER) { + if(dbv.cVal=='M') ptstr=TranslateT("Male"); + else if(dbv.cVal=='F') ptstr=TranslateT("Female"); + else unspecified=1; + } + else if(special==SVS_MONTH) { + if(dbv.bVal>0 && dbv.bVal<=12) { + pstr=str; + GetLocaleInfoA(LOCALE_USER_DEFAULT,LOCALE_SABBREVMONTHNAME1-1+dbv.bVal,str,SIZEOF(str)); + } + else unspecified=1; + } + else if(special==SVS_TIMEZONE) { + if(dbv.cVal==-100) unspecified=1; + else { + pstr=str; + mir_snprintf(str, SIZEOF(str), dbv.cVal?"UTC%+d:%02d":"UTC",-dbv.cVal/2,(dbv.cVal&1)*30); + } + } + else { + unspecified=(special==SVS_ZEROISUNSPEC && dbv.bVal==0); + pstr=_itoa(special==SVS_SIGNED?dbv.cVal:dbv.bVal,str,10); + } + break; + case DBVT_WORD: + if(special==SVS_COUNTRY) { + WORD wSave = dbv.wVal; + if (wSave == ( WORD )-1) { + char szSettingName[100]; + mir_snprintf( szSettingName, SIZEOF(szSettingName), "%sName", szSetting ); + if ( !DBGetContactSettingTString(hContact,szModule,szSettingName,&dbv)) { + ptstr = dbv.ptszVal; + unspecified = false; + break; + } + } + + pstr = Translate((char*)CallService(MS_UTILS_GETCOUNTRYBYNUMBER,wSave,0)); + unspecified=pstr==NULL; + } + else { + unspecified=(special==SVS_ZEROISUNSPEC && dbv.wVal==0); + pstr=_itoa(special==SVS_SIGNED?dbv.sVal:dbv.wVal,str,10); + } + break; + case DBVT_DWORD: + unspecified=(special==SVS_ZEROISUNSPEC && dbv.dVal==0); + if(special==SVS_IP) { + struct in_addr ia; + ia.S_un.S_addr=htonl(dbv.dVal); + pstr=inet_ntoa(ia); + if(dbv.dVal==0) unspecified=1; + } + else pstr=_itoa(special==SVS_SIGNED?dbv.lVal:dbv.dVal,str,10); + break; + case DBVT_ASCIIZ: + unspecified=(special==SVS_ZEROISUNSPEC && dbv.pszVal[0]=='\0'); + pstr=dbv.pszVal; + break; + case DBVT_UTF8: + unspecified=(special==SVS_ZEROISUNSPEC && dbv.pszVal[0]=='\0'); + #if defined( _UNICODE ) + if ( !unspecified ) + { WCHAR* wszStr; + Utf8Decode( dbv.pszVal, &wszStr ); + SetDlgItemTextW( hwndDlg, idCtrl, TranslateTS(wszStr)); + mir_free( wszStr ); + goto LBL_Exit; + } + #endif + pstr=dbv.pszVal; + Utf8Decode( dbv.pszVal, NULL ); + break; + default: pstr=str; lstrcpyA(str,"???"); break; + } } + + if (unspecified) + SetDlgItemText(hwndDlg, idCtrl, TranslateT("")); + else if ( ptstr != NULL ) + SetDlgItemText(hwndDlg, idCtrl, ptstr); + else + SetDlgItemTextA(hwndDlg, idCtrl, pstr); + +#if defined( _UNICODE ) +LBL_Exit: +#endif + EnableWindow(GetDlgItem(hwndDlg, idCtrl), !unspecified); + if (proto_service) + Proto_FreeInfoVariant(&dbv); + else + DBFreeVariant(&dbv); +} + +static INT_PTR CALLBACK SummaryDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_INFOCHANGED ) + { char *szProto; + HANDLE hContact=(HANDLE)((LPPSHNOTIFY)lParam)->lParam; + if (hContact != NULL) { + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if (szProto==NULL) break; + SetValue(hwndDlg,IDC_NICK,hContact,szProto,"Nick",0); + SetValue(hwndDlg,IDC_FIRSTNAME,hContact,szProto,"FirstName",0); + SetValue(hwndDlg,IDC_LASTNAME,hContact,szProto,"LastName",0); + SetValue(hwndDlg,IDC_EMAIL,hContact,szProto,"e-mail",0); + SetValue(hwndDlg,IDC_AGE,hContact,szProto,"Age",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_GENDER,hContact,szProto,"Gender",SVS_GENDER); + SetValue(hwndDlg,IDC_DOBDAY,hContact,szProto,"BirthDay",0); + SetValue(hwndDlg,IDC_DOBMONTH,hContact,szProto,"BirthMonth",SVS_MONTH); + SetValue(hwndDlg,IDC_DOBYEAR,hContact,szProto,"BirthYear",0); + SetValue(hwndDlg,IDC_MARITAL,hContact,szProto,"MaritalStatus",0); + } } + break; + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + case IDC_EMAIL: + if(IsWindowEnabled(GetDlgItem(hwndDlg,IDC_EMAIL))) { + TCHAR szExec[264], szEmail[256]; + GetDlgItemText(hwndDlg, IDC_EMAIL, szEmail, SIZEOF(szEmail)); + mir_sntprintf(szExec, SIZEOF(szExec), _T("mailto:%s"), szEmail); + ShellExecute(hwndDlg, _T("open"), szExec, NULL, NULL, SW_SHOW); + } + break; + } + break; + } + return FALSE; +} + +static INT_PTR CALLBACK LocationDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + TranslateDialogDefault(hwndDlg); + SetTimer(hwndDlg,1,1000,NULL); + + tmi.prepareList((HANDLE)lParam, GetDlgItem(hwndDlg, IDC_TIMEZONESELECT), TZF_PLF_CB); + SendMessage(hwndDlg,WM_TIMER,0,0); + break; + + case WM_TIMER: + { + HANDLE hContact = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + if (hContact != NULL) + { + TCHAR szTime[80]; + + if (tmi.printDateTimeByContact(hContact, _T("s"), szTime, SIZEOF(szTime), TZF_KNOWNONLY)) + { + EnableWindow(GetDlgItem(hwndDlg,IDC_LOCALTIME),FALSE); + SetDlgItemText(hwndDlg, IDC_LOCALTIME, TranslateT("")); + } + else + { + EnableWindow(GetDlgItem(hwndDlg,IDC_LOCALTIME), TRUE); + SetDlgItemText(hwndDlg, IDC_LOCALTIME, szTime); + } + } + break; + } + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_INFOCHANGED ) + { char *szProto; + HANDLE hContact=(HANDLE)((LPPSHNOTIFY)lParam)->lParam; + if (hContact != NULL) { + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if (szProto==NULL) break; + SetValue(hwndDlg,IDC_STREET,hContact,szProto,"Street",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_CITY,hContact,szProto,"City",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_STATE,hContact,szProto,"State",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_ZIP,hContact,szProto,"ZIP",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_COUNTRY,hContact,szProto,"Country",SVS_COUNTRY); + SetValue(hwndDlg,IDC_LANGUAGE1,hContact,szProto,"Language1",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_LANGUAGE2,hContact,szProto,"Language2",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_LANGUAGE3,hContact,szProto,"Language3",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_TIMEZONE,hContact,szProto,"Timezone",SVS_TIMEZONE); + } + } + break; + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + case IDC_TIMEZONESELECT: + if(HIWORD(wParam) == CBN_SELCHANGE) { + HANDLE hContact = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + SendMessage(GetParent(hwndDlg),PSM_CHANGED, 0,0); + tmi.storeListResults(hContact, GetDlgItem(hwndDlg, IDC_TIMEZONESELECT), TZF_PLF_CB); + } + break; + } + break; + } + return FALSE; +} + +static INT_PTR CALLBACK WorkDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + break; + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_INFOCHANGED) + { char *szProto; + HANDLE hContact=(HANDLE)((LPPSHNOTIFY)lParam)->lParam; + if (hContact != NULL) { + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if (szProto==NULL) break; + SetValue(hwndDlg,IDC_COMPANY,hContact,szProto,"Company",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_DEPARTMENT,hContact,szProto,"CompanyDepartment",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_POSITION,hContact,szProto,"CompanyPosition",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_STREET,hContact,szProto,"CompanyStreet",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_CITY,hContact,szProto,"CompanyCity",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_STATE,hContact,szProto,"CompanyState",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_ZIP,hContact,szProto,"CompanyZIP",SVS_ZEROISUNSPEC); + SetValue(hwndDlg,IDC_COUNTRY,hContact,szProto,"CompanyCountry",SVS_COUNTRY); + SetValue(hwndDlg,IDC_WEBPAGE,hContact,szProto,"CompanyHomepage",SVS_ZEROISUNSPEC); + } } + break; + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + case IDC_WEBPAGE: + if(IsWindowEnabled(GetDlgItem(hwndDlg,IDC_WEBPAGE))) { + char szPage[256]; + GetDlgItemTextA(hwndDlg,IDC_WEBPAGE,szPage,SIZEOF(szPage)); + CallService(MS_UTILS_OPENURL,1,(LPARAM)szPage); + } + break; + } + break; + } + return FALSE; +} + +// Resizes all columns in a listview (report style) +// to make all text visible +void ResizeColumns(HWND hwndLV) +{ + int nCol = 0; LVCOLUMN lvCol; + lvCol.mask = LVCF_WIDTH; + while(ListView_GetColumn(hwndLV, nCol++, &lvCol)) + ListView_SetColumnWidth(hwndLV, nCol-1, LVSCW_AUTOSIZE); +} + +static INT_PTR CALLBACK BackgroundDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { LVCOLUMN lvc; + RECT rc; + GetClientRect(GetDlgItem(hwndDlg,IDC_PAST),&rc); + rc.right-=GetSystemMetrics(SM_CXVSCROLL); + lvc.mask=LVCF_WIDTH; + lvc.cx=rc.right/3; + ListView_InsertColumn(GetDlgItem(hwndDlg,IDC_PAST),0,&lvc); + ListView_InsertColumn(GetDlgItem(hwndDlg,IDC_INTERESTS),0,&lvc); + lvc.cx=rc.right-rc.right/3; + ListView_InsertColumn(GetDlgItem(hwndDlg,IDC_PAST),1,&lvc); + ListView_InsertColumn(GetDlgItem(hwndDlg,IDC_INTERESTS),1,&lvc); + } + ListView_SetExtendedListViewStyleEx(GetDlgItem(hwndDlg,IDC_PAST),LVS_EX_LABELTIP,LVS_EX_LABELTIP); + ListView_SetExtendedListViewStyleEx(GetDlgItem(hwndDlg,IDC_INTERESTS),LVS_EX_LABELTIP,LVS_EX_LABELTIP); + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + if (((LPNMHDR)lParam)->code == PSN_INFOCHANGED) + { LVITEM lvi; + int i; + char idstr[33]; + DBVARIANT dbv,dbvText; + HANDLE hContact=(HANDLE)((LPPSHNOTIFY)lParam)->lParam; + + if (hContact != NULL) { + char *szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if (szProto==NULL) break; + bool proto_service = (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_INFOSETTINGSVC) == PF4_INFOSETTINGSVC; + SetValue(hwndDlg,IDC_WEBPAGE,hContact,szProto,"Homepage",SVS_ZEROISUNSPEC); + + //past + ListView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_PAST)); + lvi.mask=LVIF_TEXT; + lvi.iSubItem=0; + lvi.iItem=0; + for(i=0;;i++) { + mir_snprintf(idstr, SIZEOF(idstr), "Past%d",i); + if((proto_service && Proto_GetContactInfoSetting(hContact,szProto,szProto,idstr,&dbv,DBVT_TCHAR)) || + (!proto_service && DBGetContactSettingTString(hContact,szProto,idstr,&dbv))) + break; + mir_snprintf(idstr, SIZEOF(idstr), "Past%dText",i); + if(DBGetContactSettingTString(hContact,szProto,idstr,&dbvText)) + {if(proto_service) Proto_FreeInfoVariant(&dbv); else DBFreeVariant(&dbv); break;} + lvi.pszText=dbv.ptszVal; + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_PAST),&lvi); + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PAST),lvi.iItem,1,dbvText.ptszVal); + DBFreeVariant(&dbvText); + if(proto_service) + Proto_FreeInfoVariant(&dbv); + else + DBFreeVariant(&dbv); + lvi.iItem++; + } + + for(i=0;;i++) { + mir_snprintf(idstr, SIZEOF(idstr), "Affiliation%d", i); + if((proto_service && Proto_GetContactInfoSetting(hContact,szProto,szProto,idstr,&dbv,DBVT_TCHAR)) || + (!proto_service && DBGetContactSettingTString(hContact,szProto,idstr,&dbv))) + break; + mir_snprintf(idstr, SIZEOF(idstr), "Affiliation%dText",i); + if(DBGetContactSettingTString(hContact,szProto,idstr,&dbvText)) + {if(proto_service) Proto_FreeInfoVariant(&dbv); else DBFreeVariant(&dbv); break;} + lvi.pszText=dbv.ptszVal; + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_PAST),&lvi); + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_PAST),lvi.iItem,1,dbvText.ptszVal); + DBFreeVariant(&dbvText); + if(proto_service) + Proto_FreeInfoVariant(&dbv); + else + DBFreeVariant(&dbv); + lvi.iItem++; + } + + ResizeColumns(GetDlgItem(hwndDlg,IDC_PAST)); + + //interests + ListView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_INTERESTS)); + lvi.mask=LVIF_TEXT; + lvi.iSubItem=0; + lvi.iItem=0; + for(i=0;;i++) { + mir_snprintf(idstr, SIZEOF(idstr), "Interest%dCat", i); + if((proto_service && Proto_GetContactInfoSetting(hContact,szProto,szProto,idstr,&dbv,DBVT_TCHAR)) || + (!proto_service && DBGetContactSettingTString(hContact,szProto,idstr,&dbv))) + break; + mir_snprintf(idstr, SIZEOF(idstr), "Interest%dText", i); + if(DBGetContactSettingTString(hContact,szProto,idstr,&dbvText)) + {if(proto_service) Proto_FreeInfoVariant(&dbv); else DBFreeVariant(&dbv); break;} + lvi.pszText=dbv.ptszVal; + ListView_InsertItem(GetDlgItem(hwndDlg,IDC_INTERESTS),&lvi); + ListView_SetItemText(GetDlgItem(hwndDlg,IDC_INTERESTS),lvi.iItem,1,dbvText.ptszVal); + DBFreeVariant(&dbvText); + if(proto_service) + Proto_FreeInfoVariant(&dbv); + else + DBFreeVariant(&dbv); + lvi.iItem++; + } + ResizeColumns(GetDlgItem(hwndDlg,IDC_INTERESTS)); + } } + break; + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + case IDC_WEBPAGE: + if(IsWindowEnabled(GetDlgItem(hwndDlg,IDC_WEBPAGE))) { + char szPage[256]; + GetDlgItemTextA(hwndDlg,IDC_WEBPAGE,szPage,SIZEOF(szPage)); + CallService(MS_UTILS_OPENURL,1,(LPARAM)szPage); + } + break; + } + break; + } + return FALSE; +} + +static INT_PTR CALLBACK NotesDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { DBVARIANT dbv; + HFONT hFont; + LOGFONT lf; + HDC hDC = GetDC(hwndDlg); + lf.lfHeight = -MulDiv(10, GetDeviceCaps(hDC, LOGPIXELSY), 72); + ReleaseDC(hwndDlg, hDC); + lf.lfWidth = 0; + lf.lfEscapement = 0; + lf.lfOrientation = 0; + lf.lfWeight = FW_NORMAL; + lf.lfItalic = 0; + lf.lfUnderline = 0; + lf.lfStrikeOut = 0; + lf.lfOutPrecision = OUT_DEFAULT_PRECIS; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + lstrcpy(lf.lfFaceName, _T("Courier New")); + lf.lfCharSet = DEFAULT_CHARSET; +// hFont = (HFONT) GetStockObject(ANSI_FIXED_FONT); + hFont = CreateFontIndirect(&lf); + SendDlgItemMessage(hwndDlg, IDC_ABOUT, WM_SETFONT, (WPARAM) hFont, MAKELPARAM(TRUE, 0)); + + if(!DBGetContactSettingString((HANDLE)lParam,"UserInfo","MyNotes",&dbv)) { + SetDlgItemTextA(hwndDlg,IDC_MYNOTES,dbv.pszVal); + DBFreeVariant(&dbv); + } + } + SendDlgItemMessage(hwndDlg,IDC_MYNOTES,EM_LIMITTEXT,2048,0); + break; + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_INFOCHANGED: + { char *szProto; + HANDLE hContact=(HANDLE)((LPPSHNOTIFY)lParam)->lParam; + if (hContact != NULL) { + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if (szProto==NULL) break; + SetValue(hwndDlg,IDC_ABOUT,hContact,szProto,"About",0); + } + break; + } + case PSN_APPLY: + { HANDLE hContact=(HANDLE)((LPPSHNOTIFY)lParam)->lParam; + if(GetWindowTextLength(GetDlgItem(hwndDlg,IDC_MYNOTES))) { + char text[2048]; + GetDlgItemTextA(hwndDlg,IDC_MYNOTES,text,SIZEOF(text)); + DBWriteContactSettingString(hContact,"UserInfo","MyNotes",text); + } + else DBDeleteContactSetting(hContact,"UserInfo","MyNotes"); + break; + } + } + break; + } + break; + case WM_COMMAND: + if(wParam==MAKEWPARAM(IDC_MYNOTES,EN_CHANGE)) + SendMessage(GetParent(hwndDlg),PSM_CHANGED,0,0); + else if(LOWORD(wParam)==IDCANCEL) + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + case WM_DESTROY: + { + HFONT hFont = (HFONT)SendDlgItemMessage(hwndDlg, IDC_ABOUT, WM_GETFONT, 0, 0); + DeleteObject(hFont); + } + break; + } + return FALSE; +} + +int DetailsInit(WPARAM wParam,LPARAM lParam) +{ + OPTIONSDIALOGPAGE odp; + + if ((HANDLE)lParam == NULL) + return 0; + + if ( CallService(MS_PROTO_GETCONTACTBASEPROTO, lParam, 0) == 0 ) + return 0; + + odp.cbSize = sizeof(odp); + odp.hIcon = NULL; + odp.hInstance = hMirandaInst; + odp.flags = 0; + + odp.pfnDlgProc = SummaryDlgProc; + odp.position = -2100000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_SUMMARY); + odp.pszTitle = LPGEN("Summary"); + CallService(MS_USERINFO_ADDPAGE, wParam, ( LPARAM )&odp); + + odp.pfnDlgProc = ContactDlgProc; + odp.position = -1800000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_CONTACT); + odp.pszTitle = LPGEN("Contact"); + CallService(MS_USERINFO_ADDPAGE, wParam, ( LPARAM )&odp ); + + odp.pfnDlgProc = LocationDlgProc; + odp.position = -1500000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_LOCATION); + odp.pszTitle = LPGEN("Location"); + CallService(MS_USERINFO_ADDPAGE, wParam, ( LPARAM )&odp); + + odp.pfnDlgProc = WorkDlgProc; + odp.position = -1200000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_WORK); + odp.pszTitle = LPGEN("Work"); + CallService(MS_USERINFO_ADDPAGE, wParam, ( LPARAM )&odp); + + odp.pfnDlgProc = BackgroundDlgProc; + odp.position = -900000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_BACKGROUND); + odp.pszTitle = LPGEN("Background info"); + CallService(MS_USERINFO_ADDPAGE, wParam, ( LPARAM )&odp ); + + odp.pfnDlgProc = NotesDlgProc; + odp.position = 0; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_NOTES); + odp.pszTitle = LPGEN("Notes"); + CallService(MS_USERINFO_ADDPAGE, wParam, ( LPARAM )&odp); + return 0; +} diff --git a/src/modules/userinfo/userinfo.cpp b/src/modules/userinfo/userinfo.cpp new file mode 100644 index 0000000000..a17b2d4c71 --- /dev/null +++ b/src/modules/userinfo/userinfo.cpp @@ -0,0 +1,636 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#define UPDATEANIMFRAMES 20 + +int DetailsInit(WPARAM wParam,LPARAM lParam); +static INT_PTR CALLBACK DlgProcDetails(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +static HANDLE hWindowList=NULL; +static HANDLE hDetailsInitEvent; + +struct DetailsPageInit { + int pageCount; + OPTIONSDIALOGPAGE *odp; +}; + +struct DetailsPageData { + DLGTEMPLATE *pTemplate; + HINSTANCE hInst; + DLGPROC dlgProc; + LPARAM dlgParam; + HWND hwnd; + HTREEITEM hItem; + int changed; + TCHAR *ptszTitle, *ptszTab; +}; + +struct DetailsData { + HANDLE hContact; + HANDLE hProtoAckEvent; + HINSTANCE hInstIcmp; + HFONT hBoldFont; + int pageCount; + int currentPage; + struct DetailsPageData *opd; + RECT rcDisplay, rcDisplayTab; + int updateAnimFrame; + TCHAR szUpdating[64]; + int *infosUpdated; +}; + +static int PageSortProc(OPTIONSDIALOGPAGE *item1,OPTIONSDIALOGPAGE *item2) +{ + int res; + if (!lstrcmp(item1->ptszTitle, TranslateT("Summary"))) return -1; + if (!lstrcmp(item2->ptszTitle, TranslateT("Summary"))) return 1; + if (res = lstrcmp(item1->ptszTitle, item2->ptszTitle)) return res; + if (item1->ptszTab && !item2->ptszTab) return -1; + if (!item1->ptszTab && item2->ptszTab) return 1; + if (!item1->ptszTab && !item2->ptszTab) return 0; + if (item1->ptszTab && !lstrcmp(item1->ptszTab, TranslateT("General"))) return -1; + if (item2->ptszTab && !lstrcmp(item2->ptszTab, TranslateT("General"))) return 1; + return lstrcmp(item1->ptszTab, item2->ptszTab); +} + +static INT_PTR ShowDetailsDialogCommand(WPARAM wParam,LPARAM) +{ + HWND hwnd; + PROPSHEETHEADER psh; + struct DetailsPageInit opi; + int i; + + if(hwnd=WindowList_Find(hWindowList,(HANDLE)wParam)) { + SetForegroundWindow(hwnd); + SetFocus(hwnd); + return 0; + } + + opi.pageCount=0; + opi.odp=NULL; + NotifyEventHooks(hDetailsInitEvent,(WPARAM)&opi,wParam); + if(opi.pageCount==0) return 0; + qsort(opi.odp,opi.pageCount,sizeof(OPTIONSDIALOGPAGE),(int (*)(const void*,const void*))PageSortProc); + + ZeroMemory(&psh,sizeof(psh)); + psh.dwSize = sizeof(psh); + psh.dwFlags = PSH_PROPSHEETPAGE|PSH_NOAPPLYNOW; + psh.hwndParent = NULL; + psh.nPages = opi.pageCount; + psh.pStartPage = 0; + psh.pszCaption = (TCHAR*)wParam; //more abuses of structure: this is hContact + psh.ppsp = (PROPSHEETPAGE*)opi.odp; //blatent misuse of the structure, but what the hell + + CreateDialogParam(hMirandaInst,MAKEINTRESOURCE(IDD_DETAILS), NULL, DlgProcDetails, (LPARAM)&psh); + for(i=0;icbSize!=sizeof(OPTIONSDIALOGPAGE) + && odp->cbSize != OPTIONPAGE_OLD_SIZE2 + && odp->cbSize != OPTIONPAGE_OLD_SIZE3) + return 1; + + opi->odp=(OPTIONSDIALOGPAGE*)mir_realloc(opi->odp,sizeof(OPTIONSDIALOGPAGE)*(opi->pageCount+1)); + dst = opi->odp + opi->pageCount; + dst->cbSize = sizeof(OPTIONSDIALOGPAGE); + dst->hInstance = odp->hInstance; + dst->pfnDlgProc = odp->pfnDlgProc; + dst->position = odp->position; + if((DWORD_PTR)odp->pszTemplate&0xFFFF0000) dst->pszTemplate = mir_strdup(odp->pszTemplate); + else dst->pszTemplate = odp->pszTemplate; + + #if defined(_UNICODE) + if ( odp->flags & ODPF_UNICODE ) + { + dst->ptszTitle = (odp->ptszTitle==0) ? NULL : mir_wstrdup(odp->ptszTitle); + dst->ptszTab = (!(odp->flags & ODPF_USERINFOTAB) || !odp->ptszTab) ? NULL : mir_wstrdup(odp->ptszTab); + } + else + #endif + { + if ( odp->flags & ODPF_DONTTRANSLATE ) + dst->ptszTitle = (odp->pszTitle==0) ? NULL : mir_a2t(odp->pszTitle); + else + dst->ptszTitle = (odp->pszTitle==0) ? NULL : LangPackPcharToTchar(odp->pszTitle); + dst->ptszTab = (!(odp->flags & ODPF_USERINFOTAB) || !odp->pszTab) ? NULL : LangPackPcharToTchar(odp->pszTab); + } + + dst->pszGroup = NULL; + dst->groupPosition = odp->groupPosition; + dst->hGroupIcon = odp->hGroupIcon; + dst->hIcon = odp->hIcon; + if ( odp->cbSize == sizeof(OPTIONSDIALOGPAGE)) + dst->dwInitParam = odp->dwInitParam; + opi->pageCount++; + return 0; +} + +static void ThemeDialogBackground(HWND hwnd) +{ + if (enableThemeDialogTexture) + enableThemeDialogTexture(hwnd, ETDT_ENABLETAB); +} + +static void CreateDetailsTabs( HWND hwndDlg, struct DetailsData* dat, struct DetailsPageData* ppg ) +{ + HWND hwndTab = GetDlgItem(hwndDlg, IDC_TABS); + int i, sel=0, pages=0; + TCITEM tie; + tie.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM; + tie.iImage = -1; + TabCtrl_DeleteAllItems(hwndTab); + for ( i=0; i < dat->pageCount; i++ ) { + if (!dat->opd[i].ptszTab || lstrcmp(dat->opd[i].ptszTitle, ppg->ptszTitle)) continue; + + tie.pszText = TranslateTS(dat->opd[i].ptszTab); + tie.lParam = i; + TabCtrl_InsertItem(hwndTab, pages, &tie); + if (!lstrcmp(dat->opd[i].ptszTab,ppg->ptszTab)) + sel = pages; + pages++; + } + TabCtrl_SetCurSel(hwndTab,sel); + + LONG style = GetWindowLong(hwndTab, GWL_STYLE); + SetWindowLong(hwndTab, GWL_STYLE, pages > 1 ? style | WS_TABSTOP : style & ~WS_TABSTOP); +} + +static void CreateDetailsPageWindow( HWND hwndDlg, struct DetailsData* dat, struct DetailsPageData* ppg ) +{ + RECT *rc = ppg->ptszTab ? &dat->rcDisplayTab : &dat->rcDisplay; + ppg->hwnd=CreateDialogIndirectParam(ppg->hInst,ppg->pTemplate,hwndDlg,ppg->dlgProc,(LPARAM)dat->hContact); + ThemeDialogBackground(ppg->hwnd); + SetWindowPos(ppg->hwnd, HWND_TOP, rc->left, rc->top, rc->right - rc->left, rc->bottom - rc->top, 0); + SetWindowPos(ppg->hwnd, HWND_TOP, rc->left, rc->top, rc->right - rc->left, rc->bottom - rc->top, 0); + { + PSHNOTIFY pshn; + pshn.hdr.code = PSN_PARAMCHANGED; + pshn.hdr.hwndFrom = ppg->hwnd; + pshn.hdr.idFrom = 0; + pshn.lParam = (LPARAM)ppg->dlgParam; + SendMessage(ppg->hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + + pshn.hdr.code=PSN_INFOCHANGED; + pshn.hdr.hwndFrom=ppg->hwnd; + pshn.hdr.idFrom=0; + pshn.lParam=(LPARAM)dat->hContact; + SendMessage(ppg->hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } +} + +static int UserInfoContactDelete(WPARAM wParam,LPARAM) +{ + HWND hwnd; + hwnd=WindowList_Find(hWindowList,(HANDLE)wParam); + if(hwnd!=NULL) DestroyWindow(hwnd); + return 0; +} + +#define HM_PROTOACK (WM_USER+10) +#define M_CHECKONLINE (WM_USER+11) +static INT_PTR CALLBACK DlgProcDetails(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct DetailsData *dat =(struct DetailsData*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + Window_SetIcon_IcoLib(hwndDlg, SKINICON_OTHER_USERDETAILS); + { + PROPSHEETHEADER *psh = (PROPSHEETHEADER*)lParam; + dat = (DetailsData*)mir_calloc(sizeof(DetailsData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->hContact = (HANDLE)psh->pszCaption; + dat->hProtoAckEvent = HookEventMessage(ME_PROTO_ACK,hwndDlg,HM_PROTOACK); + WindowList_Add(hWindowList,hwndDlg,dat->hContact); + { + TCHAR *name, oldTitle[256], newTitle[256]; + if (dat->hContact == NULL) + name = TranslateT("Owner"); + else + name = cli.pfnGetContactDisplayName( dat->hContact, 0 ); + + GetWindowText( hwndDlg, oldTitle, SIZEOF( oldTitle )); + mir_sntprintf( newTitle, SIZEOF(newTitle), oldTitle, name ); + SetWindowText( hwndDlg, newTitle ); + + GetDlgItemText( hwndDlg, IDC_HEADERBAR, oldTitle, SIZEOF( oldTitle )); + mir_sntprintf( newTitle, SIZEOF(newTitle), oldTitle, name ); + SetDlgItemText( hwndDlg, IDC_HEADERBAR, newTitle ); + } + { LOGFONT lf; + HFONT hNormalFont=(HFONT)SendDlgItemMessage(hwndDlg,IDC_NAME,WM_GETFONT,0,0); + GetObject(hNormalFont,sizeof(lf),&lf); + lf.lfWeight=FW_BOLD; + dat->hBoldFont=CreateFontIndirect(&lf); + SendDlgItemMessage(hwndDlg,IDC_NAME,WM_SETFONT,(WPARAM)dat->hBoldFont,0); + } + { OPTIONSDIALOGPAGE *odp; + int i; + TVINSERTSTRUCT tvis; + DBVARIANT dbv; + + HWND hwndTree = GetDlgItem(hwndDlg, IDC_PAGETREE); + + dat->currentPage = 0; + if ( DBGetContactSettingTString( NULL, "UserInfo", "LastTab", &dbv )) + dbv.type = DBVT_DELETED; + dat->pageCount = psh->nPages; + dat->opd = (DetailsPageData*)mir_calloc(sizeof(DetailsPageData) * dat->pageCount); + odp = (OPTIONSDIALOGPAGE*)psh->ppsp; + + for ( i=0; i < dat->pageCount; i++ ) { + dat->opd[i].pTemplate = (LPDLGTEMPLATE)LockResource(LoadResource(odp[i].hInstance, + FindResourceA(odp[i].hInstance, odp[i].pszTemplate, MAKEINTRESOURCEA(5)))); + dat->opd[i].dlgProc = odp[i].pfnDlgProc; + dat->opd[i].dlgParam = odp[i].dwInitParam; + dat->opd[i].hInst = odp[i].hInstance; + + dat->opd[i].ptszTitle = odp[i].ptszTitle; + dat->opd[i].ptszTab = odp[i].ptszTab; + + if (i && dat->opd[i].ptszTab && !lstrcmp(dat->opd[i-1].ptszTitle, dat->opd[i].ptszTitle)) { + dat->opd[i].hItem = dat->opd[i-1].hItem; + continue; + } + + tvis.hParent = NULL; + tvis.hInsertAfter = TVI_LAST; + tvis.item.mask = TVIF_TEXT | TVIF_PARAM; + tvis.item.lParam = (LPARAM) i; + if (odp[i].flags & ODPF_DONTTRANSLATE) + tvis.item.pszText = mir_tstrdup(odp[i].ptszTitle); + else + tvis.item.pszText = TranslateTS(odp[i].ptszTitle); + if ( dbv.type != DBVT_DELETED && !lstrcmp( tvis.item.pszText, dbv.ptszVal )) + dat->currentPage = i; + dat->opd[i].hItem = TreeView_InsertItem(hwndTree, &tvis); + } + DBFreeVariant(&dbv); + } + + { + HWND hwndTab = GetDlgItem(hwndDlg, IDC_TABS); + + TCITEM tci; + tci.mask = TCIF_TEXT | TCIF_IMAGE; + tci.iImage = -1; + tci.pszText = _T("X"); + TabCtrl_InsertItem(hwndTab,0,&tci); + + GetWindowRect(hwndTab, &dat->rcDisplayTab); + TabCtrl_AdjustRect(hwndTab, FALSE, &dat->rcDisplayTab); + { POINT pt={0,0}; + ClientToScreen(hwndDlg, &pt); + OffsetRect(&dat->rcDisplayTab, -pt.x, -pt.y); + } + + TabCtrl_DeleteAllItems(hwndTab); + + GetWindowRect(hwndTab, &dat->rcDisplay); + TabCtrl_AdjustRect(hwndTab, FALSE, &dat->rcDisplay); + { POINT pt={0,0}; + ClientToScreen(hwndDlg, &pt); + OffsetRect(&dat->rcDisplay, -pt.x, -pt.y); + } } + + TreeView_SelectItem(GetDlgItem(hwndDlg, IDC_PAGETREE), dat->opd[dat->currentPage].hItem); + + dat->updateAnimFrame = 0; + GetDlgItemText(hwndDlg,IDC_UPDATING,dat->szUpdating,SIZEOF(dat->szUpdating)); + SendMessage(hwndDlg,M_CHECKONLINE,0,0); + if (!CallContactService(dat->hContact,PSS_GETINFO,SGIF_ONOPEN,0)) { + EnableWindow(GetDlgItem(hwndDlg,IDC_UPDATE),FALSE); + SetTimer(hwndDlg,1,100,NULL); + } else + ShowWindow(GetDlgItem(hwndDlg,IDC_UPDATING),SW_HIDE); + + SetFocus(GetDlgItem(hwndDlg, IDC_PAGETREE)); + + return TRUE; + } + case WM_TIMER: + { + TCHAR str[128]; + mir_sntprintf(str,SIZEOF(str), _T("%.*s%s%.*s"),dat->updateAnimFrame%10,_T("........."),dat->szUpdating,dat->updateAnimFrame%10,_T(".........")); + SetDlgItemText(hwndDlg,IDC_UPDATING,str); + if(++dat->updateAnimFrame==UPDATEANIMFRAMES) dat->updateAnimFrame=0; + break; + } + case WM_CTLCOLORSTATIC: + switch (GetDlgCtrlID((HWND)lParam)) { + case IDC_WHITERECT: + SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + case IDC_UPDATING: + { + COLORREF textCol,bgCol,newCol; + int ratio; + textCol=GetSysColor(COLOR_BTNTEXT); + bgCol=GetSysColor(COLOR_3DFACE); + ratio=abs(UPDATEANIMFRAMES/2-dat->updateAnimFrame)*510/UPDATEANIMFRAMES; + newCol=RGB(GetRValue(bgCol)+(GetRValue(textCol)-GetRValue(bgCol))*ratio/256, + GetGValue(bgCol)+(GetGValue(textCol)-GetGValue(bgCol))*ratio/256, + GetBValue(bgCol)+(GetBValue(textCol)-GetBValue(bgCol))*ratio/256); + SetTextColor((HDC)wParam,newCol); + SetBkColor((HDC)wParam,GetSysColor(COLOR_3DFACE)); + return (INT_PTR)GetSysColorBrush(COLOR_3DFACE); + } + default: + SetBkMode((HDC)wParam,TRANSPARENT); + return (INT_PTR)GetStockObject(NULL_BRUSH); + } + break; + + case PSM_CHANGED: + dat->opd[dat->currentPage].changed=1; + return TRUE; + + case PSM_FORCECHANGED: + { + int i; + PSHNOTIFY pshn; + pshn.hdr.code=PSN_INFOCHANGED; + pshn.hdr.idFrom=0; + pshn.lParam=(LPARAM)dat->hContact; + for(i=0;ipageCount;i++) { + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + if(dat->opd[i].hwnd!=NULL) + SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } + break; + } + case M_CHECKONLINE: + { + char *szProto; + if (dat->hContact != NULL) { + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)dat->hContact,0); + if(szProto==NULL) {EnableWindow(GetDlgItem(hwndDlg,IDC_UPDATE),FALSE); break;} + if(CallProtoService(szProto,PS_GETSTATUS,0,0)hContact==NULL && ack->type==ACKTYPE_STATUS) { + SendMessage(hwndDlg,M_CHECKONLINE,0,0); + break; + } + if(ack->hContact!=dat->hContact) break; + if(ack->type!=ACKTYPE_GETINFO) break; + SendMessage(hwndDlg,PSM_FORCECHANGED,0,0); + /* if they're not gonna send any more ACK's don't let that mean we should crash */ + if (!ack->hProcess && !ack->lParam) { + ShowWindow(GetDlgItem(hwndDlg,IDC_UPDATING),SW_HIDE); + KillTimer(hwndDlg,1); + SendMessage(hwndDlg,M_CHECKONLINE,0,0); + break; + } //if + if(dat->infosUpdated==NULL) dat->infosUpdated=(int*)mir_calloc(sizeof(int)*(INT_PTR)ack->hProcess); + if(ack->result==ACKRESULT_SUCCESS || ack->result==ACKRESULT_FAILED) dat->infosUpdated[ack->lParam]=1; + for(i=0;i<(int)ack->hProcess;i++) + if(dat->infosUpdated[i]==0) break; + if(i==(int)ack->hProcess) { + ShowWindow(GetDlgItem(hwndDlg,IDC_UPDATING),SW_HIDE); + KillTimer(hwndDlg,1); + SendMessage(hwndDlg,M_CHECKONLINE,0,0); + } } + break; + + case WM_NOTIFY: + switch(wParam) { + case IDC_TABS: + case IDC_PAGETREE: + switch(((LPNMHDR)lParam)->code) + { + case TCN_SELCHANGING: + case TVN_SELCHANGING: + if (dat->currentPage != -1 && dat->opd[dat->currentPage].hwnd != NULL) + { + PSHNOTIFY pshn; + pshn.hdr.code = PSN_KILLACTIVE; + pshn.hdr.hwndFrom = dat->opd[dat->currentPage].hwnd; + pshn.hdr.idFrom = 0; + pshn.lParam = (LPARAM)dat->hContact; + if (SendMessage(dat->opd[dat->currentPage].hwnd, WM_NOTIFY, 0, (LPARAM)&pshn)) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + } + break; + + case TCN_SELCHANGE: + if (dat->currentPage != -1 && dat->opd[dat->currentPage].hwnd != NULL) + { + HWND hwndTab = GetDlgItem(hwndDlg, IDC_TABS); + ShowWindow(dat->opd[dat->currentPage].hwnd, SW_HIDE); + + TCITEM tie; + TVITEM tvi; + + tie.mask = TCIF_PARAM; + TabCtrl_GetItem(hwndTab, TabCtrl_GetCurSel(hwndTab), &tie); + dat->currentPage = tie.lParam; + + tvi.hItem = TreeView_GetNextItem(GetDlgItem(hwndDlg,IDC_PAGETREE), NULL, TVGN_CARET); + tvi.mask = TVIF_PARAM; + tvi.lParam = dat->currentPage; + TreeView_SetItem(GetDlgItem(hwndDlg,IDC_PAGETREE), &tvi); + + if (dat->currentPage != -1) + { + if (dat->opd[dat->currentPage].hwnd == NULL) + CreateDetailsPageWindow(hwndDlg, dat, &dat->opd[dat->currentPage]); + ShowWindow(dat->opd[dat->currentPage].hwnd, SW_SHOWNA); + } + } + break; + + case TVN_SELCHANGED: + if (dat->currentPage != -1 && dat->opd[dat->currentPage].hwnd != NULL) + ShowWindow(dat->opd[dat->currentPage].hwnd, SW_HIDE); + + { + LPNMTREEVIEW pnmtv = (LPNMTREEVIEW) lParam; + TVITEM tvi = pnmtv->itemNew; + dat->currentPage = tvi.lParam; + + if(dat->currentPage != -1) + { + CreateDetailsTabs(hwndDlg, dat, &dat->opd[dat->currentPage]); + if (dat->opd[dat->currentPage].hwnd == NULL) + CreateDetailsPageWindow(hwndDlg, dat, &dat->opd[dat->currentPage]); + ShowWindow(dat->opd[dat->currentPage].hwnd, SW_SHOWNA); + + } + } + break; + } + break; + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + { + int i; + PSHNOTIFY pshn; + pshn.hdr.idFrom=0; + pshn.lParam=(LPARAM)dat->hContact; + pshn.hdr.code=PSN_RESET; + for(i=0;ipageCount;i++) { + if(dat->opd[i].hwnd==NULL || !dat->opd[i].changed) continue; + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn); + } + DestroyWindow(hwndDlg); + break; + } + case IDOK: + { + int i; + PSHNOTIFY pshn; + pshn.hdr.idFrom=0; + pshn.lParam=(LPARAM)dat->hContact; + if(dat->currentPage!=-1) { + pshn.hdr.code=PSN_KILLACTIVE; + pshn.hdr.hwndFrom=dat->opd[dat->currentPage].hwnd; + if(SendMessage(dat->opd[dat->currentPage].hwnd,WM_NOTIFY,0,(LPARAM)&pshn)) + break; + } + + pshn.hdr.code=PSN_APPLY; + for(i=0;ipageCount;i++) { + if(dat->opd[i].hwnd==NULL || !dat->opd[i].changed) continue; + pshn.hdr.hwndFrom=dat->opd[i].hwnd; + if(SendMessage(dat->opd[i].hwnd,WM_NOTIFY,0,(LPARAM)&pshn)==PSNRET_INVALID_NOCHANGEPAGE) { + TreeView_Select(GetDlgItem(hwndDlg,IDC_PAGETREE), dat->opd[i].hItem, TVGN_CARET); + if(dat->currentPage!=-1) ShowWindow(dat->opd[dat->currentPage].hwnd,SW_HIDE); + dat->currentPage=i; + ShowWindow(dat->opd[dat->currentPage].hwnd,SW_SHOW); + return 0; + } + } + DestroyWindow(hwndDlg); + break; + } + case IDC_UPDATE: + if(dat->infosUpdated!=NULL) {mir_free(dat->infosUpdated); dat->infosUpdated=NULL;} + if(dat->hContact != NULL) { + if (!CallContactService(dat->hContact,PSS_GETINFO,0,0)) { + EnableWindow(GetDlgItem(hwndDlg,IDC_UPDATE),FALSE); + ShowWindow(GetDlgItem(hwndDlg,IDC_UPDATING),SW_SHOW); + SetTimer(hwndDlg,1,100,NULL); + } + } + break; + } + break; + + case WM_CLOSE: + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDOK,BN_CLICKED),(LPARAM)GetDlgItem(hwndDlg,IDOK)); + break; + + case WM_DESTROY: + { + TCHAR name[128]; + TVITEM tvi; + tvi.mask = TVIF_TEXT; + tvi.hItem = dat->opd[dat->currentPage].hItem; + tvi.pszText=name; + tvi.cchTextMax=SIZEOF(name); + TreeView_GetItem(GetDlgItem(hwndDlg, IDC_PAGETREE), &tvi); + DBWriteContactSettingTString(NULL,"UserInfo","LastTab", name); + } + Window_FreeIcon_IcoLib(hwndDlg); + SendDlgItemMessage(hwndDlg,IDC_NAME,WM_SETFONT,SendDlgItemMessage(hwndDlg,IDC_WHITERECT,WM_GETFONT,0,0),0); + DeleteObject(dat->hBoldFont); + WindowList_Remove(hWindowList,hwndDlg); + UnhookEvent(dat->hProtoAckEvent); + { int i; + for(i=0;ipageCount;i++) + { + if(dat->opd[i].hwnd!=NULL) DestroyWindow(dat->opd[i].hwnd); + mir_free(dat->opd[i].ptszTitle); + mir_free(dat->opd[i].ptszTab); + } + } + mir_free(dat->infosUpdated); + mir_free(dat->opd); + mir_free(dat); + break; + } + return FALSE; +} + +int ShutdownUserInfo(WPARAM, LPARAM) +{ + WindowList_BroadcastAsync(hWindowList,WM_DESTROY,0,0); + return 0; +} + +int LoadUserInfoModule(void) +{ + CLISTMENUITEM mi = { 0 }; + + CreateServiceFunction(MS_USERINFO_SHOWDIALOG,ShowDetailsDialogCommand); + hDetailsInitEvent=CreateHookableEvent(ME_USERINFO_INITIALISE); + HookEvent(ME_USERINFO_INITIALISE,DetailsInit); + HookEvent(ME_DB_CONTACT_DELETED,UserInfoContactDelete); + HookEvent(ME_SYSTEM_PRESHUTDOWN,ShutdownUserInfo); + CreateServiceFunction(MS_USERINFO_ADDPAGE,AddDetailsPage); + + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + mi.position = 1000050000; + mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_USERDETAILS ); + mi.pszName = LPGEN("User &Details"); + mi.pszService = MS_USERINFO_SHOWDIALOG; + CallService(MS_CLIST_ADDCONTACTMENUITEM,0,(LPARAM)&mi); + + mi.position = 500050000; + mi.pszName = LPGEN("View/Change My &Details..."); + CallService(MS_CLIST_ADDMAINMENUITEM,0,(LPARAM)&mi); + + hWindowList = (HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST,0,0); + return 0; +} diff --git a/src/modules/useronline/useronline.cpp b/src/modules/useronline/useronline.cpp new file mode 100644 index 0000000000..23371de267 --- /dev/null +++ b/src/modules/useronline/useronline.cpp @@ -0,0 +1,117 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +static int uniqueEventId=0; + +static int UserOnlineSettingChanged(WPARAM wParam,LPARAM lParam) +{ + DBCONTACTWRITESETTING *cws=(DBCONTACTWRITESETTING*)lParam; + int newStatus,oldStatus; + + if((HANDLE)wParam==NULL || strcmp(cws->szSetting,"Status")) return 0; + newStatus=cws->value.wVal; + oldStatus=DBGetContactSettingWord((HANDLE)wParam,"UserOnline","OldStatus",ID_STATUS_OFFLINE); + DBWriteContactSettingWord((HANDLE)wParam,"UserOnline","OldStatus",(WORD)newStatus); + if(CallService(MS_IGNORE_ISIGNORED,wParam,IGNOREEVENT_USERONLINE)) return 0; + if(DBGetContactSettingByte((HANDLE)wParam,"CList","Hidden",0)) return 0; + if(newStatus==ID_STATUS_OFFLINE&&oldStatus!=ID_STATUS_OFFLINE) { + // Remove the event from the queue if it exists since they are now offline + int lastEvent = (int)DBGetContactSettingDword((HANDLE)wParam,"UserOnline","LastEvent",0); + + if (lastEvent) { + CallService(MS_CLIST_REMOVEEVENT,wParam,(LPARAM)lastEvent); + DBWriteContactSettingDword((HANDLE)wParam,"UserOnline", "LastEvent", 0); + } + } + if((newStatus==ID_STATUS_ONLINE || newStatus==ID_STATUS_FREECHAT) && + oldStatus!=ID_STATUS_ONLINE && oldStatus!=ID_STATUS_FREECHAT) { + { + DWORD ticked = db_dword_get(NULL, "UserOnline", cws->szModule, GetTickCount()); + // only play the sound (or show event) if this event happens at least 10 secs after the proto went from offline + if ( GetTickCount() - ticked > (1000*10) ) { + CLISTEVENT cle; + TCHAR tooltip[256]; + + ZeroMemory(&cle,sizeof(cle)); + cle.cbSize=sizeof(cle); + cle.flags=CLEF_ONLYAFEW | CLEF_TCHAR; + cle.hContact=(HANDLE)wParam; + cle.hDbEvent=(HANDLE)(uniqueEventId++); + cle.hIcon = LoadSkinIcon( SKINICON_OTHER_USERONLINE, false ); + cle.pszService="UserOnline/Description"; + mir_sntprintf(tooltip,SIZEOF(tooltip),TranslateT("%s is Online"), cli.pfnGetContactDisplayName(( HANDLE )wParam, 0 )); + cle.ptszTooltip=tooltip; + CallService(MS_CLIST_ADDEVENT,0,(LPARAM)&cle); + IconLib_ReleaseIcon( cle.hIcon, 0 ); + DBWriteContactSettingDword(cle.hContact,"UserOnline", "LastEvent", (DWORD)cle.hDbEvent); + SkinPlaySound("UserOnline"); + } + } + } + return 0; +} + +static int UserOnlineAck(WPARAM, LPARAM lParam) +{ + ACKDATA * ack = (ACKDATA*) lParam; + if ( ack != 0 && ack->szModule && ack->type == ACKTYPE_STATUS && ack->result == ACKRESULT_SUCCESS && ack->hProcess == (HANDLE)ID_STATUS_OFFLINE) { + // if going from offline to any other mode, remember when it happened. + db_dword_set(NULL, "UserOnline", ack->szModule, GetTickCount()); + } + return 0; +} + +static int UserOnlineModulesLoaded(WPARAM, LPARAM) +{ + // reset the counter + for ( int j = 0; j < accounts.getCount(); j++ ) + if ( Proto_IsAccountEnabled( accounts[j] )) db_dword_set( NULL, "UserOnline", accounts[j]->szModuleName, GetTickCount()); + + return 0; +} + +static int UserOnlineAccountsChanged( WPARAM eventCode, LPARAM lParam ) +{ + PROTOACCOUNT* pa = (PROTOACCOUNT*)lParam; + + switch( eventCode ) { + case PRAC_ADDED: + case PRAC_CHECKED: + // reset the counter + if ( Proto_IsAccountEnabled( pa )) + db_dword_set( NULL, "UserOnline", pa->szModuleName, GetTickCount()); + break; + } + return 0; +} + +int LoadUserOnlineModule(void) +{ + HookEvent(ME_DB_CONTACT_SETTINGCHANGED,UserOnlineSettingChanged); + HookEvent(ME_PROTO_ACK, UserOnlineAck); + HookEvent(ME_SYSTEM_MODULESLOADED, UserOnlineModulesLoaded); + HookEvent(ME_PROTO_ACCLISTCHANGED, UserOnlineAccountsChanged); + SkinAddNewSoundEx("UserOnline","Alerts","Online"); + return 0; +} diff --git a/src/modules/utils/bmpfilter.cpp b/src/modules/utils/bmpfilter.cpp new file mode 100644 index 0000000000..6f92eba126 --- /dev/null +++ b/src/modules/utils/bmpfilter.cpp @@ -0,0 +1,242 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include + +#include "m_png.h" +#include "m_imgsrvc.h" + +static INT_PTR sttBitmapLoader( const TCHAR* ptszFileName ) +{ + IPicture *pic; + HBITMAP hBmp,hBmpCopy; + HBITMAP hOldBitmap, hOldBitmap2; + BITMAP bmpInfo; + HDC hdc,hdcMem1,hdcMem2; + short picType; + + TCHAR szFilename[MAX_PATH]; + if ( !pathToAbsoluteT(ptszFileName, szFilename, NULL )) + mir_sntprintf(szFilename, SIZEOF(szFilename), _T("%s"), ptszFileName); + + int filenameLen = lstrlen(szFilename); + if ( filenameLen > 4 ) { + TCHAR* pszExt = szFilename + filenameLen - 4; + + if ( ServiceExists( MS_IMG_LOAD )) + return CallService( MS_IMG_LOAD, (WPARAM)szFilename, IMGL_TCHAR ); + + if ( !lstrcmpi( pszExt, _T(".bmp")) || !lstrcmpi( pszExt, _T(".rle"))) { + //LoadImage can do this much faster + return (INT_PTR)LoadImage( hMirandaInst, szFilename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE ); + } + + if ( !lstrcmpi( pszExt, _T(".png"))) { + HANDLE hFile, hMap = NULL; + BYTE* ppMap = NULL; + INT_PTR cbFileSize = 0; + BITMAPINFOHEADER* pDib; + BYTE* pDibBits; + + if ( !ServiceExists( MS_PNG2DIB )) { + MessageBox( NULL, TranslateT( "You need an image services plugin to process PNG images." ), TranslateT( "Error" ), MB_OK ); + return 0; + } + + if (( hFile = CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL )) != INVALID_HANDLE_VALUE ) + if (( hMap = CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, 0, NULL )) != NULL ) + if (( ppMap = ( BYTE* )MapViewOfFile( hMap, FILE_MAP_READ, 0, 0, 0 )) != NULL ) + cbFileSize = GetFileSize( hFile, NULL ); + + if ( cbFileSize != 0 ) { + PNG2DIB param; + param.pSource = ppMap; + param.cbSourceSize = cbFileSize; + param.pResult = &pDib; + if ( CallService( MS_PNG2DIB, 0, ( LPARAM )¶m )) { + pDibBits = ( BYTE* )( pDib+1 ); + HDC sDC = GetDC( NULL ); + HBITMAP hBitmap = CreateDIBitmap( sDC, pDib, CBM_INIT, pDibBits, ( BITMAPINFO* )pDib, DIB_PAL_COLORS ); + SelectObject( sDC, hBitmap ); + ReleaseDC( NULL, sDC ); + GlobalFree( pDib ); + cbFileSize = (INT_PTR)hBitmap; + } + else cbFileSize = 0; + } + + if ( ppMap != NULL ) UnmapViewOfFile( ppMap ); + if ( hMap != NULL ) CloseHandle( hMap ); + if ( hFile != NULL ) CloseHandle( hFile ); + + return (INT_PTR)cbFileSize; + } } + + if (S_OK != OleLoadPicturePath( LPOLESTR(( const wchar_t* )StrConvU(szFilename)), NULL, 0, 0, IID_IPicture, (PVOID*)&pic )) + return 0; + + pic->get_Type(&picType); + if (picType!=PICTYPE_BITMAP) { + pic->Release(); + return 0; + } + OLE_HANDLE hOleBmp; + pic->get_Handle(&hOleBmp); + hBmp = (HBITMAP)hOleBmp; + GetObject(hBmp,sizeof(bmpInfo),&bmpInfo); + + //need to copy bitmap so we can free the IPicture + hdc=GetDC(NULL); + hdcMem1=CreateCompatibleDC(hdc); + hdcMem2=CreateCompatibleDC(hdc); + hOldBitmap=( HBITMAP )SelectObject(hdcMem1,hBmp); + hBmpCopy=CreateCompatibleBitmap(hdcMem1,bmpInfo.bmWidth,bmpInfo.bmHeight); + hOldBitmap2=( HBITMAP )SelectObject(hdcMem2,hBmpCopy); + BitBlt(hdcMem2,0,0,bmpInfo.bmWidth,bmpInfo.bmHeight,hdcMem1,0,0,SRCCOPY); + SelectObject(hdcMem1,hOldBitmap); + SelectObject(hdcMem2,hOldBitmap2); + DeleteDC(hdcMem2); + DeleteDC(hdcMem1); + ReleaseDC(NULL,hdc); + + DeleteObject(hBmp); + pic->Release(); + return (INT_PTR)hBmpCopy; +} + +static INT_PTR BmpFilterLoadBitmap(WPARAM, LPARAM lParam) +{ + return sttBitmapLoader( StrConvT(( const char* )lParam )); +} + +#if defined( _UNICODE ) +static INT_PTR BmpFilterLoadBitmapW(WPARAM, LPARAM lParam) +{ + return sttBitmapLoader(( const wchar_t* )lParam ); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR BmpFilterGetStrings(WPARAM wParam,LPARAM lParam) +{ + int bytesLeft=wParam; + char *filter=(char*)lParam,*pfilter; + + lstrcpynA(filter,Translate("All Bitmaps"),bytesLeft); bytesLeft-=lstrlenA(filter); + strncat(filter," (*.bmp;*.jpg;*.gif;*.png)",bytesLeft); + pfilter=filter+lstrlenA(filter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpynA(pfilter,"*.BMP;*.RLE;*.JPG;*.JPEG;*.GIF;*.PNG",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpynA(pfilter,Translate("Windows Bitmaps"),bytesLeft); bytesLeft-=lstrlenA(pfilter); + strncat(pfilter," (*.bmp;*.rle)",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpynA(pfilter,"*.BMP;*.RLE",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpynA(pfilter,Translate("JPEG Bitmaps"),bytesLeft); bytesLeft-=lstrlenA(pfilter); + strncat(pfilter," (*.jpg;*.jpeg)",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpynA(pfilter,"*.JPG;*.JPEG",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpynA(pfilter,Translate("GIF Bitmaps"),bytesLeft); bytesLeft-=lstrlenA(pfilter); + strncat(pfilter," (*.gif)",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpynA(pfilter,"*.GIF",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpynA(pfilter,Translate("PNG Bitmaps"),bytesLeft); bytesLeft-=lstrlenA(pfilter); + strncat(pfilter," (*.png)",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpynA(pfilter,"*.PNG",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpynA(pfilter,Translate("All Files"),bytesLeft); bytesLeft-=lstrlenA(pfilter); + strncat(pfilter," (*)",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpynA(pfilter,"*",bytesLeft); + pfilter+=lstrlenA(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + if (bytesLeft) *pfilter='\0'; + return 0; +} + +#if defined( _UNICODE ) +static INT_PTR BmpFilterGetStringsW(WPARAM wParam,LPARAM lParam) +{ + int bytesLeft=wParam; + TCHAR *filter=(TCHAR*)lParam,*pfilter; + + lstrcpyn(filter,TranslateT("All Bitmaps"),bytesLeft); bytesLeft-=lstrlen(filter); + _tcsncat(filter, _T(" (*.bmp;*.jpg;*.gif;*.png)"), bytesLeft ); + pfilter=filter+lstrlen(filter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter,_T("*.BMP;*.RLE;*.JPG;*.JPEG;*.GIF;*.PNG"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("Windows Bitmaps"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter,_T(" (*.bmp;*.rle)"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter,_T("*.BMP;*.RLE"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("JPEG Bitmaps"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter,_T(" (*.jpg;*.jpeg)"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter,_T("*.JPG;*.JPEG"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("GIF Bitmaps"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter,_T(" (*.gif)"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter,_T("*.GIF"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("PNG Bitmaps"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter,_T(" (*.png)"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter,_T("*.PNG"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + lstrcpyn(pfilter,TranslateT("All Files"),bytesLeft); bytesLeft-=lstrlen(pfilter); + _tcsncat(pfilter,_T(" (*)"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + lstrcpyn(pfilter,_T("*"),bytesLeft); + pfilter+=lstrlen(pfilter)+1; bytesLeft=wParam-(pfilter-filter); + + if (bytesLeft) *pfilter='\0'; + return 0; +} +#endif + +int InitBitmapFilter(void) +{ + CreateServiceFunction(MS_UTILS_LOADBITMAP,BmpFilterLoadBitmap); + CreateServiceFunction(MS_UTILS_GETBITMAPFILTERSTRINGS,BmpFilterGetStrings); + #if defined( _UNICODE ) + CreateServiceFunction(MS_UTILS_GETBITMAPFILTERSTRINGSW,BmpFilterGetStringsW); + CreateServiceFunction(MS_UTILS_LOADBITMAPW,BmpFilterLoadBitmapW); + #endif + return 0; +} diff --git a/src/modules/utils/colourpicker.cpp b/src/modules/utils/colourpicker.cpp new file mode 100644 index 0000000000..ec1a87153c --- /dev/null +++ b/src/modules/utils/colourpicker.cpp @@ -0,0 +1,107 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +static LRESULT CALLBACK ColourPickerWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch(message) { + case WM_CREATE: + SetWindowLongPtr(hwnd,0,0); + SetWindowLongPtr(hwnd,sizeof(COLORREF),0); + break; + case CPM_SETDEFAULTCOLOUR: + SetWindowLongPtr(hwnd,sizeof(COLORREF),lParam); + break; + case CPM_GETDEFAULTCOLOUR: + return GetWindowLongPtr(hwnd,sizeof(COLORREF)); + case CPM_SETCOLOUR: + SetWindowLongPtr(hwnd,0,lParam); + InvalidateRect(hwnd,NULL,FALSE); + break; + case CPM_GETCOLOUR: + return GetWindowLongPtr(hwnd,0); + case WM_LBUTTONUP: + { + CHOOSECOLOR cc={0}; + COLORREF custColours[16]={0}; + custColours[0]=GetWindowLongPtr(hwnd,sizeof(COLORREF)); + cc.lStructSize=sizeof(CHOOSECOLOR); + cc.hwndOwner=hwnd; + cc.hInstance=(HWND)hMirandaInst; + cc.rgbResult=GetWindowLongPtr(hwnd,0); + cc.lpCustColors=custColours; + cc.Flags=CC_ANYCOLOR|CC_FULLOPEN|CC_RGBINIT; + if(ChooseColor(&cc)) { + SetWindowLongPtr(hwnd,0,cc.rgbResult); + SendMessage(GetParent(hwnd),WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(hwnd),CPN_COLOURCHANGED),(LPARAM)hwnd); + InvalidateRect(hwnd,NULL,FALSE); + } + break; + } + case WM_ENABLE: + InvalidateRect(hwnd,NULL,FALSE); + break; + case WM_NCPAINT: + case WM_PAINT: + { PAINTSTRUCT ps; + HDC hdc1; + RECT rc; + HBRUSH hBrush; + + hdc1=BeginPaint(hwnd,&ps); + GetClientRect(hwnd,&rc); + DrawEdge(hdc1,&rc,EDGE_ETCHED,BF_RECT); + InflateRect(&rc,-2,-2); + if(IsWindowEnabled(hwnd)) + hBrush=CreateSolidBrush(GetWindowLongPtr(hwnd,0)); + else + hBrush=CreateHatchBrush(HS_BDIAGONAL,GetSysColor(COLOR_GRAYTEXT)); + SetBkColor(hdc1,GetSysColor(COLOR_BTNFACE)); + FillRect(hdc1,&rc,hBrush); + DeleteObject(hBrush); + EndPaint(hwnd,&ps); + break; + } + case WM_DESTROY: + break; + } + return DefWindowProc(hwnd,message,wParam,lParam); +} + +int InitColourPicker(void) +{ + WNDCLASS wcl; + + wcl.lpfnWndProc=ColourPickerWndProc; + wcl.cbClsExtra=0; + wcl.cbWndExtra=sizeof(COLORREF)*2; + wcl.hInstance=hMirandaInst; + wcl.hCursor=NULL; + wcl.lpszClassName=WNDCLASS_COLOURPICKER; + wcl.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1); + wcl.hIcon=NULL; + wcl.lpszMenuName=NULL; + wcl.style=CS_HREDRAW|CS_VREDRAW|CS_GLOBALCLASS; + RegisterClass(&wcl); + return 0; +} diff --git a/src/modules/utils/hyperlink.cpp b/src/modules/utils/hyperlink.cpp new file mode 100644 index 0000000000..62d307a958 --- /dev/null +++ b/src/modules/utils/hyperlink.cpp @@ -0,0 +1,274 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +struct HyperlinkWndData { + HFONT hEnableFont,hDisableFont; + RECT rcText; + COLORREF enableColor, disableColor, focusColor; + BYTE flags; /* see HLKF_* */ +}; + +/* flags */ +#define HLKF_HASENABLECOLOR 0x1 /* dat->enableColor is not system default */ +#define HLKF_HASDISABLECOLOR 0x2 /* dat->disableColor is not system default */ + +/* internal messages */ +#define HLK_MEASURETEXT (WM_USER+1) +#define HLK_INVALIDATE (WM_USER+2) + +static LRESULT CALLBACK HyperlinkWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + struct HyperlinkWndData *dat=(struct HyperlinkWndData*)GetWindowLongPtr(hwnd,0); + switch(msg) { + case WM_NCCREATE: + dat=(struct HyperlinkWndData*)mir_calloc(sizeof(struct HyperlinkWndData)); + if(dat==NULL) return FALSE; /* fail creation */ + SetWindowLongPtr(hwnd,0,(LONG_PTR)dat); /* always succeeds */ + /* fall thru */ + case WM_SYSCOLORCHANGE: + if(!(dat->flags&HLKF_HASENABLECOLOR)) { + if(GetSysColorBrush(COLOR_HOTLIGHT)==NULL) dat->enableColor=RGB(0,0,255); + else dat->enableColor=GetSysColor(COLOR_HOTLIGHT); + dat->focusColor = RGB(GetRValue(dat->enableColor) / 2, GetGValue(dat->enableColor) / 2, GetBValue(dat->enableColor) / 2); + } + if(!(dat->flags&HLKF_HASDISABLECOLOR)) + dat->disableColor=GetSysColor(COLOR_GRAYTEXT); + break; + + case WM_SETFOCUS: + case WM_KILLFOCUS: + RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE); + break; + case WM_MOUSEACTIVATE: + SetFocus(hwnd); + return MA_ACTIVATE; + case WM_GETDLGCODE: + { + if (lParam) + { + MSG *msg = (MSG *) lParam; + if (msg->message == WM_KEYDOWN) + { + if (msg->wParam == VK_TAB) + return 0; + if (msg->wParam == VK_ESCAPE) + return 0; + } else + if (msg->message == WM_CHAR) + { + if (msg->wParam == '\t') + return 0; + if (msg->wParam == 27) + return 0; + } + } + return DLGC_WANTMESSAGE; + } + + case WM_KEYDOWN: + { + switch (wParam) + { + case VK_SPACE: + case VK_RETURN: + SendMessage(GetParent(hwnd),WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(hwnd),STN_CLICKED),(LPARAM)hwnd); + break; + } + return 0; + } + + case WM_LBUTTONDOWN: + { POINT pt; + POINTSTOPOINT(pt,MAKEPOINTS(lParam)); + if(!PtInRect(&dat->rcText,pt)) break; + SendMessage(GetParent(hwnd),WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(hwnd),STN_CLICKED),(LPARAM)hwnd); + return 0; + } + case WM_SETFONT: + { LOGFONT lf; + HFONT hFont; + if((HFONT)wParam==NULL) { /* use default system color */ + dat->hEnableFont=dat->hDisableFont=NULL; + return 0; + } + if(GetObject((HFONT)wParam,sizeof(lf),&lf)) { + lf.lfUnderline=1; + hFont=CreateFontIndirect(&lf); + if(hFont!=NULL) { + dat->hEnableFont=hFont; + dat->hDisableFont=(HFONT)wParam; + if(LOWORD(lParam)) SendMessage(hwnd,HLK_INVALIDATE,0,0); + SendMessage(hwnd,HLK_MEASURETEXT,0,0); + } + } + return 0; + } + case WM_ERASEBKGND: + return TRUE; + case WM_ENABLE: + case HLK_INVALIDATE: + { RECT rcWnd; + POINT pt; + HWND hwndParent; + if(!GetWindowRect(hwnd,&rcWnd)) break; + pt.x=rcWnd.left; + pt.y=rcWnd.top; + hwndParent=GetParent(hwnd); + if(hwndParent==NULL) hwndParent=hwnd; + if(!ScreenToClient(hwndParent,&pt)) break; + rcWnd.right=pt.x+(rcWnd.right-rcWnd.left); + rcWnd.bottom=pt.y+(rcWnd.bottom-rcWnd.top); + rcWnd.left=pt.x; + rcWnd.top=pt.y; + InvalidateRect(hwndParent,&rcWnd,TRUE); + return 0; + } + case WM_GETFONT: + return (LRESULT)dat->hDisableFont; + case WM_CREATE: + case HLK_MEASURETEXT: + { TCHAR szText[256]; + if(!GetWindowText(hwnd,szText,SIZEOF(szText))) return 0; + lParam=(LPARAM)szText; + /* fall thru */ + case WM_SETTEXT: + { HFONT hPrevFont = NULL; + SIZE textSize; + RECT rc; + HDC hdc; + LONG style; + BOOL fMeasured=FALSE; + hdc=GetDC(hwnd); + if(hdc==NULL) return 0; /* text change failed */ + if(dat->hEnableFont!=NULL) hPrevFont=(HFONT)SelectObject(hdc,dat->hEnableFont); + if(dat->hEnableFont==NULL || hPrevFont!=NULL) /* select failed? */ + if(GetTextExtentPoint32(hdc,(TCHAR*)lParam,lstrlen((TCHAR*)lParam),&textSize)) + if(GetClientRect(hwnd,&rc)) { + dat->rcText.top=0; + dat->rcText.bottom=dat->rcText.top+textSize.cy; + style=GetWindowLongPtr(hwnd,GWL_STYLE); + if(style&SS_CENTER) dat->rcText.left=(rc.right-textSize.cx)/2; + else if(style&SS_RIGHT) dat->rcText.left=rc.right-textSize.cx; + else dat->rcText.left=0; + dat->rcText.right=dat->rcText.left+textSize.cx; + fMeasured=TRUE; + } + if(dat->hEnableFont!=NULL && hPrevFont!=NULL) SelectObject(hdc,hPrevFont); + ReleaseDC(hwnd,hdc); + if(!fMeasured) return 0; /* text change failed */ + SendMessage(hwnd,HLK_INVALIDATE,0,0); + break; + }} + case WM_SETCURSOR: + { POINT pt; + HCURSOR hCursor; + if(!GetCursorPos(&pt)) return FALSE; + if(!ScreenToClient(hwnd,&pt)) return FALSE; + if(PtInRect(&dat->rcText,pt)) { + hCursor=(HCURSOR)GetClassLongPtr(hwnd,GCLP_HCURSOR); + if(hCursor==NULL) hCursor=LoadCursor(NULL,IDC_HAND); /* Win2000+ */ + } + else hCursor=LoadCursor(NULL,IDC_ARROW); + SetCursor(hCursor); + return TRUE; + } + case HLK_SETENABLECOLOUR: + { COLORREF prevColor=dat->enableColor; + dat->enableColor=(COLORREF)wParam; + dat->focusColor = RGB(GetRValue(dat->enableColor) / 2, GetGValue(dat->enableColor) / 2, GetBValue(dat->enableColor) / 2); + dat->flags|=HLKF_HASENABLECOLOR; + return (LRESULT)prevColor; + } + case HLK_SETDISABLECOLOUR: + { COLORREF prevColor=dat->disableColor; + dat->disableColor=(COLORREF)wParam; + dat->flags|=HLKF_HASDISABLECOLOR; + return (LRESULT)prevColor; + } + case WM_NCPAINT: + return 0; + case WM_PAINT: + { HFONT hPrevFont; + RECT rc; + TCHAR szText[256]; + UINT alignFlag; + COLORREF textColor; + PAINTSTRUCT ps; + HDC hdc; + + hdc=BeginPaint(hwnd,&ps); + if(hdc!=NULL) { + if(IsWindowEnabled(hwnd)) { + hPrevFont=(HFONT)SelectObject(hdc,dat->hEnableFont); + textColor = (GetFocus() == hwnd) ? dat->focusColor : dat->enableColor; + } else { + hPrevFont=(HFONT)SelectObject(hdc,dat->hDisableFont); + textColor=dat->disableColor; + } + if(GetClientRect(hwnd,&rc) && GetWindowText(hwnd,szText,SIZEOF(szText))) { + if (drawThemeParentBackground && IsWinVerXPPlus()) + { + BOOL fSmoothing; + UINT fSmoothingType; + SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &fSmoothing, 0); + SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &fSmoothingType, 0); + if (fSmoothing && fSmoothingType == FE_FONTSMOOTHINGCLEARTYPE) + drawThemeParentBackground(hwnd, hdc, &rc); + } + SetBkMode(hdc,TRANSPARENT); + SetTextColor(hdc,textColor); + alignFlag=(GetWindowLongPtr(hwnd,GWL_STYLE)&(SS_CENTER|SS_RIGHT|SS_LEFT)); + DrawText(hdc,szText,-1,&rc,alignFlag|DT_NOPREFIX|DT_SINGLELINE|DT_TOP); + } + if(hPrevFont!=NULL) SelectObject(hdc,hPrevFont); + EndPaint(hwnd,&ps); + } + return 0; + } + case WM_NCDESTROY: + if(dat->hEnableFont!=NULL) DeleteObject(dat->hEnableFont); + mir_free(dat); + break; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +int InitHyperlink(void) +{ + WNDCLASS wcl; + + wcl.lpfnWndProc=HyperlinkWndProc; + wcl.cbClsExtra=0; + wcl.cbWndExtra=sizeof(struct HyperlinkWndData*); + wcl.hInstance=hMirandaInst; + if(IsWinVer2000Plus()) wcl.hCursor=NULL; + else wcl.hCursor=LoadCursor(wcl.hInstance,MAKEINTRESOURCE(IDC_HYPERLINKHAND)); + wcl.lpszClassName=WNDCLASS_HYPERLINK; + wcl.hbrBackground=NULL; + wcl.hIcon=NULL; + wcl.lpszMenuName=NULL; + wcl.style=CS_HREDRAW|CS_VREDRAW|CS_GLOBALCLASS|CS_PARENTDC; + RegisterClass(&wcl); /* automatically unregistered on exit */ + return 0; +} diff --git a/src/modules/utils/imgconv.cpp b/src/modules/utils/imgconv.cpp new file mode 100644 index 0000000000..e25953e471 --- /dev/null +++ b/src/modules/utils/imgconv.cpp @@ -0,0 +1,152 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2010 Miranda IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +typedef DWORD ARGB; + +void InitBitmapInfo(BITMAPINFO &bmi, const SIZE &size) +{ + ZeroMemory(&bmi, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biBitCount = 32; + + bmi.bmiHeader.biWidth = size.cx; + bmi.bmiHeader.biHeight = size.cy; +} + +void ConvertToPARGB32(HDC hdc, ARGB *pargb, HBITMAP hbmp, SIZE& sizImage, int cxRow) +{ + BITMAPINFO bmi; + InitBitmapInfo(bmi, sizImage); + + void *pvBits = malloc(sizImage.cx * 4 * sizImage.cy); + if (GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, pvBits, &bmi, DIB_RGB_COLORS) == bmi.bmiHeader.biHeight) + { + ULONG cxDelta = cxRow - bmi.bmiHeader.biWidth; + ARGB *pargbMask = (ARGB *)pvBits; + + for (ULONG y = bmi.bmiHeader.biHeight + 1; --y; ) + { + for (ULONG x = bmi.bmiHeader.biWidth + 1; --x; ) + { + if (*pargbMask++) + { + // transparent pixel + *pargb++ = 0; + } + else + { + // opaque pixel + *pargb++ |= 0xFF000000; + } + } + + pargb += cxDelta; + } + } + free(pvBits); +} + +bool HasAlpha( ARGB *pargb, SIZE& sizImage, int cxRow) +{ + ULONG cxDelta = cxRow - sizImage.cx; + for (ULONG y = sizImage.cy; y--; ) + { + for (ULONG x = sizImage.cx; x--; ) + { + if (*pargb++ & 0xFF000000) + return true; + } + pargb += cxDelta; + } + + return false; +} + +void ConvertBufferToPARGB32(HANDLE hPaintBuffer, HDC hdc, HICON hIcon, SIZE& sizIcon) +{ + RGBQUAD *prgbQuad; + int cxRow; + HRESULT hr = getBufferedPaintBits(hPaintBuffer, &prgbQuad, &cxRow); + if (SUCCEEDED(hr)) + { + ARGB *pargb = (ARGB *)prgbQuad; + if (!HasAlpha(pargb, sizIcon, cxRow)) + { + ICONINFO info; + if (GetIconInfo(hIcon, &info)) + { + if (info.hbmMask) + ConvertToPARGB32(hdc, pargb, info.hbmMask, sizIcon, cxRow); + + DeleteObject(info.hbmColor); + DeleteObject(info.hbmMask); + } + } + } +} + +HBITMAP ConvertIconToBitmap(HICON hicon, HIMAGELIST hIml, int iconId) +{ + SIZE sizIcon; + sizIcon.cx = GetSystemMetrics(SM_CXSMICON); + sizIcon.cy = GetSystemMetrics(SM_CYSMICON); + + RECT rcIcon = { 0, 0, sizIcon.cx, sizIcon.cy }; + + HDC hdc = CreateCompatibleDC(NULL); + + BITMAPINFO bmi; + InitBitmapInfo(bmi, sizIcon); + + HBITMAP hbmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); + HBITMAP hbmpOld = (HBITMAP)SelectObject(hdc, hbmp); + + BLENDFUNCTION bfAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + BP_PAINTPARAMS paintParams = {0}; + paintParams.cbSize = sizeof(paintParams); + paintParams.dwFlags = BPPF_ERASE; + paintParams.pBlendFunction = &bfAlpha; + + HDC hdcBuffer; + HANDLE hPaintBuffer = beginBufferedPaint(hdc, &rcIcon, BPBF_DIB, &paintParams, &hdcBuffer); + if (hPaintBuffer) + { + if (hIml) + ImageList_Draw(hIml, iconId, hdc, 0, 0, ILD_TRANSPARENT); + else + DrawIconEx(hdcBuffer, 0, 0, hicon, sizIcon.cx, sizIcon.cy, 0, NULL, DI_NORMAL); + + // If icon did not have an alpha channel we need to convert buffer to PARGB + ConvertBufferToPARGB32(hPaintBuffer, hdc, hicon, sizIcon); + + // This will write the buffer contents to the destination bitmap + endBufferedPaint(hPaintBuffer, TRUE); + } + + SelectObject(hdc, hbmpOld); + DeleteDC(hdc); + + return hbmp; +} diff --git a/src/modules/utils/md5.cpp b/src/modules/utils/md5.cpp new file mode 100644 index 0000000000..0fcaccb9ae --- /dev/null +++ b/src/modules/utils/md5.cpp @@ -0,0 +1,374 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c 2874 2006-05-16 21:38:00Z ghazan $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +// (C) 2005 Joe @ Whale - changed to compile with Miranda + +#include "commonheaders.h" + + +#define T_MASK ((mir_md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + +//gfd* +static void md5_process(mir_md5_state_t *pms, const mir_md5_byte_t *data /*[64]*/) +{ + mir_md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + mir_md5_word_t t; + /* Define storage for little-endian or both types of CPUs. */ + mir_md5_word_t xbuf[16]; + const mir_md5_word_t *X; + + { + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const mir_md5_byte_t *)&w)) /* dynamic little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const mir_md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const mir_md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } + else /* dynamic big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const mir_md5_byte_t *xp = data; + int i; + + X = xbuf; /* (dynamic only) */ + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET1(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET1(a, b, c, d, 0, 7, T1); + SET1(d, a, b, c, 1, 12, T2); + SET1(c, d, a, b, 2, 17, T3); + SET1(b, c, d, a, 3, 22, T4); + SET1(a, b, c, d, 4, 7, T5); + SET1(d, a, b, c, 5, 12, T6); + SET1(c, d, a, b, 6, 17, T7); + SET1(b, c, d, a, 7, 22, T8); + SET1(a, b, c, d, 8, 7, T9); + SET1(d, a, b, c, 9, 12, T10); + SET1(c, d, a, b, 10, 17, T11); + SET1(b, c, d, a, 11, 22, T12); + SET1(a, b, c, d, 12, 7, T13); + SET1(d, a, b, c, 13, 12, T14); + SET1(c, d, a, b, 14, 17, T15); + SET1(b, c, d, a, 15, 22, T16); + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET2(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET2(a, b, c, d, 1, 5, T17); + SET2(d, a, b, c, 6, 9, T18); + SET2(c, d, a, b, 11, 14, T19); + SET2(b, c, d, a, 0, 20, T20); + SET2(a, b, c, d, 5, 5, T21); + SET2(d, a, b, c, 10, 9, T22); + SET2(c, d, a, b, 15, 14, T23); + SET2(b, c, d, a, 4, 20, T24); + SET2(a, b, c, d, 9, 5, T25); + SET2(d, a, b, c, 14, 9, T26); + SET2(c, d, a, b, 3, 14, T27); + SET2(b, c, d, a, 8, 20, T28); + SET2(a, b, c, d, 13, 5, T29); + SET2(d, a, b, c, 2, 9, T30); + SET2(c, d, a, b, 7, 14, T31); + SET2(b, c, d, a, 12, 20, T32); + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET3(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET3(a, b, c, d, 5, 4, T33); + SET3(d, a, b, c, 8, 11, T34); + SET3(c, d, a, b, 11, 16, T35); + SET3(b, c, d, a, 14, 23, T36); + SET3(a, b, c, d, 1, 4, T37); + SET3(d, a, b, c, 4, 11, T38); + SET3(c, d, a, b, 7, 16, T39); + SET3(b, c, d, a, 10, 23, T40); + SET3(a, b, c, d, 13, 4, T41); + SET3(d, a, b, c, 0, 11, T42); + SET3(c, d, a, b, 3, 16, T43); + SET3(b, c, d, a, 6, 23, T44); + SET3(a, b, c, d, 9, 4, T45); + SET3(d, a, b, c, 12, 11, T46); + SET3(c, d, a, b, 15, 16, T47); + SET3(b, c, d, a, 2, 23, T48); + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET4(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET4(a, b, c, d, 0, 6, T49); + SET4(d, a, b, c, 7, 10, T50); + SET4(c, d, a, b, 14, 15, T51); + SET4(b, c, d, a, 5, 21, T52); + SET4(a, b, c, d, 12, 6, T53); + SET4(d, a, b, c, 3, 10, T54); + SET4(c, d, a, b, 10, 15, T55); + SET4(b, c, d, a, 1, 21, T56); + SET4(a, b, c, d, 8, 6, T57); + SET4(d, a, b, c, 15, 10, T58); + SET4(c, d, a, b, 6, 15, T59); + SET4(b, c, d, a, 13, 21, T60); + SET4(a, b, c, d, 4, 6, T61); + SET4(d, a, b, c, 11, 10, T62); + SET4(c, d, a, b, 2, 15, T63); + SET4(b, c, d, a, 9, 21, T64); + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void md5_init(mir_md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void md5_append(mir_md5_state_t *pms, const mir_md5_byte_t *data, int nbytes) +{ + const mir_md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + mir_md5_word_t nbits = (mir_md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void md5_finish(mir_md5_state_t *pms, mir_md5_byte_t digest[16]) +{ + static const mir_md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + mir_md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (mir_md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (mir_md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + +void md5_hash_string(const mir_md5_byte_t *data, int len, mir_md5_byte_t digest[16]) +{ + mir_md5_state_t state; + md5_init(&state); + md5_append(&state, data, len); + md5_finish(&state, digest); +} + +INT_PTR GetMD5Interface(WPARAM, LPARAM lParam) +{ + struct MD5_INTERFACE *md5i = (struct MD5_INTERFACE*) lParam; + if ( md5i == NULL ) + return 1; + if ( md5i->cbSize != sizeof( struct MD5_INTERFACE )) + return 1; + + md5i->md5_init = md5_init; + md5i->md5_append = md5_append; + md5i->md5_finish = md5_finish; + md5i->md5_hash = md5_hash_string; + return 0; +} diff --git a/src/modules/utils/openurl.cpp b/src/modules/utils/openurl.cpp new file mode 100644 index 0000000000..ef66d89f8c --- /dev/null +++ b/src/modules/utils/openurl.cpp @@ -0,0 +1,228 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include + +#define DDEMESSAGETIMEOUT 1000 +#define WNDCLASS_DDEMSGWINDOW _T("MirandaDdeMsgWindow") + +struct DdeMsgWindowData { + int fAcked,fData; + HWND hwndDde; +}; + +static LRESULT CALLBACK DdeMessageWindow(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + struct DdeMsgWindowData *dat; + ATOM hSzItem; + HGLOBAL hDdeData; + + dat=(struct DdeMsgWindowData*)GetWindowLongPtr(hwnd,0); + switch(msg) { + case WM_DDE_ACK: + dat->fAcked=1; + dat->hwndDde=(HWND)wParam; + return 0; + case WM_DDE_DATA: + UnpackDDElParam(msg,lParam,(PUINT_PTR)&hDdeData,(PUINT_PTR)&hSzItem); + dat->fData=1; + if(hDdeData) { + DDEDATA *data; + int release; + data=(DDEDATA*)GlobalLock(hDdeData); + if(data->fAckReq) { + DDEACK ack={0}; + PostMessage((HWND)wParam,WM_DDE_ACK,(WPARAM)hwnd,PackDDElParam(WM_DDE_ACK,*(PUINT)&ack,(UINT)hSzItem)); + } + else GlobalDeleteAtom(hSzItem); + release=data->fRelease; + GlobalUnlock(hDdeData); + if(release) GlobalFree(hDdeData); + } + else GlobalDeleteAtom(hSzItem); + return 0; + } + return DefWindowProc(hwnd,msg,wParam,lParam); +} + +static int DoDdeRequest(const char *szItemName,HWND hwndDdeMsg) +{ + ATOM hSzItemName; + DWORD timeoutTick,thisTick; + MSG msg; + struct DdeMsgWindowData *dat=(struct DdeMsgWindowData*)GetWindowLongPtr(hwndDdeMsg,0); + + hSzItemName=GlobalAddAtomA(szItemName); + if(!PostMessage(dat->hwndDde,WM_DDE_REQUEST,(WPARAM)hwndDdeMsg,MAKELPARAM(CF_TEXT,hSzItemName))) { + GlobalDeleteAtom(hSzItemName); + return 1; + } + timeoutTick=GetTickCount()+5000; + dat->fData=0; dat->fAcked=0; + do { + if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if(dat->fData || dat->fAcked) break; + thisTick=GetTickCount(); + if(thisTick>timeoutTick) break; + } while(MsgWaitForMultipleObjects(0,NULL,FALSE,timeoutTick-thisTick,QS_ALLINPUT)==WAIT_OBJECT_0); + + if(!dat->fData) { + GlobalDeleteAtom(hSzItemName); + return 1; + } + return 0; +} + +//see Q160957 and http://developer.netscape.com/docs/manuals/communicator/DDE/index.htm +static int DdeOpenUrl(const char *szBrowser,char *szUrl,int newWindow,HWND hwndDdeMsg) +{ + ATOM hSzBrowser,hSzTopic; + DWORD_PTR dwResult; + char *szItemName; + struct DdeMsgWindowData *dat=(struct DdeMsgWindowData*)GetWindowLongPtr(hwndDdeMsg,0); + + hSzBrowser=GlobalAddAtomA(szBrowser); + hSzTopic=GlobalAddAtomA("WWW_OpenURL"); + dat->fAcked=0; + if(!SendMessageTimeout(HWND_BROADCAST,WM_DDE_INITIATE,(WPARAM)hwndDdeMsg,MAKELPARAM(hSzBrowser,hSzTopic),SMTO_ABORTIFHUNG|SMTO_NORMAL,DDEMESSAGETIMEOUT,&dwResult) + || !dat->fAcked) { + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + return 1; + } + szItemName=(char*)mir_alloc(lstrlenA(szUrl)+7); + wsprintfA(szItemName,"\"%s\",,%d",szUrl,newWindow?0:-1); + if(DoDdeRequest(szItemName,hwndDdeMsg)) { + mir_free(szItemName); + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + return 1; + } + PostMessage(dat->hwndDde,WM_DDE_TERMINATE,(WPARAM)hwndDdeMsg,0); + GlobalDeleteAtom(hSzTopic); + GlobalDeleteAtom(hSzBrowser); + mir_free(szItemName); + return 0; +} + +typedef struct { + char *szUrl; + int newWindow; +} TOpenUrlInfo; + +static void OpenURLThread(void *arg) +{ + TOpenUrlInfo *hUrlInfo = (TOpenUrlInfo*)arg; + char *szResult; + HWND hwndDdeMsg; + struct DdeMsgWindowData msgWndData={0}; + char *pszProtocol; + HKEY hKey; + char szSubkey[80]; + char szCommandName[MAX_PATH]; + DWORD dataLength; + int success=0; + + if (!hUrlInfo->szUrl) return; + hwndDdeMsg=CreateWindow(WNDCLASS_DDEMSGWINDOW,_T(""),0,0,0,0,0,NULL,NULL,hMirandaInst,NULL); + SetWindowLongPtr(hwndDdeMsg,0,(LONG_PTR)&msgWndData); + + if(!_strnicmp(hUrlInfo->szUrl,"ftp:",4) || !_strnicmp(hUrlInfo->szUrl,"ftp.",4)) pszProtocol="ftp"; + if(!_strnicmp(hUrlInfo->szUrl,"mailto:",7)) pszProtocol="mailto"; + if(!_strnicmp(hUrlInfo->szUrl,"news:",5)) pszProtocol="news"; + else pszProtocol="http"; + wsprintfA(szSubkey,"%s\\shell\\open\\command",pszProtocol); + if(RegOpenKeyExA(HKEY_CURRENT_USER,szSubkey,0,KEY_QUERY_VALUE,&hKey)==ERROR_SUCCESS + || RegOpenKeyExA(HKEY_CLASSES_ROOT,szSubkey,0,KEY_QUERY_VALUE,&hKey)==ERROR_SUCCESS) { + dataLength=SIZEOF(szCommandName); + if(RegQueryValueEx(hKey,NULL,NULL,NULL,(PBYTE)szCommandName,&dataLength)==ERROR_SUCCESS) { + _strlwr(szCommandName); + if(strstr(szCommandName,"mozilla") || strstr(szCommandName,"netscape")) + success=(DdeOpenUrl("mozilla",hUrlInfo->szUrl,hUrlInfo->newWindow,hwndDdeMsg)==0 || DdeOpenUrl("netscape",hUrlInfo->szUrl,hUrlInfo->newWindow,hwndDdeMsg)==0); + else if(strstr(szCommandName,"iexplore") || strstr(szCommandName,"msimn")) + success=0==DdeOpenUrl("iexplore",hUrlInfo->szUrl,hUrlInfo->newWindow,hwndDdeMsg); + else if(strstr(szCommandName,"opera")) + success=0==DdeOpenUrl("opera",hUrlInfo->szUrl,hUrlInfo->newWindow,hwndDdeMsg); + //opera's the default anyway + } + RegCloseKey(hKey); + } + + DestroyWindow(hwndDdeMsg); + if(success) return; + + //wack a protocol on it + if((isalpha(hUrlInfo->szUrl[0]) && hUrlInfo->szUrl[1]==':') || hUrlInfo->szUrl[0]=='\\') { + szResult=(char*)mir_alloc(lstrlenA(hUrlInfo->szUrl)+9); + wsprintfA(szResult,"file:///%s",hUrlInfo->szUrl); + } + else { + int i; + for(i=0;isalpha(hUrlInfo->szUrl[i]);i++); + if(hUrlInfo->szUrl[i]==':') szResult=mir_strdup(hUrlInfo->szUrl); + else { + if(!_strnicmp(hUrlInfo->szUrl,"ftp.",4)) { + szResult=(char*)mir_alloc(lstrlenA(hUrlInfo->szUrl)+7); + wsprintfA(szResult,"ftp://%s",hUrlInfo->szUrl); + } + else { + szResult=(char*)mir_alloc(lstrlenA(hUrlInfo->szUrl)+8); + wsprintfA(szResult,"http://%s",hUrlInfo->szUrl); + } + } + } + ShellExecuteA(NULL, "open", szResult, NULL, NULL, SW_SHOWDEFAULT); + mir_free(szResult); + mir_free(hUrlInfo->szUrl); + mir_free(hUrlInfo); + return; +} + +static INT_PTR OpenURL(WPARAM wParam,LPARAM lParam) { + TOpenUrlInfo *hUrlInfo = (TOpenUrlInfo*)mir_alloc(sizeof(TOpenUrlInfo)); + hUrlInfo->szUrl = (char*)lParam?mir_strdup((char*)lParam):NULL; + hUrlInfo->newWindow = (int)wParam; + forkthread(OpenURLThread, 0, (void*)hUrlInfo); + return 0; +} + +int InitOpenUrl(void) +{ + WNDCLASS wcl; + wcl.lpfnWndProc=DdeMessageWindow; + wcl.cbClsExtra=0; + wcl.cbWndExtra=sizeof(void*); + wcl.hInstance=hMirandaInst; + wcl.hCursor=NULL; + wcl.lpszClassName=WNDCLASS_DDEMSGWINDOW; + wcl.hbrBackground=NULL; + wcl.hIcon=NULL; + wcl.lpszMenuName=NULL; + wcl.style=0; + RegisterClass(&wcl); + CreateServiceFunction(MS_UTILS_OPENURL,OpenURL); + return 0; +} diff --git a/src/modules/utils/path.cpp b/src/modules/utils/path.cpp new file mode 100644 index 0000000000..80333a3075 --- /dev/null +++ b/src/modules/utils/path.cpp @@ -0,0 +1,600 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "../database/profilemanager.h" +#include "../srfile/file.h" + +extern TCHAR g_profileDir[MAX_PATH]; + +static char szMirandaPath[MAX_PATH]; +static char szMirandaPathLower[MAX_PATH]; + +static INT_PTR replaceVars(WPARAM wParam, LPARAM lParam); + +static int pathIsAbsolute(const char *path) +{ + if ( strlen(path) <= 2 ) + return 0; + if ((path[1]==':'&&path[2]=='\\')||(path[0]=='\\'&&path[1]=='\\')) + return 1; + return 0; +} + +static INT_PTR pathToRelative(WPARAM wParam, LPARAM lParam) +{ + char *pSrc = (char*)wParam; + char *pOut = (char*)lParam; + if (!pSrc||!strlen(pSrc)||strlen(pSrc)>MAX_PATH) return 0; + if (!pathIsAbsolute(pSrc)) { + mir_snprintf(pOut, MAX_PATH, "%s", pSrc); + return strlen(pOut); + } + else { + char szTmp[MAX_PATH]; + + mir_snprintf(szTmp, SIZEOF(szTmp), "%s", pSrc); + _strlwr(szTmp); + if (strstr(szTmp, szMirandaPathLower)) { + mir_snprintf(pOut, MAX_PATH, "%s", pSrc+strlen(szMirandaPathLower)); + return strlen(pOut); + } + else { + mir_snprintf(pOut, MAX_PATH, "%s", pSrc); + return strlen(pOut); + } + } +} + +int pathToAbsolute(const char *pSrc, char *pOut, char* base) +{ + if ( !pSrc || !strlen( pSrc ) || strlen( pSrc ) > MAX_PATH ) + return 0; + + if ( base == NULL ) + base = szMirandaPath; + + char buf[MAX_PATH]; + if ( pSrc[0] < ' ') + return mir_snprintf( pOut, MAX_PATH, "%s", pSrc ); + else if ( pathIsAbsolute( pSrc )) + return GetFullPathNameA(pSrc, MAX_PATH, pOut, NULL); + else if ( pSrc[0] != '\\' ) + mir_snprintf( buf, MAX_PATH, "%s%s", base, pSrc ); + else + mir_snprintf( buf, MAX_PATH, "%s%s", base, pSrc+1 ); + + return GetFullPathNameA(buf, MAX_PATH, pOut, NULL); +} + +static INT_PTR pathToAbsolute(WPARAM wParam, LPARAM lParam) +{ + return pathToAbsolute((char*)wParam, (char*)lParam, szMirandaPath); +} + +void CreatePathToFile( char* szFilePath ) +{ + char* pszLastBackslash = strrchr( szFilePath, '\\' ); + if ( pszLastBackslash == NULL ) + return; + + *pszLastBackslash = '\0'; + CreateDirectoryTree( szFilePath ); + *pszLastBackslash = '\\'; +} + +int CreateDirectoryTree( const char *szDir ) +{ + DWORD dwAttributes; + char *pszLastBackslash, szTestDir[ MAX_PATH ]; + + lstrcpynA( szTestDir, szDir, SIZEOF( szTestDir )); + if (( dwAttributes = GetFileAttributesA( szTestDir )) != INVALID_FILE_ATTRIBUTES && ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )) + return 0; + + pszLastBackslash = strrchr( szTestDir, '\\' ); + if ( pszLastBackslash == NULL ) + return 0; + + *pszLastBackslash = '\0'; + CreateDirectoryTree( szTestDir ); + *pszLastBackslash = '\\'; + return ( CreateDirectoryA( szTestDir, NULL ) == 0 ) ? GetLastError() : 0; +} + +static INT_PTR createDirTree(WPARAM, LPARAM lParam) +{ + if ( lParam == 0 ) + return 1; + + return CreateDirectoryTree(( char* )lParam ); +} + +#ifdef _UNICODE +static TCHAR szMirandaPathW[MAX_PATH]; +static TCHAR szMirandaPathWLower[MAX_PATH]; + +static int pathIsAbsoluteW(const TCHAR *path) +{ + if ( lstrlen(path) <= 2 ) + return 0; + if ((path[1]==':'&&path[2]=='\\')||(path[0]=='\\'&&path[1]=='\\')) + return 1; + return 0; +} + +static INT_PTR pathToRelativeW(WPARAM wParam, LPARAM lParam) +{ + TCHAR *pSrc = (TCHAR*)wParam; + TCHAR *pOut = (TCHAR*)lParam; + if ( !pSrc || !lstrlen(pSrc) || lstrlen(pSrc) > MAX_PATH ) + return 0; + + if ( !pathIsAbsoluteW( pSrc )) + mir_sntprintf(pOut, MAX_PATH, _T("%s"), pSrc); + else { + TCHAR szTmp[MAX_PATH]; + + mir_sntprintf(szTmp, SIZEOF(szTmp), _T("%s"), pSrc); + _tcslwr(szTmp); + if (_tcsstr(szTmp, szMirandaPathWLower)) + mir_sntprintf(pOut, MAX_PATH, _T("%s"), pSrc+lstrlen(szMirandaPathWLower)); + else + mir_sntprintf(pOut, MAX_PATH, _T("%s"), pSrc); + } + return lstrlen(pOut); +} + +int pathToAbsoluteW(const TCHAR *pSrc, TCHAR *pOut, TCHAR* base) +{ + if ( !pSrc || !wcslen(pSrc) || wcslen(pSrc) > MAX_PATH) + return 0; + + if ( base == NULL ) + base = szMirandaPathW; + + TCHAR buf[MAX_PATH]; + if ( pSrc[0] < ' ') + return mir_sntprintf( pOut, MAX_PATH, _T("%s"), pSrc ); + else if ( pathIsAbsoluteW( pSrc )) + return GetFullPathName(pSrc, MAX_PATH, pOut, NULL); + else if ( pSrc[0] != '\\' ) + mir_sntprintf( buf, MAX_PATH, _T("%s%s"), base, pSrc ); + else + mir_sntprintf( buf, MAX_PATH, _T("%s%s"), base, pSrc+1 ); + + return GetFullPathName(buf, MAX_PATH, pOut, NULL); +} + +static INT_PTR pathToAbsoluteW(WPARAM wParam, LPARAM lParam) +{ + return pathToAbsoluteW((TCHAR*)wParam, (TCHAR*)lParam, szMirandaPathW); +} + +void CreatePathToFileW( WCHAR* wszFilePath ) +{ + WCHAR* pszLastBackslash = wcsrchr( wszFilePath, '\\' ); + if ( pszLastBackslash == NULL ) + return; + + *pszLastBackslash = '\0'; + CreateDirectoryTreeW( wszFilePath ); + *pszLastBackslash = '\\'; +} + +int CreateDirectoryTreeW( const WCHAR* szDir ) +{ + DWORD dwAttributes; + WCHAR* pszLastBackslash, szTestDir[ MAX_PATH ]; + + lstrcpynW( szTestDir, szDir, SIZEOF( szTestDir )); + if (( dwAttributes = GetFileAttributesW( szTestDir )) != INVALID_FILE_ATTRIBUTES && ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )) + return 0; + + pszLastBackslash = wcsrchr( szTestDir, '\\' ); + if ( pszLastBackslash == NULL ) + return 0; + + *pszLastBackslash = '\0'; + CreateDirectoryTreeW( szTestDir ); + *pszLastBackslash = '\\'; + return ( CreateDirectoryW( szTestDir, NULL ) == 0 ) ? GetLastError() : 0; +} + +static INT_PTR createDirTreeW(WPARAM, LPARAM lParam) +{ + if ( lParam == 0 ) + return 1; + + return CreateDirectoryTreeW(( WCHAR* )lParam ); +} + +int InitPathUtilsW(void) +{ + GetModuleFileName(hMirandaInst, szMirandaPathW, SIZEOF(szMirandaPathW)); + TCHAR *p = _tcsrchr(szMirandaPathW,'\\'); + if ( p ) + p[1] = 0; + mir_sntprintf(szMirandaPathWLower, SIZEOF(szMirandaPathWLower), _T("%s"), szMirandaPathW); + _tcslwr(szMirandaPathWLower); + CreateServiceFunction(MS_UTILS_PATHTORELATIVEW, pathToRelativeW); + CreateServiceFunction(MS_UTILS_PATHTOABSOLUTEW, pathToAbsoluteW); + CreateServiceFunction(MS_UTILS_CREATEDIRTREEW, createDirTreeW); + return 0; +} +#endif + +TCHAR *GetContactID(HANDLE hContact) +{ + TCHAR *theValue = {0}; + char *szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (DBGetContactSettingByte(hContact, szProto, "ChatRoom", 0) == 1) { + DBVARIANT dbv; + if (!DBGetContactSettingTString(hContact, szProto, "ChatRoomID", &dbv)) { + theValue = (TCHAR *)mir_tstrdup(dbv.ptszVal); + DBFreeVariant(&dbv); + return theValue; + } } + else { + CONTACTINFO ci = {0}; + ci.cbSize = sizeof(ci); + ci.hContact = hContact; + ci.szProto = szProto; + ci.dwFlag = CNF_UNIQUEID | CNF_TCHAR; + if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) { + switch (ci.type) { + case CNFT_ASCIIZ: + return (TCHAR *)ci.pszVal; + break; + case CNFT_DWORD: + return _itot(ci.dVal, (TCHAR *)mir_alloc(sizeof(TCHAR)*32), 10); + break; + } } } + return NULL; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Variables parser + +#define XSTR(target, s) _xstrselect(target, s, _T(s)) + +static __forceinline int _xcscmp(const char *s1, const char *s2) { return strcmp(s1, s2); } +static __forceinline int _xcsncmp(const char *s1, const char *s2, size_t n) { return strncmp(s1, s2, n); } +static __forceinline size_t _xcslen(const char *s1) { return strlen(s1); } +static __forceinline char *_xcscpy(char *s1, const char *s2) { return strcpy(s1, s2); } +static __forceinline char *_xcsncpy(char *s1, const char *s2, size_t n) { return strncpy(s1, s2, n); } +static __forceinline char *_xstrselect(char *, char *s1, TCHAR *s2) { return s1; } +static __forceinline char *_itox(char *, int a) { return itoa(a, (char *)mir_alloc(sizeof(char)*20), 10); } +static __forceinline char *mir_a2x(char *, char *s) { return mir_strdup(s); } +static __forceinline char *GetContactNickX(char *, HANDLE hContact) +{ + return mir_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0)); +} +static __forceinline char *GetContactIDX(char *, HANDLE hContact) +{ + TCHAR *id = GetContactID(hContact); + char* res = mir_t2a(id); + mir_free(id); + return res; +} +static __forceinline char *GetEnvironmentVariableX(char *variable) +{ + char result[512]; + if (GetEnvironmentVariableA(variable, result, SIZEOF(result))) + return mir_strdup(result); + return NULL; +} +static __forceinline char *GetProfileDirX( char* ) +{ + return mir_t2a( g_profileDir ); +} +static __forceinline char *SHGetSpecialFolderPathX(int iCSIDL, char* var) +{ + char result[512]; + if (shGetSpecialFolderPathA && shGetSpecialFolderPathA(NULL, result, iCSIDL, FALSE)) + return mir_strdup(result); + return NULL; +} +static __forceinline char *GetModulePathX(char *, HMODULE hModule) +{ + char result[MAX_PATH]; + GetModuleFileNameA(hModule, result, sizeof(result)); + char* str = strrchr(result, '\\'); + if (str) *str = 0; + return mir_strdup(result); +} +static __forceinline char *GetUserNameX(char *) +{ + char result[128]; + DWORD size = SIZEOF(result); + if (GetUserNameA(result, &size)) + return mir_strdup(result); + return NULL; +} +static __forceinline char *GetProfileNameX(char *) +{ + TCHAR szProfileName[MAX_PATH]; + _tcscpy( szProfileName, g_profileName ); + TCHAR *pos = _tcsrchr(szProfileName, '.'); + if ( lstrcmp( pos, _T(".dat")) == 0 ) + *pos = 0; + return mir_t2a( szProfileName ); +} +static __forceinline char *GetPathVarX(char *, int code) +{ + TCHAR szFullPath[MAX_PATH], szProfileName[MAX_PATH]; + _tcscpy( szProfileName, g_profileName ); + _tcslwr( szProfileName ); + TCHAR *pos = _tcsrchr(szProfileName, '.'); + if ( lstrcmp( pos, _T(".dat")) == 0 ) + *pos = 0; + + switch( code ) { + case 1: + mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\AvatarCache"), g_profileDir, szProfileName); + break; + case 2: + mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\Logs"), g_profileDir, szProfileName); + break; + case 3: + mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s"), g_profileDir, szProfileName); + break; + } + return makeFileName( szFullPath ); +} + +#ifdef _UNICODE +static __forceinline int _xcscmp(const TCHAR *s1, const TCHAR *s2) { return _tcscmp(s1, s2); } +static __forceinline int _xcsncmp(const TCHAR *s1, const TCHAR *s2, size_t n) { return _tcsncmp(s1, s2, n); } +static __forceinline size_t _xcslen(const TCHAR *s1) { return _tcslen(s1); } +static __forceinline TCHAR *_xcscpy(TCHAR *s1, const TCHAR *s2) { return _tcscpy(s1, s2); } +static __forceinline TCHAR *_xcsncpy(TCHAR *s1, const TCHAR *s2, size_t n) { return _tcsncpy(s1, s2, n); } +static __forceinline TCHAR *_xstrselect(TCHAR *, char *s1, TCHAR *s2) { return s2; } +static __forceinline TCHAR *_itox(TCHAR *, int a) { return _itot(a, (TCHAR *)mir_alloc(sizeof(TCHAR)*20), 10); } +static __forceinline TCHAR *mir_a2x(TCHAR *, char *s) { return mir_a2t(s); } +static __forceinline TCHAR *GetContactNickX(TCHAR *, HANDLE hContact) +{ + return mir_tstrdup((TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR)); +} +static __forceinline TCHAR *GetContactIDX(TCHAR *, HANDLE hContact) +{ + return GetContactID(hContact); +} +static __forceinline TCHAR *GetEnvironmentVariableX(TCHAR *variable) +{ + TCHAR result[512]; + if (GetEnvironmentVariable(variable, result, SIZEOF(result))) + return mir_tstrdup(result); + return NULL; +} +static __forceinline TCHAR *SHGetSpecialFolderPathX(int iCSIDL, TCHAR* var) +{ + TCHAR result[512]; + if (shGetSpecialFolderPath && shGetSpecialFolderPath(NULL, result, iCSIDL, FALSE)) + return mir_tstrdup(result); + return NULL; +} +static __forceinline TCHAR *GetProfileDirX( TCHAR* ) +{ + return mir_tstrdup( g_profileDir ); +} +static __forceinline TCHAR *GetModulePathX(TCHAR *, HMODULE hModule) +{ + TCHAR result[MAX_PATH]; + GetModuleFileName(hModule, result, SIZEOF(result)); + TCHAR* str = _tcsrchr(result, '\\'); + if (str) *str = 0; + return mir_tstrdup(result); +} +static __forceinline TCHAR *GetUserNameX(TCHAR *) +{ + TCHAR result[128]; + DWORD size = SIZEOF(result); + if (GetUserName(result, &size)) + return mir_tstrdup(result); + return NULL; +} +static __forceinline TCHAR *GetProfileNameX(TCHAR *) +{ + TCHAR szProfileName[MAX_PATH]; + _tcscpy( szProfileName, g_profileName ); + TCHAR *pos = _tcsrchr(szProfileName, '.'); + if ( lstrcmp( pos, _T(".dat")) == 0 ) + *pos = 0; + return mir_tstrdup( szProfileName ); +} +static __forceinline TCHAR *GetPathVarX(TCHAR *, int code) +{ + TCHAR szFullPath[MAX_PATH], szProfileName[MAX_PATH]; + _tcscpy( szProfileName, g_profileName ); + TCHAR *pos = _tcsrchr(szProfileName, '.'); + if ( lstrcmp( pos, _T(".dat")) == 0 ) + *pos = 0; + + switch( code ) { + case 1: + mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\AvatarCache"), g_profileDir, szProfileName); + break; + case 2: + mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\Logs"), g_profileDir, szProfileName); + break; + case 3: + mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s"), g_profileDir, szProfileName); + break; + } + return mir_tstrdup( szFullPath ); +} +#endif + +template +XCHAR *GetInternalVariable(XCHAR *key, size_t keyLength, HANDLE hContact) +{ + XCHAR *theValue = NULL; + XCHAR *theKey = (XCHAR *)_alloca(sizeof(XCHAR) * (keyLength + 1)); + _xcsncpy(theKey, key, keyLength); + theKey[keyLength] = 0; + + if (hContact) { + if (!_xcscmp(theKey, XSTR(key, "nick"))) + theValue = GetContactNickX(key, hContact); + else if (!_xcscmp(theKey, XSTR(key, "proto"))) + theValue = mir_a2x(key, (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact,0)); + else if (!_xcscmp(theKey, XSTR(key, "userid"))) + theValue = GetContactIDX(key, hContact); + } + + if (!theValue) { + if (!_xcscmp(theKey, XSTR(key, "miranda_path"))) + theValue = GetModulePathX(key, NULL); + else if (!_xcscmp(theKey, XSTR(key, "appdata"))) + theValue = SHGetSpecialFolderPathX(CSIDL_APPDATA, theKey); + else if (!_xcscmp(theKey, XSTR(key, "mydocuments"))) + theValue = SHGetSpecialFolderPathX(CSIDL_PERSONAL, theKey); + else if (!_xcscmp(theKey, XSTR(key, "desktop"))) + theValue = SHGetSpecialFolderPathX(CSIDL_DESKTOPDIRECTORY, theKey); + else if (!_xcscmp(theKey, XSTR(key, "miranda_profile"))) + theValue = GetProfileDirX(key); + else if (!_xcscmp(theKey, XSTR(key, "miranda_profilename"))) + theValue = GetProfileNameX(key); + else if (!_xcscmp(theKey, XSTR(key, "username"))) + theValue = GetUserNameX(key); + else if (!_xcscmp(theKey, XSTR(key, "miranda_avatarcache"))) + theValue = GetPathVarX(key,1); + else if (!_xcscmp(theKey, XSTR(key, "miranda_logpath"))) + theValue = GetPathVarX(key,2); + else if (!_xcscmp(theKey, XSTR(key, "miranda_userdata"))) + theValue = GetPathVarX(key,3); + } + + if (!theValue) + theValue = GetEnvironmentVariableX(theKey); + + return theValue; +} + +template +XCHAR *GetVariableFromArray(REPLACEVARSARRAY *vars, XCHAR *key, size_t keyLength, HANDLE hContact, bool *bFree) +{ + *bFree = false; + for (REPLACEVARSARRAY *var = vars; var && var->lptzKey; ++var) + if ((_xcslen((XCHAR *)var->lptzKey) == keyLength) && !_xcsncmp(key, (XCHAR *)var->lptzKey, keyLength)) + return (XCHAR *)var->lptzValue; + + *bFree = true; + return GetInternalVariable(key, keyLength, hContact); +} + +template +XCHAR *ReplaceVariables(XCHAR *str, REPLACEVARSDATA *data) +{ + if (!str) + return NULL; + + XCHAR *p; + XCHAR *varStart = 0; + size_t length = 0; + bool bFree; + + for (p = str; *p; ++p) { + if (*p == '%') { + if (varStart) { + if (p == varStart) + length++; + else if (XCHAR *value = GetVariableFromArray(data->variables, varStart, p-varStart, data->hContact, &bFree)) { + length += _xcslen(value); + if (bFree) mir_free(value); + } + else // variable not found + length += p-varStart+2; + + varStart = 0; + } + else varStart = p+1; + } + else if (!varStart) + length++; + } + + XCHAR *result = (XCHAR *)mir_alloc(sizeof(XCHAR) * (length + 1)); + XCHAR *q = result; + varStart = NULL; + + for (p = str; *p; ++p) { + if (*p == '%') { + if (varStart) { + if (p == varStart) + *q++ = '%'; + else if (XCHAR *value = GetVariableFromArray(data->variables, varStart, p-varStart, data->hContact, &bFree)) { + _xcscpy(q, value); + q += _xcslen(value); + if (bFree) mir_free(value); + } + else { + // variable not found + _xcsncpy(q, varStart-1, p-varStart+2); + q += p-varStart+2; + } + varStart = 0; + } + else varStart = p+1; + } + else if (!varStart) + *q++ = *p; + } + + *q = 0; + + return result; +} + +static INT_PTR replaceVars(WPARAM wParam, LPARAM lParam) +{ + REPLACEVARSDATA *data = (REPLACEVARSDATA *)lParam; + if (!(data->dwFlags & RVF_UNICODE)) + return (INT_PTR)ReplaceVariables((char *)wParam, data); + +#ifdef _UNICODE + return (INT_PTR)ReplaceVariables((WCHAR *)wParam, data); +#else + return NULL; +#endif +} + +int InitPathUtils(void) +{ + char *p = 0; + GetModuleFileNameA(hMirandaInst, szMirandaPath, SIZEOF(szMirandaPath)); + p = strrchr(szMirandaPath,'\\'); + if ( p ) + p[1] = 0; + mir_snprintf(szMirandaPathLower, MAX_PATH, "%s", szMirandaPath); + _strlwr(szMirandaPathLower); + CreateServiceFunction(MS_UTILS_PATHTORELATIVE, pathToRelative); + CreateServiceFunction(MS_UTILS_PATHTOABSOLUTE, pathToAbsolute); + CreateServiceFunction(MS_UTILS_CREATEDIRTREE, createDirTree); + CreateServiceFunction(MS_UTILS_REPLACEVARS, replaceVars); +#ifdef _UNICODE + return InitPathUtilsW(); +#else + return 0; +#endif +} diff --git a/src/modules/utils/resizer.cpp b/src/modules/utils/resizer.cpp new file mode 100644 index 0000000000..ea0daa057a --- /dev/null +++ b/src/modules/utils/resizer.cpp @@ -0,0 +1,152 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +typedef struct { + DWORD helpID; + DWORD exStyle; + DWORD style; + short x; + short y; + short cx; + short cy; + DWORD id; +} START_OF_DLGITEMTEMPLATEEX; + +typedef struct { + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; +} START_OF_DLGTEMPLATEEX; + +INT_PTR ResizeDialog(WPARAM, LPARAM lParam) +{ + UTILRESIZEDIALOG *urd=(UTILRESIZEDIALOG*)lParam; + HDWP hDwp; + int i; + DLGITEMTEMPLATE *pItem = NULL; + START_OF_DLGITEMTEMPLATEEX *pItemEx = NULL; + RECT rc; + PWORD pWord; + DLGTEMPLATE *pTemplate; + START_OF_DLGTEMPLATEEX *pTemplateEx; + UTILRESIZECONTROL urc; + int procResult; + int extendedDlg,itemCount; + + if(urd==NULL||urd->cbSize!=sizeof(UTILRESIZEDIALOG)) return 1; + pTemplate=(DLGTEMPLATE*)LockResource(LoadResource(urd->hInstance,FindResourceA(urd->hInstance,urd->lpTemplate,MAKEINTRESOURCEA(5)))); + pTemplateEx=(START_OF_DLGTEMPLATEEX*)pTemplate; + extendedDlg=pTemplateEx->signature==0xFFFF; + if(extendedDlg && pTemplateEx->dlgVer!=1) + return 1; + + if(extendedDlg) pWord=(PWORD)(pTemplateEx+1); + else pWord=(PWORD)(pTemplate+1); + if(*pWord==0xFFFF) pWord+=2; else while(*pWord++); //menu + if(*pWord==0xFFFF) pWord+=2; else while(*pWord++); //class + while(*pWord++); //title + if(extendedDlg) { + if(pTemplateEx->style&DS_SETFONT) { + pWord+=3; //font size,weight,italic + while(*pWord++); //font name + } + } + else { + if(pTemplate->style&DS_SETFONT) { + pWord++; //font size + while(*pWord++); //font name + } + } + + urc.cbSize=sizeof(UTILRESIZECONTROL); + rc.left=0; rc.top=0; + if(extendedDlg) {rc.right=pTemplateEx->cx; rc.bottom=pTemplateEx->cy;} + else {rc.right=pTemplate->cx; rc.bottom=pTemplate->cy;} + MapDialogRect(urd->hwndDlg,&rc); + urc.dlgOriginalSize.cx=rc.right; urc.dlgOriginalSize.cy=rc.bottom; + GetClientRect(urd->hwndDlg,&rc); + urc.dlgNewSize.cx=rc.right; urc.dlgNewSize.cy=rc.bottom; + + if(extendedDlg) itemCount=pTemplateEx->cDlgItems; + else itemCount=pTemplate->cdit; + hDwp=BeginDeferWindowPos(itemCount); + for(i=0;iid; + urc.rcItem.left=pItemEx->x; urc.rcItem.top=pItemEx->y; + urc.rcItem.right=urc.rcItem.left+pItemEx->cx; urc.rcItem.bottom=urc.rcItem.top+pItemEx->cy; + } + else { + pItem=(DLGITEMTEMPLATE*)pWord; + pWord=(PWORD)(pItem+1); + + urc.wId=pItem->id; + urc.rcItem.left=pItem->x; urc.rcItem.top=pItem->y; + urc.rcItem.right=urc.rcItem.left+pItem->cx; urc.rcItem.bottom=urc.rcItem.top+pItem->cy; + } + if(*pWord==0xFFFF) pWord+=2; else while(*pWord++); //menu + if(*pWord==0xFFFF) pWord+=2; else while(*pWord++); //class + pWord+=1+(1+*pWord)/2; //creation data + + if(urc.wId==65535) continue; //using this breaks the dwp, so just ignore it + + MapDialogRect(urd->hwndDlg,&urc.rcItem); + procResult=(urd->pfnResizer)(urd->hwndDlg,urd->lParam,&urc); + if(procResult&RD_ANCHORX_RIGHT) { + urc.rcItem.left+=urc.dlgNewSize.cx-urc.dlgOriginalSize.cx; + urc.rcItem.right+=urc.dlgNewSize.cx-urc.dlgOriginalSize.cx; + } + else if(procResult&RD_ANCHORX_WIDTH) + urc.rcItem.right+=urc.dlgNewSize.cx-urc.dlgOriginalSize.cx; + else if(procResult&RD_ANCHORX_CENTRE) { + urc.rcItem.left+=(urc.dlgNewSize.cx-urc.dlgOriginalSize.cx)/2; + urc.rcItem.right+=(urc.dlgNewSize.cx-urc.dlgOriginalSize.cx)/2; + } + if(procResult&RD_ANCHORY_BOTTOM) { + urc.rcItem.top+=urc.dlgNewSize.cy-urc.dlgOriginalSize.cy; + urc.rcItem.bottom+=urc.dlgNewSize.cy-urc.dlgOriginalSize.cy; + } + else if(procResult&RD_ANCHORY_HEIGHT) + urc.rcItem.bottom+=urc.dlgNewSize.cy-urc.dlgOriginalSize.cy; + else if(procResult&RD_ANCHORY_CENTRE) { + urc.rcItem.top+=(urc.dlgNewSize.cy-urc.dlgOriginalSize.cy)/2; + urc.rcItem.bottom+=(urc.dlgNewSize.cy-urc.dlgOriginalSize.cy)/2; + } + hDwp = DeferWindowPos(hDwp,GetDlgItem(urd->hwndDlg,extendedDlg?pItemEx->id:pItem->id),0,urc.rcItem.left,urc.rcItem.top,urc.rcItem.right-urc.rcItem.left,urc.rcItem.bottom-urc.rcItem.top,SWP_NOZORDER); + } + EndDeferWindowPos(hDwp); + return 0; +} diff --git a/src/modules/utils/sha1.cpp b/src/modules/utils/sha1.cpp new file mode 100644 index 0000000000..c89a60ca46 --- /dev/null +++ b/src/modules/utils/sha1.cpp @@ -0,0 +1,175 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is SHA 180-1 Reference Implementation (Compact version). + * + * The Initial Developer of the Original Code is + * Paul Kocher of Cryptography Research. + * Portions created by the Initial Developer are Copyright (C) 1995-9 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "commonheaders.h" + +static void shaHashBlock(mir_sha1_ctx *ctx); + +void shaInit(mir_sha1_ctx *ctx) { + int i; + + ctx->lenW = 0; + ctx->sizeHi = ctx->sizeLo = 0; + + /* Initialize H with the magic constants (see FIPS180 for constants) + */ + ctx->H[0] = 0x67452301L; + ctx->H[1] = 0xefcdab89L; + ctx->H[2] = 0x98badcfeL; + ctx->H[3] = 0x10325476L; + ctx->H[4] = 0xc3d2e1f0L; + + for (i = 0; i < 80; i++) + ctx->W[i] = 0; +} + + +void shaUpdate(mir_sha1_ctx *ctx, mir_sha1_byte_t *dataIn, int len) { + int i; + + /* Read the data into W and process blocks as they get full + */ + for (i = 0; i < len; i++) { + ctx->W[ctx->lenW / 4] <<= 8; + ctx->W[ctx->lenW / 4] |= (unsigned long)dataIn[i]; + if ((++ctx->lenW) % 64 == 0) { + shaHashBlock(ctx); + ctx->lenW = 0; + } + ctx->sizeLo += 8; + ctx->sizeHi += (ctx->sizeLo < 8); + } +} + + +void shaFinal(mir_sha1_ctx *ctx, mir_sha1_byte_t hashout[20]) { + unsigned char pad0x80 = 0x80; + unsigned char pad0x00 = 0x00; + unsigned char padlen[8]; + int i; + + /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length + */ + padlen[0] = (unsigned char)((ctx->sizeHi >> 24) & 255); + padlen[1] = (unsigned char)((ctx->sizeHi >> 16) & 255); + padlen[2] = (unsigned char)((ctx->sizeHi >> 8) & 255); + padlen[3] = (unsigned char)((ctx->sizeHi >> 0) & 255); + padlen[4] = (unsigned char)((ctx->sizeLo >> 24) & 255); + padlen[5] = (unsigned char)((ctx->sizeLo >> 16) & 255); + padlen[6] = (unsigned char)((ctx->sizeLo >> 8) & 255); + padlen[7] = (unsigned char)((ctx->sizeLo >> 0) & 255); + shaUpdate(ctx, &pad0x80, 1); + while (ctx->lenW != 56) + shaUpdate(ctx, &pad0x00, 1); + shaUpdate(ctx, padlen, 8); + + /* Output hash + */ + for (i = 0; i < 20; i++) { + hashout[i] = (unsigned char)(ctx->H[i / 4] >> 24); + ctx->H[i / 4] <<= 8; + } + + /* + * Re-initialize the context (also zeroizes contents) + */ + shaInit(ctx); +} + + +void shaBlock(mir_sha1_byte_t *dataIn, int len, mir_sha1_byte_t hashout[20]) { + mir_sha1_ctx ctx; + + shaInit(&ctx); + shaUpdate(&ctx, dataIn, len); + shaFinal(&ctx, hashout); +} + + +#define SHA_ROTL(X,n) (((X) << (n)) | ((X) >> (32-(n)))) + +static void shaHashBlock(mir_sha1_ctx *ctx) { + int t; + unsigned long A,B,C,D,E,TEMP; + + for (t = 16; t <= 79; t++) + ctx->W[t] = + SHA_ROTL(ctx->W[t-3] ^ ctx->W[t-8] ^ ctx->W[t-14] ^ ctx->W[t-16], 1); + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + for (t = 0; t <= 19; t++) { + TEMP = SHA_ROTL(A,5) + (((C^D)&B)^D) + E + ctx->W[t] + 0x5a827999L; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (t = 20; t <= 39; t++) { + TEMP = SHA_ROTL(A,5) + (B^C^D) + E + ctx->W[t] + 0x6ed9eba1L; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (t = 40; t <= 59; t++) { + TEMP = SHA_ROTL(A,5) + ((B&C)|(D&(B|C))) + E + ctx->W[t] + 0x8f1bbcdcL; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + for (t = 60; t <= 79; t++) { + TEMP = SHA_ROTL(A,5) + (B^C^D) + E + ctx->W[t] + 0xca62c1d6L; + E = D; D = C; C = SHA_ROTL(B, 30); B = A; A = TEMP; + } + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; +} + +INT_PTR GetSHA1Interface(WPARAM, LPARAM lParam) +{ + struct SHA1_INTERFACE *sha1i = (struct SHA1_INTERFACE*) lParam; + if ( sha1i == NULL ) + return 1; + if ( sha1i->cbSize != sizeof( struct SHA1_INTERFACE )) + return 1; + + sha1i->sha1_init = shaInit; + sha1i->sha1_append = shaUpdate; + sha1i->sha1_finish = shaFinal; + sha1i->sha1_hash = shaBlock; + return 0; +} diff --git a/src/modules/utils/timeutils.cpp b/src/modules/utils/timeutils.cpp new file mode 100644 index 0000000000..a0d4c02e46 --- /dev/null +++ b/src/modules/utils/timeutils.cpp @@ -0,0 +1,277 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. + +implements services to handle location - based timezones, instead of +simple UTC offsets. +*/ + +#include "commonheaders.h" + +//KB167296 +void UnixTimeToFileTime(time_t ts, LPFILETIME pft) +{ + unsigned __int64 ll = UInt32x32To64(ts, 10000000) + 116444736000000000i64; + pft->dwLowDateTime = (DWORD)ll; + pft->dwHighDateTime = ll >> 32; +} + +time_t FileTimeToUnixTime(LPFILETIME pft) +{ + unsigned __int64 ll = (unsigned __int64)pft->dwHighDateTime << 32 | pft->dwLowDateTime; + ll -= 116444736000000000i64; + return (time_t)(ll / 10000000); +} + +void FormatTime(const SYSTEMTIME *st, const TCHAR *szFormat, TCHAR *szDest, int cbDest) +{ + if (szDest == NULL || cbDest == 0) return; + + TCHAR *pDest = szDest; + int destCharsLeft = cbDest - 1; + + for (const TCHAR* pFormat = szFormat; *pFormat; ++pFormat) + { + DWORD fmt; + bool date, iso = false; + switch (*pFormat) + { + case 't': + fmt = TIME_NOSECONDS; + date = false; + break; + + case 's': + fmt = 0; + date = false; + break; + + case 'm': + fmt = TIME_NOMINUTESORSECONDS; + date = false; + break; + + case 'd': + fmt = DATE_SHORTDATE; + date = true; + break; + + case 'D': + fmt = DATE_LONGDATE; + date = true; + break; + + case 'I': + iso = true; + break; + + default: + if (destCharsLeft--) + *pDest++ = *pFormat; + continue; + } + + TCHAR dateTimeStr[64]; + int dateTimeStrLen; + + if (iso) + { + dateTimeStrLen = mir_sntprintf(dateTimeStr, SIZEOF(dateTimeStr), + _T("%d-%02d-%02dT%02d:%02d:%02dZ"), + st->wYear, st->wMonth, st->wDay, + st->wHour, st->wMinute, st->wSecond) + 1; + } + else if (date) + dateTimeStrLen = GetDateFormat(LOCALE_USER_DEFAULT, fmt, st, NULL, + dateTimeStr, SIZEOF(dateTimeStr)); + else + dateTimeStrLen = GetTimeFormat(LOCALE_USER_DEFAULT, fmt, st, NULL, + dateTimeStr, SIZEOF(dateTimeStr)); + + if (dateTimeStrLen) --dateTimeStrLen; + if (destCharsLeft < dateTimeStrLen) dateTimeStrLen = destCharsLeft; + memcpy(pDest, dateTimeStr, dateTimeStrLen * sizeof(dateTimeStr[0])); + destCharsLeft -= dateTimeStrLen; + pDest += dateTimeStrLen; + } + *pDest = 0; +} + + +#ifndef _UNICODE +void ConvertToAbsolute (const SYSTEMTIME * pstLoc, const SYSTEMTIME * pstDst, SYSTEMTIME * pstDstAbs) +{ + static int iDays [12] = { 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 } ; + int iDay ; + + // Set up the aboluste date structure except for wDay, which we must find + + pstDstAbs->wYear = pstLoc->wYear ; // Notice from local date/time + pstDstAbs->wMonth = pstDst->wMonth ; + pstDstAbs->wDayOfWeek = pstDst->wDayOfWeek ; + + pstDstAbs->wHour = pstDst->wHour ; + pstDstAbs->wMinute = pstDst->wMinute ; + pstDstAbs->wSecond = pstDst->wSecond ; + pstDstAbs->wMilliseconds = pstDst->wMilliseconds ; + + // Fix the iDays array for leap years + + if ((pstLoc->wYear % 4 == 0) && ((pstLoc->wYear % 100 != 0) || + (pstLoc->wYear % 400 == 0))) + { + iDays[1] = 29 ; + } + + // Find a day of the month that falls on the same + // day of the week as the transition. + + // Suppose today is the 20th of the month (pstLoc->wDay = 20) + // Suppose today is a Wednesday (pstLoc->wDayOfWeek = 3) + // Suppose the transition occurs on a Friday (pstDst->wDayOfWeek = 5) + // Then iDay = 31, meaning that the 31st falls on a Friday + // (The 7 is this formula avoids negatives.) + + iDay = pstLoc->wDay + pstDst->wDayOfWeek + 7 - pstLoc->wDayOfWeek ; + + // Now shrink iDay to a value between 1 and 7. + + iDay = (iDay - 1) % 7 + 1 ; + + // Now iDay is a day of the month ranging from 1 to 7. + // Recall that the wDay field of the structure can range + // from 1 to 5, 1 meaning "first", 2 meaning "second", + // and 5 meaning "last". + // So, increase iDay so it's the proper day of the month. + + iDay += 7 * (pstDst->wDay - 1) ; + + // Could be that iDay overshot the end of the month, so + // fix it up using the number of days in each month + + if (iDay > iDays[pstDst->wMonth - 1]) + iDay -= 7 ; + + // Assign that day to the structure. + + pstDstAbs->wDay = iDay ; +} + +BOOL LocalGreaterThanTransition (const SYSTEMTIME * pstLoc, const SYSTEMTIME * pstTran) +{ + FILETIME ftLoc, ftTran ; + LARGE_INTEGER liLoc, liTran ; + SYSTEMTIME stTranAbs ; + + // Easy case: Just compare the two months + + if (pstLoc->wMonth != pstTran->wMonth) + return (pstLoc->wMonth > pstTran->wMonth) ; + + // Well, we're in a transition month. That requires a bit more work. + + // Check if pstDst is in absolute or day-in-month format. + // (See documentation of TIME_ZONE_INFORMATION, StandardDate field.) + + if (pstTran->wYear) // absolute format (haven't seen one yet!) + { + stTranAbs = * pstTran ; + } + else // day-in-month format + { + ConvertToAbsolute (pstLoc, pstTran, &stTranAbs) ; + } + + // Now convert both date/time structures to large integers & compare + + SystemTimeToFileTime (pstLoc, &ftLoc) ; + liLoc = * (LARGE_INTEGER *) (void *) &ftLoc ; + + SystemTimeToFileTime (&stTranAbs, &ftTran) ; + liTran = * (LARGE_INTEGER *) (void *) &ftTran ; + + return (liLoc.QuadPart > liTran.QuadPart) ; +} + +BOOL MySystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION ptzi, LPSYSTEMTIME pstUtc, LPSYSTEMTIME pstLoc) +{ + // st is UTC + + FILETIME ft ; + LARGE_INTEGER li ; + SYSTEMTIME stDst ; + + if (IsWinVerNT()) + return SystemTimeToTzSpecificLocalTime(ptzi, pstUtc, pstLoc); + + // Convert time to a LARGE_INTEGER and subtract the bias + + SystemTimeToFileTime (pstUtc, &ft) ; + li = * (LARGE_INTEGER *) (void *) &ft; + li.QuadPart -= (LONGLONG) 600000000 * ptzi->Bias ; + + // Convert to a local date/time before application of daylight saving time. + // The local date/time must be used to determine when the conversion occurs. + + ft = * (FILETIME *) (void *) &li ; + FileTimeToSystemTime (&ft, pstLoc) ; + + // Find the time assuming Daylight Saving Time + + li.QuadPart -= (LONGLONG) 600000000 * ptzi->DaylightBias ; + ft = * (FILETIME *) (void *) &li ; + FileTimeToSystemTime (&ft, &stDst) ; + + // Now put li back the way it was + + li.QuadPart += (LONGLONG) 600000000 * ptzi->DaylightBias ; + + if (ptzi->StandardDate.wMonth) // ie, daylight savings time + { + // Northern hemisphere + if ((ptzi->DaylightDate.wMonth < ptzi->StandardDate.wMonth) && + + (stDst.wMonth >= pstLoc->wMonth) && // avoid the end of year problem + + LocalGreaterThanTransition (pstLoc, &ptzi->DaylightDate) && + !LocalGreaterThanTransition (&stDst, &ptzi->StandardDate)) + { + li.QuadPart -= (LONGLONG) 600000000 * ptzi->DaylightBias ; + } + // Southern hemisphere + + else if ((ptzi->StandardDate.wMonth < ptzi->DaylightDate.wMonth) && + (!LocalGreaterThanTransition (&stDst, &ptzi->StandardDate) || + LocalGreaterThanTransition (pstLoc, &ptzi->DaylightDate))) + { + li.QuadPart -= (LONGLONG) 600000000 * ptzi->DaylightBias ; + } + else + { + li.QuadPart -= (LONGLONG) 600000000 * ptzi->StandardBias ; + } + } + + ft = * (FILETIME *) (void *) &li ; + FileTimeToSystemTime (&ft, pstLoc) ; + return TRUE ; +} +#endif diff --git a/src/modules/utils/timezones.cpp b/src/modules/utils/timezones.cpp new file mode 100644 index 0000000000..7d22cbec1e --- /dev/null +++ b/src/modules/utils/timezones.cpp @@ -0,0 +1,662 @@ +/* +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2010 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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. + +implements services to handle location - based timezones, instead of +simple UTC offsets. +*/ + +#include + +TIME_API tmi; + +#if _MSC_VER < 1500 + typedef struct _TIME_DYNAMIC_ZONE_INFORMATION_T { + LONG Bias; + WCHAR StandardName[ 32 ]; + SYSTEMTIME StandardDate; + LONG StandardBias; + WCHAR DaylightName[ 32 ]; + SYSTEMTIME DaylightDate; + LONG DaylightBias; + WCHAR TimeZoneKeyName[ 128 ]; + BOOLEAN DynamicDaylightTimeDisabled; + } DYNAMIC_TIME_ZONE_INFORMATION; +#endif + +typedef DWORD (WINAPI *pfnGetDynamicTimeZoneInformation_t)(DYNAMIC_TIME_ZONE_INFORMATION *pdtzi); +static pfnGetDynamicTimeZoneInformation_t pfnGetDynamicTimeZoneInformation; + +typedef HRESULT (WINAPI *pfnSHLoadIndirectString_t)(LPCWSTR pszSource, LPWSTR pszOutBuf, UINT cchOutBuf, void **ppvReserved); +static pfnSHLoadIndirectString_t pfnSHLoadIndirectString; + +typedef LANGID (WINAPI *pfnGetUserDefaultUILanguage_t)(void); +static pfnGetUserDefaultUILanguage_t pfnGetUserDefaultUILanguage; + +typedef LANGID (WINAPI *pfnGetSystemDefaultUILanguage_t)(void); +static pfnGetSystemDefaultUILanguage_t pfnGetSystemDefaultUILanguage; + +typedef LPARAM (WINAPI *pfnSendMessageW_t)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +static pfnSendMessageW_t pfnSendMessageW; + +typedef struct _REG_TZI_FORMAT +{ + LONG Bias; + LONG StandardBias; + LONG DaylightBias; + SYSTEMTIME StandardDate; + SYSTEMTIME DaylightDate; +} REG_TZI_FORMAT; + +#define MIM_TZ_DISPLAYLEN 128 + +struct MIM_TIMEZONE +{ + unsigned hash; + int offset; + + TCHAR tszName[MIM_TZ_NAMELEN]; // windows name for the time zone + wchar_t szDisplay[MIM_TZ_DISPLAYLEN]; // more descriptive display name (that's what usually appears in dialogs) + // every hour should be sufficient. + TIME_ZONE_INFORMATION tzi; + + static int compareBias(const MIM_TIMEZONE* p1, const MIM_TIMEZONE* p2) + { return p2->tzi.Bias - p1->tzi.Bias; } +}; + +typedef struct +{ + DWORD timestamp; // last time updated + MIM_TIMEZONE myTZ; // set to my own timezone +} TZ_INT_INFO; + +static TZ_INT_INFO myInfo; +bool muiInstalled; + +static OBJLIST g_timezones(55, NumericKeySortT); +static LIST g_timezonesBias(55, MIM_TIMEZONE::compareBias); + +void FormatTime (const SYSTEMTIME *st, const TCHAR *szFormat, TCHAR *szDest, int cbDest); +void UnixTimeToFileTime(time_t ts, LPFILETIME pft); +time_t FileTimeToUnixTime(LPFILETIME pft); + +#ifdef _UNICODE +#define fnSystemTimeToTzSpecificLocalTime SystemTimeToTzSpecificLocalTime +#else +BOOL MySystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION ptzi, LPSYSTEMTIME pstUtc, LPSYSTEMTIME pstLoc); +#define fnSystemTimeToTzSpecificLocalTime MySystemTimeToTzSpecificLocalTime +#endif + + +static int timeapiGetTimeZoneTime(HANDLE hTZ, SYSTEMTIME *st) +{ + if (st == NULL) return 1; + + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + if (tz == UTC_TIME_HANDLE) + GetSystemTime(st); + else if (tz && tz != &myInfo.myTZ) + { + SYSTEMTIME sto; + GetSystemTime(&sto); + return !fnSystemTimeToTzSpecificLocalTime(&tz->tzi, &sto, st); + } + else + GetLocalTime(st); + + return 0; +} + +static LPCTSTR timeapiGetTzName(HANDLE hTZ) +{ + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + if (tz == NULL) + return myInfo.myTZ.tszName; + else if (tz == UTC_TIME_HANDLE) + return _T("UTC"); + + return tz->tszName; +} + +static void CalcTsOffset(MIM_TIMEZONE *tz) +{ + SYSTEMTIME st, stl; + GetSystemTime(&st); + + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + time_t ts1 = FileTimeToUnixTime(&ft); + + if (!fnSystemTimeToTzSpecificLocalTime(&tz->tzi, &st, &stl)) + return; + + SystemTimeToFileTime(&stl, &ft); + time_t ts2 = FileTimeToUnixTime(&ft); + + tz->offset = ts2 - ts1; +} + +static bool IsSameTime(MIM_TIMEZONE *tz) +{ + SYSTEMTIME st, stl; + + if (tz == &myInfo.myTZ) + return true; + + timeapiGetTimeZoneTime(tz, &stl); + timeapiGetTimeZoneTime(NULL, &st); + + return st.wHour == stl.wHour && st.wMinute == stl.wMinute; +} + +static HANDLE timeapiGetInfoByName(LPCTSTR tszName, DWORD dwFlags) +{ + if (tszName == NULL) + return (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY)) ? NULL : &myInfo.myTZ; + + if (_tcscmp(myInfo.myTZ.tszName, tszName) == 0) + return (dwFlags & TZF_DIFONLY) ? NULL : &myInfo.myTZ; + + MIM_TIMEZONE tzsearch; + tzsearch.hash = hashstr(tszName); + + MIM_TIMEZONE *tz = g_timezones.find(&tzsearch); + if (tz == NULL) + return (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY)) ? NULL : &myInfo.myTZ; + + if (dwFlags & TZF_DIFONLY) + return IsSameTime(tz) ? NULL : tz; + + return tz; +} + +static HANDLE timeapiGetInfoByContact(HANDLE hContact, DWORD dwFlags) +{ + if (hContact == NULL) + return (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY)) ? NULL : &myInfo.myTZ; + + DBVARIANT dbv; + if (!DBGetContactSettingTString(hContact, "UserInfo", "TzName", &dbv)) + { + HANDLE res = timeapiGetInfoByName(dbv.ptszVal, dwFlags); + DBFreeVariant(&dbv); + if (res) return res; + } + + signed char timezone = (signed char)DBGetContactSettingByte(hContact, "UserInfo", "Timezone", -1); + if (timezone == -1) + { + char* szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (!DBGetContactSettingTString(hContact, szProto, "TzName", &dbv)) + { + HANDLE res = timeapiGetInfoByName(dbv.ptszVal, dwFlags); + DBFreeVariant(&dbv); + if (res) return res; + } + timezone = (signed char)DBGetContactSettingByte(hContact, szProto, "Timezone", -1); + } + + if (timezone != -1) + { + MIM_TIMEZONE tzsearch; + tzsearch.tzi.Bias = timezone * 30; + if (myInfo.myTZ.tzi.Bias == tzsearch.tzi.Bias) + { + if (dwFlags & TZF_DIFONLY) return NULL; + return &myInfo.myTZ; + } + + int i = g_timezonesBias.getIndex(&tzsearch); + while (i >= 0 && g_timezonesBias[i]->tzi.Bias == tzsearch.tzi.Bias) --i; + + int delta = LONG_MAX; + for (int j = ++i; j < g_timezonesBias.getCount() && g_timezonesBias[j]->tzi.Bias == tzsearch.tzi.Bias; ++j) + { + int delta1 = abs(g_timezonesBias[j]->tzi.DaylightDate.wMonth - myInfo.myTZ.tzi.DaylightDate.wMonth); + if (delta1 <= delta) + { + delta = delta1; + i = j; + } + } + + if (i >= 0) + { + MIM_TIMEZONE *tz = g_timezonesBias[i]; + return ((dwFlags & TZF_DIFONLY) && IsSameTime(tz)) ? NULL : tz; + } + } + return (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY)) ? NULL : &myInfo.myTZ; +} + +static void timeapiSetInfoByContact(HANDLE hContact, HANDLE hTZ) +{ + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + + if (hContact == NULL) return; + + if (tz) + { + DBWriteContactSettingTString(hContact, "UserInfo", "TzName", tz->tszName); + DBWriteContactSettingByte(hContact, "UserInfo", "Timezone", (char)((tz->tzi.Bias + tz->tzi.StandardBias) / 30)); + } + else + { + DBDeleteContactSetting(hContact, "UserInfo", "TzName"); + DBDeleteContactSetting(hContact, "UserInfo", "Timezone"); + } +} + +static int timeapiPrintDateTime(HANDLE hTZ, LPCTSTR szFormat, LPTSTR szDest, int cbDest, DWORD dwFlags) +{ + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + if (tz == NULL && (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY))) + return 1; + + SYSTEMTIME st; + if (timeapiGetTimeZoneTime(tz, &st)) + return 1; + + FormatTime(&st, szFormat, szDest, cbDest); + + return 0; +} + +static int timeapiPrintTimeStamp(HANDLE hTZ, time_t ts, LPCTSTR szFormat, LPTSTR szDest, int cbDest, DWORD dwFlags) +{ + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + if (tz == NULL && (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY))) + return 1; + + FILETIME ft; + + if (tz == NULL) tz = &myInfo.myTZ; + if (tz == NULL) + { + FILETIME lft; + + UnixTimeToFileTime(ts, &lft); + FileTimeToLocalFileTime(&lft, &ft); + } + else if (tz == UTC_TIME_HANDLE) + UnixTimeToFileTime(ts, &ft); + else + { + if (tz->offset == INT_MIN) + CalcTsOffset(tz); + + UnixTimeToFileTime(ts + tz->offset, &ft); + } + + SYSTEMTIME st; + FileTimeToSystemTime(&ft, &st); + + FormatTime(&st, szFormat, szDest, cbDest); + + return 0; +} + +static LPTIME_ZONE_INFORMATION timeapiGetTzi(HANDLE hTZ) +{ + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + return tz ? &tz->tzi : &myInfo.myTZ.tzi; +} + + +static time_t timeapiTimeStampToTimeZoneTimeStamp(HANDLE hTZ, time_t ts) +{ + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ; + + if (tz == NULL) tz = &myInfo.myTZ; + if (tz == NULL) + { + FILETIME ft, lft; + + UnixTimeToFileTime(ts, &ft); + FileTimeToLocalFileTime(&ft, &lft); + return FileTimeToUnixTime(&lft); + } + else if (tz == UTC_TIME_HANDLE) + return ts; + + if (tz->offset == INT_MIN) + CalcTsOffset(tz); + + return ts + tz->offset; +} + +typedef struct +{ + UINT addStr, getSel, setSel, getData, setData; +} ListMessages; + +static const ListMessages lbMessages = +{ LB_ADDSTRING, LB_GETCURSEL, LB_SETCURSEL, LB_GETITEMDATA, LB_SETITEMDATA }; + +static const ListMessages cbMessages = +{ CB_ADDSTRING, CB_GETCURSEL, CB_SETCURSEL, CB_GETITEMDATA, CB_SETITEMDATA }; + +static const ListMessages *GetListMessages(HWND hWnd, DWORD dwFlags) +{ + if (!(dwFlags & (TZF_PLF_CB | TZF_PLF_LB))) + { + TCHAR tszClassName[128]; + GetClassName(hWnd, tszClassName, SIZEOF(tszClassName)); + if (!_tcsicmp(tszClassName, _T("COMBOBOX"))) + dwFlags |= TZF_PLF_CB; + else if(!_tcsicmp(tszClassName, _T("LISTBOX"))) + dwFlags |= TZF_PLF_LB; + } + if (dwFlags & TZF_PLF_CB) + return & cbMessages; + else if(dwFlags & TZF_PLF_LB) + return & lbMessages; + else + return NULL; +} + + +static int timeapiSelectListItem(HANDLE hContact, HWND hWnd, DWORD dwFlags) +{ + if (hWnd == NULL) // nothing to do + return -1; + + const ListMessages *lstMsg = GetListMessages(hWnd, dwFlags); + if (lstMsg == NULL) return -1; + + int iSelection = 0; + if (hContact) + { + DBVARIANT dbv; + if (!DBGetContactSettingTString(hContact, "UserInfo", "TzName", &dbv)) + { + unsigned hash = hashstr(dbv.ptszVal); + for (int i = 0; i < g_timezonesBias.getCount(); ++i) + { + if (hash == g_timezonesBias[i]->hash) + { + iSelection = i + 1; + break; + } + } + DBFreeVariant(&dbv); + } + } + + SendMessage(hWnd, lstMsg->setSel, iSelection, 0); + return iSelection; +} + + +static int timeapiPrepareList(HANDLE hContact, HWND hWnd, DWORD dwFlags) +{ + if (hWnd == NULL) // nothing to do + return 0; + + const ListMessages *lstMsg = GetListMessages(hWnd, dwFlags); + if (lstMsg == NULL) return 0; + + SendMessage(hWnd, lstMsg->addStr, 0, (LPARAM)TranslateT("")); + + for (int i = 0; i < g_timezonesBias.getCount(); ++i) + { + MIM_TIMEZONE *tz = g_timezonesBias[i]; + + if (pfnSendMessageW) + pfnSendMessageW(hWnd, lstMsg->addStr, 0, (LPARAM)tz->szDisplay); + else + SendMessage(hWnd, lstMsg->addStr, 0, (LPARAM)StrConvTu(tz->szDisplay)); + + SendMessage(hWnd, lstMsg->setData, i + 1, (LPARAM)tz); + } + + return timeapiSelectListItem(hContact, hWnd, dwFlags); +} + + +static void timeapiStoreListResult(HANDLE hContact, HWND hWnd, DWORD dwFlags) +{ + const ListMessages *lstMsg = GetListMessages(hWnd, dwFlags); + if (lstMsg == NULL) return; + + LRESULT offset = SendMessage(hWnd, lstMsg->getSel, 0, 0); + if (offset > 0) + { + MIM_TIMEZONE *tz = (MIM_TIMEZONE*)SendMessage(hWnd, lstMsg->getData, offset, 0); + if ((INT_PTR)tz != CB_ERR && tz != NULL) + timeapiSetInfoByContact(hContact, tz); + } + else + timeapiSetInfoByContact(hContact, NULL); +} + + +static INT_PTR GetTimeApi( WPARAM, LPARAM lParam ) +{ + TIME_API* tmi = (TIME_API*)lParam; + if (tmi == NULL) + return FALSE; + + if (tmi->cbSize != sizeof(TIME_API)) + return FALSE; + + tmi->createByName = timeapiGetInfoByName; + tmi->createByContact = timeapiGetInfoByContact; + tmi->storeByContact = timeapiSetInfoByContact; + + tmi->printDateTime = timeapiPrintDateTime; + tmi->printTimeStamp = timeapiPrintTimeStamp; + + tmi->prepareList = timeapiPrepareList; + tmi->selectListItem = timeapiSelectListItem; + tmi->storeListResults = timeapiStoreListResult; + + tmi->getTimeZoneTime = timeapiGetTimeZoneTime; + tmi->timeStampToTimeZoneTimeStamp = timeapiTimeStampToTimeZoneTimeStamp; + tmi->getTzi = timeapiGetTzi; + tmi->getTzName = timeapiGetTzName; + + return TRUE; +} + +static INT_PTR TimestampToLocal(WPARAM wParam, LPARAM) +{ + return timeapiTimeStampToTimeZoneTimeStamp(NULL, (time_t)wParam); +} + +static INT_PTR TimestampToStringT(WPARAM wParam, LPARAM lParam) +{ + DBTIMETOSTRINGT *tts = (DBTIMETOSTRINGT*)lParam; + if (tts == NULL) return 0; + + timeapiPrintTimeStamp(NULL, (time_t)wParam, tts->szFormat, tts->szDest, tts->cbDest, 0); + return 0; +} + +#ifdef _UNICODE +static INT_PTR TimestampToStringA(WPARAM wParam, LPARAM lParam) +{ + DBTIMETOSTRING *tts = (DBTIMETOSTRING*)lParam; + if (tts == NULL) return 0; + + TCHAR *szDest = (TCHAR*)alloca(tts->cbDest); + timeapiPrintTimeStamp(NULL, (time_t)wParam, StrConvT(tts->szFormat), szDest, tts->cbDest, 0); + WideCharToMultiByte(CP_ACP, 0, szDest, -1, tts->szDest, tts->cbDest, NULL, NULL); + return 0; +} +#endif + +void GetLocalizedString(HKEY hSubKey, const TCHAR *szName, wchar_t *szBuf, DWORD cbLen) +{ + szBuf[0] = 0; + if (muiInstalled) + { + TCHAR tszTempBuf[MIM_TZ_NAMELEN], tszName[30]; + mir_sntprintf(tszName, SIZEOF(tszName), _T("MUI_%s"), szName); + DWORD dwLength = cbLen * sizeof(TCHAR); + if (ERROR_SUCCESS == RegQueryValueEx(hSubKey, tszName, NULL, NULL, (unsigned char *)tszTempBuf, &dwLength)) + { + tszTempBuf[min(dwLength / sizeof(TCHAR), cbLen - 1)] = 0; + if (pfnSHLoadIndirectString) + pfnSHLoadIndirectString(StrConvU(tszTempBuf), szBuf, cbLen, NULL); + } + } + if (szBuf[0] == 0) + { + DWORD dwLength = cbLen * sizeof(wchar_t); + +#ifdef _UNICODE + RegQueryValueEx(hSubKey, szName, NULL, NULL, (unsigned char *)szBuf, &dwLength); + szBuf[min(dwLength / sizeof(TCHAR), cbLen - 1)] = 0; +#else + char* szBufP = (char*)alloca(dwLength); + RegQueryValueEx(hSubKey, szName, NULL, NULL, (unsigned char *)szBufP, &dwLength); + szBufP[min(dwLength, cbLen * sizeof(wchar_t) - 1)] = 0; + MultiByteToWideChar(CP_ACP, 0, szBufP, -1, szBuf, cbLen); +#endif + } +} + +void RecalculateTime(void) +{ + GetTimeZoneInformation(&myInfo.myTZ.tzi); + myInfo.timestamp = time(NULL); + myInfo.myTZ.offset = INT_MIN; + + bool found = false; + DYNAMIC_TIME_ZONE_INFORMATION dtzi; + + if (pfnGetDynamicTimeZoneInformation && pfnGetDynamicTimeZoneInformation(&dtzi) != TIME_ZONE_ID_INVALID) + { + TCHAR *myTzKey = mir_u2t(dtzi.TimeZoneKeyName); + _tcscpy(myInfo.myTZ.tszName, myTzKey); + mir_free(myTzKey); + found = true; + } + + for (int i = 0; i < g_timezones.getCount(); ++i) + { + MIM_TIMEZONE &tz = g_timezones[i]; + if (tz.offset != INT_MIN) tz.offset = INT_MIN; + + if (!found) + { + if (!wcscmp(tz.tzi.StandardName, myInfo.myTZ.tzi.StandardName) || + !wcscmp(tz.tzi.DaylightName, myInfo.myTZ.tzi.DaylightName)) + { + _tcscpy(myInfo.myTZ.tszName, tz.tszName); + found = true; + } + } + } +} + +void InitTimeZones(void) +{ + REG_TZI_FORMAT tzi; + HKEY hKey; + + const TCHAR *tszKey = IsWinVerNT() ? + _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones") : + _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"); + + /* + * use GetDynamicTimeZoneInformation() on Vista+ - this will return a structure with + * the registry key name, so finding our own time zone later will be MUCH easier for + * localized systems or systems with a MUI pack installed + */ + if (IsWinVerVistaPlus()) + pfnGetDynamicTimeZoneInformation = (pfnGetDynamicTimeZoneInformation_t)GetProcAddress(GetModuleHandle(_T("kernel32")), "GetDynamicTimeZoneInformation"); + + if (IsWinVer2000Plus()) + { + pfnSHLoadIndirectString = (pfnSHLoadIndirectString_t)GetProcAddress(GetModuleHandle(_T("shlwapi")), "SHLoadIndirectString"); + pfnGetSystemDefaultUILanguage = (pfnGetSystemDefaultUILanguage_t)GetProcAddress(GetModuleHandle(_T("kernel32")), "GetSystemDefaultUILanguage"); + pfnGetUserDefaultUILanguage = (pfnGetUserDefaultUILanguage_t)GetProcAddress(GetModuleHandle(_T("kernel32")), "GetUserDefaultUILanguage"); + muiInstalled = pfnSHLoadIndirectString && pfnGetSystemDefaultUILanguage() != pfnGetUserDefaultUILanguage(); + } + + pfnSendMessageW = (pfnSendMessageW_t)GetProcAddress(GetModuleHandle(_T("user32")), "SendMessageW"); + + if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, tszKey, 0, KEY_ENUMERATE_SUB_KEYS, &hKey)) + { + DWORD dwIndex = 0; + HKEY hSubKey; + TCHAR tszName[MIM_TZ_NAMELEN]; + + DWORD dwSize = SIZEOF(tszName); + while (ERROR_NO_MORE_ITEMS != RegEnumKeyEx(hKey, dwIndex++, tszName, &dwSize, NULL, NULL, 0, NULL)) + { + if (ERROR_SUCCESS == RegOpenKeyEx(hKey, tszName, 0, KEY_QUERY_VALUE, &hSubKey)) + { + dwSize = sizeof(tszName); + + DWORD dwLength = sizeof(tzi); + if (ERROR_SUCCESS != RegQueryValueEx(hSubKey, _T("TZI"), NULL, NULL, (unsigned char *)&tzi, &dwLength)) + continue; + + MIM_TIMEZONE *tz = new MIM_TIMEZONE; + + tz->tzi.Bias = tzi.Bias; + tz->tzi.StandardDate = tzi.StandardDate; + tz->tzi.StandardBias = tzi.StandardBias; + tz->tzi.DaylightDate = tzi.DaylightDate; + tz->tzi.DaylightBias = tzi.DaylightBias; + + _tcscpy(tz->tszName, tszName); + tz->hash = hashstr(tszName); + tz->offset = INT_MIN; + + GetLocalizedString(hSubKey, _T("Display"), tz->szDisplay, SIZEOF(tz->szDisplay)); + GetLocalizedString(hSubKey, _T("Std"), tz->tzi.StandardName, SIZEOF(tz->tzi.StandardName)); + GetLocalizedString(hSubKey, _T("Dlt"), tz->tzi.DaylightName, SIZEOF(tz->tzi.DaylightName)); + + g_timezones.insert(tz); + g_timezonesBias.insert(tz); + + RegCloseKey(hSubKey); + } + dwSize = SIZEOF(tszName); + } + RegCloseKey(hKey); + } + + RecalculateTime(); + + CreateServiceFunction(MS_SYSTEM_GET_TMI, GetTimeApi); + + CreateServiceFunction(MS_DB_TIME_TIMESTAMPTOLOCAL, TimestampToLocal); + CreateServiceFunction(MS_DB_TIME_TIMESTAMPTOSTRINGT, TimestampToStringT); +#ifdef _UNICODE + CreateServiceFunction(MS_DB_TIME_TIMESTAMPTOSTRING, TimestampToStringA); +#else + CreateServiceFunction(MS_DB_TIME_TIMESTAMPTOSTRING, TimestampToStringT); +#endif + + + tmi.cbSize = sizeof(tmi); + GetTimeApi(0, (LPARAM)&tmi); +} + +void UninitTimeZones(void) +{ + g_timezonesBias.destroy(); + g_timezones.destroy(); +} diff --git a/src/modules/utils/utf.cpp b/src/modules/utils/utf.cpp new file mode 100644 index 0000000000..ad6683d2ed --- /dev/null +++ b/src/modules/utils/utf.cpp @@ -0,0 +1,413 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + + Copyright 2000 Alexandre Julliard of Wine project + (UTF-8 conversion routines) + +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 "commonheaders.h" + +/* number of following bytes in sequence based on first byte value (for bytes above 0x7f) */ +static const char utf8_length[128] = +{ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x80-0x8f */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x90-0x9f */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xa0-0xaf */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xb0-0xbf */ + 0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0xc0-0xcf */ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 0xd0-0xdf */ + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 0xe0-0xef */ + 3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0 /* 0xf0-0xff */ +}; + +/* first byte mask depending on UTF-8 sequence length */ +static const unsigned char utf8_mask[4] = { 0x7f, 0x1f, 0x0f, 0x07 }; + +/* minimum Unicode value depending on UTF-8 sequence length */ +static const unsigned int utf8_minval[4] = { 0x0, 0x80, 0x800, 0x10000 }; + + +/* get the next char value taking surrogates into account */ +static unsigned int getSurrogateValue(const wchar_t *src, unsigned int srclen) +{ + if (src[0] >= 0xd800 && src[0] <= 0xdfff) /* surrogate pair */ + { + if (src[0] > 0xdbff || /* invalid high surrogate */ + srclen <= 1 || /* missing low surrogate */ + src[1] < 0xdc00 || src[1] > 0xdfff) /* invalid low surrogate */ + return 0; + return 0x10000 + ((src[0] & 0x3ff) << 10) + (src[1] & 0x3ff); + } + return src[0]; +} + +/* query necessary dst length for src string */ +static int Ucs2toUtf8Len(const wchar_t *src, unsigned int srclen) +{ + int len; + unsigned int val; + + for (len = 0; srclen; srclen--, src++) + { + if (*src < 0x80) /* 0x00-0x7f: 1 byte */ + { + len++; + continue; + } + if (*src < 0x800) /* 0x80-0x7ff: 2 bytes */ + { + len += 2; + continue; + } + if (!(val = getSurrogateValue(src, srclen))) + { + return -2; + } + if (val < 0x10000) /* 0x800-0xffff: 3 bytes */ + len += 3; + else /* 0x10000-0x10ffff: 4 bytes */ + { + len += 4; + src++; + srclen--; + } + } + return len; +} + +int Ucs2toUtf8Len(const wchar_t *src) +{ + if ( src == 0 ) + return 0; + + return Ucs2toUtf8Len( src, (int)wcslen( src )); +} + +/* wide char to UTF-8 string conversion */ +/* return -1 on dst buffer overflow, -2 on invalid input char */ +int Ucs2toUtf8(const wchar_t *src, int srclen, char *dst, int dstlen) +{ + int len; + + for (len = dstlen; srclen; srclen--, src++) + { + WCHAR ch = *src; + unsigned int val; + + if (ch < 0x80) /* 0x00-0x7f: 1 byte */ + { + if (!len--) return -1; /* overflow */ + *dst++ = ch; + continue; + } + + if (ch < 0x800) /* 0x80-0x7ff: 2 bytes */ + { + if ((len -= 2) < 0) return -1; /* overflow */ + dst[1] = 0x80 | (ch & 0x3f); + ch >>= 6; + dst[0] = 0xc0 | ch; + dst += 2; + continue; + } + + if (!(val = getSurrogateValue(src, srclen))) + { + return -2; + } + + if (val < 0x10000) /* 0x800-0xffff: 3 bytes */ + { + if ((len -= 3) < 0) return -1; /* overflow */ + dst[2] = 0x80 | (val & 0x3f); + val >>= 6; + dst[1] = 0x80 | (val & 0x3f); + val >>= 6; + dst[0] = 0xe0 | val; + dst += 3; + } + else /* 0x10000-0x10ffff: 4 bytes */ + { + if ((len -= 4) < 0) return -1; /* overflow */ + dst[3] = 0x80 | (val & 0x3f); + val >>= 6; + dst[2] = 0x80 | (val & 0x3f); + val >>= 6; + dst[1] = 0x80 | (val & 0x3f); + val >>= 6; + dst[0] = 0xf0 | val; + dst += 4; + src++; + srclen--; + } + } + return dstlen - len; +} + +/* helper for the various utf8 mbstowcs functions */ +static unsigned int decodeUtf8Char(unsigned char ch, const char **str, const char *strend) +{ + unsigned int len = utf8_length[ch-0x80]; + unsigned int res = ch & utf8_mask[len]; + const char *end = *str + len; + + if (end > strend) return ~0; + switch(len) + { + case 3: + if ((ch = end[-3] ^ 0x80) >= 0x40) break; + res = (res << 6) | ch; + (*str)++; + case 2: + if ((ch = end[-2] ^ 0x80) >= 0x40) break; + res = (res << 6) | ch; + (*str)++; + case 1: + if ((ch = end[-1] ^ 0x80) >= 0x40) break; + res = (res << 6) | ch; + (*str)++; + if (res < utf8_minval[len]) break; + return res; + } + return ~0; +} + +/* query necessary dst length for src string */ +static inline int Utf8toUcs2Len(const char *src, int srclen) +{ + int ret = 0; + unsigned int res; + const char *srcend = src + srclen; + + while (src < srcend) + { + unsigned char ch = *src++; + if (ch < 0x80) /* special fast case for 7-bit ASCII */ + { + ret++; + continue; + } + if ((res = decodeUtf8Char(ch, &src, srcend)) <= 0x10ffff) + { + if (res > 0xffff) ret++; + ret++; + } + else return -2; /* bad char */ + /* otherwise ignore it */ + } + return ret; +} + +/* UTF-8 to wide char string conversion */ +/* return -1 on dst buffer overflow, -2 on invalid input char */ +int Utf8toUcs2(const char *src, int srclen, wchar_t *dst, int dstlen) +{ + unsigned int res; + const char *srcend = src + srclen; + wchar_t *dstend = dst + dstlen; + + while ((dst < dstend) && (src < srcend)) + { + unsigned char ch = *src++; + if (ch < 0x80) /* special fast case for 7-bit ASCII */ + { + *dst++ = ch; + continue; + } + if ((res = decodeUtf8Char(ch, &src, srcend)) <= 0xffff) + { + *dst++ = res; + } + else if (res <= 0x10ffff) /* we need surrogates */ + { + if (dst == dstend - 1) return -1; /* overflow */ + res -= 0x10000; + *dst++ = 0xd800 | (res >> 10); + *dst++ = 0xdc00 | (res & 0x3ff); + } + else return -2; /* bad char */ + /* otherwise ignore it */ + } + if (src < srcend) return -1; /* overflow */ + return dstlen - (dstend - dst); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Utf8Decode - converts UTF8-encoded string to the UCS2/MBCS format + +char* Utf8DecodeCP(char* str, int codepage, wchar_t** ucs2) +{ + int len; + bool needs_free = false; + wchar_t* tempBuf = NULL; + if ( ucs2 ) + *ucs2 = NULL; + + if (str == NULL) + return NULL; + + len = (int)strlen(str); + + if (len < 2) { + if (ucs2 != NULL) { + *ucs2 = tempBuf = (wchar_t*)mir_alloc((len + 1) * sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, str, len, tempBuf, len); + tempBuf[len] = 0; + } + return str; + } + + int destlen = Utf8toUcs2Len(str, len); + if (destlen < 0) + return NULL; + + if (ucs2 == NULL) { + __try + { + tempBuf = (wchar_t*)alloca((destlen + 1) * sizeof(wchar_t)); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + tempBuf = NULL; + needs_free = true; + } + } + + if ( tempBuf == NULL ) { + tempBuf = (wchar_t*)mir_alloc((destlen + 1) * sizeof(wchar_t)); + if ( tempBuf == NULL ) + return NULL; + } + + Utf8toUcs2(str, len, tempBuf, destlen); + tempBuf[destlen] = 0; + WideCharToMultiByte(codepage, 0, tempBuf, -1, str, len + 1, "?", NULL); + + if (ucs2) + *ucs2 = tempBuf; + else if (needs_free) + mir_free(tempBuf); + + return str; +} + +char* Utf8Decode(char* str, wchar_t** ucs2) +{ + return Utf8DecodeCP(str, LangPackGetDefaultCodePage(), ucs2); +} + +wchar_t* Utf8DecodeUcs2(const char* str) +{ + if (str == NULL) + return NULL; + + int len = (int)strlen(str); + + int destlen = Utf8toUcs2Len(str, len); + if (destlen < 0) return NULL; + + wchar_t* ucs2 = (wchar_t*)mir_alloc((destlen + 1) * sizeof(wchar_t)); + if (ucs2 == NULL) return NULL; + + if (Utf8toUcs2(str, len, ucs2, destlen) >= 0) + { + ucs2[destlen] = 0; + return ucs2; + } + + mir_free(ucs2); + + return NULL; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Utf8Encode - converts MBCS string to the UTF8-encoded format + +char* Utf8EncodeCP(const char* src, int codepage) +{ + int len; + bool needs_free = false; + char* result = NULL; + wchar_t* tempBuf; + + if (src == NULL) + return NULL; + + len = (int)strlen(src); + + __try + { + tempBuf = (wchar_t*)alloca((len + 1) * sizeof(wchar_t)); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + tempBuf = (wchar_t*)mir_alloc((len + 1) * sizeof(wchar_t)); + if (tempBuf == NULL) return NULL; + needs_free = true; + } + + len = MultiByteToWideChar(codepage, 0, src, -1, tempBuf, len + 1); + + int destlen = Ucs2toUtf8Len(tempBuf, len); + if (destlen >= 0) + { + result = (char*)mir_alloc(destlen + 1); + if (result) + { + Ucs2toUtf8(tempBuf, len, result, destlen); + result[destlen] = 0; + } + } + + if (needs_free) + mir_free(tempBuf); + + return result; +} + +char* Utf8Encode(const char* src) +{ + return Utf8EncodeCP(src, LangPackGetDefaultCodePage()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Utf8Encode - converts UCS2 string to the UTF8-encoded format + +char* Utf8EncodeUcs2(const wchar_t* src) +{ + if (src == NULL) + return NULL; + + int len = (int)wcslen(src); + + int destlen = Ucs2toUtf8Len(src, len); + if (destlen < 0) return NULL; + + char* result = (char*)mir_alloc(destlen + 1); + if (result == NULL) + return NULL; + + Ucs2toUtf8(src, len, result, destlen); + result[destlen] = 0; + + return result; +} diff --git a/src/modules/utils/utils.cpp b/src/modules/utils/utils.cpp new file mode 100644 index 0000000000..9e81b3084b --- /dev/null +++ b/src/modules/utils/utils.cpp @@ -0,0 +1,587 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +INT_PTR ResizeDialog(WPARAM wParam,LPARAM lParam); +int InitOpenUrl(void); +int InitWindowList(void); +void FreeWindowList(void); +int InitHyperlink(void); +int InitColourPicker(void); +int InitBitmapFilter(void); +void InitXmlApi(void); +void InitTimeZones(void); +void UninitTimeZones(void); + +INT_PTR GetMD5Interface(WPARAM, LPARAM); +INT_PTR GetSHA1Interface(WPARAM, LPARAM); + +static BOOL bModuleInitialized = FALSE; + +static struct CountryListEntry countries[]={ + {0 ,"Unspecified"}, + {9999,"Other"}, + {0xFFFF,"Unknown"}, + {93 ,"Afghanistan"}, + {355 ,"Albania"}, + {213 ,"Algeria"}, + {376 ,"Andorra"}, + {244 ,"Angola"}, + {1264,"Anguilla"}, /* change county code to NANP (from 101) */ + {1268,"Antigua and Barbuda"}, /* change county code to NANP (from 1021) */ +// {5902,"Antilles"}, /* removed: it is not a country, it's a group of islands from diffrent countries (all are included in the list)*/ + {54 ,"Argentina"}, + {374 ,"Armenia"}, + {297 ,"Aruba"}, + {247 ,"Ascension Island"}, + {61 ,"Australia"}, + {6720 ,"Australia, Antarctic Territory"}, /* added country code 672(0)*/ + {614 ,"Australia, Christmas Island"}, /* rename (from Christmas Island) and change to official county code 61(4) (from 672) */ + {61891,"Australia, Cocos (Keeling) Islands"}, /* rename and change to official county code 61(891) (from 6102) */ + {6723 ,"Australia, Norfolk Island"}, /* rename (from Norfolk Island) and change to official county code 672(3) (from 6722) */ + {43 ,"Austria"}, + {994 ,"Azerbaijan"}, + {1242,"Bahamas"}, /* change county code to NANP (from 103) */ + {973 ,"Bahrain"}, + {880 ,"Bangladesh"}, + {1246,"Barbados"}, /* change county code to NANP (from 103) */ +// {120 ,"Barbuda"}, /* removed: it is not a country and no special island, see Antigua and Barbuda*/ + {375 ,"Belarus"}, + {32 ,"Belgium"}, + {501 ,"Belize"}, + {229 ,"Benin"}, + {1441,"Bermuda"}, /* change county code to NANP (from 105) */ + {975 ,"Bhutan"}, + {591 ,"Bolivia"}, + {387 ,"Bosnia and Herzegovina"}, + {267 ,"Botswana"}, + {55 ,"Brazil"}, + {673 ,"Brunei"}, + {359 ,"Bulgaria"}, + {226 ,"Burkina Faso"}, + {257 ,"Burundi"}, + {855 ,"Cambodia"}, + {237 ,"Cameroon"}, + {1002,"Canada"}, /* change county code to NANP (from 107 to virtual 1(002) -> reflect NANP*/ + {238 ,"Cape Verde Islands"}, + {1345,"Cayman Islands"}, /* change county code to NANP (from 108) */ + {236 ,"Central African Republic"}, + {235 ,"Chad"}, + {56 ,"Chile, Republic of"}, + {86 ,"China"}, +// {6101,"Cocos-Keeling Islands"}, /* removed (double): see Australia, Cocos (Keeling) Islands */ + {57 ,"Colombia"}, + {269 ,"Comoros"}, /* change county code (from 2691) */ + {243 ,"Congo, Democratic Republic of (Zaire)"}, + {242 ,"Congo, Republic of the"}, + {682 ,"Cook Islands"}, + {506 ,"Costa Rica"}, + {225 ,"Cote d'Ivoire (Ivory Coast)"}, + {385 ,"Croatia"}, + {53 ,"Cuba"}, + {357 ,"Greek, Republic of South Cyprus"}, /* rename coz Turkey, Republic of Northern Cyprus */ + {420 ,"Czech Republic"}, + {45 ,"Denmark"}, + {246 ,"Diego Garcia"}, + {253 ,"Djibouti"}, + {1767,"Dominica"}, /* change county code to NANP (from 109) */ + {1809,"Dominican Republic"}, /* change county code to NANP 809, 829, 849 (from 110) */ + {593 ,"Ecuador"}, + {20 ,"Egypt"}, + {503 ,"El Salvador"}, + {240 ,"Equatorial Guinea"}, + {291 ,"Eritrea"}, + {372 ,"Estonia"}, + {251 ,"Ethiopia"}, + {3883,"Europe"}, /* add county code +388 3 official European Telephony Numbering Space*/ + {298 ,"Faeroe Islands"}, + {500 ,"Falkland Islands"}, + {679 ,"Fiji"}, + {358 ,"Finland"}, + {33 ,"France"}, + {5901,"French Antilles"}, + {594 ,"French Guiana"}, + {689 ,"French Polynesia"}, + {241 ,"Gabon"}, + {220 ,"Gambia"}, + {995 ,"Georgia"}, + {49 ,"Germany"}, + {233 ,"Ghana"}, + {350 ,"Gibraltar"}, + {30 ,"Greece"}, + {299 ,"Greenland"}, + {1473,"Grenada"}, /* change county code to NANP (from 111) */ + {590 ,"Guadeloupe"}, + {1671,"Guam, US Territory of"}, /* change county code to NANP (from 671) */ + {502 ,"Guatemala"}, + {224 ,"Guinea"}, + {245 ,"Guinea-Bissau"}, + {592 ,"Guyana"}, + {509 ,"Haiti"}, + {504 ,"Honduras"}, + {852 ,"Hong Kong"}, + {36 ,"Hungary"}, + {354 ,"Iceland"}, + {91 ,"India"}, + {62 ,"Indonesia"}, + {98 ,"Iran (Islamic Republic of)"}, + {964 ,"Iraq"}, + {353 ,"Ireland"}, + {972 ,"Israel"}, + {39 ,"Italy"}, + {1876,"Jamaica"}, /* change county code to NANP (from 112) */ + {81 ,"Japan"}, + {962 ,"Jordan"}, + {705 ,"Kazakhstan"}, + {254 ,"Kenya"}, + {686 ,"Kiribati"}, + {850 ,"Korea, North"}, + {82 ,"Korea, South"}, + {965 ,"Kuwait"}, + {996 ,"Kyrgyzstan"}, /* change county code (from 706) */ + {856 ,"Laos"}, + {371 ,"Latvia"}, + {961 ,"Lebanon"}, + {266 ,"Lesotho"}, + {231 ,"Liberia"}, + {218 ,"Libyan Arab Jamahiriya"}, + {423 ,"Liechtenstein"}, /* change county code (from 4101) */ + {370 ,"Lithuania"}, + {352 ,"Luxembourg"}, + {853 ,"Macau"}, + {389 ,"Macedonia, Republic of"}, /* rename coz war */ + {261 ,"Madagascar"}, + {265 ,"Malawi"}, + {60 ,"Malaysia"}, + {960 ,"Maldives"}, + {223 ,"Mali"}, + {356 ,"Malta"}, + {692 ,"Marshall Islands"}, + {596 ,"Martinique"}, + {222 ,"Mauritania"}, + {230 ,"Mauritius"}, + {262 ,"Mayotte Island"}, /* change county code coz bug (from 269) */ + {52 ,"Mexico"}, + {691 ,"Micronesia, Federated States of"}, + {373 ,"Moldova, Republic of"}, + {377 ,"Monaco"}, + {976 ,"Mongolia"}, + {1664,"Montserrat"}, /* change county code to NANP (from 113) */ + {212 ,"Morocco"}, + {258 ,"Mozambique"}, + {95 ,"Myanmar"}, + {264 ,"Namibia"}, + {674 ,"Nauru"}, + {977 ,"Nepal"}, + {31 ,"Netherlands"}, + {599 ,"Netherlands Antilles"}, /* dissolved 2010 */ + {5995 ,"St. Maarten"}, /* add new country in 2010 (from Netherlands Antilles) */ + {5999 ,"Curacao"}, /* add new country in 2010 (from Netherlands Antilles) */ + {5997 ,"Netherlands (Bonaire Island)"}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */ + {59946,"Netherlands (Saba Island)"}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */ + {59938,"Netherlands (St. Eustatius Island)"}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */ + // {114 ,"Nevis"}, /* removed: it is not a country, it's part of Saint Kitts and Nevis*/ + {687 ,"New Caledonia"}, + {64 ,"New Zealand"}, + {505 ,"Nicaragua"}, + {227 ,"Niger"}, + {234 ,"Nigeria"}, + {683 ,"Niue"}, + {1670,"Northern Mariana Islands, US Territory of"}, /* added NANP */ + {47 ,"Norway"}, + {968 ,"Oman"}, + {92 ,"Pakistan"}, + {680 ,"Palau"}, + {507 ,"Panama"}, + {675 ,"Papua New Guinea"}, + {595 ,"Paraguay"}, + {51 ,"Peru"}, + {63 ,"Philippines"}, + {48 ,"Poland"}, + {351 ,"Portugal"}, + {1939,"Puerto Rico"}, /* change county code to NANP 939, 787 (from 121) */ + {974 ,"Qatar"}, + {262 ,"Reunion Island"}, + {40 ,"Romania"}, +// {6701,"Rota Island"}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */ + {7 ,"Russia"}, + {250 ,"Rwanda"}, + {1684,"Samoa (USA)"}, /* rename (from American Samoa) change county code to NANP (from 684) */ + {685 ,"Samoa, Western"}, /* rename (from Western Samoa) */ + {290 ,"Saint Helena"}, +// {115 ,"Saint Kitts"}, /* removed: it is not a country it is part of Saint Kitts and Nevis*/ + {1869,"Saint Kitts and Nevis"}, /* change county code to NANP (from 1141) */ + {1758,"Saint Lucia"}, /* change county code to NANP (from 122) */ + {508 ,"Saint Pierre and Miquelon"}, + {1784,"Saint Vincent and the Grenadines"}, /* change county code to NANP (from 116) */ +// {670 ,"Saipan Island"}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */ + {378 ,"San Marino"}, + {239 ,"Sao Tome and Principe"}, + {966 ,"Saudi Arabia"}, + {442 ,"Scotland"}, + {221 ,"Senegal"}, + {248 ,"Seychelles"}, + {232 ,"Sierra Leone"}, + {65 ,"Singapore"}, + {421 ,"Slovakia"}, + {386 ,"Slovenia"}, + {677 ,"Solomon Islands"}, + {252 ,"Somalia"}, + {27 ,"South Africa"}, + {34 ,"Spain"}, + {3492,"Spain, Canary Islands"}, /*rename and change county code to 34(92) spain + canary code*/ + {94 ,"Sri Lanka"}, + {249 ,"Sudan"}, + {597 ,"Suriname"}, + {268 ,"Swaziland"}, + {46 ,"Sweden"}, + {41 ,"Switzerland"}, + {963 ,"Syrian Arab Republic"}, + {886 ,"Taiwan"}, + {992 ,"Tajikistan"}, /* change county code (from 708) */ + {255 ,"Tanzania"}, + {66 ,"Thailand"}, +// {6702,"Tinian Island"}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */ + {670 ,"Timor, East"}, /* added (is part off Northern Mariana Islands but not US Territory*/ + {228 ,"Togo"}, + {690 ,"Tokelau"}, + {676 ,"Tonga"}, + {1868,"Trinidad and Tobago"}, /* change county code to NANP (from 1141) */ + {216 ,"Tunisia"}, + {90 ,"Turkey"}, + {90392,"Turkey, Republic of Northern Cyprus"}, /* added (is diffrent from Greek part)*/ + {993 ,"Turkmenistan"}, /* change county code (from 709) */ + {1649,"Turks and Caicos Islands"}, /* change county code to NANP (from 118) */ + {688 ,"Tuvalu"}, + {256 ,"Uganda"}, + {380 ,"Ukraine"}, + {971 ,"United Arab Emirates"}, + {44 ,"United Kingdom"}, + {598 ,"Uruguay"}, + {1 ,"USA"}, + {998 ,"Uzbekistan"}, /* change county code (from 711) */ + {678 ,"Vanuatu"}, + {379 ,"Vatican City"}, + {58 ,"Venezuela"}, + {84 ,"Vietnam"}, + {1284,"Virgin Islands (UK)"}, /* change county code to NANP (from 105) - rename coz Virgin Islands (USA) */ + {1340,"Virgin Islands (USA)"}, /* change county code to NANP (from 123) */ + {441 ,"Wales"}, + {681 ,"Wallis and Futuna Islands"}, + {967 ,"Yemen"}, + {38 ,"Yugoslavia"}, /* added for old values like birth-country */ + {381 ,"Serbia, Republic of"}, /* rename need (from Yugoslavia)*/ + {383 ,"Kosovo, Republic of"}, /*change country code (from 3811), rename need (from Yugoslavia - Serbia) */ + {382 ,"Montenegro, Republic of"}, /* rename need (from Yugoslavia - Montenegro) */ + {260 ,"Zambia"}, + {263 ,"Zimbabwe"}, +}; + +static INT_PTR GetCountryByNumber(WPARAM wParam, LPARAM) +{ + int i; + + for(i=0; i < SIZEOF(countries); i++ ) + if((int)wParam==countries[i].id) return (INT_PTR)countries[i].szName; + return (INT_PTR)NULL; +} + +static INT_PTR GetCountryList(WPARAM wParam,LPARAM lParam) +{ + *(int*)wParam = SIZEOF(countries); + *(struct CountryListEntry**)lParam=countries; + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR SaveWindowPosition(WPARAM, LPARAM lParam) +{ + SAVEWINDOWPOS *swp=(SAVEWINDOWPOS*)lParam; + WINDOWPLACEMENT wp; + char szSettingName[64]; + + wp.length=sizeof(wp); + GetWindowPlacement(swp->hwnd,&wp); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sx", swp->szNamePrefix); + DBWriteContactSettingDword(swp->hContact,swp->szModule,szSettingName,wp.rcNormalPosition.left); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sy", swp->szNamePrefix); + DBWriteContactSettingDword(swp->hContact,swp->szModule,szSettingName,wp.rcNormalPosition.top); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%swidth", swp->szNamePrefix); + DBWriteContactSettingDword(swp->hContact,swp->szModule,szSettingName,wp.rcNormalPosition.right-wp.rcNormalPosition.left); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sheight", swp->szNamePrefix); + DBWriteContactSettingDword(swp->hContact,swp->szModule,szSettingName,wp.rcNormalPosition.bottom-wp.rcNormalPosition.top); + return 0; +} + + +static INT_PTR AssertInsideScreen(WPARAM wParam, LPARAM lParam) +{ + LPRECT rc = (LPRECT) wParam; + if (rc == NULL) + return -1; + + RECT rcScreen; + SystemParametersInfo(SPI_GETWORKAREA, 0, &rcScreen, FALSE); + + if (MyMonitorFromWindow) + { + if (MyMonitorFromRect(rc, MONITOR_DEFAULTTONULL)) + return 0; + + MONITORINFO mi = {0}; + HMONITOR hMonitor = MyMonitorFromRect(rc, MONITOR_DEFAULTTONEAREST); + mi.cbSize = sizeof(mi); + if (MyGetMonitorInfo(hMonitor, &mi)) + rcScreen = mi.rcWork; + } + else + { + RECT rcDest; + if (IntersectRect(&rcDest, &rcScreen, rc)) + return 0; + } + + if (rc->top >= rcScreen.bottom) + OffsetRect(rc, 0, rcScreen.bottom - rc->bottom); + else if (rc->bottom <= rcScreen.top) + OffsetRect(rc, 0, rcScreen.top - rc->top); + if (rc->left >= rcScreen.right) + OffsetRect(rc, rcScreen.right - rc->right, 0); + else if (rc->right <= rcScreen.left) + OffsetRect(rc, rcScreen.left - rc->left, 0); + + return 1; +} + + +static INT_PTR RestoreWindowPosition(WPARAM wParam,LPARAM lParam) +{ + SAVEWINDOWPOS *swp=(SAVEWINDOWPOS*)lParam; + WINDOWPLACEMENT wp; + char szSettingName[64]; + int x,y; + + wp.length=sizeof(wp); + GetWindowPlacement(swp->hwnd,&wp); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sx", swp->szNamePrefix); + x=DBGetContactSettingDword(swp->hContact,swp->szModule,szSettingName,-1); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sy", swp->szNamePrefix); + y=(int)DBGetContactSettingDword(swp->hContact,swp->szModule,szSettingName,-1); + if(x==-1) return 1; + if(wParam&RWPF_NOSIZE) { + OffsetRect(&wp.rcNormalPosition,x-wp.rcNormalPosition.left,y-wp.rcNormalPosition.top); + } + else { + wp.rcNormalPosition.left=x; + wp.rcNormalPosition.top=y; + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%swidth", swp->szNamePrefix); + wp.rcNormalPosition.right=wp.rcNormalPosition.left+DBGetContactSettingDword(swp->hContact,swp->szModule,szSettingName,-1); + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sheight", swp->szNamePrefix); + wp.rcNormalPosition.bottom=wp.rcNormalPosition.top+DBGetContactSettingDword(swp->hContact,swp->szModule,szSettingName,-1); + } + wp.flags=0; + if (wParam & RWPF_HIDDEN) + wp.showCmd = SW_HIDE; + if (wParam & RWPF_NOACTIVATE) + wp.showCmd = SW_SHOWNOACTIVATE; + + if (!(wParam & RWPF_NOMOVE)) + AssertInsideScreen((WPARAM) &wp.rcNormalPosition, 0); + + SetWindowPlacement(swp->hwnd,&wp); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR RestartMiranda(WPARAM, LPARAM) +{ + TCHAR mirandaPath[ MAX_PATH ], cmdLine[ 100 ]; + PROCESS_INFORMATION pi; + STARTUPINFO si = { 0 }; + si.cb = sizeof(si); + GetModuleFileName( NULL, mirandaPath, SIZEOF(mirandaPath)); + mir_sntprintf( cmdLine, SIZEOF( cmdLine ), _T("/restart:%d"), GetCurrentProcessId()); + CreateProcess( mirandaPath, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +typedef BOOL (APIENTRY *PGENRANDOM)( PVOID, ULONG ); + +static INT_PTR GenerateRandom(WPARAM wParam, LPARAM lParam) +{ + if (wParam == 0 || lParam == 0) return 0; + + PGENRANDOM pfnRtlGenRandom = NULL; + HMODULE hModule = GetModuleHandleA("advapi32"); + if (hModule) + { + pfnRtlGenRandom = (PGENRANDOM)GetProcAddress(hModule, "SystemFunction036"); + if (pfnRtlGenRandom) + { + if (!pfnRtlGenRandom((PVOID)lParam, wParam)) + pfnRtlGenRandom = NULL; + } + } + if (pfnRtlGenRandom == NULL) + { + srand(GetTickCount()); + unsigned short* buf = (unsigned short*)lParam; + for ( ; (long)(wParam-=2) >= 0; ) + *(buf++) = (unsigned short)rand(); + if (lParam < 0) + *(char*)buf = (char)(rand() & 0xFF); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +#if defined( _UNICODE ) +char* __fastcall rtrim(char* str) +{ + if (str == NULL) return NULL; + char* p = strchr(str, 0); + while (--p >= str) + { + switch (*p) + { + case ' ': case '\t': case '\n': case '\r': + *p = 0; break; + default: + return str; + } + } + return str; +} +#endif + +TCHAR* __fastcall rtrim(TCHAR *str) +{ + if (str == NULL) return NULL; + TCHAR* p = _tcschr(str, 0); + while (--p >= str) + { + switch (*p) + { + case ' ': case '\t': case '\n': case '\r': + *p = 0; break; + default: + return str; + } + } + return str; +} + +char* __fastcall ltrim(char* str) +{ + if (str == NULL) return NULL; + char* p = str; + + for (;;) + { + switch (*p) + { + case ' ': case '\t': case '\n': case '\r': + ++p; break; + default: + memmove(str, p, strlen(p) + 1); + return str; + } + } +} + +char* __fastcall ltrimp(char* str) +{ + if (str == NULL) return NULL; + char* p = str; + + for (;;) + { + switch (*p) + { + case ' ': case '\t': case '\n': case '\r': + ++p; break; + default: + return p; + } + } +} + +bool __fastcall wildcmp(char * name, char * mask) +{ + char * last='\0'; + for(;; mask++, name++) + { + if(*mask != '?' && *mask != *name) break; + if(*name == '\0') return ((BOOL)!*mask); + } + if(*mask != '*') return FALSE; + for(;; mask++, name++) + { + while(*mask == '*') + { + last = mask++; + if(*mask == '\0') return ((BOOL)!*mask); /* true */ + } + if(*name == '\0') return ((BOOL)!*mask); /* *mask == EOS */ + if(*mask != '?' && *mask != *name) name -= (size_t)(mask - last) - 1, mask = last; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int LoadUtilsModule(void) +{ + bModuleInitialized = TRUE; + + CreateServiceFunction(MS_UTILS_RESIZEDIALOG,ResizeDialog); + CreateServiceFunction(MS_UTILS_SAVEWINDOWPOSITION,SaveWindowPosition); + CreateServiceFunction(MS_UTILS_RESTOREWINDOWPOSITION,RestoreWindowPosition); + CreateServiceFunction(MS_UTILS_ASSERTINSIDESCREEN,AssertInsideScreen); + CreateServiceFunction(MS_UTILS_GETCOUNTRYBYNUMBER,GetCountryByNumber); + CreateServiceFunction(MS_UTILS_GETCOUNTRYLIST,GetCountryList); + CreateServiceFunction(MS_UTILS_GETRANDOM,GenerateRandom); + CreateServiceFunction(MS_SYSTEM_RESTART,RestartMiranda); + CreateServiceFunction(MS_SYSTEM_GET_MD5I,GetMD5Interface); + CreateServiceFunction(MS_SYSTEM_GET_SHA1I,GetSHA1Interface); + InitOpenUrl(); + InitWindowList(); + InitHyperlink(); + InitColourPicker(); + InitBitmapFilter(); + InitXmlApi(); + InitTimeZones(); + return 0; +} + +void UnloadUtilsModule(void) +{ + if ( !bModuleInitialized ) return; + + FreeWindowList(); + UninitTimeZones(); +} diff --git a/src/modules/utils/windowlist.cpp b/src/modules/utils/windowlist.cpp new file mode 100644 index 0000000000..b7ea59eb58 --- /dev/null +++ b/src/modules/utils/windowlist.cpp @@ -0,0 +1,101 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +static WINDOWLISTENTRY *windowList=NULL; +static int windowListCount=0; +static int nextWindowListId=1; + +static INT_PTR AllocWindowList(WPARAM, LPARAM) +{ + return nextWindowListId++; +} + +static INT_PTR AddToWindowList(WPARAM, LPARAM lParam) +{ + windowList=(WINDOWLISTENTRY*)mir_realloc(windowList,sizeof(WINDOWLISTENTRY)*(windowListCount+1)); + windowList[windowListCount++]=*(WINDOWLISTENTRY*)lParam; + return 0; +} + +static INT_PTR RemoveFromWindowList(WPARAM wParam,LPARAM lParam) +{ + int i; + for(i=0;imessage,msg->wParam,msg->lParam); + return 0; +} + +static INT_PTR BroadcastToWindowListAsync(WPARAM wParam,LPARAM lParam) +{ + int i; + MSG *msg=(MSG*)lParam; + for(i=0;imessage,msg->wParam,msg->lParam); + return 0; +} + +int InitWindowList(void) +{ + CreateServiceFunction(MS_UTILS_ALLOCWINDOWLIST,AllocWindowList); + CreateServiceFunction(MS_UTILS_ADDTOWINDOWLIST,AddToWindowList); + CreateServiceFunction(MS_UTILS_REMOVEFROMWINDOWLIST,RemoveFromWindowList); + CreateServiceFunction(MS_UTILS_BROADCASTTOWINDOWLIST,BroadcastToWindowList); + CreateServiceFunction(MS_UTILS_BROADCASTTOWINDOWLIST_ASYNC,BroadcastToWindowListAsync); + CreateServiceFunction(MS_UTILS_FINDWINDOWINLIST,FindInWindowList); + return 0; +} + +void FreeWindowList(void) +{ + if ( windowList ) { + mir_free(windowList); + windowList = NULL; + } + windowListCount=0; + nextWindowListId=1; +} diff --git a/src/modules/visibility/visibility.cpp b/src/modules/visibility/visibility.cpp new file mode 100644 index 0000000000..aa319cf715 --- /dev/null +++ b/src/modules/visibility/visibility.cpp @@ -0,0 +1,297 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" + +static void SetListGroupIcons(HWND hwndList,HANDLE hFirstItem,HANDLE hParentItem,int *groupChildCount) +{ + int typeOfFirst; + int iconOn[2]={1,1}; + int childCount[2]={0,0},i; + int iImage; + HANDLE hItem,hChildItem; + + typeOfFirst=SendMessage(hwndList,CLM_GETITEMTYPE,(WPARAM)hFirstItem,0); + //check groups + if(typeOfFirst==CLCIT_GROUP) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hFirstItem); + while(hItem) { + hChildItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_CHILD,(LPARAM)hItem); + if(hChildItem) SetListGroupIcons(hwndList,hChildItem,hItem,childCount); + for( i=0; i < SIZEOF(iconOn); i++) + if(iconOn[i] && SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,i)==0) iconOn[i]=0; + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hItem); + } + //check contacts + if(typeOfFirst==CLCIT_CONTACT) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hFirstItem); + while(hItem) { + for ( i=0; i < SIZEOF(iconOn); i++) { + iImage=SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,i); + if(iconOn[i] && iImage==0) iconOn[i]=0; + if(iImage!=0xFF) childCount[i]++; + } + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hItem); + } + //set icons + for( i=0; i < SIZEOF(iconOn); i++) { + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hParentItem,MAKELPARAM(i,childCount[i]?(iconOn[i]?i+1:0):0xFF)); + if(groupChildCount) groupChildCount[i]+=childCount[i]; + } +} + +static void SetAllChildIcons(HWND hwndList,HANDLE hFirstItem,int iColumn,int iImage) +{ + int typeOfFirst,iOldIcon; + HANDLE hItem,hChildItem; + + typeOfFirst=SendMessage(hwndList,CLM_GETITEMTYPE,(WPARAM)hFirstItem,0); + //check groups + if(typeOfFirst==CLCIT_GROUP) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hFirstItem); + while(hItem) { + hChildItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_CHILD,(LPARAM)hItem); + if(hChildItem) SetAllChildIcons(hwndList,hChildItem,iColumn,iImage); + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTGROUP,(LPARAM)hItem); + } + //check contacts + if(typeOfFirst==CLCIT_CONTACT) hItem=hFirstItem; + else hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hFirstItem); + while(hItem) { + iOldIcon=SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,iColumn); + if(iOldIcon!=0xFF && iOldIcon!=iImage) SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(iColumn,iImage)); + hItem=(HANDLE)SendMessage(hwndList,CLM_GETNEXTITEM,CLGN_NEXTCONTACT,(LPARAM)hItem); + } +} + +static void ResetListOptions(HWND hwndList) +{ + int i; + + SendMessage(hwndList,CLM_SETBKBITMAP,0,(LPARAM)(HBITMAP)NULL); + SendMessage(hwndList,CLM_SETBKCOLOR,GetSysColor(COLOR_WINDOW),0); + SendMessage(hwndList,CLM_SETGREYOUTFLAGS,0,0); + SendMessage(hwndList,CLM_SETLEFTMARGIN,2,0); + SendMessage(hwndList,CLM_SETINDENT,10,0); + for(i=0;i<=FONTID_MAX;i++) + SendMessage(hwndList,CLM_SETTEXTCOLOR,i,GetSysColor(COLOR_WINDOWTEXT)); + SetWindowLongPtr(hwndList,GWL_STYLE,GetWindowLongPtr(hwndList,GWL_STYLE)|CLS_SHOWHIDDEN); +} + +static void SetAllContactIcons(HWND hwndList) +{ + HANDLE hContact,hItem; + char *szProto; + DWORD flags; + WORD status; + + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST,0,0); + do { + hItem=(HANDLE)SendMessage(hwndList,CLM_FINDCONTACT,(WPARAM)hContact,0); + if(hItem) { + szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0); + if(szProto==NULL) {flags=0; status=0;} + else { + flags=CallProtoService(szProto,PS_GETCAPS,PFLAGNUM_1,0); + status=DBGetContactSettingWord(hContact,szProto,"ApparentMode",0); + } + if(flags&PF1_INVISLIST) { + if(SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(0,0))==0xFF) + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(0,status==ID_STATUS_ONLINE?1:0)); + } + if(flags&PF1_VISLIST) { + if(SendMessage(hwndList,CLM_GETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(1,0))==0xFF) + SendMessage(hwndList,CLM_SETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(1,status==ID_STATUS_OFFLINE?2:0)); + } + } + } while(hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDNEXT,(WPARAM)hContact,0)); +} + +static INT_PTR CALLBACK DlgProcVisibilityOpts(HWND hwndDlg, UINT msg, WPARAM, LPARAM lParam) +{ + static HICON hVisibleIcon,hInvisibleIcon; + static HANDLE hItemAll; + + switch (msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { HIMAGELIST hIml; + hIml=ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),(IsWinVerXPPlus()?ILC_COLOR32:ILC_COLOR16)|ILC_MASK,3,3); + ImageList_AddIcon_IconLibLoaded(hIml,SKINICON_OTHER_SMALLDOT); + ImageList_AddIcon_IconLibLoaded(hIml,SKINICON_STATUS_INVISIBLE); + ImageList_AddIcon_IconLibLoaded(hIml,SKINICON_STATUS_OFFLINE); + SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_SETEXTRAIMAGELIST,0,(LPARAM)hIml); + hVisibleIcon=ImageList_GetIcon(hIml,1,ILD_NORMAL); + SendDlgItemMessage(hwndDlg,IDC_VISIBLEICON,STM_SETICON,(WPARAM)hVisibleIcon,0); + hInvisibleIcon=ImageList_GetIcon(hIml,2,ILD_NORMAL); + SendDlgItemMessage(hwndDlg,IDC_INVISIBLEICON,STM_SETICON,(WPARAM)hInvisibleIcon,0); + } + + ResetListOptions(GetDlgItem(hwndDlg,IDC_LIST)); + SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_SETEXTRACOLUMNS,2,0); + + { CLCINFOITEM cii={0}; + cii.cbSize=sizeof(cii); + cii.flags=CLCIIF_GROUPFONT; + cii.pszText=TranslateT("** All contacts **"); + hItemAll=(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_ADDINFOITEM,0,(LPARAM)&cii); + } + + SetAllContactIcons(GetDlgItem(hwndDlg,IDC_LIST)); + SetListGroupIcons(GetDlgItem(hwndDlg,IDC_LIST),(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETNEXTITEM,CLGN_ROOT,0),hItemAll,NULL); + return TRUE; + case WM_SETFOCUS: + SetFocus(GetDlgItem(hwndDlg,IDC_LIST)); + break; + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case IDC_LIST: + switch (((LPNMHDR)lParam)->code) + { + case CLN_NEWCONTACT: + case CLN_LISTREBUILT: + SetAllContactIcons(GetDlgItem(hwndDlg,IDC_LIST)); + //fall through + case CLN_CONTACTMOVED: + SetListGroupIcons(GetDlgItem(hwndDlg,IDC_LIST),(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETNEXTITEM,CLGN_ROOT,0),hItemAll,NULL); + break; + case CLN_OPTIONSCHANGED: + ResetListOptions(GetDlgItem(hwndDlg,IDC_LIST)); + break; + case NM_CLICK: + { HANDLE hItem; + NMCLISTCONTROL *nm=(NMCLISTCONTROL*)lParam; + DWORD hitFlags; + int iImage; + int itemType; + + // Make sure we have an extra column + if (nm->iColumn == -1) + break; + + // Find clicked item + hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_HITTEST, (WPARAM)&hitFlags, MAKELPARAM(nm->pt.x,nm->pt.y)); + // Nothing was clicked + if (hItem == NULL) break; + // It was not a visbility icon + if (!(hitFlags & CLCHT_ONITEMEXTRA)) break; + + // Get image in clicked column (0=none, 1=visible, 2=invisible) + iImage = SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nm->iColumn, 0)); + if (iImage == 0) + iImage=nm->iColumn + 1; + else + if (iImage == 1 || iImage == 2) + iImage = 0; + + // Get item type (contact, group, etc...) + itemType = SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETITEMTYPE, (WPARAM)hItem, 0); + + // Update list, making sure that the options are mutually exclusive + if (itemType == CLCIT_CONTACT) { // A contact + SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nm->iColumn, iImage)); + if (iImage && SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(nm->iColumn?0:1,0))!=0xFF) + SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nm->iColumn?0:1, 0)); + } + else if (itemType == CLCIT_INFO) { // All Contacts + SetAllChildIcons(GetDlgItem(hwndDlg, IDC_LIST), hItem, nm->iColumn, iImage); + if (iImage) + SetAllChildIcons(GetDlgItem(hwndDlg, IDC_LIST), hItem, nm->iColumn?0:1, 0); + } + else if (itemType == CLCIT_GROUP) { // A group + hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem); + if (hItem) { + SetAllChildIcons(GetDlgItem(hwndDlg, IDC_LIST), hItem, nm->iColumn, iImage); + if (iImage) + SetAllChildIcons(GetDlgItem(hwndDlg, IDC_LIST), hItem, nm->iColumn?0:1, 0); + } + } + // Update the all/none icons + SetListGroupIcons(GetDlgItem(hwndDlg, IDC_LIST), (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETNEXTITEM, CLGN_ROOT, 0), hItemAll, NULL); + + // Activate Apply button + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + } + break; + case 0: + switch (((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { HANDLE hContact,hItem; + int set,i,iImage; + + hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDFIRST,0,0); + do { + hItem=(HANDLE)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_FINDCONTACT,(WPARAM)hContact,0); + if(hItem) { + set=0; + for(i=0;i<2;i++) { + iImage=SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETEXTRAIMAGE,(WPARAM)hItem,MAKELPARAM(i,0)); + if(iImage==i+1) { + CallContactService(hContact,PSS_SETAPPARENTMODE,iImage==1?ID_STATUS_ONLINE:ID_STATUS_OFFLINE,0); + set=1; + break; + } + } + if(!set) CallContactService(hContact,PSS_SETAPPARENTMODE,0,0); + } + } while(hContact=(HANDLE)CallService(MS_DB_CONTACT_FINDNEXT,(WPARAM)hContact,0)); + return TRUE; + } + } + break; + } + break; + case WM_DESTROY: + DestroyIcon(hVisibleIcon); + DestroyIcon(hInvisibleIcon); + { HIMAGELIST hIml=(HIMAGELIST)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETEXTRAIMAGELIST,0,0); + ImageList_Destroy(hIml); + } + break; + } + return FALSE; +} + +static int VisibilityOptInitialise(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.position = 850000000; + odp.hInstance = hMirandaInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_VISIBILITY); + odp.pszTitle = LPGEN("Visibility"); + odp.pszGroup = LPGEN("Status"); + odp.pfnDlgProc = DlgProcVisibilityOpts; + odp.flags = ODPF_BOLDGROUPS; + CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp ); + return 0; +} + +int LoadVisibilityModule(void) +{ + HookEvent(ME_OPT_INITIALISE,VisibilityOptInitialise); + return 0; +} diff --git a/src/modules/xml/xmlApi.cpp b/src/modules/xml/xmlApi.cpp new file mode 100644 index 0000000000..08887dd448 --- /dev/null +++ b/src/modules/xml/xmlApi.cpp @@ -0,0 +1,446 @@ +/* + +Miranda IM: the free IM client for Microsoft* Windows* + +Copyright 2000-2009 Miranda ICQ/IM project, +all portions of this codebase are copyrighted to the people +listed in contributors.txt. + +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 "commonheaders.h" +#include "xmlParser.h" + +static HXML xmlapiCreateNode( LPCTSTR name, LPCTSTR text, char isDeclaration ) +{ + XMLNode result = XMLNode::createXMLTopNode( name, isDeclaration ); + if ( text ) + result.updateText( text ); + return result.detach(); +} + +static void xmlapiDestroyNode( HXML n ) +{ + XMLNode tmp; tmp.attach(n); +} + +static HXML xmlapiParseString( LPCTSTR str, int* datalen, LPCTSTR tag ) +{ + if (str == NULL) return NULL; + + XMLResults res; + XMLNode result = XMLNode::parseString( str, tag, &res ); + + if ( datalen != NULL ) + datalen[0] += res.nChars; + + return (res.error == eXMLErrorNone || (tag != NULL && res.error == eXMLErrorMissingEndTag)) ? result.detach() : NULL; +} + +static HXML xmlapiAddChild( HXML _n, LPCTSTR name, LPCTSTR text ) +{ + XMLNode result = XMLNode(_n).addChild( name ); + if ( text != NULL ) + result.updateText( text ); + return result; +} + +static void xmlapiAddChild2( HXML _child, HXML _parent ) +{ + XMLNode child(_child), parent(_parent); + parent.addChild( child ); +} + +static HXML xmlapiCopyNode( HXML _n ) +{ + XMLNode result = XMLNode(_n); + return result.detach(); +} + +static LPCTSTR xmlapiGetAttr( HXML _n, int i ) +{ + return XMLNode(_n).getAttributeValue( i ); +} + +static int xmlapiGetAttrCount( HXML _n ) +{ + return XMLNode(_n).nAttribute(); +} + +static LPCTSTR xmlapiGetAttrName( HXML _n, int i ) +{ + return XMLNode(_n).getAttributeName( i ); +} + +static HXML xmlapiGetChild( HXML _n, int i ) +{ + return XMLNode(_n).getChildNode( i ); +} + +static HXML xmlapiGetChildByAttrValue( HXML _n, LPCTSTR name, LPCTSTR attrName, LPCTSTR attrValue ) +{ + return XMLNode(_n).getChildNodeWithAttribute( name, attrName, attrValue ); +} + +static int xmlapiGetChildCount( HXML _n ) +{ + return XMLNode(_n).nChildNode(); +} + +static HXML xmlapiGetFirstChild( HXML _n ) +{ + return XMLNode(_n).getChildNode( 0 ); +} + +static HXML xmlapiGetNthChild( HXML _n, LPCTSTR name, int i ) +{ + return XMLNode(_n).getChildNode( name, i ); +} + +static HXML xmlapiGetNextChild( HXML _n, LPCTSTR name, int* i ) +{ + return XMLNode(_n).getChildNode( name, i ); +} + +static HXML xmlapiGetNextNode( HXML _n ) +{ + return XMLNode(_n).getNextNode( ); +} + +static HXML xmlapiGetChildByPath( HXML _n, LPCTSTR path, char createNodeIfMissing ) +{ + return XMLNode(_n).getChildNodeByPath( path, createNodeIfMissing ); +} + +static LPCTSTR xmlapiGetName( HXML _n ) +{ + return XMLNode(_n).getName(); +} + +static HXML xmlapiGetParent( HXML _n ) +{ + return XMLNode(_n).getParentNode(); +} + +static LPCTSTR xmlapiGetText( HXML _n ) +{ + return XMLNode(_n).getInnerText(); +} + +static LPCTSTR xmlapiGetAttrValue( HXML _n, LPCTSTR attrName ) +{ + return XMLNode(_n).getAttribute( attrName ); +} + +static void xmlapiSetText( HXML _n, LPCTSTR _text ) +{ + XMLNode(_n).updateText( _text ); +} + +static LPTSTR xmlapiToString( HXML _n, int* datalen ) +{ + return XMLNode(_n).createXMLString( 0, datalen ); +} + +static void xmlapiAddAttr( HXML _n, LPCTSTR attrName, LPCTSTR attrValue ) +{ + if ( attrName != NULL && attrValue != NULL ) + XMLNode(_n).addAttribute( attrName, attrValue ); +} + +static void xmlapiAddAttrInt( HXML _n, LPCTSTR attrName, int attrValue ) +{ + TCHAR buf[40]; + _itot( attrValue, buf, 10 ); + XMLNode(_n).addAttribute( attrName, buf ); +} + +static void xmlapiFree( void* p ) +{ + free( p ); +} + +// XML API v2 methods +static int xmlapiGetTextCount( HXML _n ) +{ + return XMLNode(_n).nText(); +} + +static LPCTSTR xmlapiGetTextByIndex( HXML _n, int i ) +{ + return XMLNode(_n).getText( i ); +} + +static void xmlapiSetTextByIndex( HXML _n, int i, LPCTSTR value ) +{ + XMLNode(_n).updateText( value, i ); +} + +static void xmlapiAddText( HXML _n, LPCTSTR value, XML_ELEMENT_POS pos ) +{ + XMLNode(_n).addText( value, ( XMLElementPosition )pos ); +} + +static LPTSTR xmlapiToStringWithFormatting( HXML _n, int* datalen ) +{ + return XMLNode(_n).createXMLString( 1, datalen ); +} + +static int xmlapiGetClearCount( HXML _n ) +{ + return XMLNode(_n).nClear(); +} + +static LPCTSTR xmlapiGetClear( HXML _n, int i, LPCTSTR *openTag, LPCTSTR *closeTag ) +{ + XMLClear c = XMLNode(_n).getClear( i ); + if ( openTag ) + *openTag = c.lpszOpenTag; + if ( closeTag ) + *closeTag = c.lpszCloseTag; + return c.lpszValue; +} + +static void xmlapiAddClear( HXML _n, LPCTSTR lpszValue, LPCTSTR openTag, LPCTSTR closeTag, XML_ELEMENT_POS pos ) +{ + XMLNode(_n).addClear( lpszValue, openTag, closeTag, ( XMLElementPosition )pos ); +} + +static void xmlapiSetClear( HXML _n, int i, LPCTSTR lpszValue ) +{ + XMLNode(_n).updateClear( lpszValue, i ); +} + +static int xmlapiGetElement( HXML _n, XML_ELEMENT_POS pos, XML_ELEMENT_TYPE *type, HXML *child, LPCTSTR *value, LPCTSTR *name, LPCTSTR *openTag, LPCTSTR *closeTag ) +{ + // reset all values + if ( child ) + *child = NULL; + if ( value ) + *value = NULL; + if ( name ) + *name = NULL; + if ( openTag ) + *openTag = NULL; + if ( closeTag ) + *closeTag = NULL; + + if ( !type || pos >= XMLNode(_n).nElement()) + return false; + XMLNodeContents c( XMLNode(_n).enumContents( ( XMLElementPosition )pos )); + switch ( c.etype ) { + case eNodeChild: + { + *type = XML_ELEM_TYPE_CHILD; + if ( child ) + *child = c.child; + } break; + case eNodeAttribute: + { + *type = XML_ELEM_TYPE_ATTRIBUTE; + if ( name ) + *name = c.attrib.lpszName; + if ( value ) + *value = c.attrib.lpszValue; + } break; + case eNodeText: + { + *type = XML_ELEM_TYPE_TEXT; + if ( value ) + *value = c.text; + } break; + case eNodeClear: + { + *type = XML_ELEM_TYPE_CLEAR; + if ( value ) + *value = c.clear.lpszValue; + if ( openTag ) + *openTag = c.clear.lpszOpenTag; + if ( closeTag ) + *closeTag = c.clear.lpszCloseTag; + } break; + case eNodeNULL: + { + return false; + } break; + } + return true; +} + +static int xmlapiGetElementCount( HXML _n ) +{ + return XMLNode(_n).nElement(); +} + +static char xmlapiIsDeclaration( HXML _n ) +{ + return XMLNode(_n).isDeclaration(); +} + +static HXML xmlapiDeepCopy( HXML _n ) +{ + return XMLNode(_n).deepCopy().detach(); +} + +static HXML xmlapiAddChildEx( HXML _n, LPCTSTR name, char isDeclaration, XML_ELEMENT_POS pos ) +{ + return XMLNode(_n).addChild( name, isDeclaration, ( XMLElementPosition )pos ); +} + +static void xmlapiAddChildEx2( HXML _n, HXML parent, XML_ELEMENT_POS pos ) +{ + XMLNode(_n).addChild( parent, ( XMLElementPosition )pos ); +} + +static void xmlapiSetAttrByIndex( HXML _n, int i, LPCTSTR value ) +{ + XMLNode(_n).updateAttribute( value, NULL, i ); +} + +static void xmlapiSetAttrByName( HXML _n, LPCTSTR name, LPCTSTR value ) +{ + XMLNode(_n).updateAttribute( value, NULL, name ); +} + +static void xmlapiDeleteNodeContent( HXML _n ) +{ + XMLNode(_n).deleteNodeContent(); +} + +static void xmlapiDeleteAttrByIndex( HXML _n, int i ) +{ + XMLNode(_n).deleteAttribute( i ); +} + +static void xmlapiDeleteAttrByName( HXML _n, LPCTSTR name ) +{ + XMLNode(_n).deleteAttribute( name ); +} + +static void xmlapiDeleteText( HXML _n, int i ) +{ + XMLNode(_n).deleteText( i ); +} + +static void xmlapiDeleteClear( HXML _n, int i ) +{ + XMLNode(_n).deleteClear( i ); +} + +static XML_ELEMENT_POS xmlapiPositionOfText( HXML _n, int i ) +{ + return ( XML_ELEMENT_POS )XMLNode(_n).positionOfText( i ); +} + +static XML_ELEMENT_POS xmlapiPositionOfClear( HXML _n, int i ) +{ + return ( XML_ELEMENT_POS )XMLNode(_n).positionOfClear( i ); +} + +static XML_ELEMENT_POS xmlapiPositionOfChildByIndex( HXML _n, int i ) +{ + return ( XML_ELEMENT_POS )XMLNode(_n).positionOfChildNode( i ); +} + +static XML_ELEMENT_POS xmlapiPositionOfChildByNode( HXML _n, HXML child ) +{ + return ( XML_ELEMENT_POS )XMLNode(_n).positionOfChildNode( child ); +} + +static XML_ELEMENT_POS xmlapiPositionOfChildByName( HXML _n, LPCTSTR name, int i ) +{ + return ( XML_ELEMENT_POS )XMLNode(_n).positionOfChildNode( name, i ); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR GetXmlApi( WPARAM, LPARAM lParam ) +{ + XML_API* xi = ( XML_API* )lParam; + if ( xi == NULL ) + return FALSE; + + if ( xi->cbSize != XML_API_SIZEOF_V1 && xi->cbSize != sizeof(XML_API)) + return FALSE; + + xi->createNode = xmlapiCreateNode; + xi->destroyNode = xmlapiDestroyNode; + + xi->parseString = xmlapiParseString; + xi->toString = xmlapiToString; + xi->freeMem = xmlapiFree; + + xi->addChild = xmlapiAddChild; + xi->addChild2 = xmlapiAddChild2; + xi->copyNode = xmlapiCopyNode; + xi->getChild = xmlapiGetChild; + xi->getChildByAttrValue = xmlapiGetChildByAttrValue; + xi->getChildCount = xmlapiGetChildCount; + xi->getFirstChild = xmlapiGetFirstChild; + xi->getNthChild = xmlapiGetNthChild; + xi->getNextChild = xmlapiGetNextChild; + xi->getNextNode = xmlapiGetNextNode; + xi->getChildByPath = xmlapiGetChildByPath; + xi->getName = xmlapiGetName; + xi->getParent = xmlapiGetParent; + xi->getText = xmlapiGetText; + xi->setText = xmlapiSetText; + + xi->getAttr = xmlapiGetAttr; + xi->getAttrCount = xmlapiGetAttrCount; + xi->getAttrName = xmlapiGetAttrName; + xi->getAttrValue = xmlapiGetAttrValue; + xi->addAttr = xmlapiAddAttr; + xi->addAttrInt = xmlapiAddAttrInt; + + if ( xi->cbSize > XML_API_SIZEOF_V1 ) { + xi->isDeclaration = xmlapiIsDeclaration; + xi->toStringWithFormatting = xmlapiToStringWithFormatting; + xi->deepCopy = xmlapiDeepCopy; + xi->setAttrByIndex = xmlapiSetAttrByIndex; + xi->setAttrByName = xmlapiSetAttrByName; + xi->addChildEx = xmlapiAddChildEx; + xi->addChildEx2 = xmlapiAddChildEx2; + xi->getTextCount = xmlapiGetTextCount; + xi->getTextByIndex = xmlapiGetTextByIndex; + xi->addText = xmlapiAddText; + xi->setTextByIndex = xmlapiSetTextByIndex; + xi->getClearCount = xmlapiGetClearCount; + xi->getClear = xmlapiGetClear; + xi->addClear = xmlapiAddClear; + xi->setClear = xmlapiSetClear; + xi->getElementCount = xmlapiGetElementCount; + xi->getElement = xmlapiGetElement; + + xi->deleteNodeContent = xmlapiDeleteNodeContent; + xi->deleteAttrByIndex = xmlapiDeleteAttrByIndex; + xi->deleteAttrByName = xmlapiDeleteAttrByName; + xi->deleteText = xmlapiDeleteText; + xi->deleteClear = xmlapiDeleteClear; + + xi->positionOfChildByIndex = xmlapiPositionOfChildByIndex; + xi->positionOfChildByNode = xmlapiPositionOfChildByNode; + xi->positionOfChildByName = xmlapiPositionOfChildByName; + xi->positionOfText = xmlapiPositionOfText; + xi->positionOfClear = xmlapiPositionOfClear; + } + return TRUE; +} + +void InitXmlApi( void ) +{ + CreateServiceFunction( MS_SYSTEM_GET_XI, GetXmlApi ); +} diff --git a/src/modules/xml/xmlParser.cpp b/src/modules/xml/xmlParser.cpp new file mode 100644 index 0000000000..f2afae563f --- /dev/null +++ b/src/modules/xml/xmlParser.cpp @@ -0,0 +1,3061 @@ +/** +**************************************************************************** +*

XML.c - implementation file for basic XML parser written in ANSI C++ +* for portability. It works by using recursion and a node tree for breaking +* down the elements of an XML document.

+* +* @version V2.43 +* @author Frank Vanden Berghen +* +* NOTE: +* +* If you add "#define STRICT_PARSING", on the first line of this file +* the parser will see the following XML-stream: +* some textother text +* as an error. Otherwise, this tring will be equivalent to: +* some textother text +* +* NOTE: +* +* If you add "#define APPROXIMATE_PARSING" on the first line of this file +* the parser will see the following XML-stream: +* +* +* +* as equivalent to the following XML-stream: +* +* +* +* This can be useful for badly-formed XML-streams but prevent the use +* of the following XML-stream (problem is: tags at contiguous levels +* have the same names): +* +* +* +* +* +* +* NOTE: +* +* If you add "#define _XMLPARSER_NO_MESSAGEBOX_" on the first line of this file +* the "openFileHelper" function will always display error messages inside the +* console instead of inside a message-box-window. Message-box-windows are +* available on windows 9x/NT/2000/XP/Vista only. +* +* Copyright (c) 2002, Business-Insight +* Business-Insight +* All rights reserved. +* See the file "AFPL-license.txt" about the licensing terms +* +**************************************************************************** +*/ + +#include "commonheaders.h" +#include "xmlParser.h" + +#include +#include +#include +#include +#include + +XMLCSTR XMLNode::getVersion() { return _CXML("v2.43"); } +void freeXMLString(XMLSTR t){if(t)free(t);} + +static XMLNode::XMLCharEncoding characterEncoding=XMLNode::char_encoding_UTF8; +static char guessWideCharChars=1, dropWhiteSpace=1, removeCommentsInMiddleOfText=1; + +inline int mmin( const int t1, const int t2 ) { return t1 < t2 ? t1 : t2; } + +// You can modify the initialization of the variable "XMLClearTags" below +// to change the clearTags that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. +// The "") }, + { _CXML("") }, + { _CXML("") }, + { _CXML("
")    ,5,  _CXML("
") }, + // { _CXML("")}, + { NULL ,0, NULL } +}; + +// You can modify the initialization of the variable "XMLEntities" below +// to change the character entities that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. Additionally, the syntaxes " " and " " are recognized. +typedef struct { XMLCSTR s; int l; XMLCHAR c;} XMLCharacterEntity; +static XMLCharacterEntity XMLEntities[] = +{ + { _CXML("&" ), 5, _CXML('&' )}, + { _CXML("<" ), 4, _CXML('<' )}, + { _CXML(">" ), 4, _CXML('>' )}, + { _CXML("""), 6, _CXML('\"')}, + { _CXML("'"), 6, _CXML('\'')}, + { NULL , 0, '\0' } +}; + +// When rendering the XMLNode to a string (using the "createXMLString" function), +// you can ask for a beautiful formatting. This formatting is using the +// following indentation character: +#define INDENTCHAR _CXML('\t') + +// The following function parses the XML errors into a user friendly string. +// You can edit this to change the output language of the library to something else. +XMLCSTR XMLNode::getError(XMLError xerror) +{ + switch (xerror) + { + case eXMLErrorNone: return _CXML("No error"); + case eXMLErrorMissingEndTag: return _CXML("Warning: Unmatched end tag"); + case eXMLErrorNoXMLTagFound: return _CXML("Warning: No XML tag found"); + case eXMLErrorEmpty: return _CXML("Error: No XML data"); + case eXMLErrorMissingTagName: return _CXML("Error: Missing start tag name"); + case eXMLErrorMissingEndTagName: return _CXML("Error: Missing end tag name"); + case eXMLErrorUnmatchedEndTag: return _CXML("Error: Unmatched end tag"); + case eXMLErrorUnmatchedEndClearTag: return _CXML("Error: Unmatched clear tag end"); + case eXMLErrorUnexpectedToken: return _CXML("Error: Unexpected token found"); + case eXMLErrorNoElements: return _CXML("Error: No elements found"); + case eXMLErrorFileNotFound: return _CXML("Error: File not found"); + case eXMLErrorFirstTagNotFound: return _CXML("Error: First Tag not found"); + case eXMLErrorUnknownCharacterEntity:return _CXML("Error: Unknown character entity"); + case eXMLErrorCharacterCodeAbove255: return _CXML("Error: Character code above 255 is forbidden in MultiByte char mode."); + case eXMLErrorCharConversionError: return _CXML("Error: unable to convert between WideChar and MultiByte chars"); + case eXMLErrorCannotOpenWriteFile: return _CXML("Error: unable to open file for writing"); + case eXMLErrorCannotWriteFile: return _CXML("Error: cannot write into file"); + + case eXMLErrorBase64DataSizeIsNotMultipleOf4: return _CXML("Warning: Base64-string length is not a multiple of 4"); + case eXMLErrorBase64DecodeTruncatedData: return _CXML("Warning: Base64-string is truncated"); + case eXMLErrorBase64DecodeIllegalCharacter: return _CXML("Error: Base64-string contains an illegal character"); + case eXMLErrorBase64DecodeBufferTooSmall: return _CXML("Error: Base64 decode output buffer is too small"); + }; + return _CXML("Unknown"); +} + +///////////////////////////////////////////////////////////////////////// +// Here start the abstraction layer to be OS-independent // +///////////////////////////////////////////////////////////////////////// + +// Here is an abstraction layer to access some common string manipulation functions. +// The abstraction layer is currently working for gcc, Microsoft Visual Studio 6.0, +// Microsoft Visual Studio .NET, CC (sun compiler) and Borland C++. +// If you plan to "port" the library to a new system/compiler, all you have to do is +// to edit the following lines. +#ifdef XML_NO_WIDE_CHAR +char myIsTextWideChar(const void *b, int len) { return FALSE; } +#else +#if defined (UNDER_CE) || !defined(_XMLWINDOWS) +char myIsTextWideChar(const void *b, int len) // inspired by the Wine API: RtlIsTextUnicode +{ +#ifdef sun + // for SPARC processors: wchar_t* buffers must always be alligned, otherwise it's a char* buffer. + if ((((unsigned long)b)%sizeof(wchar_t))!=0) return FALSE; +#endif + const wchar_t *s=(const wchar_t*)b; + + // buffer too small: + if (len<(int)sizeof(wchar_t)) return FALSE; + + // odd length test + if (len&1) return FALSE; + + /* only checks the first 256 characters */ + len=mmin(256,len/sizeof(wchar_t)); + + // Check for the special byte order: + if (*((unsigned short*)s) == 0xFFFE) return TRUE; // IS_TEXT_UNICODE_REVERSE_SIGNATURE; + if (*((unsigned short*)s) == 0xFEFF) return TRUE; // IS_TEXT_UNICODE_SIGNATURE + + // checks for ASCII characters in the UNICODE stream + int i,stats=0; + for (i=0; ilen/2) return TRUE; + + // Check for UNICODE NULL chars + for (i=0; i +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncasecmp(c1,c2,l);} +static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncmp(c1,c2,l);} +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return wscasecmp(c1,c2); } +#else +static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncmp(c1,c2,l);} +#ifdef __linux__ +// for gcc/linux +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncasecmp(c1,c2,l);} +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return wcscasecmp(c1,c2); } +#else +#include +// for gcc/non-linux (MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 4.3.2, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin, mingw) +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) +{ + wchar_t left,right; + do + { + left=towlower(*c1++); right=towlower(*c2++); + } while (left&&(left==right)); + return (int)left-(int)right; +} +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) +{ + wchar_t left,right; + while(l--) + { + left=towlower(*c1++); right=towlower(*c2++); + if ((!left)||(left!=right)) return (int)left-(int)right; + } + return 0; +} +#endif +#endif +static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)wcsstr(c1,c2); } +static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)wcscpy(c1,c2); } +static inline FILE *xfopen(XMLCSTR filename,XMLCSTR mode) +{ + char *filenameAscii=myWideCharToMultiByte(filename); + FILE *f; + if (mode[0]==_CXML('r')) f=fopen(filenameAscii,"rb"); + else f=fopen(filenameAscii,"wb"); + free(filenameAscii); + return f; +} +#else +static inline FILE *xfopen(XMLCSTR filename,XMLCSTR mode) { return fopen(filename,mode); } +static inline int xstrlen(XMLCSTR c) { return strlen(c); } +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncasecmp(c1,c2,l);} +static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncmp(c1,c2,l);} +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return strcasecmp(c1,c2); } +static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)strstr(c1,c2); } +static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)strcpy(c1,c2); } +#endif +static inline int _strnicmp(const char *c1,const char *c2, int l) { return strncasecmp(c1,c2,l);} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// the "xmltoc,xmltob,xmltoi,xmltol,xmltof,xmltoa" functions // +/////////////////////////////////////////////////////////////////////////////// +// These 6 functions are not used inside the XMLparser. +// There are only here as "convenience" functions for the user. +// If you don't need them, you can delete them without any trouble. +#ifdef _XMLWIDECHAR +#ifdef _XMLWINDOWS +// for Microsoft Visual Studio 6.0 and Microsoft Visual Studio .NET and Borland C++ Builder 6.0 +char xmltob(XMLCSTR t,char v){ if (t&&(*t)) return (char)_wtoi(t); return v; } +int xmltoi(XMLCSTR t,int v){ if (t&&(*t)) return _wtoi(t); return v; } +long xmltol(XMLCSTR t,long v){ if (t&&(*t)) return _wtol(t); return v; } +double xmltof(XMLCSTR t,double v){ if (t&&(*t)) swscanf(t, L"%lf", &v); /*v=_wtof(t);*/ return v; } +#else +#ifdef sun +// for CC +#include +char xmltob(XMLCSTR t,char v){ if (t) return (char)wstol(t,NULL,10); return v; } +int xmltoi(XMLCSTR t,int v){ if (t) return (int)wstol(t,NULL,10); return v; } +long xmltol(XMLCSTR t,long v){ if (t) return wstol(t,NULL,10); return v; } +#else +// for gcc +char xmltob(XMLCSTR t,char v){ if (t) return (char)wcstol(t,NULL,10); return v; } +int xmltoi(XMLCSTR t,int v){ if (t) return (int)wcstol(t,NULL,10); return v; } +long xmltol(XMLCSTR t,long v){ if (t) return wcstol(t,NULL,10); return v; } +#endif +double xmltof(XMLCSTR t,double v){ if (t&&(*t)) swscanf(t, L"%lf", &v); /*v=_wtof(t);*/ return v; } +#endif +#else +char xmltob(XMLCSTR t,char v){ if (t&&(*t)) return (char)atoi(t); return v; } +int xmltoi(XMLCSTR t,int v){ if (t&&(*t)) return atoi(t); return v; } +long xmltol(XMLCSTR t,long v){ if (t&&(*t)) return atol(t); return v; } +double xmltof(XMLCSTR t,double v){ if (t&&(*t)) return atof(t); return v; } +#endif +XMLCSTR xmltoa(XMLCSTR t,XMLCSTR v){ if (t) return t; return v; } +XMLCHAR xmltoc(XMLCSTR t,const XMLCHAR v){ if (t&&(*t)) return *t; return v; } + +///////////////////////////////////////////////////////////////////////// +// the "openFileHelper" function // +///////////////////////////////////////////////////////////////////////// + +// Since each application has its own way to report and deal with errors, you should modify & rewrite +// the following "openFileHelper" function to get an "error reporting mechanism" tailored to your needs. +XMLNode XMLNode::openFileHelper(XMLCSTR filename, XMLCSTR tag) +{ + // guess the value of the global parameter "characterEncoding" + // (the guess is based on the first 200 bytes of the file). + FILE *f=xfopen(filename,_CXML("rb")); + if (f) + { + char bb[205]; + int l=(int)fread(bb,1,200,f); + setGlobalOptions(guessCharEncoding(bb,l),guessWideCharChars,dropWhiteSpace,removeCommentsInMiddleOfText); + fclose(f); + } + + // parse the file + XMLResults pResults; + XMLNode xnode=XMLNode::parseFile(filename,tag,&pResults); + + // display error message (if any) + if (pResults.error != eXMLErrorNone) + { + // create message + char message[2000],*s1=(char*)"",*s3=(char*)""; XMLCSTR s2=_CXML(""); + if (pResults.error==eXMLErrorFirstTagNotFound) { s1=(char*)"First Tag should be '"; s2=tag; s3=(char*)"'.\n"; } + sprintf(message, +#ifdef _XMLWIDECHAR + "XML Parsing error inside file '%S'.\n%S\nAt line %i, column %i.\n%s%S%s" +#else + "XML Parsing error inside file '%s'.\n%s\nAt line %i, column %i.\n%s%s%s" +#endif + ,filename,XMLNode::getError(pResults.error),pResults.nLine,pResults.nColumn,s1,s2,s3); + + // display message +#if defined(_XMLWINDOWS) && !defined(UNDER_CE) && !defined(_XMLPARSER_NO_MESSAGEBOX_) + MessageBoxA(NULL,message,"XML Parsing error",MB_OK|MB_ICONERROR|MB_TOPMOST); +#else + printf("%s",message); +#endif + exit(255); + } + return xnode; +} + +///////////////////////////////////////////////////////////////////////// +// Here start the core implementation of the XMLParser library // +///////////////////////////////////////////////////////////////////////// + +// You should normally not change anything below this point. + +#ifndef _XMLWIDECHAR +// If "characterEncoding=ascii" then we assume that all characters have the same length of 1 byte. +// If "characterEncoding=UTF8" then the characters have different lengths (from 1 byte to 4 bytes). +// If "characterEncoding=ShiftJIS" then the characters have different lengths (from 1 byte to 2 bytes). +// This table is used as lookup-table to know the length of a character (in byte) based on the +// content of the first byte of the character. +// (note: if you modify this, you must always have XML_utf8ByteTable[0]=0 ). +static const char XML_utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 End of ASCII range + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 0x80 to 0xc1 invalid + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 0xc2 to 0xdf 2 byte + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,// 0xe0 0xe0 to 0xef 3 byte + 4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; +static const char XML_legacyByteTable[256] = +{ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +}; +static const char XML_sjisByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x80 0x81 to 0x9F 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xc0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 0xe0 to 0xef 2 bytes + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 +}; +static const char XML_gb2312ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xa0 0xa1 to 0xf7 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xb0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 + 2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1 // 0xf0 +}; +static const char XML_gbk_big5_ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x80 0x81 to 0xfe 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x90 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xa0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xb0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1 // 0xf0 +}; +static const char *XML_ByteTable=(const char *)XML_utf8ByteTable; // the default is "characterEncoding=XMLNode::encoding_UTF8" +#endif + + +XMLNode XMLNode::emptyXMLNode; +XMLClear XMLNode::emptyXMLClear={ NULL, NULL, NULL}; +XMLAttribute XMLNode::emptyXMLAttribute={ NULL, NULL}; + +// Enumeration used to decipher what type a token is +typedef enum XMLTokenTypeTag +{ + eTokenText = 0, + eTokenQuotedText, + eTokenTagStart, /* "<" */ + eTokenTagEnd, /* "" */ + eTokenEquals, /* "=" */ + eTokenDeclaration, /* "" */ + eTokenClear, + eTokenError +} XMLTokenType; + +// Main structure used for parsing XML +typedef struct XML +{ + XMLCSTR lpXML; + XMLCSTR lpszText; + int nIndex,nIndexMissigEndTag; + enum XMLError error; + XMLCSTR lpEndTag; + int cbEndTag; + XMLCSTR lpNewElement; + int cbNewElement; + int nFirst; +} XML; + +typedef struct +{ + ALLXMLClearTag *pClr; + XMLCSTR pStr; +} NextToken; + +// Enumeration used when parsing attributes +typedef enum Attrib +{ + eAttribName = 0, + eAttribEquals, + eAttribValue +} Attrib; + +// Enumeration used when parsing elements to dictate whether we are currently +// inside a tag +typedef enum XMLStatus +{ + eInsideTag = 0, + eOutsideTag +} XMLStatus; + +XMLError XMLNode::writeToFile(XMLCSTR filename, const char *encoding, char nFormat) const +{ + if (!d) return eXMLErrorNone; + FILE *f=xfopen(filename,_CXML("wb")); + if (!f) return eXMLErrorCannotOpenWriteFile; +#ifdef _XMLWIDECHAR + unsigned char h[2]={ 0xFF, 0xFE }; + if (!fwrite(h,2,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + if ((!isDeclaration())&&((d->lpszName)||(!getChildNode().isDeclaration()))) + { + if (!fwrite(L"\n",sizeof(wchar_t)*40,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } +#else + if ((!isDeclaration())&&((d->lpszName)||(!getChildNode().isDeclaration()))) + { + if (characterEncoding==char_encoding_UTF8) + { + // header so that windows recognize the file as UTF-8: + unsigned char h[3]={0xEF,0xBB,0xBF}; + if (!fwrite(h,3,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + encoding="utf-8"; + } else if (characterEncoding==char_encoding_ShiftJIS) encoding="SHIFT-JIS"; + + if (!encoding) encoding="ISO-8859-1"; + if (fprintf(f,"\n",encoding)<0) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } else + { + if (characterEncoding==char_encoding_UTF8) + { + unsigned char h[3]={0xEF,0xBB,0xBF}; + if (!fwrite(h,3,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } + } +#endif + int i; + XMLSTR t=createXMLString(nFormat,&i); + if (!fwrite(t,sizeof(XMLCHAR)*i,1,f)) + { + free(t); + fclose(f); + return eXMLErrorCannotWriteFile; + } + if (fclose(f)!=0) + { + free(t); + return eXMLErrorCannotWriteFile; + } + free(t); + return eXMLErrorNone; +} + +// Duplicate a given string. +XMLSTR stringDup(XMLCSTR lpszData, int cbData) +{ + if (lpszData==NULL) return NULL; + + XMLSTR lpszNew; + if (cbData==-1) cbData=(int)xstrlen(lpszData); + lpszNew = (XMLSTR)malloc((cbData+1) * sizeof(XMLCHAR)); + if (lpszNew) + { + memcpy(lpszNew, lpszData, (cbData) * sizeof(XMLCHAR)); + lpszNew[cbData] = (XMLCHAR)NULL; + } + return lpszNew; +} + +XMLSTR ToXMLStringTool::toXMLUnSafe(XMLSTR dest,XMLCSTR source) +{ + XMLSTR dd=dest; + XMLCHAR ch; + XMLCharacterEntity *entity; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) {xstrcpy(dest,entity->s); dest+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLWIDECHAR + *(dest++)=*(source++); +#else + switch(XML_ByteTable[(unsigned char)ch]) + { + case 4: *(dest++)=*(source++); + case 3: *(dest++)=*(source++); + case 2: *(dest++)=*(source++); + case 1: *(dest++)=*(source++); + } +#endif +out_of_loop1: + ; + } + *dest=0; + return dd; +} + +// private (used while rendering): +int ToXMLStringTool::lengthXMLString(XMLCSTR source) +{ + int r=0; + XMLCharacterEntity *entity; + XMLCHAR ch; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) { r+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLWIDECHAR + r++; source++; +#else + ch=XML_ByteTable[(unsigned char)ch]; r+=ch; source+=ch; +#endif +out_of_loop1: + ; + } + return r; +} + +ToXMLStringTool::~ToXMLStringTool(){ freeBuffer(); } +void ToXMLStringTool::freeBuffer(){ if (buf) free(buf); buf=NULL; buflen=0; } +XMLSTR ToXMLStringTool::toXML(XMLCSTR source) +{ + if (!source) + { + if (buflen<1) { buflen=1; buf=(XMLSTR)malloc(sizeof(XMLCHAR)); } + *buf=0; + return buf; + } + int l=lengthXMLString(source)+1; + if (l>buflen) { freeBuffer(); buflen=l; buf=(XMLSTR)malloc(l*sizeof(XMLCHAR)); } + return toXMLUnSafe(buf,source); +} + +// private: +XMLSTR fromXMLString(XMLCSTR s, int lo, XML *pXML) +{ + // This function is the opposite of the function "toXMLString". It decodes the escape + // sequences &, ", ', <, > and replace them by the characters + // &,",',<,>. This function is used internally by the XML Parser. All the calls to + // the XML library will always gives you back "decoded" strings. + // + // in: string (s) and length (lo) of string + // out: new allocated string converted from xml + if (!s) return NULL; + + int ll=0,j; + XMLSTR d; + XMLCSTR ss=s; + XMLCharacterEntity *entity; + while ((lo>0)&&(*s)) + { + if (*s==_CXML('&')) + { + if ((lo>2)&&(s[1]==_CXML('#'))) + { + s+=2; lo-=2; + if ((*s==_CXML('X'))||(*s==_CXML('x'))) { s++; lo--; } + while ((*s)&&(*s!=_CXML(';'))&&((lo--)>0)) s++; + if (*s!=_CXML(';')) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + s++; lo--; + } else + { + entity=XMLEntities; + do + { + if ((lo>=entity->l)&&(xstrnicmp(s,entity->s,entity->l)==0)) { s+=entity->l; lo-=entity->l; break; } + entity++; + } while(entity->s); + if (!entity->s) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + } + } else + { +#ifdef _XMLWIDECHAR + s++; lo--; +#else + j=XML_ByteTable[(unsigned char)*s]; s+=j; lo-=j; ll+=j-1; +#endif + } + ll++; + } + + d=(XMLSTR)malloc((ll+1)*sizeof(XMLCHAR)); + s=d; + while (ll-->0) + { + if (*ss==_CXML('&')) + { + if (ss[1]==_CXML('#')) + { + ss+=2; j=0; + if ((*ss==_CXML('X'))||(*ss==_CXML('x'))) + { + ss++; + while (*ss!=_CXML(';')) + { + if ((*ss>=_CXML('0'))&&(*ss<=_CXML('9'))) j=(j<<4)+*ss-_CXML('0'); + else if ((*ss>=_CXML('A'))&&(*ss<=_CXML('F'))) j=(j<<4)+*ss-_CXML('A')+10; + else if ((*ss>=_CXML('a'))&&(*ss<=_CXML('f'))) j=(j<<4)+*ss-_CXML('a')+10; + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } else + { + while (*ss!=_CXML(';')) + { + if ((*ss>=_CXML('0'))&&(*ss<=_CXML('9'))) j=(j*10)+*ss-_CXML('0'); + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } +#ifndef _XMLWIDECHAR + if (j>255) { free((void*)s); pXML->error=eXMLErrorCharacterCodeAbove255;return NULL;} +#endif + (*d++)=(XMLCHAR)j; ss++; + } else + { + entity=XMLEntities; + do + { + if (xstrnicmp(ss,entity->s,entity->l)==0) { *(d++)=entity->c; ss+=entity->l; break; } + entity++; + } while(entity->s); + } + } else + { +#ifdef _XMLWIDECHAR + *(d++)=*(ss++); +#else + switch(XML_ByteTable[(unsigned char)*ss]) + { + case 4: *(d++)=*(ss++); ll--; + case 3: *(d++)=*(ss++); ll--; + case 2: *(d++)=*(ss++); ll--; + case 1: *(d++)=*(ss++); + } +#endif + } + } + *d=0; + +#ifndef _XMLWIDECHAR + if (characterEncoding != XMLNode::char_encoding_legacy) + Utf8Decode((XMLSTR)s, NULL ); +#endif + + return (XMLSTR)s; +} + +#define XML_isSPACECHAR(ch) ((ch==_CXML('\n'))||(ch==_CXML(' '))||(ch== _CXML('\t'))||(ch==_CXML('\r'))) + +// private: +char myTagCompare(XMLCSTR cclose, XMLCSTR copen) +// !!!! WARNING strange convention&: +// return 0 if equals +// return 1 if different +{ + if (!cclose) return 1; + int l=(int)xstrlen(cclose); + if (xstrnicmp(cclose, copen, l)!=0) return 1; + const XMLCHAR c=copen[l]; + if (XML_isSPACECHAR(c)|| + (c==_CXML('/' ))|| + (c==_CXML('<' ))|| + (c==_CXML('>' ))|| + (c==_CXML('=' ))) return 0; + return 1; +} + +// Obtain the next character from the string. +static inline XMLCHAR getNextChar(XML *pXML) +{ + XMLCHAR ch = pXML->lpXML[pXML->nIndex]; +#ifdef _XMLWIDECHAR + if (ch!=0) pXML->nIndex++; +#else + pXML->nIndex+=XML_ByteTable[(unsigned char)ch]; +#endif + return ch; +} + +// Find the next token in a string. +// pcbToken contains the number of characters that have been read. +static NextToken GetNextToken(XML *pXML, int *pcbToken, enum XMLTokenTypeTag *pType) +{ + NextToken result; + XMLCHAR ch; + XMLCHAR chTemp; + int indexStart,nFoundMatch,nIsText=FALSE; + result.pClr=NULL; // prevent warning + + // Find next non-white space character + do { indexStart=pXML->nIndex; ch=getNextChar(pXML); } while XML_isSPACECHAR(ch); + + if (ch) + { + // Cache the current string pointer + result.pStr = &pXML->lpXML[indexStart]; + + // check for standard tokens + switch(ch) + { + // Check for quotes + case _CXML('\''): + case _CXML('\"'): + // Type of token + *pType = eTokenQuotedText; + chTemp = ch; + + // Set the size + nFoundMatch = FALSE; + + // Search through the string to find a matching quote + while((ch = getNextChar(pXML))) + { + if (ch==chTemp) { nFoundMatch = TRUE; break; } + if (ch==_CXML('<')) break; + } + + // If we failed to find a matching quote + if (nFoundMatch == FALSE) + { + pXML->nIndex=indexStart+1; + nIsText=TRUE; + break; + } + + // 4.02.2002 + // if (FindNonWhiteSpace(pXML)) pXML->nIndex--; + + break; + + // Equals (used with attribute values) + case _CXML('='): + *pType = eTokenEquals; + break; + + // Close tag + case _CXML('>'): + *pType = eTokenCloseTag; + break; + + // Check for tag start and tag end + case _CXML('<'): + + { + // First check whether the token is in the clear tag list (meaning it + // does not need formatting). + ALLXMLClearTag *ctag=XMLClearTags; + do + { + if (!xstrncmp(ctag->lpszOpen, result.pStr, ctag->openTagLen)) + { + result.pClr=ctag; + pXML->nIndex+=ctag->openTagLen-1; + *pType=eTokenClear; + return result; + } + ctag++; + } while(ctag->lpszOpen); + + // Peek at the next character to see if we have an end tag 'lpXML[pXML->nIndex]; + + // If we have a tag end... + if (chTemp == _CXML('/')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenTagEnd; + } + + // If we have an XML declaration tag + else if (chTemp == _CXML('?')) + { + + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenDeclaration; + } + + // Otherwise we must have a start tag + else + { + *pType = eTokenTagStart; + } + break; + } + + // Check to see if we have a short hand type end tag ('/>'). + case _CXML('/'): + + // Peek at the next character to see if we have a short end tag '/>' + chTemp = pXML->lpXML[pXML->nIndex]; + + // If we have a short hand end tag... + if (chTemp == _CXML('>')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenShortHandClose; + break; + } + + // If we haven't found a short hand closing tag then drop into the + // text process + + // Other characters + default: + nIsText = TRUE; + } + + // If this is a TEXT node + if (nIsText) + { + // Indicate we are dealing with text + *pType = eTokenText; + while((ch = getNextChar(pXML))) + { + if XML_isSPACECHAR(ch) + { + indexStart++; break; + + } else if (ch==_CXML('/')) + { + // If we find a slash then this maybe text or a short hand end tag + // Peek at the next character to see it we have short hand end tag + ch=pXML->lpXML[pXML->nIndex]; + // If we found a short hand end tag then we need to exit the loop + if (ch==_CXML('>')) { pXML->nIndex--; break; } + + } else if ((ch==_CXML('<'))||(ch==_CXML('>'))||(ch==_CXML('='))) + { + pXML->nIndex--; break; + } + } + } + *pcbToken = pXML->nIndex-indexStart; + } else + { + // If we failed to obtain a valid character + *pcbToken = 0; + *pType = eTokenError; + result.pStr=NULL; + } + + return result; +} + +XMLCSTR XMLNode::updateName_WOSD(XMLSTR lpszName) +{ + if (!d) { free(lpszName); return NULL; } + if (d->lpszName&&(lpszName!=d->lpszName)) free((void*)d->lpszName); + d->lpszName=lpszName; + return lpszName; +} + +// private: +XMLNode::XMLNode(struct XMLNodeDataTag *p){ d=p; (p->ref_count)++; } +XMLNode::XMLNode(XMLNodeData *pParent, XMLSTR lpszName, char isDeclaration) +{ + d=(XMLNodeData*)malloc(sizeof(XMLNodeData)); + d->ref_count=1; + + d->lpszName=NULL; + d->nChild= 0; + d->nText = 0; + d->nClear = 0; + d->nAttribute = 0; + + d->isDeclaration = isDeclaration; + + d->pParent = pParent; + d->pChild= NULL; + d->pText= NULL; + d->pClear= NULL; + d->pAttribute= NULL; + d->pOrder= NULL; + + d->pInnerText= NULL; + + updateName_WOSD(lpszName); + + d->lpszNS = NULL; + if ( lpszName && pParent && pParent->lpszName && !pParent->isDeclaration) { + TCHAR* p = _tcschr( lpszName, ':' ); + if ( p ) { + *p = 0; + d->lpszNS = d->lpszName; + d->lpszName = p+1; + } + } +} + +XMLNode XMLNode::createXMLTopNode_WOSD(XMLSTR lpszName, char isDeclaration) { return XMLNode(NULL,lpszName,isDeclaration); } +XMLNode XMLNode::createXMLTopNode(XMLCSTR lpszName, char isDeclaration) { return XMLNode(NULL,stringDup(lpszName),isDeclaration); } + +#define MEMORYINCREASE 50 + +static inline void myFree(void *p) { if (p) free(p); } +static inline void *myRealloc(void *p, int newsize, int memInc, int sizeofElem) +{ + if (p==NULL) { if (memInc) return malloc(memInc*sizeofElem); return malloc(sizeofElem); } + if ((memInc==0)||((newsize%memInc)==0)) p=realloc(p,(newsize+memInc)*sizeofElem); + // if (!p) + // { + // printf("XMLParser Error: Not enough memory! Aborting...\n"); exit(220); + // } + return p; +} + +// private: +XMLElementPosition XMLNode::findPosition(XMLNodeData *d, int index, XMLElementType xxtype) +{ + if (index<0) return -1; + int i=0,j=(int)((index<<2)+xxtype),*o=d->pOrder; while (o[i]!=j) i++; return i; +} + +// private: +// update "order" information when deleting a content of a XMLNode +int XMLNode::removeOrderElement(XMLNodeData *d, XMLElementType t, int index) +{ + int n=d->nChild+d->nText+d->nClear, *o=d->pOrder,i=findPosition(d,index,t); + memmove(o+i, o+i+1, (n-i)*sizeof(int)); + for (;ipOrder=(int)realloc(d->pOrder,n*sizeof(int)); + // but we skip reallocation because it's too time consuming. + // Anyway, at the end, it will be free'd completely at once. + return i; +} + +void *XMLNode::addToOrder(int memoryIncrease,int *_pos, int nc, void *p, int size, XMLElementType xtype) +{ + // in: *_pos is the position inside d->pOrder ("-1" means "EndOf") + // out: *_pos is the index inside p + p=myRealloc(p,(nc+1),memoryIncrease,size); + int n=d->nChild+d->nText+d->nClear; + d->pOrder=(int*)myRealloc(d->pOrder,n+1,memoryIncrease*3,sizeof(int)); + int pos=*_pos,*o=d->pOrder; + + if ((pos<0)||(pos>=n)) { *_pos=nc; o[n]=(int)((nc<<2)+xtype); return p; } + + int i=pos; + memmove(o+i+1, o+i, (n-i)*sizeof(int)); + + while ((pos>2; + memmove(((char*)p)+(pos+1)*size,((char*)p)+pos*size,(nc-pos)*size); + + return p; +} + +// Add a child node to the given element. +XMLNode XMLNode::addChild_priv(int memoryIncrease, XMLSTR lpszName, char isDeclaration, int pos) +{ + if (!lpszName) return emptyXMLNode; + d->pChild=(XMLNode*)addToOrder(memoryIncrease,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=NULL; + d->pChild[pos]=XMLNode(d,lpszName,isDeclaration); + d->nChild++; + return d->pChild[pos]; +} + +// Add an attribute to an element. +XMLAttribute *XMLNode::addAttribute_priv(int memoryIncrease,XMLSTR lpszName, XMLSTR lpszValuev) +{ + if (!lpszName) return &emptyXMLAttribute; + if (!d) { myFree(lpszName); myFree(lpszValuev); return &emptyXMLAttribute; } + int nc=d->nAttribute; + d->pAttribute=(XMLAttribute*)myRealloc(d->pAttribute,(nc+1),memoryIncrease,sizeof(XMLAttribute)); + XMLAttribute *pAttr=d->pAttribute+nc; + pAttr->lpszName = lpszName; + pAttr->lpszValue = lpszValuev; + d->nAttribute++; + + TCHAR* p = _tcschr( lpszName, ':' ); + if ( p ) + if ( !lstrcmp( p+1, d->lpszNS ) || ( d->pParent && !lstrcmp( p+1, d->pParent->lpszNS ))) + *p = 0; + + return pAttr; +} + +// Add text to the element. +XMLCSTR XMLNode::addText_priv(int memoryIncrease, XMLSTR lpszValue, int pos) +{ + if (!lpszValue) return NULL; + if (!d) { myFree(lpszValue); return NULL; } + invalidateInnerText(); + d->pText=(XMLCSTR*)addToOrder(memoryIncrease,&pos,d->nText,d->pText,sizeof(XMLSTR),eNodeText); + d->pText[pos]=lpszValue; + d->nText++; + return lpszValue; +} + +// Add clear (unformatted) text to the element. +XMLClear *XMLNode::addClear_priv(int memoryIncrease, XMLSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, int pos) +{ + if (!lpszValue) return &emptyXMLClear; + if (!d) { myFree(lpszValue); return &emptyXMLClear; } + invalidateInnerText(); + d->pClear=(XMLClear *)addToOrder(memoryIncrease,&pos,d->nClear,d->pClear,sizeof(XMLClear),eNodeClear); + XMLClear *pNewClear=d->pClear+pos; + pNewClear->lpszValue = lpszValue; + if (!lpszOpen) lpszOpen=XMLClearTags->lpszOpen; + if (!lpszClose) lpszClose=XMLClearTags->lpszClose; + pNewClear->lpszOpenTag = lpszOpen; + pNewClear->lpszCloseTag = lpszClose; + d->nClear++; + return pNewClear; +} + +// private: +// Parse a clear (unformatted) type node. +char XMLNode::parseClearTag(void *px, void *_pClear) +{ + XML *pXML=(XML *)px; + ALLXMLClearTag pClear=*((ALLXMLClearTag*)_pClear); + int cbTemp=0; + XMLCSTR lpszTemp=NULL; + XMLCSTR lpXML=&pXML->lpXML[pXML->nIndex]; + static XMLCSTR docTypeEnd=_CXML("]>"); + + // Find the closing tag + // Seems the ')) { lpszTemp=pCh; break; } +#ifdef _XMLWIDECHAR + pCh++; +#else + pCh+=XML_ByteTable[(unsigned char)(*pCh)]; +#endif + } + } else lpszTemp=xstrstr(lpXML, pClear.lpszClose); + + if (lpszTemp) + { + // Cache the size and increment the index + cbTemp = (int)(lpszTemp - lpXML); + + pXML->nIndex += cbTemp+(int)xstrlen(pClear.lpszClose); + + // Add the clear node to the current element + addClear_priv(MEMORYINCREASE,cbTemp?stringDup(lpXML,cbTemp):NULL, pClear.lpszOpen, pClear.lpszClose,-1); + return 0; + } + + // If we failed to find the end tag + pXML->error = eXMLErrorUnmatchedEndClearTag; + return 1; +} + +void XMLNode::exactMemory(XMLNodeData *d) +{ + if (d->pOrder) d->pOrder=(int*)realloc(d->pOrder,(d->nChild+d->nText+d->nClear)*sizeof(int)); + if (d->pChild) d->pChild=(XMLNode*)realloc(d->pChild,d->nChild*sizeof(XMLNode)); + if (d->pAttribute) d->pAttribute=(XMLAttribute*)realloc(d->pAttribute,d->nAttribute*sizeof(XMLAttribute)); + if (d->pText) d->pText=(XMLCSTR*)realloc(d->pText,d->nText*sizeof(XMLSTR)); + if (d->pClear) d->pClear=(XMLClear *)realloc(d->pClear,d->nClear*sizeof(XMLClear)); +} + +char XMLNode::maybeAddTxT(void *pa, XMLCSTR tokenPStr) +{ + XML *pXML=(XML *)pa; + XMLCSTR lpszText=pXML->lpszText; + if (!lpszText) return 0; + if (dropWhiteSpace) while (XML_isSPACECHAR(*lpszText)&&(lpszText!=tokenPStr)) lpszText++; + int cbText = (int)(tokenPStr - lpszText); + if (!cbText) { pXML->lpszText=NULL; return 0; } + if (dropWhiteSpace) { cbText--; while ((cbText)&&XML_isSPACECHAR(lpszText[cbText])) cbText--; cbText++; } + if (!cbText) { pXML->lpszText=NULL; return 0; } + XMLSTR lpt=fromXMLString(lpszText,cbText,pXML); + if (!lpt) return 1; + pXML->lpszText=NULL; + if (removeCommentsInMiddleOfText && d->nText && d->nClear) + { + // if the previous insertion was a comment () AND + // if the previous previous insertion was a text then, delete the comment and append the text + int n=d->nChild+d->nText+d->nClear-1,*o=d->pOrder; + if (((o[n]&3)==eNodeClear)&&((o[n-1]&3)==eNodeText)) + { + int i=o[n]>>2; + if (d->pClear[i].lpszOpenTag==XMLClearTags[2].lpszOpen) + { + deleteClear(i); + i=o[n-1]>>2; + n=xstrlen(d->pText[i]); + int n2=xstrlen(lpt)+1; + d->pText[i]=(XMLSTR)realloc((void*)d->pText[i],(n+n2)*sizeof(XMLCHAR)); + if (!d->pText[i]) return 1; + memcpy((void*)(d->pText[i]+n),lpt,n2*sizeof(XMLCHAR)); + free(lpt); + return 0; + } + } + } + addText_priv(MEMORYINCREASE,lpt,-1); + return 0; +} +// private: +// Recursively parse an XML element. +int XMLNode::ParseXMLElement(void *pa) +{ + XML *pXML=(XML *)pa; + int cbToken; + enum XMLTokenTypeTag xtype; + NextToken token; + XMLCSTR lpszTemp=NULL; + int cbTemp=0; + char nDeclaration; + XMLNode pNew; + enum XMLStatus status; // inside or outside a tag + enum Attrib attrib = eAttribName; + + assert(pXML); + + // If this is the first call to the function + if (pXML->nFirst) + { + // Assume we are outside of a tag definition + pXML->nFirst = FALSE; + status = eOutsideTag; + } else + { + // If this is not the first call then we should only be called when inside a tag. + status = eInsideTag; + } + + // Iterate through the tokens in the document + for(;;) + { + // Obtain the next token + token = GetNextToken(pXML, &cbToken, &xtype); + + if (xtype != eTokenError) + { + // Check the current status + switch(status) + { + + // If we are outside of a tag definition + case eOutsideTag: + + // Check what type of token we obtained + switch(xtype) + { + // If we have found text or quoted text + case eTokenText: + case eTokenCloseTag: /* '>' */ + case eTokenShortHandClose: /* '/>' */ + case eTokenQuotedText: + case eTokenEquals: + break; + + // If we found a start tag '<' and declarations 'error = eXMLErrorMissingTagName; + return FALSE; + } + + // If we found a new element which is the same as this + // element then we need to pass this back to the caller.. + +#ifdef APPROXIMATE_PARSING + if (d->lpszName && + myTagCompare(d->lpszName, token.pStr) == 0) + { + // Indicate to the caller that it needs to create a + // new element. + pXML->lpNewElement = token.pStr; + pXML->cbNewElement = cbToken; + return TRUE; + } else +#endif + { + // If the name of the new element differs from the name of + // the current element we need to add the new element to + // the current one and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(token.pStr,cbToken), nDeclaration,-1); + + while (!pNew.isEmpty()) + { + // Callself to process the new node. If we return + // FALSE this means we dont have any more + // processing to do... + + if (!pNew.ParseXMLElement(pXML)) return FALSE; + else + { + // If the call to recurse this function + // evented in a end tag specified in XML then + // we need to unwind the calls to this + // function until we find the appropriate node + // (the element name and end tag name must + // match) + if (pXML->cbEndTag) + { + // If we are back at the root node then we + // have an unmatched end tag + if (!d->lpszName) + { + pXML->error=eXMLErrorUnmatchedEndTag; + return FALSE; + } + + // If the end tag matches the name of this + // element then we only need to unwind + // once more... + + if (myTagCompare(d->lpszName, pXML->lpEndTag)==0) + { + pXML->cbEndTag = 0; + } + + return TRUE; + } else + if (pXML->cbNewElement) + { + // If the call indicated a new element is to + // be created on THIS element. + + // If the name of this element matches the + // name of the element we need to create + // then we need to return to the caller + // and let it process the element. + + if (myTagCompare(d->lpszName, pXML->lpNewElement)==0) + { + return TRUE; + } + + // Add the new element and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(pXML->lpNewElement,pXML->cbNewElement),0,-1); + pXML->cbNewElement = 0; + } + else + { + // If we didn't have a new element to create + pNew = emptyXMLNode; + + } + } + } + } + break; + + // If we found an end tag + case eTokenTagEnd: + + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + + // Find the name of the end tag + token = GetNextToken(pXML, &cbTemp, &xtype); + + // The end tag should be text + if (xtype != eTokenText) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + lpszTemp = token.pStr; + + // After the end tag we should find a closing tag + token = GetNextToken(pXML, &cbToken, &xtype); + if (xtype != eTokenCloseTag) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + pXML->lpszText=pXML->lpXML+pXML->nIndex; + + // We need to return to the previous caller. If the name + // of the tag cannot be found we need to keep returning to + // caller until we find a match + if (!d->lpszNS) { + if (myTagCompare(d->lpszName, lpszTemp) != 0) +#ifdef STRICT_PARSING + { +LBL_Error: + pXML->error=eXMLErrorUnmatchedEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + return FALSE; + } +#else + { +LBL_Error: + pXML->error=eXMLErrorMissingEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + pXML->lpEndTag = lpszTemp; + pXML->cbEndTag = cbTemp; + } +#endif + } + else { + const TCHAR* p = _tcschr( lpszTemp, ':' ); + if ( !p ) + goto LBL_Error; + + if (myTagCompare(d->lpszName, p+1) != 0) + goto LBL_Error; + } + + // Return to the caller + exactMemory(d); + return TRUE; + + // If we found a clear (unformatted) token + case eTokenClear: + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + if (parseClearTag(pXML, token.pClr)) return FALSE; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + default: + break; + } + break; + + // If we are inside a tag definition we need to search for attributes + case eInsideTag: + + // Check what part of the attribute (name, equals, value) we + // are looking for. + switch(attrib) + { + // If we are looking for a new attribute + case eAttribName: + + // Check what the current token type is + switch(xtype) + { + // If the current type is text... + // Eg. 'attribute' + case eTokenText: + // Cache the token then indicate that we are next to + // look for the equals + lpszTemp = token.pStr; + cbTemp = cbToken; + attrib = eAttribEquals; + break; + + // If we found a closing tag... + // Eg. '>' + case eTokenCloseTag: + // We are now outside the tag + status = eOutsideTag; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + // If we found a short hand '/>' closing tag then we can + // return to the caller + case eTokenShortHandClose: + exactMemory(d); + pXML->lpszText=pXML->lpXML+pXML->nIndex; + return TRUE; + + // Errors... + case eTokenQuotedText: /* '"SomeText"' */ + case eTokenTagStart: /* '<' */ + case eTokenTagEnd: /* 'error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an equals + case eAttribEquals: + // Check what the current token type is + switch(xtype) + { + // If the current type is text... + // Eg. 'Attribute AnotherAttribute' + case eTokenText: + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + // Cache the token then indicate. We are next to + // look for the equals attribute + lpszTemp = token.pStr; + cbTemp = cbToken; + break; + + // If we found a closing tag 'Attribute >' or a short hand + // closing tag 'Attribute />' + case eTokenShortHandClose: + case eTokenCloseTag: + // If we are a declaration element 'lpszText=pXML->lpXML+pXML->nIndex; + + if (d->isDeclaration && + (lpszTemp[cbTemp-1]) == _CXML('?')) + { + cbTemp--; + if (d->pParent && d->pParent->pParent) xtype = eTokenShortHandClose; + } + + if (cbTemp) + { + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + } + + // If this is the end of the tag then return to the caller + if (xtype == eTokenShortHandClose) + { + exactMemory(d); + return TRUE; + } + + // We are now outside the tag + status = eOutsideTag; + break; + + // If we found the equals token... + // Eg. 'Attribute =' + case eTokenEquals: + // Indicate that we next need to search for the value + // for the attribute + attrib = eAttribValue; + break; + + // Errors... + case eTokenQuotedText: /* 'Attribute "InvalidAttr"'*/ + case eTokenTagStart: /* 'Attribute <' */ + case eTokenTagEnd: /* 'Attribute error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an attribute value + case eAttribValue: + // Check what the current token type is + switch(xtype) + { + // If the current type is text or quoted text... + // Eg. 'Attribute = "Value"' or 'Attribute = Value' or + // 'Attribute = 'Value''. + case eTokenText: + case eTokenQuotedText: + // If we are a declaration element 'isDeclaration && + (token.pStr[cbToken-1]) == _CXML('?')) + { + cbToken--; + } + + if (cbTemp) + { + // Add the valued attribute to the list + if (xtype==eTokenQuotedText) { token.pStr++; cbToken-=2; } + XMLSTR attrVal=(XMLSTR)token.pStr; + if (attrVal) + { + attrVal=fromXMLString(attrVal,cbToken,pXML); + if (!attrVal) return FALSE; + } + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp),attrVal); + } + + // Indicate we are searching for a new attribute + attrib = eAttribName; + break; + + // Errors... + case eTokenTagStart: /* 'Attr = <' */ + case eTokenTagEnd: /* 'Attr = ' */ + case eTokenShortHandClose: /* "Attr = />" */ + case eTokenEquals: /* 'Attr = =' */ + case eTokenDeclaration: /* 'Attr = error = eXMLErrorUnexpectedToken; + return FALSE; + break; + default: break; + } + } + } + } + // If we failed to obtain the next token + else + { + if ((!d->isDeclaration)&&(d->pParent)) + { +#ifdef STRICT_PARSING + pXML->error=eXMLErrorUnmatchedEndTag; +#else + pXML->error=eXMLErrorMissingEndTag; +#endif + pXML->nIndexMissigEndTag=pXML->nIndex; + } + maybeAddTxT(pXML,pXML->lpXML+pXML->nIndex); + return FALSE; + } + } +} + +// Count the number of lines and columns in an XML string. +static void CountLinesAndColumns(XMLCSTR lpXML, int nUpto, XMLResults *pResults) +{ + XMLCHAR ch; + assert(lpXML); + assert(pResults); + + struct XML xml={ lpXML,lpXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + pResults->nLine = 1; + pResults->nColumn = 1; + while (xml.nIndexnColumn++; + else + { + pResults->nLine++; + pResults->nColumn=1; + } + } +} + +// Parse XML and return the root element. +XMLNode XMLNode::parseString(XMLCSTR lpszXML, XMLCSTR tag, XMLResults *pResults) +{ + if (!lpszXML) + { + if (pResults) + { + pResults->error=eXMLErrorNoElements; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + + XMLNode xnode(NULL,NULL,FALSE); + struct XML xml={ lpszXML, lpszXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + // Create header element + xnode.ParseXMLElement(&xml); + enum XMLError error = xml.error; + if (!xnode.nChildNode()) error=eXMLErrorNoXMLTagFound; + if ((xnode.nChildNode()==1)&&(xnode.nElement()==1)) xnode=xnode.getChildNode(); // skip the empty node + + // If no error occurred + if ((error==eXMLErrorNone)||(error==eXMLErrorMissingEndTag)||(error==eXMLErrorNoXMLTagFound)) + { + XMLCSTR name=xnode.getName(); + if (tag&&(*tag)&&((!name)||(xstricmp(name,tag)))) + { + xnode=xnode.getChildNode(tag); + if (xnode.isEmpty()) + { + if (pResults) + { + pResults->error=eXMLErrorFirstTagNotFound; + pResults->nLine=0; + pResults->nColumn=0; + pResults->nChars=xml.nIndex; + } + return emptyXMLNode; + } + } + } else + { + // Cleanup: this will destroy all the nodes + xnode = emptyXMLNode; + } + + + // If we have been given somewhere to place results + if (pResults) + { + pResults->error = error; + + // If we have an error + if (error!=eXMLErrorNone) + { + if (error==eXMLErrorMissingEndTag) xml.nIndex=xml.nIndexMissigEndTag; + // Find which line and column it starts on. + CountLinesAndColumns(xml.lpXML, xml.nIndex, pResults); + } + + pResults->nChars = xml.nIndex; + } + return xnode; +} + +XMLNode XMLNode::parseFile(XMLCSTR filename, XMLCSTR tag, XMLResults *pResults) +{ + if (pResults) { pResults->nLine=0; pResults->nColumn=0; } + FILE *f=xfopen(filename,_CXML("rb")); + if (f==NULL) { if (pResults) pResults->error=eXMLErrorFileNotFound; return emptyXMLNode; } + fseek(f,0,SEEK_END); + int l=(int)ftell(f),headerSz=0; + if (!l) { if (pResults) pResults->error=eXMLErrorEmpty; fclose(f); return emptyXMLNode; } + fseek(f,0,SEEK_SET); + unsigned char *buf=(unsigned char*)malloc(l+4); + l=(int)fread(buf,1,l,f); + fclose(f); + buf[l]=0;buf[l+1]=0;buf[l+2]=0;buf[l+3]=0; +#ifdef _XMLWIDECHAR + if (guessWideCharChars) + { + if (!myIsTextWideChar(buf,l)) + { + XMLNode::XMLCharEncoding ce=XMLNode::char_encoding_legacy; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) { headerSz=3; ce=XMLNode::char_encoding_UTF8; } + XMLSTR b2=myMultiByteToWideChar((const char*)(buf+headerSz),ce); + if (!b2) + { + // todo: unable to convert + } + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + } + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } +#else + if (guessWideCharChars) + { + if (myIsTextWideChar(buf,l)) + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + char *b2=myWideCharToMultiByte((const wchar_t*)(buf+headerSz)); + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } +#endif + + if (!buf) { if (pResults) pResults->error=eXMLErrorCharConversionError; return emptyXMLNode; } + XMLNode x=parseString((XMLSTR)(buf+headerSz),tag,pResults); + free(buf); + return x; +} + +static inline void charmemset(XMLSTR dest,XMLCHAR c,int l) { while (l--) *(dest++)=c; } +// private: +// Creates an user friendly XML string from a given element with +// appropriate white space and carriage returns. +// +// This recurses through all subnodes then adds contents of the nodes to the +// string. +int XMLNode::CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat) +{ + int nResult = 0; + int cb=nFormat<0?0:nFormat; + int cbElement; + int nChildFormat=-1; + int nElementI=pEntry->nChild+pEntry->nText+pEntry->nClear; + int i,j; + if ((nFormat>=0)&&(nElementI==1)&&(pEntry->nText==1)&&(!pEntry->isDeclaration)) nFormat=-2; + + assert(pEntry); + +#define LENSTR(lpsz) (lpsz ? xstrlen(lpsz) : 0) + + // If the element has no name then assume this is the head node. + cbElement = (int)LENSTR(pEntry->lpszName); + + if (cbElement) + { + // "isDeclaration) lpszMarker[nResult++]=_CXML('?'); + xstrcpy(&lpszMarker[nResult], pEntry->lpszName); + nResult+=cbElement; + lpszMarker[nResult++]=_CXML(' '); + + } else + { + nResult+=cbElement+2+cb; + if (pEntry->isDeclaration) nResult++; + } + + // Enumerate attributes and add them to the string + XMLAttribute *pAttr=pEntry->pAttribute; + for (i=0; inAttribute; i++) + { + // "Attrib + cb = (int)LENSTR(pAttr->lpszName); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pAttr->lpszName); + nResult += cb; + // "Attrib=Value " + if (pAttr->lpszValue) + { + cb=(int)ToXMLStringTool::lengthXMLString(pAttr->lpszValue); + if (lpszMarker) + { + lpszMarker[nResult]=_CXML('='); + lpszMarker[nResult+1]=_CXML('"'); + if (cb) ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult+2],pAttr->lpszValue); + lpszMarker[nResult+cb+2]=_CXML('"'); + } + nResult+=cb+3; + } + if (lpszMarker) lpszMarker[nResult] = _CXML(' '); + nResult++; + } + pAttr++; + } + + if (pEntry->isDeclaration) + { + if (lpszMarker) + { + lpszMarker[nResult-1]=_CXML('?'); + lpszMarker[nResult]=_CXML('>'); + } + nResult++; + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult]=_CXML('\n'); + nResult++; + } + } else + // If there are child nodes we need to terminate the start tag + if (nElementI) + { + if (lpszMarker) lpszMarker[nResult-1]=_CXML('>'); + if (nFormat>=0) + { + if (lpszMarker) lpszMarker[nResult]=_CXML('\n'); + nResult++; + } + } else nResult--; + } + + // Calculate the child format for when we recurse. This is used to + // determine the number of spaces used for prefixes. + if (nFormat!=-1) + { + if (cbElement&&(!pEntry->isDeclaration)) nChildFormat=nFormat+1; + else nChildFormat=nFormat; + } + + // Enumerate through remaining children + for (i=0; ipOrder[i]; + switch((XMLElementType)(j&3)) + { + // Text nodes + case eNodeText: + { + // "Text" + XMLCSTR pChild=pEntry->pText[j>>2]; + cb = (int)ToXMLStringTool::lengthXMLString(pChild); + if (cb) + { + if (nFormat>=0) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult],INDENTCHAR,nFormat+1); + ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult+nFormat+1],pChild); + lpszMarker[nResult+nFormat+1+cb]=_CXML('\n'); + } + nResult+=cb+nFormat+2; + } else + { + if (lpszMarker) ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult], pChild); + nResult += cb; + } + } + break; + } + + // Clear type nodes + case eNodeClear: + { + XMLClear *pChild=pEntry->pClear+(j>>2); + // "OpenTag" + cb = (int)LENSTR(pChild->lpszOpenTag); + if (cb) + { + if (nFormat!=-1) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult], INDENTCHAR, nFormat+1); + xstrcpy(&lpszMarker[nResult+nFormat+1], pChild->lpszOpenTag); + } + nResult+=cb+nFormat+1; + } + else + { + if (lpszMarker)xstrcpy(&lpszMarker[nResult], pChild->lpszOpenTag); + nResult += cb; + } + } + + // "OpenTag Value" + cb = (int)LENSTR(pChild->lpszValue); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pChild->lpszValue); + nResult += cb; + } + + // "OpenTag Value CloseTag" + cb = (int)LENSTR(pChild->lpszCloseTag); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pChild->lpszCloseTag); + nResult += cb; + } + + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult] = _CXML('\n'); + nResult++; + } + break; + } + + // Element nodes + case eNodeChild: + { + // Recursively add child nodes + nResult += CreateXMLStringR(pEntry->pChild[j>>2].d, lpszMarker ? lpszMarker + nResult : 0, nChildFormat); + break; + } + default: break; + } + } + + if ((cbElement)&&(!pEntry->isDeclaration)) + { + // If we have child entries we need to use long XML notation for + // closing the element - "blah blah blah" + if (nElementI) + { + // "\0" + if (lpszMarker) + { + if (nFormat >=0) + { + charmemset(&lpszMarker[nResult], INDENTCHAR,nFormat); + nResult+=nFormat; + } + + lpszMarker[nResult]=_CXML('<'); lpszMarker[nResult+1]=_CXML('/'); + nResult += 2; + xstrcpy(&lpszMarker[nResult], pEntry->lpszName); + nResult += cbElement; + + lpszMarker[nResult]=_CXML('>'); + if (nFormat == -1) nResult++; + else + { + lpszMarker[nResult+1]=_CXML('\n'); + nResult+=2; + } + } else + { + if (nFormat>=0) nResult+=cbElement+4+nFormat; + else if (nFormat==-1) nResult+=cbElement+3; + else nResult+=cbElement+4; + } + } else + { + // If there are no children we can use shorthand XML notation - + // "" + // "/>\0" + if (lpszMarker) + { + lpszMarker[nResult]=_CXML('/'); lpszMarker[nResult+1]=_CXML('>'); + if (nFormat != -1) lpszMarker[nResult+2]=_CXML('\n'); + } + nResult += nFormat == -1 ? 2 : 3; + } + } + + return nResult; +} + +#undef LENSTR + +// Create an XML string +// @param int nFormat - 0 if no formatting is required +// otherwise nonzero for formatted text +// with carriage returns and indentation. +// @param int *pnSize - [out] pointer to the size of the +// returned string not including the +// NULL terminator. +// @return XMLSTR - Allocated XML string, you must free +// this with free(). +XMLSTR XMLNode::createXMLString(int nFormat, int *pnSize) const +{ + if (!d) { if (pnSize) *pnSize=0; return NULL; } + + XMLSTR lpszResult = NULL; + int cbStr; + + // Recursively Calculate the size of the XML string + if (!dropWhiteSpace) nFormat=0; + nFormat = nFormat ? 0 : -1; + cbStr = CreateXMLStringR(d, 0, nFormat); + // Alllocate memory for the XML string + the NULL terminator and + // create the recursively XML string. + lpszResult=(XMLSTR)malloc((cbStr+1)*sizeof(XMLCHAR)); + CreateXMLStringR(d, lpszResult, nFormat); + lpszResult[cbStr]=_CXML('\0'); + if (pnSize) *pnSize = cbStr; + return lpszResult; +} + +int XMLNode::detachFromParent(XMLNodeData *d) +{ + XMLNode *pa=d->pParent->pChild; + int i=0; + while (((void*)(pa[i].d))!=((void*)d)) i++; + d->pParent->nChild--; + if (d->pParent->nChild) memmove(pa+i,pa+i+1,(d->pParent->nChild-i)*sizeof(XMLNode)); + else { free(pa); d->pParent->pChild=NULL; } + return removeOrderElement(d->pParent,eNodeChild,i); +} + +XMLNode::~XMLNode() +{ + if (!d) return; + d->ref_count--; + emptyTheNode(0); +} +void XMLNode::deleteNodeContent() +{ + if (!d) return; + if (d->pParent) { detachFromParent(d); d->pParent=NULL; d->ref_count--; } + emptyTheNode(1); +} +void XMLNode::emptyTheNode(char force) +{ + XMLNodeData *dd=d; // warning: must stay this way! + if ((dd->ref_count==0)||force) + { + if (d->pParent) detachFromParent(d); + int i; + XMLNode *pc; + for(i=0; inChild; i++) + { + pc=dd->pChild+i; + pc->d->pParent=NULL; + pc->d->ref_count--; + pc->emptyTheNode(force); + } + myFree(dd->pChild); + for(i=0; inText; i++) free((void*)dd->pText[i]); + myFree(dd->pText); + for(i=0; inClear; i++) free((void*)dd->pClear[i].lpszValue); + myFree(dd->pClear); + for(i=0; inAttribute; i++) + { + free((void*)dd->pAttribute[i].lpszName); + if (dd->pAttribute[i].lpszValue) free((void*)dd->pAttribute[i].lpszValue); + } + myFree(dd->pAttribute); + myFree(dd->pOrder); + myFree(dd->pInnerText); + if (dd->lpszNS) + myFree((void*)dd->lpszNS); + else + myFree((void*)dd->lpszName); + dd->nChild=0; dd->nText=0; dd->nClear=0; dd->nAttribute=0; + dd->pChild=NULL; dd->pText=NULL; dd->pClear=NULL; dd->pAttribute=NULL; + dd->pOrder=NULL; dd->pInnerText=NULL; dd->lpszNS=dd->lpszName=NULL; dd->pParent=NULL; + } + if (dd->ref_count==0) + { + free(dd); + d=NULL; + } +} +void XMLNode::invalidateInnerText() +{ + if (!d) return; + myFree(d->pInnerText); + d->pInnerText= NULL; +} + +XMLNode& XMLNode::operator=( const XMLNode& A ) +{ + // shallow copy + if (this != &A) + { + if (d) { d->ref_count--; emptyTheNode(0); } + d=A.d; + if (d) (d->ref_count) ++ ; + } + return *this; +} + +XMLNode::XMLNode(const XMLNode &A) +{ + // shallow copy + d=A.d; + if (d) (d->ref_count)++ ; +} + +XMLNode XMLNode::deepCopy() const +{ + if (!d) return XMLNode::emptyXMLNode; + XMLNode x(NULL,stringDup(d->lpszName),d->isDeclaration); + XMLNodeData *p=x.d; + int n=d->nAttribute; + if (n) + { + p->nAttribute=n; p->pAttribute=(XMLAttribute*)malloc(n*sizeof(XMLAttribute)); + while (n--) + { + p->pAttribute[n].lpszName=stringDup(d->pAttribute[n].lpszName); + p->pAttribute[n].lpszValue=stringDup(d->pAttribute[n].lpszValue); + } + } + if (d->pOrder) + { + n=(d->nChild+d->nText+d->nClear)*sizeof(int); p->pOrder=(int*)malloc(n); memcpy(p->pOrder,d->pOrder,n); + } + n=d->nText; + if (n) + { + p->nText=n; p->pText=(XMLCSTR*)malloc(n*sizeof(XMLCSTR)); + while(n--) p->pText[n]=stringDup(d->pText[n]); + } + n=d->nClear; + if (n) + { + p->nClear=n; p->pClear=(XMLClear*)malloc(n*sizeof(XMLClear)); + while (n--) + { + p->pClear[n].lpszCloseTag=d->pClear[n].lpszCloseTag; + p->pClear[n].lpszOpenTag=d->pClear[n].lpszOpenTag; + p->pClear[n].lpszValue=stringDup(d->pClear[n].lpszValue); + } + } + n=d->nChild; + if (n) + { + p->nChild=n; p->pChild=(XMLNode*)malloc(n*sizeof(XMLNode)); + while (n--) + { + p->pChild[n].d=NULL; + p->pChild[n]=d->pChild[n].deepCopy(); + p->pChild[n].d->pParent=p; + } + } + return x; +} + +XMLNode XMLNode::addChild(XMLNode childNode, int pos) +{ + XMLNodeData *dc=childNode.d; + if ((!dc)||(!d)) return childNode; + if (!dc->lpszName) + { + // this is a root node: todo: correct fix + int j=pos; + while (dc->nChild) + { + addChild(dc->pChild[0],j); + if (pos>=0) j++; + } + return childNode; + } + if (dc->pParent) { if ((detachFromParent(dc)<=pos)&&(dc->pParent==d)) pos--; } else dc->ref_count++; + dc->pParent=d; + // int nc=d->nChild; + // d->pChild=(XMLNode*)myRealloc(d->pChild,(nc+1),memoryIncrease,sizeof(XMLNode)); + d->pChild=(XMLNode*)addToOrder(0,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=dc; + d->nChild++; + return childNode; +} + +void XMLNode::deleteAttribute(int i) +{ + if ((!d)||(i<0)||(i>=d->nAttribute)) return; + d->nAttribute--; + XMLAttribute *p=d->pAttribute+i; + free((void*)p->lpszName); + if (p->lpszValue) free((void*)p->lpszValue); + if (d->nAttribute) memmove(p,p+1,(d->nAttribute-i)*sizeof(XMLAttribute)); else { free(p); d->pAttribute=NULL; } +} + +void XMLNode::deleteAttribute(XMLAttribute *a){ if (a) deleteAttribute(a->lpszName); } +void XMLNode::deleteAttribute(XMLCSTR lpszName) +{ + int j=0; + getAttribute(lpszName,&j); + if (j) deleteAttribute(j-1); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,int i) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); if (lpszNewName) free(lpszNewName); return NULL; } + if (i>=d->nAttribute) + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + return NULL; + } + XMLAttribute *p=d->pAttribute+i; + if (p->lpszValue&&p->lpszValue!=lpszNewValue) free((void*)p->lpszValue); + p->lpszValue=lpszNewValue; + if (lpszNewName&&p->lpszName!=lpszNewName) { free((void*)p->lpszName); p->lpszName=lpszNewName; }; + return p; +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) +{ + if (oldAttribute) return updateAttribute_WOSD((XMLSTR)newAttribute->lpszValue,(XMLSTR)newAttribute->lpszName,oldAttribute->lpszName); + return addAttribute_WOSD((XMLSTR)newAttribute->lpszName,(XMLSTR)newAttribute->lpszValue); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,XMLCSTR lpszOldName) +{ + int j=0; + getAttribute(lpszOldName,&j); + if (j) return updateAttribute_WOSD(lpszNewValue,lpszNewName,j-1); + else + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + else return addAttribute_WOSD(stringDup(lpszOldName),lpszNewValue); + } +} + +int XMLNode::indexText(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nText; + if (!lpszValue) { if (l) return 0; return -1; } + XMLCSTR *p=d->pText; + for (i=0; i=d->nText)) return; + invalidateInnerText(); + d->nText--; + XMLCSTR *p=d->pText+i; + free((void*)*p); + if (d->nText) memmove(p,p+1,(d->nText-i)*sizeof(XMLCSTR)); else { free(p); d->pText=NULL; } + removeOrderElement(d,eNodeText,i); +} + +void XMLNode::deleteText(XMLCSTR lpszValue) { deleteText(indexText(lpszValue)); } + +XMLCSTR XMLNode::updateText_WOSD(XMLSTR lpszNewValue, int i) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); return NULL; } + if (i>=d->nText) return addText_WOSD(lpszNewValue); + invalidateInnerText(); + XMLCSTR *p=d->pText+i; + if (*p!=lpszNewValue) { free((void*)*p); *p=lpszNewValue; } + return lpszNewValue; +} + +XMLCSTR XMLNode::updateText_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); return NULL; } + int i=indexText(lpszOldValue); + if (i>=0) return updateText_WOSD(lpszNewValue,i); + return addText_WOSD(lpszNewValue); +} + +void XMLNode::deleteClear(int i) +{ + if ((!d)||(i<0)||(i>=d->nClear)) return; + invalidateInnerText(); + d->nClear--; + XMLClear *p=d->pClear+i; + free((void*)p->lpszValue); + if (d->nClear) memmove(p,p+1,(d->nClear-i)*sizeof(XMLClear)); else { free(p); d->pClear=NULL; } + removeOrderElement(d,eNodeClear,i); +} + +int XMLNode::indexClear(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nClear; + if (!lpszValue) { if (l) return 0; return -1; } + XMLClear *p=d->pClear; + for (i=0; ilpszValue); } + +XMLClear *XMLNode::updateClear_WOSD(XMLSTR lpszNewContent, int i) +{ + if (!d) { if (lpszNewContent) free(lpszNewContent); return NULL; } + if (i>=d->nClear) return addClear_WOSD(lpszNewContent); + invalidateInnerText(); + XMLClear *p=d->pClear+i; + if (lpszNewContent!=p->lpszValue) { free((void*)p->lpszValue); p->lpszValue=lpszNewContent; } + return p; +} + +XMLClear *XMLNode::updateClear_WOSD(XMLSTR lpszNewContent, XMLCSTR lpszOldValue) +{ + if (!d) { if (lpszNewContent) free(lpszNewContent); return NULL; } + int i=indexClear(lpszOldValue); + if (i>=0) return updateClear_WOSD(lpszNewContent,i); + return addClear_WOSD(lpszNewContent); +} + +XMLClear *XMLNode::updateClear_WOSD(XMLClear *newP,XMLClear *oldP) +{ + if (oldP) return updateClear_WOSD((XMLSTR)newP->lpszValue,(XMLSTR)oldP->lpszValue); + return NULL; +} + +int XMLNode::nChildNode(XMLCSTR name) const +{ + if (!d) return 0; + int i,j=0,n=d->nChild; + XMLNode *pc=d->pChild; + for (i=0; id->lpszName, name)==0) j++; + pc++; + } + return j; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int *j) const +{ + if (!d) return emptyXMLNode; + int i=0,n=d->nChild; + if (j) i=*j; + XMLNode *pc=d->pChild+i; + for (; id->lpszName, name)) + { + if (j) *j=i+1; + return *pc; + } + pc++; + } + return emptyXMLNode; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int j) const +{ + if (!d) return emptyXMLNode; + if (j>=0) + { + int i=0; + while (j-->0) getChildNode(name,&i); + return getChildNode(name,&i); + } + int i=d->nChild; + while (i--) if (!xstricmp(name,d->pChild[i].d->lpszName)) break; + if (i<0) return emptyXMLNode; + return getChildNode(i); +} + +XMLNode XMLNode::getNextNode() const +{ + if (!d) return emptyXMLNode; + XMLNodeDataTag *par=d->pParent; + if (!par) return emptyXMLNode; + int i,n=par->nChild; + for (i=0; ipChild[i].d == d) break; + } + return XMLNode(par).getChildNode(d->lpszName, &++i); +} + +XMLNode XMLNode::getChildNodeByPath(XMLCSTR _path, char createMissing, XMLCHAR sep) +{ + XMLSTR path=stringDup(_path); + XMLNode x=getChildNodeByPathNonConst(path,createMissing,sep); + if (path) free(path); + return x; +} + +XMLNode XMLNode::getChildNodeByPathNonConst(XMLSTR path, char createIfMissing, XMLCHAR sep) +{ + if ((!path)||(!(*path))) return *this; + XMLNode xn,xbase=*this; + XMLCHAR *tend1,sepString[2]; sepString[0]=sep; sepString[1]=0; + tend1=xstrstr(path,sepString); + while(tend1) + { + *tend1=0; + xn=xbase.getChildNode(path); + if (xn.isEmpty()) + { + if (createIfMissing) xn=xbase.addChild(path); + else { *tend1=sep; return XMLNode::emptyXMLNode; } + } + *tend1=sep; + xbase=xn; + path=tend1+1; + tend1=xstrstr(path,sepString); + } + xn=xbase.getChildNode(path); + if (xn.isEmpty()&&createIfMissing) xn=xbase.addChild(path); + return xn; +} + +XMLElementPosition XMLNode::positionOfText (int i) const { if (i>=d->nText ) i=d->nText-1; return findPosition(d,i,eNodeText ); } +XMLElementPosition XMLNode::positionOfClear (int i) const { if (i>=d->nClear) i=d->nClear-1; return findPosition(d,i,eNodeClear); } +XMLElementPosition XMLNode::positionOfChildNode(int i) const { if (i>=d->nChild) i=d->nChild-1; return findPosition(d,i,eNodeChild); } +XMLElementPosition XMLNode::positionOfText (XMLCSTR lpszValue) const { return positionOfText (indexText (lpszValue)); } +XMLElementPosition XMLNode::positionOfClear(XMLCSTR lpszValue) const { return positionOfClear(indexClear(lpszValue)); } +XMLElementPosition XMLNode::positionOfClear(XMLClear *a) const { if (a) return positionOfClear(a->lpszValue); return positionOfClear(); } +XMLElementPosition XMLNode::positionOfChildNode(XMLNode x) const +{ + if ((!d)||(!x.d)) return -1; + XMLNodeData *dd=x.d; + XMLNode *pc=d->pChild; + int i=d->nChild; + while (i--) if (pc[i].d==dd) return findPosition(d,i,eNodeChild); + return -1; +} +XMLElementPosition XMLNode::positionOfChildNode(XMLCSTR name, int count) const +{ + if (!name) return positionOfChildNode(count); + int j=0; + do { getChildNode(name,&j); if (j<0) return -1; } while (count--); + return findPosition(d,j-1,eNodeChild); +} + +XMLNode XMLNode::getChildNodeWithAttribute(XMLCSTR name,XMLCSTR attributeName,XMLCSTR attributeValue, int *k) const +{ + int i=0,j; + if (k) i=*k; + XMLNode x; + XMLCSTR t; + do + { + x=getChildNode(name,&i); + if (!x.isEmpty()) + { + if (attributeValue) + { + j=0; + do + { + t=x.getAttribute(attributeName,&j); + if (t&&(xstricmp(attributeValue,t)==0)) { if (k) *k=i; return x; } + } while (t); + } else + { + if (x.isAttributeSet(attributeName)) { if (k) *k=i; return x; } + } + } + } while (!x.isEmpty()); + return emptyXMLNode; +} + +// Find an attribute on an node. +XMLCSTR XMLNode::getAttribute(XMLCSTR lpszAttrib, int *j) const +{ + if (!d) return NULL; + int i=0,n=d->nAttribute; + if (j) i=*j; + XMLAttribute *pAttr=d->pAttribute+i; + for (; ilpszName, lpszAttrib)==0) + { + if (j) *j=i+1; + return pAttr->lpszValue; + } + pAttr++; + } + return NULL; +} + +char XMLNode::isAttributeSet(XMLCSTR lpszAttrib) const +{ + if (!d) return FALSE; + int i,n=d->nAttribute; + XMLAttribute *pAttr=d->pAttribute; + for (i=0; ilpszName, lpszAttrib)==0) + { + return TRUE; + } + pAttr++; + } + return FALSE; +} + +XMLCSTR XMLNode::getAttribute(XMLCSTR name, int j) const +{ + if (!d) return NULL; + int i=0; + while (j-->0) getAttribute(name,&i); + return getAttribute(name,&i); +} + +XMLNodeContents XMLNode::enumContents(int i) const +{ + XMLNodeContents c; + if (!d) { c.etype=eNodeNULL; return c; } + if (inAttribute) + { + c.etype=eNodeAttribute; + c.attrib=d->pAttribute[i]; + return c; + } + i-=d->nAttribute; + c.etype=(XMLElementType)(d->pOrder[i]&3); + i=(d->pOrder[i])>>2; + switch (c.etype) + { + case eNodeChild: c.child = d->pChild[i]; break; + case eNodeText: c.text = d->pText[i]; break; + case eNodeClear: c.clear = d->pClear[i]; break; + default: break; + } + return c; +} + +XMLCSTR XMLNode::getInnerText() const +{ + if (!d) return NULL; + if (nText() <= 1 && nClear() == 0) return getText(); + if (d->pInnerText) return d->pInnerText; + + int count = nElement(); + int i, length = 1; + for (i = 0; i < count; ++i) + { + XMLNodeContents c = enumContents(i); + switch (c.etype) + { + case eNodeText: + length += xstrlen(c.text); + break; + case eNodeClear: + length += xstrlen(c.clear.lpszValue); + break; + } + } + XMLCHAR *buf = (XMLCHAR *)malloc(sizeof(XMLCHAR) * length); + XMLCHAR *pos = buf; + for (i = 0; i < count; ++i) + { + XMLNodeContents c = enumContents(i); + switch (c.etype) + { + case eNodeText: + xstrcpy(pos, c.text); + pos += xstrlen(c.text); + break; + case eNodeClear: + xstrcpy(pos, c.clear.lpszValue); + pos += xstrlen(c.clear.lpszValue); + break; + } + } + return d->pInnerText = buf; +} + +XMLCSTR XMLNode::getName() const { if (!d) return NULL; return d->lpszName; } +int XMLNode::nText() const { if (!d) return 0; return d->nText; } +int XMLNode::nChildNode() const { if (!d) return 0; return d->nChild; } +int XMLNode::nAttribute() const { if (!d) return 0; return d->nAttribute; } +int XMLNode::nClear() const { if (!d) return 0; return d->nClear; } +int XMLNode::nElement() const { if (!d) return 0; return d->nAttribute+d->nChild+d->nText+d->nClear; } +XMLClear XMLNode::getClear (int i) const { if ((!d)||(i>=d->nClear )) return emptyXMLClear; return d->pClear[i]; } +XMLAttribute XMLNode::getAttribute (int i) const { if ((!d)||(i>=d->nAttribute)) return emptyXMLAttribute; return d->pAttribute[i]; } +XMLCSTR XMLNode::getAttributeName (int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszName; } +XMLCSTR XMLNode::getAttributeValue(int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszValue; } +XMLCSTR XMLNode::getText (int i) const { if ((!d)||(i>=d->nText )) return NULL; return d->pText[i]; } +XMLNode XMLNode::getChildNode (int i) const { if ((!d)||(i>=d->nChild )) return emptyXMLNode; return d->pChild[i]; } +XMLNode XMLNode::getParentNode ( ) const { if ((!d)||(!d->pParent )) return emptyXMLNode; return XMLNode(d->pParent); } +char XMLNode::isDeclaration ( ) const { if (!d) return 0; return d->isDeclaration; } +char XMLNode::isEmpty ( ) const { return (d==NULL); } +XMLNode XMLNode::emptyNode ( ) { return XMLNode::emptyXMLNode; } + +XMLNode XMLNode::addChild(XMLCSTR lpszName, char isDeclaration, XMLElementPosition pos) +{ return addChild_priv(0,stringDup(lpszName),isDeclaration,pos); } +XMLNode XMLNode::addChild_WOSD(XMLSTR lpszName, char isDeclaration, XMLElementPosition pos) +{ return addChild_priv(0,lpszName,isDeclaration,pos); } +XMLAttribute *XMLNode::addAttribute(XMLCSTR lpszName, XMLCSTR lpszValue) +{ return addAttribute_priv(0,stringDup(lpszName),stringDup(lpszValue)); } +XMLAttribute *XMLNode::addAttribute_WOSD(XMLSTR lpszName, XMLSTR lpszValuev) +{ return addAttribute_priv(0,lpszName,lpszValuev); } +XMLCSTR XMLNode::addText(XMLCSTR lpszValue, XMLElementPosition pos) +{ return addText_priv(0,stringDup(lpszValue),pos); } +XMLCSTR XMLNode::addText_WOSD(XMLSTR lpszValue, XMLElementPosition pos) +{ return addText_priv(0,lpszValue,pos); } +XMLClear *XMLNode::addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, XMLElementPosition pos) +{ return addClear_priv(0,stringDup(lpszValue),lpszOpen,lpszClose,pos); } +XMLClear *XMLNode::addClear_WOSD(XMLSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, XMLElementPosition pos) +{ return addClear_priv(0,lpszValue,lpszOpen,lpszClose,pos); } +XMLCSTR XMLNode::updateName(XMLCSTR lpszName) +{ return updateName_WOSD(stringDup(lpszName)); } +XMLAttribute *XMLNode::updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) +{ return updateAttribute_WOSD(stringDup(newAttribute->lpszValue),stringDup(newAttribute->lpszName),oldAttribute->lpszName); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,int i) +{ return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),i); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName) +{ return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),lpszOldName); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, int i) +{ return updateText_WOSD(stringDup(lpszNewValue),i); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) +{ return updateText_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewContent, int i) +{ return updateClear_WOSD(stringDup(lpszNewContent),i); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) +{ return updateClear_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLClear *newP,XMLClear *oldP) +{ return updateClear_WOSD(stringDup(newP->lpszValue),oldP->lpszValue); } + +char XMLNode::setGlobalOptions(XMLCharEncoding _characterEncoding, char _guessWideCharChars, + char _dropWhiteSpace, char _removeCommentsInMiddleOfText) +{ + guessWideCharChars=_guessWideCharChars; dropWhiteSpace=_dropWhiteSpace; removeCommentsInMiddleOfText=_removeCommentsInMiddleOfText; +#ifdef _XMLWIDECHAR + if (_characterEncoding) characterEncoding=_characterEncoding; +#else + switch(_characterEncoding) + { + case char_encoding_UTF8: characterEncoding=_characterEncoding; XML_ByteTable=XML_utf8ByteTable; break; + case char_encoding_legacy: characterEncoding=_characterEncoding; XML_ByteTable=XML_legacyByteTable; break; + case char_encoding_ShiftJIS: characterEncoding=_characterEncoding; XML_ByteTable=XML_sjisByteTable; break; + case char_encoding_GB2312: characterEncoding=_characterEncoding; XML_ByteTable=XML_gb2312ByteTable; break; + case char_encoding_Big5: + case char_encoding_GBK: characterEncoding=_characterEncoding; XML_ByteTable=XML_gbk_big5_ByteTable; break; + default: return 1; + } +#endif + return 0; +} + +XMLNode::XMLCharEncoding XMLNode::guessCharEncoding(void *buf,int l, char useXMLEncodingAttribute) +{ +#ifdef _XMLWIDECHAR + return (XMLCharEncoding)0; +#else + if (l<25) return (XMLCharEncoding)0; + if (guessWideCharChars&&(myIsTextWideChar(buf,l))) return (XMLCharEncoding)0; + unsigned char *b=(unsigned char*)buf; + if ((b[0]==0xef)&&(b[1]==0xbb)&&(b[2]==0xbf)) return char_encoding_UTF8; + + // Match utf-8 model ? + XMLCharEncoding bestGuess=char_encoding_UTF8; + int i=0; + while (i>2 ]; + *(curr++)=base64EncodeTable[(inbuf[0]<<4)&0x3F]; + *(curr++)=base64Fillchar; + *(curr++)=base64Fillchar; + } else if (eLen==2) + { + j=(inbuf[0]<<8)|inbuf[1]; + *(curr++)=base64EncodeTable[ j>>10 ]; + *(curr++)=base64EncodeTable[(j>> 4)&0x3f]; + *(curr++)=base64EncodeTable[(j<< 2)&0x3f]; + *(curr++)=base64Fillchar; + } + *(curr++)=0; + return (XMLSTR)buf; +} + +unsigned int XMLParserBase64Tool::decodeSize(XMLCSTR data,XMLError *xe) +{ + if (!data) return 0; + if (xe) *xe=eXMLErrorNone; + int size=0; + unsigned char c; + //skip any extra characters (e.g. newlines or spaces) + while (*data) + { +#ifdef _XMLWIDECHAR + if (*data>255) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + c=base64DecodeTable[(unsigned char)(*data)]; + if (c<97) size++; + else if (c==98) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } + data++; + } + if (xe&&(size%4!=0)) *xe=eXMLErrorBase64DataSizeIsNotMultipleOf4; + if (size==0) return 0; + do { data--; size--; } while(*data==base64Fillchar); size++; + return (unsigned int)((size*3)/4); +} + +unsigned char XMLParserBase64Tool::decode(XMLCSTR data, unsigned char *buf, int len, XMLError *xe) +{ + if (!data) return 0; + if (xe) *xe=eXMLErrorNone; + int i=0,p=0; + unsigned char d,c; + for(;;) + { + +#ifdef _XMLWIDECHAR +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { \ + if (data[i]>255){ c=98; break; } \ + c=base64DecodeTable[(unsigned char)data[i++]]; \ + }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#else +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { c=base64DecodeTable[(unsigned char)data[i++]]; }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { return 2; } + if (c==96) + { + if (p==(int)len) return 2; + if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; + return 1; + } + + BASE64DECODE_READ_NEXT_CHAR(d) + if ((d==99)||(d==96)) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) { if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; return 0; } + buf[p++]=(unsigned char)((c<<2)|((d>>4)&0x3)); + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (c==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (c==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=(unsigned char)(((d<<4)&0xf0)|((c>>2)&0xf)); + + BASE64DECODE_READ_NEXT_CHAR(d) + if (d==99 ) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (d==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (d==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=(unsigned char)(((c<<6)&0xc0)|d); + } +} +#undef BASE64DECODE_READ_NEXT_CHAR + +void XMLParserBase64Tool::alloc(int newsize) +{ + if ((!buf)&&(newsize)) { buf=malloc(newsize); buflen=newsize; return; } + if (newsize>buflen) { buf=realloc(buf,newsize); buflen=newsize; } +} + +unsigned char *XMLParserBase64Tool::decode(XMLCSTR data, int *outlen, XMLError *xe) +{ + if (xe) *xe=eXMLErrorNone; + if (!data) { *outlen=0; return (unsigned char*)""; } + unsigned int len=decodeSize(data,xe); + if (outlen) *outlen=len; + if (!len) return NULL; + alloc(len+1); + if(!decode(data,(unsigned char*)buf,len,xe)){ return NULL; } + return (unsigned char*)buf; +} + +////////////////////////////////////////////////////////// +// Helpers for external C APIs. // +////////////////////////////////////////////////////////// + +XMLNode::XMLNode( HXML h ) : +d(( XMLNodeDataTag* )h ) +{ + if (d) + d->ref_count++; +} + +void XMLNode::attach( HXML h ) +{ + d = ( XMLNodeDataTag* )h; +} + +HXML XMLNode::detach() +{ + HXML res = (HXML)d; + d = NULL; + return res; +} diff --git a/src/modules/xml/xmlParser.h b/src/modules/xml/xmlParser.h new file mode 100644 index 0000000000..5a216ab0cb --- /dev/null +++ b/src/modules/xml/xmlParser.h @@ -0,0 +1,746 @@ +/****************************************************************************/ +/*! \mainpage XMLParser library + * \section intro_sec Introduction + * + * This is a basic XML parser written in ANSI C++ for portability. + * It works by using recursion and a node tree for breaking + * down the elements of an XML document. + * + * @version V2.43 + * @author Frank Vanden Berghen + * + * Copyright (c) 2002, Business-Insight + * Business-Insight + * All rights reserved. + * See the file AFPL-license.txt about the licensing terms + * + * \section tutorial First Tutorial + * You can follow a simple Tutorial to know the basics... + * + * \section usage General usage: How to include the XMLParser library inside your project. + * + * The library is composed of two files: xmlParser.cpp and + * xmlParser.h. These are the ONLY 2 files that you need when + * using the library inside your own projects. + * + * All the functions of the library are documented inside the comments of the file + * xmlParser.h. These comments can be transformed in + * full-fledged HTML documentation using the DOXYGEN software: simply type: "doxygen doxy.cfg" + * + * By default, the XMLParser library uses (char*) for string representation.To use the (wchar_t*) + * version of the library, you need to define the "_UNICODE" preprocessor definition variable + * (this is usually done inside your project definition file) (This is done automatically for you + * when using Visual Studio). + * + * \section example Advanced Tutorial and Many Examples of usage. + * + * Some very small introductory examples are described inside the Tutorial file + * xmlParser.html + * + * Some additional small examples are also inside the file xmlTest.cpp + * (for the "char*" version of the library) and inside the file + * xmlTestUnicode.cpp (for the "wchar_t*" + * version of the library). If you have a question, please review these additionnal examples + * before sending an e-mail to the author. + * + * To build the examples: + * - linux/unix: type "make" + * - solaris: type "make -f makefile.solaris" + * - windows: Visual Studio: double-click on xmlParser.dsw + * (under Visual Studio .NET, the .dsp and .dsw files will be automatically converted to .vcproj and .sln files) + * + * In order to build the examples you need some additional files: + * - linux/unix: makefile + * - solaris: makefile.solaris + * - windows: Visual Studio: *.dsp, xmlParser.dsw and also xmlParser.lib and xmlParser.dll + * + * \section debugging Debugging with the XMLParser library + * + * \subsection debugwin Debugging under WINDOWS + * + * Inside Visual C++, the "debug versions" of the memory allocation functions are + * very slow: Do not forget to compile in "release mode" to get maximum speed. + * When I had to debug a software that was using the XMLParser Library, it was usually + * a nightmare because the library was sooOOOoooo slow in debug mode (because of the + * slow memory allocations in Debug mode). To solve this + * problem, during all the debugging session, I am now using a very fast DLL version of the + * XMLParser Library (the DLL is compiled in release mode). Using the DLL version of + * the XMLParser Library allows me to have lightening XML parsing speed even in debug! + * Other than that, the DLL version is useless: In the release version of my tool, + * I always use the normal, ".cpp"-based, XMLParser Library (I simply include the + * xmlParser.cpp and + * xmlParser.h files into the project). + * + * The file XMLNodeAutoexp.txt contains some + * "tweaks" that improve substancially the display of the content of the XMLNode objects + * inside the Visual Studio Debugger. Believe me, once you have seen inside the debugger + * the "smooth" display of the XMLNode objects, you cannot live without it anymore! + * + * \subsection debuglinux Debugging under LINUX/UNIX + * + * The speed of the debug version of the XMLParser library is tolerable so no extra + * work.has been done. + * + ****************************************************************************/ + +#ifndef __INCLUDE_XML_NODE__ +#define __INCLUDE_XML_NODE__ + +#include + +#ifdef _UNICODE +// If you comment the next "define" line then the library will never "switch to" _UNICODE (wchar_t*) mode (16/32 bits per characters). +// This is useful when you get error messages like: +// 'XMLNode::openFileHelper' : cannot convert parameter 2 from 'const char [5]' to 'const wchar_t *' +// The _XMLWIDECHAR preprocessor variable force the XMLParser library into either utf16/32-mode (the proprocessor variable +// must be defined) or utf8-mode(the pre-processor variable must be undefined). +#define _XMLWIDECHAR +#endif + +#if defined(WIN32) || defined(UNDER_CE) || defined(_WIN32) || defined(WIN64) || defined(__BORLANDC__) +// comment the next line if you are under windows and the compiler is not Microsoft Visual Studio (6.0 or .NET) or Borland +#define _XMLWINDOWS +#endif + +#ifdef XMLDLLENTRY +#undef XMLDLLENTRY +#endif +#ifdef _USE_XMLPARSER_DLL +#ifdef _DLL_EXPORTS_ +#define XMLDLLENTRY __declspec(dllexport) +#else +#define XMLDLLENTRY __declspec(dllimport) +#endif +#else +#define XMLDLLENTRY +#endif + +// uncomment the next line if you want no support for wchar_t* (no need for the or libraries anymore to compile) +//#define XML_NO_WIDE_CHAR + +#ifdef XML_NO_WIDE_CHAR +#undef _XMLWINDOWS +#undef _XMLWIDECHAR +#endif + +#ifdef _XMLWINDOWS +#include +#else +#define XMLDLLENTRY +#ifndef XML_NO_WIDE_CHAR +#include // to have 'wcsrtombs' for ANSI version + // to have 'mbsrtowcs' for WIDECHAR version +#endif +#endif + +// Some common types for char set portable code +#ifdef _XMLWIDECHAR + #define _CXML(c) L ## c + #define XMLCSTR const wchar_t * + #define XMLSTR wchar_t * + #define XMLCHAR wchar_t +#else + #define _CXML(c) c + #define XMLCSTR const char * + #define XMLSTR char * + #define XMLCHAR char +#endif +#ifndef FALSE + #define FALSE 0 +#endif /* FALSE */ +#ifndef TRUE + #define TRUE 1 +#endif /* TRUE */ + + +/// Enumeration for XML parse errors. +typedef enum XMLError +{ + eXMLErrorNone = 0, + eXMLErrorMissingEndTag, + eXMLErrorNoXMLTagFound, + eXMLErrorEmpty, + eXMLErrorMissingTagName, + eXMLErrorMissingEndTagName, + eXMLErrorUnmatchedEndTag, + eXMLErrorUnmatchedEndClearTag, + eXMLErrorUnexpectedToken, + eXMLErrorNoElements, + eXMLErrorFileNotFound, + eXMLErrorFirstTagNotFound, + eXMLErrorUnknownCharacterEntity, + eXMLErrorCharacterCodeAbove255, + eXMLErrorCharConversionError, + eXMLErrorCannotOpenWriteFile, + eXMLErrorCannotWriteFile, + + eXMLErrorBase64DataSizeIsNotMultipleOf4, + eXMLErrorBase64DecodeIllegalCharacter, + eXMLErrorBase64DecodeTruncatedData, + eXMLErrorBase64DecodeBufferTooSmall +} XMLError; + + +/// Enumeration used to manage type of data. Use in conjunction with structure XMLNodeContents +typedef enum XMLElementType +{ + eNodeChild=0, + eNodeAttribute=1, + eNodeText=2, + eNodeClear=3, + eNodeNULL=4 +} XMLElementType; + +/// Structure used to obtain error details if the parse fails. +typedef struct XMLResults +{ + enum XMLError error; + int nLine,nColumn,nChars; +} XMLResults; + +/// Structure for XML clear (unformatted) node (usually comments) +typedef struct XMLClear { + XMLCSTR lpszValue; XMLCSTR lpszOpenTag; XMLCSTR lpszCloseTag; +} XMLClear; + +/// Structure for XML attribute. +typedef struct XMLAttribute { + XMLCSTR lpszName; XMLCSTR lpszValue; +} XMLAttribute; + +/// XMLElementPosition are not interchangeable with simple indexes +typedef int XMLElementPosition; + +struct XMLNodeContents; + +/** @defgroup XMLParserGeneral The XML parser */ + +/// Main Class representing a XML node +/** + * All operations are performed using this class. + * \note The constructors of the XMLNode class are protected, so use instead one of these four methods to get your first instance of XMLNode: + *
    + *
  • XMLNode::parseString
  • + *
  • XMLNode::parseFile
  • + *
  • XMLNode::openFileHelper
  • + *
  • XMLNode::createXMLTopNode (or XMLNode::createXMLTopNode_WOSD)
  • + *
*/ +typedef struct XMLDLLENTRY XMLNode +{ +private: + + struct XMLNodeDataTag; + + /// Constructors are protected, so use instead one of: XMLNode::parseString, XMLNode::parseFile, XMLNode::openFileHelper, XMLNode::createXMLTopNode + XMLNode(struct XMLNodeDataTag *pParent, XMLSTR lpszName, char isDeclaration); + /// Constructors are protected, so use instead one of: XMLNode::parseString, XMLNode::parseFile, XMLNode::openFileHelper, XMLNode::createXMLTopNode + XMLNode(struct XMLNodeDataTag *p); + +public: + static XMLCSTR getVersion();///< Return the XMLParser library version number + + /** @defgroup conversions Parsing XML files/strings to an XMLNode structure and Rendering XMLNode's to files/string. + * @ingroup XMLParserGeneral + * @{ */ + + /// Parse an XML string and return the root of a XMLNode tree representing the string. + static XMLNode parseString (XMLCSTR lpXMLString, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + /**< The "parseString" function parse an XML string and return the root of a XMLNode tree. The "opposite" of this function is + * the function "createXMLString" that re-creates an XML string from an XMLNode tree. If the XML document is corrupted, the + * "parseString" method will initialize the "pResults" variable with some information that can be used to trace the error. + * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the + * beginning of the "xmlParser.cpp" file. + * + * @param lpXMLString the XML string to parse + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + * @param pResults a pointer to a XMLResults variable that will contain some information that can be used to trace the XML parsing error. You can have a user-friendly explanation of the parsing error with the "getError" function. + */ + + /// Parse an XML file and return the root of a XMLNode tree representing the file. + static XMLNode parseFile (XMLCSTR filename, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + /**< The "parseFile" function parse an XML file and return the root of a XMLNode tree. The "opposite" of this function is + * the function "writeToFile" that re-creates an XML file from an XMLNode tree. If the XML document is corrupted, the + * "parseFile" method will initialize the "pResults" variable with some information that can be used to trace the error. + * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the + * beginning of the "xmlParser.cpp" file. + * + * @param filename the path to the XML file to parse + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + * @param pResults a pointer to a XMLResults variable that will contain some information that can be used to trace the XML parsing error. You can have a user-friendly explanation of the parsing error with the "getError" function. + */ + + /// Parse an XML file and return the root of a XMLNode tree representing the file. A very crude error checking is made. An attempt to guess the Char Encoding used in the file is made. + static XMLNode openFileHelper(XMLCSTR filename, XMLCSTR tag=NULL); + /**< The "openFileHelper" function reports to the screen all the warnings and errors that occurred during parsing of the XML file. + * This function also tries to guess char Encoding (UTF-8, ASCII or SHIT-JIS) based on the first 200 bytes of the file. Since each + * application has its own way to report and deal with errors, you should rather use the "parseFile" function to parse XML files + * and program yourself thereafter an "error reporting" tailored for your needs (instead of using the very crude "error reporting" + * mechanism included inside the "openFileHelper" function). + * + * If the XML document is corrupted, the "openFileHelper" method will: + * - display an error message on the console (or inside a messageBox for windows). + * - stop execution (exit). + * + * I strongly suggest that you write your own "openFileHelper" method tailored to your needs. If you still want to parse + * the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the beginning of the "xmlParser.cpp" file. + * + * @param filename the path of the XML file to parse. + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + */ + + static XMLCSTR getError(XMLError error); ///< this gives you a user-friendly explanation of the parsing error + + /// Create an XML string starting from the current XMLNode. + XMLSTR createXMLString(int nFormat=1, int *pnSize=NULL) const; + /**< The returned string should be free'd using the "freeXMLString" function. + * + * If nFormat==0, no formatting is required otherwise this returns an user friendly XML string from a given element + * with appropriate white spaces and carriage returns. if pnSize is given it returns the size in character of the string. */ + + /// Save the content of an xmlNode inside a file + XMLError writeToFile(XMLCSTR filename, + const char *encoding=NULL, + char nFormat=1) const; + /**< If nFormat==0, no formatting is required otherwise this returns an user friendly XML string from a given element with appropriate white spaces and carriage returns. + * If the global parameter "characterEncoding==encoding_UTF8", then the "encoding" parameter is ignored and always set to "utf-8". + * If the global parameter "characterEncoding==encoding_ShiftJIS", then the "encoding" parameter is ignored and always set to "SHIFT-JIS". + * If "_XMLWIDECHAR=1", then the "encoding" parameter is ignored and always set to "utf-16". + * If no "encoding" parameter is given the "ISO-8859-1" encoding is used. */ + /** @} */ + + /** @defgroup navigate Navigate the XMLNode structure + * @ingroup XMLParserGeneral + * @{ */ + XMLCSTR getName() const; ///< name of the node + XMLCSTR getText(int i=0) const; ///< return ith text field + XMLCSTR getInnerText() const; + int nText() const; ///< nbr of text field + XMLNode getParentNode() const; ///< return the parent node + XMLNode getChildNode(int i=0) const; ///< return ith child node + XMLNode getChildNode(XMLCSTR name, int i) const; ///< return ith child node with specific name (return an empty node if failing). If i==-1, this returns the last XMLNode with the given name. + XMLNode getChildNode(XMLCSTR name, int *i=NULL) const; ///< return next child node with specific name (return an empty node if failing) + XMLNode getChildNodeWithAttribute(XMLCSTR tagName, + XMLCSTR attributeName, + XMLCSTR attributeValue=NULL, + int *i=NULL) const; ///< return child node with specific name/attribute (return an empty node if failing) + XMLNode getChildNodeByPath(XMLSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path. WARNING: the value of the parameter "path" is destroyed! + XMLNode getChildNodeByPath(XMLCSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path + XMLNode getChildNodeByPathNonConst(XMLSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path. + XMLNode getNextNode() const; + + int nChildNode(XMLCSTR name) const; ///< return the number of child node with specific name + int nChildNode() const; ///< nbr of child node + XMLAttribute getAttribute(int i=0) const; ///< return ith attribute + XMLCSTR getAttributeName(int i=0) const; ///< return ith attribute name + XMLCSTR getAttributeValue(int i=0) const; ///< return ith attribute value + char isAttributeSet(XMLCSTR name) const; ///< test if an attribute with a specific name is given + XMLCSTR getAttribute(XMLCSTR name, int i) const; ///< return ith attribute content with specific name (return a NULL if failing) + XMLCSTR getAttribute(XMLCSTR name, int *i=NULL) const; ///< return next attribute content with specific name (return a NULL if failing) + int nAttribute() const; ///< nbr of attribute + XMLClear getClear(int i=0) const; ///< return ith clear field (comments) + int nClear() const; ///< nbr of clear field + XMLNodeContents enumContents(XMLElementPosition i) const; ///< enumerate all the different contents (attribute,child,text, clear) of the current XMLNode. The order is reflecting the order of the original file/string. NOTE: 0 <= i < nElement(); + int nElement() const; ///< nbr of different contents for current node + char isEmpty() const; ///< is this node Empty? + char isDeclaration() const; ///< is this node a declaration + XMLNode deepCopy() const; ///< deep copy (duplicate/clone) a XMLNode + static XMLNode emptyNode(); ///< return XMLNode::emptyXMLNode; + /** @} */ + + ~XMLNode(); + XMLNode(const XMLNode &A); ///< to allow shallow/fast copy: + XMLNode& operator=( const XMLNode& A ); ///< to allow shallow/fast copy: + + XMLNode(): d(NULL){}; + static XMLNode emptyXMLNode; + static XMLClear emptyXMLClear; + static XMLAttribute emptyXMLAttribute; + + /** helpers for external C applications **/ + XMLNode( HXML h ); + void attach( HXML h ); + HXML detach(); + operator HXML() const { return (HXML)d; } + + /** @defgroup xmlModify Create or Update the XMLNode structure + * @ingroup XMLParserGeneral + * The functions in this group allows you to create from scratch (or update) a XMLNode structure. Start by creating your top + * node with the "createXMLTopNode" function and then add new nodes with the "addChild" function. The parameter 'pos' gives + * the position where the childNode, the text or the XMLClearTag will be inserted. The default value (pos=-1) inserts at the + * end. The value (pos=0) insert at the beginning (Insertion at the beginning is slower than at the end).
+ * + * REMARK: 0 <= pos < nChild()+nText()+nClear()
+ */ + + /** @defgroup creation Creating from scratch a XMLNode structure + * @ingroup xmlModify + * @{ */ + static XMLNode createXMLTopNode(XMLCSTR lpszName, char isDeclaration=FALSE); ///< Create the top node of an XMLNode structure + XMLNode addChild(XMLCSTR lpszName, char isDeclaration=FALSE, XMLElementPosition pos=-1); ///< Add a new child node + XMLNode addChild(XMLNode nodeToAdd, XMLElementPosition pos=-1); ///< If the "nodeToAdd" has some parents, it will be detached from it's parents before being attached to the current XMLNode + XMLAttribute *addAttribute(XMLCSTR lpszName, XMLCSTR lpszValuev); ///< Add a new attribute + XMLCSTR addText(XMLCSTR lpszValue, XMLElementPosition pos=-1); ///< Add a new text content + XMLClear *addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, XMLElementPosition pos=-1); + /**< Add a new clear tag + * @param lpszOpen default value "" + */ + /** @} */ + + /** @defgroup xmlUpdate Updating Nodes + * @ingroup xmlModify + * Some update functions: + * @{ + */ + XMLCSTR updateName(XMLCSTR lpszName); ///< change node's name + XMLAttribute *updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName=NULL,int i=0); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName);///< set lpszNewName=NULL if you don't want to change the name of the attribute if the attribute to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, int i=0); ///< if the text to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the text to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewContent, int i=0); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLClear *newP,XMLClear *oldP); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the clearTag to update is missing, a new one will be added + /** @} */ + + /** @defgroup xmlDelete Deleting Nodes or Attributes + * @ingroup xmlModify + * Some deletion functions: + * @{ + */ + /// The "deleteNodeContent" function forces the deletion of the content of this XMLNode and the subtree. + void deleteNodeContent(); + /**< \note The XMLNode instances that are referring to the part of the subtree that has been deleted CANNOT be used anymore!!. Unexpected results will occur if you continue using them. */ + void deleteAttribute(int i=0); ///< Delete the ith attribute of the current XMLNode + void deleteAttribute(XMLCSTR lpszName); ///< Delete the attribute with the given name (the "strcmp" function is used to find the right attribute) + void deleteAttribute(XMLAttribute *anAttribute); ///< Delete the attribute with the name "anAttribute->lpszName" (the "strcmp" function is used to find the right attribute) + void deleteText(int i=0); ///< Delete the Ith text content of the current XMLNode + void deleteText(XMLCSTR lpszValue); ///< Delete the text content "lpszValue" inside the current XMLNode (direct "pointer-to-pointer" comparison is used to find the right text) + void deleteClear(int i=0); ///< Delete the Ith clear tag inside the current XMLNode + void deleteClear(XMLCSTR lpszValue); ///< Delete the clear tag "lpszValue" inside the current XMLNode (direct "pointer-to-pointer" comparison is used to find the clear tag) + void deleteClear(XMLClear *p); ///< Delete the clear tag "p" inside the current XMLNode (direct "pointer-to-pointer" comparison on the lpszName of the clear tag is used to find the clear tag) + /** @} */ + + /** @defgroup xmlWOSD ???_WOSD functions. + * @ingroup xmlModify + * The strings given as parameters for the "add" and "update" methods that have a name with + * the postfix "_WOSD" (that means "WithOut String Duplication")(for example "addText_WOSD") + * will be free'd by the XMLNode class. For example, it means that this is incorrect: + * \code + * xNode.addText_WOSD("foo"); + * xNode.updateAttribute_WOSD("#newcolor" ,NULL,"color"); + * \endcode + * In opposition, this is correct: + * \code + * xNode.addText("foo"); + * xNode.addText_WOSD(stringDup("foo")); + * xNode.updateAttribute("#newcolor" ,NULL,"color"); + * xNode.updateAttribute_WOSD(stringDup("#newcolor"),NULL,"color"); + * \endcode + * Typically, you will never do: + * \code + * char *b=(char*)malloc(...); + * xNode.addText(b); + * free(b); + * \endcode + * ... but rather: + * \code + * char *b=(char*)malloc(...); + * xNode.addText_WOSD(b); + * \endcode + * ('free(b)' is performed by the XMLNode class) + * @{ */ + static XMLNode createXMLTopNode_WOSD(XMLSTR lpszName, char isDeclaration=FALSE); ///< Create the top node of an XMLNode structure + XMLNode addChild_WOSD(XMLSTR lpszName, char isDeclaration=FALSE, XMLElementPosition pos=-1); ///< Add a new child node + XMLAttribute *addAttribute_WOSD(XMLSTR lpszName, XMLSTR lpszValue); ///< Add a new attribute + XMLCSTR addText_WOSD(XMLSTR lpszValue, XMLElementPosition pos=-1); ///< Add a new text content + XMLClear *addClear_WOSD(XMLSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, XMLElementPosition pos=-1); ///< Add a new clear Tag + + XMLCSTR updateName_WOSD(XMLSTR lpszName); ///< change node's name + XMLAttribute *updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName=NULL,int i=0); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,XMLCSTR lpszOldName); ///< set lpszNewName=NULL if you don't want to change the name of the attribute if the attribute to update is missing, a new one will be added + XMLCSTR updateText_WOSD(XMLSTR lpszNewValue, int i=0); ///< if the text to update is missing, a new one will be added + XMLCSTR updateText_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the text to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLSTR lpszNewContent, int i=0); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLClear *newP,XMLClear *oldP); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the clearTag to update is missing, a new one will be added + /** @} */ + + /** @defgroup xmlPosition Position helper functions (use in conjunction with the update&add functions + * @ingroup xmlModify + * These are some useful functions when you want to insert a childNode, a text or a XMLClearTag in the + * middle (at a specified position) of a XMLNode tree already constructed. The value returned by these + * methods is to be used as last parameter (parameter 'pos') of addChild, addText or addClear. + * @{ */ + XMLElementPosition positionOfText(int i=0) const; + XMLElementPosition positionOfText(XMLCSTR lpszValue) const; + XMLElementPosition positionOfClear(int i=0) const; + XMLElementPosition positionOfClear(XMLCSTR lpszValue) const; + XMLElementPosition positionOfClear(XMLClear *a) const; + XMLElementPosition positionOfChildNode(int i=0) const; + XMLElementPosition positionOfChildNode(XMLNode x) const; + XMLElementPosition positionOfChildNode(XMLCSTR name, int i=0) const; ///< return the position of the ith childNode with the specified name if (name==NULL) return the position of the ith childNode + /** @} */ + + /// Enumeration for XML character encoding. + typedef enum XMLCharEncoding + { + char_encoding_error=0, + char_encoding_UTF8=1, + char_encoding_legacy=2, + char_encoding_ShiftJIS=3, + char_encoding_GB2312=4, + char_encoding_Big5=5, + char_encoding_GBK=6 // this is actually the same as Big5 + } XMLCharEncoding; + + /** \addtogroup conversions + * @{ */ + + /// Sets the global options for the conversions + static char setGlobalOptions(XMLCharEncoding characterEncoding=XMLNode::char_encoding_UTF8, char guessWideCharChars=1, + char dropWhiteSpace=1, char removeCommentsInMiddleOfText=1); + /**< The "setGlobalOptions" function allows you to change four global parameters that affect string & file + * parsing. First of all, you most-probably will never have to change these 3 global parameters. + * + * @param guessWideCharChars If "guessWideCharChars"=1 and if this library is compiled in WideChar mode, then the + * XMLNode::parseFile and XMLNode::openFileHelper functions will test if the file contains ASCII + * characters. If this is the case, then the file will be loaded and converted in memory to + * WideChar before being parsed. If 0, no conversion will be performed. + * + * @param guessWideCharChars If "guessWideCharChars"=1 and if this library is compiled in ASCII/UTF8/char* mode, then the + * XMLNode::parseFile and XMLNode::openFileHelper functions will test if the file contains WideChar + * characters. If this is the case, then the file will be loaded and converted in memory to + * ASCII/UTF8/char* before being parsed. If 0, no conversion will be performed. + * + * @param characterEncoding This parameter is only meaningful when compiling in char* mode (multibyte character mode). + * In wchar_t* (wide char mode), this parameter is ignored. This parameter should be one of the + * three currently recognized encodings: XMLNode::encoding_UTF8, XMLNode::encoding_ascii, + * XMLNode::encoding_ShiftJIS. + * + * @param dropWhiteSpace In most situations, text fields containing only white spaces (and carriage returns) + * are useless. Even more, these "empty" text fields are annoying because they increase the + * complexity of the user's code for parsing. So, 99% of the time, it's better to drop + * the "empty" text fields. However The XML specification indicates that no white spaces + * should be lost when parsing the file. So to be perfectly XML-compliant, you should set + * dropWhiteSpace=0. A note of caution: if you set "dropWhiteSpace=0", the parser will be + * slower and your code will be more complex. + * + * @param removeCommentsInMiddleOfText To explain this parameter, let's consider this code: + * \code + * XMLNode x=XMLNode::parseString("foobarchu","a"); + * \endcode + * If removeCommentsInMiddleOfText=0, then we will have: + * \code + * x.getText(0) -> "foo" + * x.getText(1) -> "bar" + * x.getText(2) -> "chu" + * x.getClear(0) --> "" + * x.getClear(1) --> "" + * \endcode + * If removeCommentsInMiddleOfText=1, then we will have: + * \code + * x.getText(0) -> "foobar" + * x.getText(1) -> "chu" + * x.getClear(0) --> "" + * \endcode + * + * \return "0" when there are no errors. If you try to set an unrecognized encoding then the return value will be "1" to signal an error. + * + * \note Sometime, it's useful to set "guessWideCharChars=0" to disable any conversion + * because the test to detect the file-type (ASCII/UTF8/char* or WideChar) may fail (rarely). */ + + /// Guess the character encoding of the string (ascii, utf8 or shift-JIS) + static XMLCharEncoding guessCharEncoding(void *buffer, int bufLen, char useXMLEncodingAttribute=1); + /**< The "guessCharEncoding" function try to guess the character encoding. You most-probably will never + * have to use this function. It then returns the appropriate value of the global parameter + * "characterEncoding" described in the XMLNode::setGlobalOptions. The guess is based on the content of a buffer of length + * "bufLen" bytes that contains the first bytes (minimum 25 bytes; 200 bytes is a good value) of the + * file to be parsed. The XMLNode::openFileHelper function is using this function to automatically compute + * the value of the "characterEncoding" global parameter. There are several heuristics used to do the + * guess. One of the heuristic is based on the "encoding" attribute. The original XML specifications + * forbids to use this attribute to do the guess but you can still use it if you set + * "useXMLEncodingAttribute" to 1 (this is the default behavior and the behavior of most parsers). + * If an inconsistency in the encoding is detected, then the return value is "0". */ + /** @} */ + +private: + // these are functions and structures used internally by the XMLNode class (don't bother about them): + + typedef struct XMLNodeDataTag // to allow shallow copy and "intelligent/smart" pointers (automatic delete): + { + XMLCSTR lpszName; // Element name (=NULL if root) + XMLCSTR lpszNS; // Namespace + int nChild, // Number of child nodes + nText, // Number of text fields + nClear, // Number of Clear fields (comments) + nAttribute; // Number of attributes + char isDeclaration; // Whether node is an XML declaration - '' + struct XMLNodeDataTag *pParent; // Pointer to parent element (=NULL if root) + XMLNode *pChild; // Array of child nodes + XMLCSTR *pText; // Array of text fields + XMLClear *pClear; // Array of clear fields + XMLAttribute *pAttribute; // Array of attributes + int *pOrder; // order of the child_nodes,text_fields,clear_fields + int ref_count; // for garbage collection (smart pointers) + XMLSTR pInnerText; // cached value of inner text, for memory manadgement purposes + } XMLNodeData; + XMLNodeData *d; + + char parseClearTag(void *px, void *pa); + char maybeAddTxT(void *pa, XMLCSTR tokenPStr); + int ParseXMLElement(void *pXML); + void *addToOrder(int memInc, int *_pos, int nc, void *p, int size, XMLElementType xtype); + int indexText(XMLCSTR lpszValue) const; + int indexClear(XMLCSTR lpszValue) const; + XMLNode addChild_priv(int,XMLSTR,char,int); + XMLAttribute *addAttribute_priv(int,XMLSTR,XMLSTR); + XMLCSTR addText_priv(int,XMLSTR,int); + XMLClear *addClear_priv(int,XMLSTR,XMLCSTR,XMLCSTR,int); + void emptyTheNode(char force); + void invalidateInnerText(); + static inline XMLElementPosition findPosition(XMLNodeData *d, int index, XMLElementType xtype); + static int CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat); + static int removeOrderElement(XMLNodeData *d, XMLElementType t, int index); + static void exactMemory(XMLNodeData *d); + static int detachFromParent(XMLNodeData *d); +} XMLNode; + +/// This structure is given by the function XMLNode::enumContents. +typedef struct XMLNodeContents +{ + /// This dictates what's the content of the XMLNodeContent + enum XMLElementType etype; + /**< should be an union to access the appropriate data. Compiler does not allow union of object with constructor... too bad. */ + XMLNode child; + XMLAttribute attrib; + XMLCSTR text; + XMLClear clear; + +} XMLNodeContents; + +/** @defgroup StringAlloc String Allocation/Free functions +* @ingroup xmlModify +* @{ */ +/// Duplicate (copy in a new allocated buffer) the source string. +XMLDLLENTRY XMLSTR stringDup(XMLCSTR source, int cbData=-1); +/**< This is +* a very handy function when used with all the "XMLNode::*_WOSD" functions (\link xmlWOSD \endlink). +* @param cbData If !=0 then cbData is the number of chars to duplicate. New strings allocated with +* this function should be free'd using the "freeXMLString" function. */ + +/// to free the string allocated inside the "stringDup" function or the "createXMLString" function. +XMLDLLENTRY void freeXMLString(XMLSTR t); // {free(t);} +/** @} */ + +/** @defgroup atoX ato? like functions +* @ingroup XMLParserGeneral +* The "xmlto?" functions are equivalents to the atoi, atol, atof functions. +* The only difference is: If the variable "xmlString" is NULL, than the return value +* is "defautValue". These 6 functions are only here as "convenience" functions for the +* user (they are not used inside the XMLparser). If you don't need them, you can +* delete them without any trouble. +* +* @{ */ +XMLDLLENTRY char xmltob(XMLCSTR xmlString,char defautValue=0); +XMLDLLENTRY int xmltoi(XMLCSTR xmlString,int defautValue=0); +XMLDLLENTRY long xmltol(XMLCSTR xmlString,long defautValue=0); +XMLDLLENTRY double xmltof(XMLCSTR xmlString,double defautValue=.0); +XMLDLLENTRY XMLCSTR xmltoa(XMLCSTR xmlString,XMLCSTR defautValue=_CXML("")); +XMLDLLENTRY XMLCHAR xmltoc(XMLCSTR xmlString,const XMLCHAR defautValue=_CXML('\0')); +/** @} */ + +/** @defgroup ToXMLStringTool Helper class to create XML files using "printf", "fprintf", "cout",... functions. +* @ingroup XMLParserGeneral +* @{ */ +/// Helper class to create XML files using "printf", "fprintf", "cout",... functions. +/** The ToXMLStringTool class helps you creating XML files using "printf", "fprintf", "cout",... functions. +* The "ToXMLStringTool" class is processing strings so that all the characters +* &,",',<,> are replaced by their XML equivalent: +* \verbatim &, ", ', <, > \endverbatim +* Using the "ToXMLStringTool class" and the "fprintf function" is THE most efficient +* way to produce VERY large XML documents VERY fast. +* \note If you are creating from scratch an XML file using the provided XMLNode class +* you must not use the "ToXMLStringTool" class (because the "XMLNode" class does the +* processing job for you during rendering).*/ +typedef struct XMLDLLENTRY ToXMLStringTool +{ +public: + ToXMLStringTool(): buf(NULL),buflen(0){} + ~ToXMLStringTool(); + void freeBuffer();///