diff options
author | George Hazan <george.hazan@gmail.com> | 2015-06-13 16:55:17 +0000 |
---|---|---|
committer | George Hazan <george.hazan@gmail.com> | 2015-06-13 16:55:17 +0000 |
commit | cbe3cb21f5bca61a03bbd4ae811ee906e09b3f4f (patch) | |
tree | 4854fb66f4d59940efa3c1590237915851074dbf /src/mir_app | |
parent | 351bcbec48ed77af5f8efcc4d5198707922c5d86 (diff) |
- miranda32.exe now does nothing bug extends PATH to %miranda_root%\libs and loads mir_app.dll;
- everything that was in miranda32.exe (including resources) moved to mir_app.dll;
- exports from mir_app.dll now available for using directly, without perversions;
- src/stdplug.h deleted;
git-svn-id: http://svn.miranda-ng.org/main/trunk@14143 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'src/mir_app')
226 files changed, 60295 insertions, 0 deletions
diff --git a/src/mir_app/mir_app.vcxproj b/src/mir_app/mir_app.vcxproj new file mode 100644 index 0000000000..66bd019032 --- /dev/null +++ b/src/mir_app/mir_app.vcxproj @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{538E451F-E667-4D07-BCE6-976ECC7BB8D1}</ProjectGuid>
+ <ProjectName>mir_app</ProjectName>
+ </PropertyGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(ProjectDir)..\..\build\vc.common\lib.props" />
+ </ImportGroup>
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <PreprocessorDefinitions Condition="'$(Configuration)'=='Debug'">MIR_APP_EXPORTS;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'$(Configuration)'=='Release'">MIR_APP_EXPORTS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <ModuleDefinitionFile>src/mir_app.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Condition="'$(VisualStudioVersion)' == '10.0'" Include="..\..\plugins\zlib\zlib_10.vcxproj">
+ <Project>{e2a369cd-eda3-414f-8ad0-e732cd7ee68c}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Condition="'$(VisualStudioVersion)' == '12.0'" Include="..\..\plugins\zlib\zlib_12.vcxproj">
+ <Project>{e2a369cd-eda3-414f-8ad0-e732cd7ee68c}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+ </ItemGroup>
+</Project>
\ No newline at end of file diff --git a/src/mir_app/mir_app.vcxproj.filters b/src/mir_app/mir_app.vcxproj.filters new file mode 100644 index 0000000000..de5ad9f66c --- /dev/null +++ b/src/mir_app/mir_app.vcxproj.filters @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" />
+</Project>
\ No newline at end of file diff --git a/src/mir_app/res/Icon_exit.ico b/src/mir_app/res/Icon_exit.ico Binary files differnew file mode 100644 index 0000000000..1d4579a29b --- /dev/null +++ b/src/mir_app/res/Icon_exit.ico diff --git a/src/mir_app/res/Icon_show_hide.ico b/src/mir_app/res/Icon_show_hide.ico Binary files differnew file mode 100644 index 0000000000..4e76501b02 --- /dev/null +++ b/src/mir_app/res/Icon_show_hide.ico diff --git a/src/mir_app/res/Off.ico b/src/mir_app/res/Off.ico Binary files differnew file mode 100644 index 0000000000..5917d0f1e4 --- /dev/null +++ b/src/mir_app/res/Off.ico diff --git a/src/mir_app/res/On.ico b/src/mir_app/res/On.ico Binary files differnew file mode 100644 index 0000000000..e5f5b8157a --- /dev/null +++ b/src/mir_app/res/On.ico diff --git a/src/mir_app/res/_blank.ico b/src/mir_app/res/_blank.ico Binary files differnew file mode 100644 index 0000000000..d8a5b3d80d --- /dev/null +++ b/src/mir_app/res/_blank.ico diff --git a/src/mir_app/res/always_visible.ico b/src/mir_app/res/always_visible.ico Binary files differnew file mode 100644 index 0000000000..1bc8480355 --- /dev/null +++ b/src/mir_app/res/always_visible.ico diff --git a/src/mir_app/res/auth_add.ico b/src/mir_app/res/auth_add.ico Binary files differnew file mode 100644 index 0000000000..90c6b83639 --- /dev/null +++ b/src/mir_app/res/auth_add.ico diff --git a/src/mir_app/res/auth_grant.ico b/src/mir_app/res/auth_grant.ico Binary files differnew file mode 100644 index 0000000000..58acccb83f --- /dev/null +++ b/src/mir_app/res/auth_grant.ico diff --git a/src/mir_app/res/auth_request.ico b/src/mir_app/res/auth_request.ico Binary files differnew file mode 100644 index 0000000000..85ea50a284 --- /dev/null +++ b/src/mir_app/res/auth_request.ico diff --git a/src/mir_app/res/auth_revoke.ico b/src/mir_app/res/auth_revoke.ico Binary files differnew file mode 100644 index 0000000000..e91ee67dc9 --- /dev/null +++ b/src/mir_app/res/auth_revoke.ico diff --git a/src/mir_app/res/chat_channel.ico b/src/mir_app/res/chat_channel.ico Binary files differnew file mode 100644 index 0000000000..d2a23e3798 --- /dev/null +++ b/src/mir_app/res/chat_channel.ico diff --git a/src/mir_app/res/chat_join.ico b/src/mir_app/res/chat_join.ico Binary files differnew file mode 100644 index 0000000000..5bbc6c5643 --- /dev/null +++ b/src/mir_app/res/chat_join.ico diff --git a/src/mir_app/res/chat_leave.ico b/src/mir_app/res/chat_leave.ico Binary files differnew file mode 100644 index 0000000000..01b2cc66a2 --- /dev/null +++ b/src/mir_app/res/chat_leave.ico diff --git a/src/mir_app/res/check_off.ico b/src/mir_app/res/check_off.ico Binary files differnew file mode 100644 index 0000000000..2a78c302df --- /dev/null +++ b/src/mir_app/res/check_off.ico diff --git a/src/mir_app/res/check_on.ico b/src/mir_app/res/check_on.ico Binary files differnew file mode 100644 index 0000000000..0ba1d546f3 --- /dev/null +++ b/src/mir_app/res/check_on.ico diff --git a/src/mir_app/res/contact_add.ico b/src/mir_app/res/contact_add.ico Binary files differnew file mode 100644 index 0000000000..fcfa03a6ad --- /dev/null +++ b/src/mir_app/res/contact_add.ico diff --git a/src/mir_app/res/contact_delete.ico b/src/mir_app/res/contact_delete.ico Binary files differnew file mode 100644 index 0000000000..086b3574e7 --- /dev/null +++ b/src/mir_app/res/contact_delete.ico diff --git a/src/mir_app/res/contact_groups.ico b/src/mir_app/res/contact_groups.ico Binary files differnew file mode 100644 index 0000000000..3429a768b6 --- /dev/null +++ b/src/mir_app/res/contact_groups.ico diff --git a/src/mir_app/res/contact_rename.ico b/src/mir_app/res/contact_rename.ico Binary files differnew file mode 100644 index 0000000000..928d569c90 --- /dev/null +++ b/src/mir_app/res/contact_rename.ico diff --git a/src/mir_app/res/contact_view_details.ico b/src/mir_app/res/contact_view_details.ico Binary files differnew file mode 100644 index 0000000000..d0af928496 --- /dev/null +++ b/src/mir_app/res/contact_view_details.ico diff --git a/src/mir_app/res/cursor_drag_copy.cur b/src/mir_app/res/cursor_drag_copy.cur Binary files differnew file mode 100644 index 0000000000..89c7c960d5 --- /dev/null +++ b/src/mir_app/res/cursor_drag_copy.cur diff --git a/src/mir_app/res/cursor_drop_user.cur b/src/mir_app/res/cursor_drop_user.cur Binary files differnew file mode 100644 index 0000000000..a84b19e28b --- /dev/null +++ b/src/mir_app/res/cursor_drop_user.cur diff --git a/src/mir_app/res/cursor_hyperlink.cur b/src/mir_app/res/cursor_hyperlink.cur Binary files differnew file mode 100644 index 0000000000..f0f548c828 --- /dev/null +++ b/src/mir_app/res/cursor_hyperlink.cur diff --git a/src/mir_app/res/female.ico b/src/mir_app/res/female.ico Binary files differnew file mode 100644 index 0000000000..feb57cd26c --- /dev/null +++ b/src/mir_app/res/female.ico diff --git a/src/mir_app/res/group_closed.ico b/src/mir_app/res/group_closed.ico Binary files differnew file mode 100644 index 0000000000..71a0020637 --- /dev/null +++ b/src/mir_app/res/group_closed.ico diff --git a/src/mir_app/res/group_opened.ico b/src/mir_app/res/group_opened.ico Binary files differnew file mode 100644 index 0000000000..2f6641c564 --- /dev/null +++ b/src/mir_app/res/group_opened.ico diff --git a/src/mir_app/res/icon_accmgr.ico b/src/mir_app/res/icon_accmgr.ico Binary files differnew file mode 100644 index 0000000000..c1d3f44606 --- /dev/null +++ b/src/mir_app/res/icon_accmgr.ico diff --git a/src/mir_app/res/icon_all.ico b/src/mir_app/res/icon_all.ico Binary files differnew file mode 100644 index 0000000000..0d1e5ac373 --- /dev/null +++ b/src/mir_app/res/icon_all.ico diff --git a/src/mir_app/res/icon_ansi.ico b/src/mir_app/res/icon_ansi.ico Binary files differnew file mode 100644 index 0000000000..879f84c164 --- /dev/null +++ b/src/mir_app/res/icon_ansi.ico diff --git a/src/mir_app/res/icon_auth_request.ico b/src/mir_app/res/icon_auth_request.ico Binary files differnew file mode 100644 index 0000000000..7cb1691367 --- /dev/null +++ b/src/mir_app/res/icon_auth_request.ico diff --git a/src/mir_app/res/icon_changefont.ico b/src/mir_app/res/icon_changefont.ico Binary files differnew file mode 100644 index 0000000000..9901568e67 --- /dev/null +++ b/src/mir_app/res/icon_changefont.ico diff --git a/src/mir_app/res/icon_connecting.ico b/src/mir_app/res/icon_connecting.ico Binary files differnew file mode 100644 index 0000000000..df6c8fc04e --- /dev/null +++ b/src/mir_app/res/icon_connecting.ico diff --git a/src/mir_app/res/icon_down_arrow.ico b/src/mir_app/res/icon_down_arrow.ico Binary files differnew file mode 100644 index 0000000000..79ec3929df --- /dev/null +++ b/src/mir_app/res/icon_down_arrow.ico diff --git a/src/mir_app/res/icon_error.ico b/src/mir_app/res/icon_error.ico Binary files differnew file mode 100644 index 0000000000..a566cf9917 --- /dev/null +++ b/src/mir_app/res/icon_error.ico diff --git a/src/mir_app/res/icon_fatal.ico b/src/mir_app/res/icon_fatal.ico Binary files differnew file mode 100644 index 0000000000..3212038565 --- /dev/null +++ b/src/mir_app/res/icon_fatal.ico diff --git a/src/mir_app/res/icon_file.ico b/src/mir_app/res/icon_file.ico Binary files differnew file mode 100644 index 0000000000..098a7cc0e4 --- /dev/null +++ b/src/mir_app/res/icon_file.ico diff --git a/src/mir_app/res/icon_find_user.ico b/src/mir_app/res/icon_find_user.ico Binary files differnew file mode 100644 index 0000000000..ca7c3d5547 --- /dev/null +++ b/src/mir_app/res/icon_find_user.ico diff --git a/src/mir_app/res/icon_frame.ico b/src/mir_app/res/icon_frame.ico Binary files differnew file mode 100644 index 0000000000..342d3dd641 --- /dev/null +++ b/src/mir_app/res/icon_frame.ico diff --git a/src/mir_app/res/icon_help.ico b/src/mir_app/res/icon_help.ico Binary files differnew file mode 100644 index 0000000000..7d7fe6d402 --- /dev/null +++ b/src/mir_app/res/icon_help.ico diff --git a/src/mir_app/res/icon_history.ico b/src/mir_app/res/icon_history.ico Binary files differnew file mode 100644 index 0000000000..10bbd7aaf7 --- /dev/null +++ b/src/mir_app/res/icon_history.ico diff --git a/src/mir_app/res/icon_loaded.ico b/src/mir_app/res/icon_loaded.ico Binary files differnew file mode 100644 index 0000000000..85f450977c --- /dev/null +++ b/src/mir_app/res/icon_loaded.ico diff --git a/src/mir_app/res/icon_loaded_gray.ico b/src/mir_app/res/icon_loaded_gray.ico Binary files differnew file mode 100644 index 0000000000..d213f0c34f --- /dev/null +++ b/src/mir_app/res/icon_loaded_gray.ico diff --git a/src/mir_app/res/icon_mail.ico b/src/mir_app/res/icon_mail.ico Binary files differnew file mode 100644 index 0000000000..7a5e461f6f --- /dev/null +++ b/src/mir_app/res/icon_mail.ico diff --git a/src/mir_app/res/icon_mainmenu.ico b/src/mir_app/res/icon_mainmenu.ico Binary files differnew file mode 100644 index 0000000000..82807dbbab --- /dev/null +++ b/src/mir_app/res/icon_mainmenu.ico diff --git a/src/mir_app/res/icon_message.ico b/src/mir_app/res/icon_message.ico Binary files differnew file mode 100644 index 0000000000..5d758744c0 --- /dev/null +++ b/src/mir_app/res/icon_message.ico diff --git a/src/mir_app/res/icon_notify.ico b/src/mir_app/res/icon_notify.ico Binary files differnew file mode 100644 index 0000000000..a116a1631c --- /dev/null +++ b/src/mir_app/res/icon_notify.ico diff --git a/src/mir_app/res/icon_notloaded.ico b/src/mir_app/res/icon_notloaded.ico Binary files differnew file mode 100644 index 0000000000..071e527d95 --- /dev/null +++ b/src/mir_app/res/icon_notloaded.ico diff --git a/src/mir_app/res/icon_notloaded_gray.ico b/src/mir_app/res/icon_notloaded_gray.ico Binary files differnew file mode 100644 index 0000000000..f70074c878 --- /dev/null +++ b/src/mir_app/res/icon_notloaded_gray.ico diff --git a/src/mir_app/res/icon_options.ico b/src/mir_app/res/icon_options.ico Binary files differnew file mode 100644 index 0000000000..245a4185ab --- /dev/null +++ b/src/mir_app/res/icon_options.ico diff --git a/src/mir_app/res/icon_search_all.ico b/src/mir_app/res/icon_search_all.ico Binary files differnew file mode 100644 index 0000000000..a88bc841fa --- /dev/null +++ b/src/mir_app/res/icon_search_all.ico diff --git a/src/mir_app/res/icon_small_dot.ico b/src/mir_app/res/icon_small_dot.ico Binary files differnew file mode 100644 index 0000000000..faade362bf --- /dev/null +++ b/src/mir_app/res/icon_small_dot.ico diff --git a/src/mir_app/res/icon_sms.ico b/src/mir_app/res/icon_sms.ico Binary files differnew file mode 100644 index 0000000000..98bd33e6d9 --- /dev/null +++ b/src/mir_app/res/icon_sms.ico diff --git a/src/mir_app/res/icon_typing.ico b/src/mir_app/res/icon_typing.ico Binary files differnew file mode 100644 index 0000000000..6264e76a93 --- /dev/null +++ b/src/mir_app/res/icon_typing.ico diff --git a/src/mir_app/res/icon_undo.ico b/src/mir_app/res/icon_undo.ico Binary files differnew file mode 100644 index 0000000000..3479bceb4c --- /dev/null +++ b/src/mir_app/res/icon_undo.ico diff --git a/src/mir_app/res/icon_unicode.ico b/src/mir_app/res/icon_unicode.ico Binary files differnew file mode 100644 index 0000000000..ae62c85e23 --- /dev/null +++ b/src/mir_app/res/icon_unicode.ico diff --git a/src/mir_app/res/icon_url.ico b/src/mir_app/res/icon_url.ico Binary files differnew file mode 100644 index 0000000000..dbf58aeb04 --- /dev/null +++ b/src/mir_app/res/icon_url.ico diff --git a/src/mir_app/res/icon_warning.ico b/src/mir_app/res/icon_warning.ico Binary files differnew file mode 100644 index 0000000000..dfa6fed1b3 --- /dev/null +++ b/src/mir_app/res/icon_warning.ico diff --git a/src/mir_app/res/icon_window.ico b/src/mir_app/res/icon_window.ico Binary files differnew file mode 100644 index 0000000000..5d4cb7e9fd --- /dev/null +++ b/src/mir_app/res/icon_window.ico diff --git a/src/mir_app/res/icon_windows.ico b/src/mir_app/res/icon_windows.ico Binary files differnew file mode 100644 index 0000000000..ffb6c440bf --- /dev/null +++ b/src/mir_app/res/icon_windows.ico diff --git a/src/mir_app/res/male.ico b/src/mir_app/res/male.ico Binary files differnew file mode 100644 index 0000000000..9b478e2412 --- /dev/null +++ b/src/mir_app/res/male.ico diff --git a/src/mir_app/res/meta_add.ico b/src/mir_app/res/meta_add.ico Binary files differnew file mode 100644 index 0000000000..814383341c --- /dev/null +++ b/src/mir_app/res/meta_add.ico diff --git a/src/mir_app/res/meta_convert.ico b/src/mir_app/res/meta_convert.ico Binary files differnew file mode 100644 index 0000000000..51417bdb8c --- /dev/null +++ b/src/mir_app/res/meta_convert.ico diff --git a/src/mir_app/res/meta_edit.ico b/src/mir_app/res/meta_edit.ico Binary files differnew file mode 100644 index 0000000000..9696507f31 --- /dev/null +++ b/src/mir_app/res/meta_edit.ico diff --git a/src/mir_app/res/meta_menu.ico b/src/mir_app/res/meta_menu.ico Binary files differnew file mode 100644 index 0000000000..2e1fdcdf78 --- /dev/null +++ b/src/mir_app/res/meta_menu.ico diff --git a/src/mir_app/res/meta_menuof.ico b/src/mir_app/res/meta_menuof.ico Binary files differnew file mode 100644 index 0000000000..779fe3db15 --- /dev/null +++ b/src/mir_app/res/meta_menuof.ico diff --git a/src/mir_app/res/meta_remove2.ico b/src/mir_app/res/meta_remove2.ico Binary files differnew file mode 100644 index 0000000000..c05c19eb8f --- /dev/null +++ b/src/mir_app/res/meta_remove2.ico diff --git a/src/mir_app/res/meta_set_as_default.ico b/src/mir_app/res/meta_set_as_default.ico Binary files differnew file mode 100644 index 0000000000..e880efd0d1 --- /dev/null +++ b/src/mir_app/res/meta_set_as_default.ico diff --git a/src/mir_app/res/miranda_home.ico b/src/mir_app/res/miranda_home.ico Binary files differnew file mode 100644 index 0000000000..b4bf45aacb --- /dev/null +++ b/src/mir_app/res/miranda_home.ico diff --git a/src/mir_app/res/miranda_logo.ico b/src/mir_app/res/miranda_logo.ico Binary files differnew file mode 100644 index 0000000000..768612f041 --- /dev/null +++ b/src/mir_app/res/miranda_logo.ico diff --git a/src/mir_app/res/miranda_manager.ico b/src/mir_app/res/miranda_manager.ico Binary files differnew file mode 100644 index 0000000000..92a6224751 --- /dev/null +++ b/src/mir_app/res/miranda_manager.ico diff --git a/src/mir_app/res/never_visible.ico b/src/mir_app/res/never_visible.ico Binary files differnew file mode 100644 index 0000000000..12a69a5807 --- /dev/null +++ b/src/mir_app/res/never_visible.ico diff --git a/src/mir_app/res/resource.rc b/src/mir_app/res/resource.rc new file mode 100644 index 0000000000..75136e9e14 --- /dev/null +++ b/src/mir_app/res/resource.rc @@ -0,0 +1,1327 @@ +// Microsoft Visual C++ generated resource script.
+//
+#include "..\src\resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include <windows.h>
+#include <winres.h>
+#include "../include/statusmodes.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_ACCFORM DIALOGEX 0, 0, 205, 119
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Enter account name (for example, My Google)",IDC_STATIC,7,7,191,9
+ EDITTEXT IDC_ACCNAME,7,18,191,12,ES_AUTOHSCROLL
+ LTEXT "Choose the protocol type",IDC_STATIC,7,35,191,11
+ COMBOBOX IDC_PROTOTYPECOMBO,7,46,191,60,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Specify the internal account name (optional)",IDC_STATIC,7,65,191,8
+ EDITTEXT IDC_ACCINTERNALNAME,7,76,191,12,ES_AUTOHSCROLL
+ PUSHBUTTON "OK",IDOK,92,98,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,148,98,50,14
+END
+
+IDD_ADDCONTACT DIALOGEX 0, 0, 230, 156
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Add contact"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "&Add",IDOK,20,135,72,14
+ PUSHBUTTON "&Cancel",IDCANCEL,139,135,71,14
+ EDITTEXT IDC_MYHANDLE,6,16,90,12,ES_AUTOHSCROLL
+ COMBOBOX IDC_GROUP,112,16,110,60,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Send ""You were added""",IDC_ADDED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,44,204,10
+ CONTROL "Send authorization request",IDC_AUTH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,56,204,10
+ CONTROL "Open contact's chat window",IDC_OPEN_WINDOW,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,68,204,10
+ LTEXT "Custom name:",IDC_STATIC,6,4,70,10
+ LTEXT "Group:",IDC_STATIC,112,4,70,10
+ GROUPBOX "Options",IDC_STATIC,7,33,216,48
+ GROUPBOX "Authorization request",IDC_STATIC,7,84,216,45
+ EDITTEXT IDC_AUTHREQ,13,95,204,29,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL
+END
+
+IDD_DELETECONTACT DIALOGEX 0, 0, 294, 93
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Delete contact"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ PUSHBUTTON "&Yes",IDYES,58,38,65,14
+ DEFPUSHBUTTON "&No",IDNO,172,38,65,14
+ CONTROL "Hide from list only, in order to keep their history and ignore/visibility settings",IDC_HIDE,
+ "Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,7,65,280,9
+ LTEXT "Use Options->Contacts->Ignore to unhide contacts.",IDC_STATIC,20,78,257,8
+ CONTROL "Are you sure you want to delete %s?",IDC_TOPLINE,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,7,7,270,8
+ LTEXT "This will erase all history and settings for this contact!",IDC_STATIC,7,18,239,14
+END
+
+IDD_OPT_CONTACT DIALOGEX 0, 0, 313, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Contact display options",IDC_STATIC,7,7,299,228
+ CTEXT "Instead of displaying contacts by their nickname,\ndrag to choose another order:",IDC_STATIC,64,39,181,32
+ CONTROL "Tree1",IDC_NAMEORDER,"SysTreeView32",TVS_NOHSCROLL | TVS_NOTOOLTIPS | TVS_FULLROWSELECT | WS_BORDER | WS_TABSTOP,65,78,182,93
+END
+
+IDD_PROFILEMANAGER DIALOGEX 0, 0, 219, 211
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+CAPTION "Miranda NG profile manager"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Manage your Miranda NG profile",IDC_NAME,"MHeaderbarCtrl",0x0,0,0,220,25
+ CONTROL "Tab1",IDC_TABS,"SysTabControl32",TCS_HOTTRACK,2,31,215,137
+ PUSHBUTTON "&Run",IDOK,169,195,48,14
+ PUSHBUTTON "&Exit",IDCANCEL,112,195,48,14
+ RTEXT "Start in service mode with",IDC_SM_LABEL,10,178,106,10
+ COMBOBOX IDC_SM_COMBO,118,176,98,12,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+END
+
+IDD_FINDADD DIALOGEX 0, 0, 427, 257
+STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Find/add contacts"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Search:",IDC_STATIC,5,32,37,8
+ CONTROL "",IDC_PROTOLIST,"ComboBoxEx32",CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP,42,30,80,88
+ GROUPBOX "",IDC_PROTOIDGROUP,5,46,117,32
+ CONTROL "",IDC_BYPROTOID,"Button",BS_AUTORADIOBUTTON,11,46,85,10
+ EDITTEXT IDC_PROTOID,11,59,105,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_EMAILGROUP,5,83,117,32
+ CONTROL "E-mail address",IDC_BYEMAIL,"Button",BS_AUTORADIOBUTTON,11,83,80,10
+ EDITTEXT IDC_EMAIL,11,96,105,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_NAMEGROUP,5,120,117,59
+ CONTROL "Name",IDC_BYNAME,"Button",BS_AUTORADIOBUTTON,11,120,49,10
+ LTEXT "Nick:",IDC_STNAMENICK,11,136,31,8,NOT WS_GROUP
+ EDITTEXT IDC_NAMENICK,42,134,74,12,ES_AUTOHSCROLL
+ LTEXT "First:",IDC_STNAMEFIRST,11,149,31,8,NOT WS_GROUP
+ EDITTEXT IDC_NAMEFIRST,42,147,74,12,ES_AUTOHSCROLL
+ LTEXT "Last:",IDC_STNAMELAST,11,163,31,8,NOT WS_GROUP
+ EDITTEXT IDC_NAMELAST,42,161,74,12,ES_AUTOHSCROLL
+ GROUPBOX "",IDC_ADVANCEDGROUP,5,184,117,32
+ CONTROL "Advanced",IDC_BYADVANCED,"Button",BS_AUTORADIOBUTTON,11,184,66,10
+ CONTROL "Advanced >>",IDC_ADVANCED,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,11,196,105,14
+ GROUPBOX "",IDC_TINYEXTENDEDGROUP,5,190,117,25
+ CONTROL "&Search", IDOK, "MButtonClass", WS_TABSTOP, 5, 222, 117, 17
+ CONTROL "List1",IDC_RESULTS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE | WS_BORDER | WS_TABSTOP,129,30,293,191
+ CONTROL "More options",IDC_MOREOPTIONS,"MButtonClass",WS_DISABLED | WS_TABSTOP,272,225,77,14,WS_EX_NOACTIVATE | 0x10000000L
+ PUSHBUTTON "Add to list",IDC_ADD,354,225,68,14,WS_DISABLED
+ CONTROL "",IDC_STATUSBAR,"msctls_statusbar32",WS_TABSTOP | 0x100,0,244,427,10
+ CONTROL "Custom",IDC_BYCUSTOM,"Button",BS_AUTORADIOBUTTON,11,190,89,10
+ CONTROL "Here you can add contacts to your contact list",IDC_HEADERBAR,
+ "MHeaderbarCtrl",0x0,0,0,427,25
+END
+
+IDD_OPTIONS DIALOGEX 0, 0, 428, 303
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Options"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ COMBOBOX IDC_KEYWORD_FILTER,336,6,88,79,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Configure your Miranda NG options",IDC_HEADERBAR,
+ "MHeaderbarCtrl",0x0,0,0,428,25
+ DEFPUSHBUTTON "OK",IDOK,265,283,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,319,283,50,14
+ PUSHBUTTON "Apply",IDC_APPLY,374,283,50,14
+ CONTROL "Tree1",IDC_PAGETREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_NOHSCROLL | WS_BORDER | WS_TABSTOP,4,30,102,249
+ CONTROL "",IDC_TAB,"SysTabControl32",NOT WS_VISIBLE | WS_TABSTOP,111,30,313,249
+ CONTROL "Switch to simple options",IDC_MODERN,"Hyperlink",WS_TABSTOP,5,284,102,10,WS_EX_TRANSPARENT
+ CTEXT "Please select a subentry from the list",IDC_STNOPAGE,111,30,313,251,SS_CENTERIMAGE
+END
+
+IDD_OPTIONSPAGE DIALOGEX 0, 0, 321, 303
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Options"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ COMBOBOX IDC_KEYWORD_FILTER,336,37,88,79,CBS_DROPDOWN | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Configure your Miranda NG options",IDC_HEADERBAR,
+ "MHeaderbarCtrl",0x0,0,0,325,25
+ DEFPUSHBUTTON "OK",IDOK,158,283,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,212,283,50,14
+ PUSHBUTTON "Apply",IDC_APPLY,267,283,50,14
+ CONTROL "",IDC_TAB,"SysTabControl32",NOT WS_VISIBLE | WS_TABSTOP,4,32,313,249
+ CONTROL "Switch to simple options",IDC_MODERN,"Hyperlink",WS_TABSTOP,347,254,62,10,WS_EX_TRANSPARENT
+ CTEXT "Please select a subentry from the list",IDC_STNOPAGE,4,30,313,251,SS_CENTERIMAGE
+ CONTROL "",IDC_PAGETREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_NOHSCROLL | NOT WS_VISIBLE | WS_BORDER | WS_TABSTOP,347,86,52,137
+END
+
+IDD_INSTALLINI DIALOGEX 0, 0, 213, 103
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Install database settings"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Yes",IDOK,26,84,50,14
+ PUSHBUTTON "No",IDCANCEL,81,84,50,14
+ LTEXT "A file containing new database settings has been placed in the Miranda NG directory.",IDC_STATIC,5,5,203,16
+ LTEXT "Do you want to import the settings now?",IDC_STATIC,5,69,203,8
+ PUSHBUTTON "No to all",IDC_NOTOALL,136,84,50,14
+ LTEXT "",IDC_ININAME,5,24,143,16,SS_NOPREFIX | SS_CENTERIMAGE
+ PUSHBUTTON "&View contents",IDC_VIEWINI,150,25,58,14
+ LTEXT "Security systems to prevent malicious changes are in place and you will be warned before changes that are not known to be safe.",IDC_SECURITYINFO,5,43,203,24
+END
+
+IDD_WARNINICHANGE DIALOGEX 0, 0, 187, 113
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Database setting change"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Database settings are being imported from",IDC_STATIC,5,5,177,8
+ CONTROL "",IDC_ININAME,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,5,13,177,8
+ LTEXT "This file wishes to change the setting",IDC_STATIC,5,24,177,8
+ CONTROL "",IDC_SETTINGNAME,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,12,33,170,8
+ LTEXT "to the value",IDC_STATIC,5,42,177,8
+ CONTROL "",IDC_NEWVALUE,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,12,51,170,8
+ LTEXT "",IDC_SECURITYINFO,5,60,177,8
+ LTEXT "Do you want to allow this change?",IDC_STATIC,5,71,177,8
+ CONTROL "&Allow all further changes to this section",IDC_WARNNOMORE,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,80,169,10
+ DEFPUSHBUTTON "&Yes",IDYES,5,94,50,14
+ PUSHBUTTON "&No",IDNO,59,94,50,14
+ PUSHBUTTON "Cancel import",IDCANCEL,123,94,59,14
+END
+
+IDD_INIIMPORTDONE DIALOGEX 0, 0, 186, 73
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Database import complete"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "The import has completed from",IDC_STATIC,5,5,176,8
+ CONTROL "",IDC_ININAME,"Static",SS_SIMPLE | SS_NOPREFIX | WS_GROUP,5,13,176,8
+ LTEXT "What do you want to do with the file now?",IDC_STATIC,5,24,176,8
+ PUSHBUTTON "&Recycle",IDC_RECYCLE,5,36,50,14
+ PUSHBUTTON "&Delete",IDC_DELETE,68,36,50,14
+ EDITTEXT IDC_NEWNAME,5,55,117,12,ES_AUTOHSCROLL
+ PUSHBUTTON "&Move/Rename",IDC_MOVE,124,54,57,14
+ PUSHBUTTON "&Leave",IDC_LEAVE,131,36,50,14
+END
+
+IDD_NETLIBLOGOPTS DIALOGEX 0, 0, 314, 259
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Netlib log options"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Show",IDC_STATIC,6,2,302,118
+ CONTROL "Tree1",IDC_FILTER,"SysTreeView32",TVS_DISABLEDRAGDROP | TVS_NOTOOLTIPS | TVS_NONEVENHEIGHT | WS_BORDER | WS_HSCROLL | WS_TABSTOP,14,13,160,81
+ CONTROL "Received bytes",IDC_DUMPRECV,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,181,13,122,10
+ CONTROL "Sent bytes",IDC_DUMPSENT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,181,26,122,10
+ CONTROL "Additional data due to proxy communication",IDC_DUMPPROXY,
+ "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,181,39,122,18
+ CONTROL "SSL traffic",IDC_DUMPSSL,"Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,181,56,122,10
+ CONTROL "Text dumps where available",IDC_TEXTDUMPS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,181,69,122,10
+ CONTROL "Auto-detect text",IDC_AUTODETECTTEXT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,181,83,122,10
+ CONTROL "Calling modules' names",IDC_SHOWNAMES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,181,96,122,10
+ COMBOBOX IDC_TIMEFORMAT,14,100,160,69,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "Log to",IDC_STATIC,6,122,302,66
+ CONTROL "OutputDebugString()",IDC_TOOUTPUTDEBUGSTRING,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,132,122,10
+ CONTROL "File",IDC_TOFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,146,44,10
+ EDITTEXT IDC_FILENAME,14,159,270,12,ES_AUTOHSCROLL
+ PUSHBUTTON "...",IDC_FILENAMEBROWSE,288,159,15,12
+ LTEXT "",IDC_PATH,14,174,294,10
+ GROUPBOX "Run program when Miranda NG starts (e.g., tail -f, dbgview, etc.):",IDC_STATIC,6,188,302,32
+ EDITTEXT IDC_RUNATSTART,14,200,203,12,ES_AUTOHSCROLL
+ PUSHBUTTON "...",IDC_RUNATSTARTBROWSE,220,200,15,12
+ PUSHBUTTON "Run now",IDC_RUNNOW,241,200,60,12
+ CONTROL "Show this dialog box when Miranda NG starts",IDC_SHOWTHISDLGATSTART,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,226,294,10
+ DEFPUSHBUTTON "OK",IDOK,194,241,54,14
+ PUSHBUTTON "Cancel",IDCANCEL,254,241,54,14
+END
+
+IDD_OPT_SOUND DIALOGEX 0, 0, 285, 240
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Sounds",IDC_STATIC,4,4,277,154
+ PUSHBUTTON "&Change...",IDC_CHANGE,16,213,50,14
+ PUSHBUTTON "&Preview",IDC_PREVIEW,70,213,50,14
+ CONTROL "Download more sounds",IDC_GETMORE,"Hyperlink",WS_TABSTOP | 0x2,127,216,137,8
+ CONTROL "Tree1",IDC_SOUNDTREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS | TVS_FULLROWSELECT | WS_BORDER | WS_TABSTOP,12,33,261,119
+ GROUPBOX "Sound information",IDC_SGROUP,4,162,277,71
+ LTEXT "Location:",IDC_SLOC,17,190,40,9
+ EDITTEXT IDC_LOCATION,65,190,208,13,ES_AUTOHSCROLL | ES_READONLY
+ LTEXT "Name:",IDC_NAME,17,174,39,9
+ LTEXT "",IDC_NAMEVAL,64,174,209,9
+ CONTROL "Enable sound events",IDC_ENABLESOUNDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,17,261,10
+END
+
+IDD_OPT_ICONS DIALOGEX 0, 0, 257, 193
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Icons",IDC_STICONSGROUP,4,4,248,185
+ LTEXT "Show category:",IDC_STATIC,12,18,66,16
+ LISTBOX IDC_CATEGORYLIST,114,17,129,41,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "List1",IDC_PREVIEW,"SysListView32",LVS_SINGLESEL | LVS_AUTOARRANGE | WS_BORDER | WS_TABSTOP,12,66,231,83
+ PUSHBUTTON "&Load icon set...",IDC_LOADICONS,12,158,94,13
+ PUSHBUTTON "&Import icons >>",IDC_IMPORT,154,158,89,13
+ CONTROL "Download more icons",IDC_GETMORE,"Hyperlink",WS_TABSTOP | 0x1,12,178,232,8
+ LTEXT "",IDC_STSIMPLERIGHT,200,104,8,32,NOT WS_VISIBLE
+END
+
+IDD_OPT_IGNORE DIALOGEX 0, 0, 313, 240
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "The following events are being ignored:",IDC_STATIC,8,14,297,8
+ ICON IDI_RECVMSG,IDC_MSGICON,8,172,20,20,SS_CENTERIMAGE
+ LTEXT "Messages",IDC_STATIC,28,178,70,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_URL,IDC_URLICON,8,187,20,20,SS_CENTERIMAGE
+ LTEXT "URLs",IDC_STATIC,28,193,70,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_FILE,IDC_FILEICON,8,202,20,20,SS_CENTERIMAGE
+ LTEXT "Files",IDC_STATIC,28,208,70,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_USERONLINE,IDC_ONLINEICON,96,172,20,20,SS_CENTERIMAGE
+ LTEXT "Online notification",IDC_STATIC,116,178,107,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_MIRANDA,IDC_AUTHICON,96,187,20,20,SS_CENTERIMAGE
+ LTEXT "Auth requests",IDC_STATIC,116,193,107,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_FILLEDBLOB,IDC_ALLICON,221,187,20,20,SS_CENTERIMAGE
+ LTEXT "All events",IDC_STATIC,241,193,66,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_EMPTYBLOB,IDC_NONEICON,221,202,20,20,SS_CENTERIMAGE
+ LTEXT "None",IDC_STATIC,241,208,66,8,SS_NOPREFIX | SS_CENTERIMAGE
+ CTEXT "Only the ticked contacts will be shown on the main contact list",IDC_STCHECKMARKS,8,227,297,8
+ CONTROL "",IDC_LIST,"CListControl",WS_TABSTOP | 0x3da,8,26,297,146,WS_EX_CLIENTEDGE
+ GROUPBOX "Ignore",IDC_STATIC,0,0,313,240
+ ICON IDI_ADDCONTACT,IDC_ADDED,96,202,20,20,SS_CENTERIMAGE
+ LTEXT "Added notification",IDC_STATIC,116,208,107,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_TYPING,IDC_TYPINGICON,221,172,20,20,SS_CENTERIMAGE
+ LTEXT "Typing",IDC_STATIC,241,178,66,8,SS_NOPREFIX | SS_CENTERIMAGE
+END
+
+IDD_OPT_VISIBILITY DIALOGEX 0, 0, 313, 240
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Visibility",IDC_STATIC,0,0,313,240
+ CONTROL "",IDC_LIST,"CListControl",WS_TABSTOP | 0x1d0,8,14,297,183,WS_EX_CLIENTEDGE
+ ICON IDI_ONLINE,IDC_VISIBLEICON,8,203,20,20,SS_CENTERIMAGE
+ LTEXT "You are visible to this person even when in invisible mode",IDC_STATIC,26,209,279,8,SS_NOPREFIX | SS_CENTERIMAGE
+ ICON IDI_INVISIBLE,IDC_INVISIBLEICON,8,218,20,20,SS_CENTERIMAGE
+ LTEXT "You are never visible to this person",IDC_STATIC,26,224,279,8,SS_NOPREFIX | SS_CENTERIMAGE
+END
+
+IDD_ICONINDEX DIALOGEX 0, 0, 219, 197
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Icon index"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ RTEXT "Icon library:",IDC_STATIC,4,6,54,8
+ EDITTEXT IDC_ICONSET,60,4,139,12,ES_AUTOHSCROLL
+ PUSHBUTTON "...",IDC_BROWSE,201,5,14,10
+ LTEXT "Drag icons to main list to assign them:",IDC_STATIC,4,18,211,8
+ CONTROL "List1",IDC_PREVIEW,"SysListView32",LVS_SINGLESEL | LVS_AUTOARRANGE | WS_BORDER | WS_TABSTOP,4,27,211,82
+ GROUPBOX "Import multiple",IDC_IMPORTMULTI,4,122,211,61
+ CONTROL "To main icons",IDC_TOMAIN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,132,74,12
+ CONTROL "To",IDC_TOPROTO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,165,25,12
+ COMBOBOX IDC_PROTOLIST,34,164,79,58,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "<< &Import",IDC_IMPORT,164,164,48,14,WS_DISABLED
+ CONTROL "Download more icons",IDC_GETMORE,"Hyperlink",WS_TABSTOP | 0x1,4,185,211,8
+ CONTROL "To default status icons",IDC_TODEFICON,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,148,123,12
+END
+
+IDD_OPT_NETLIB DIALOGEX 0, 0, 313, 235
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ COMBOBOX IDC_NETLIBUSERS,0,1,231,110,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Logging...",IDC_LOGOPTIONS,241,1,60,13
+ GROUPBOX "Outgoing connections",IDC_STATIC12,0,18,313,148
+ CONTROL "Use proxy server",IDC_USEPROXY,"Button",BS_3STATE | WS_TABSTOP,7,29,159,9
+ RTEXT "Type:",IDC_STATIC21,7,43,69,8,WS_DISABLED
+ COMBOBOX IDC_PROXYTYPE,80,40,86,62,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP
+ RTEXT "Host:",IDC_STATIC22,7,58,69,8,WS_DISABLED
+ EDITTEXT IDC_PROXYHOST,80,56,86,12,ES_AUTOHSCROLL | WS_DISABLED
+ RTEXT "Port:",IDC_STATIC23,177,58,33,8,WS_DISABLED
+ EDITTEXT IDC_PROXYPORT,213,56,35,12,ES_AUTOHSCROLL | ES_NUMBER | WS_DISABLED
+ LTEXT "(often %d)",IDC_STOFTENPORT,258,57,48,8,WS_DISABLED
+ CONTROL "Use custom login (domain login picked up automatically)",IDC_PROXYAUTH,
+ "Button",BS_3STATE | WS_DISABLED | WS_TABSTOP,20,80,286,10
+ RTEXT "Username:",IDC_STATIC31,7,94,69,8,WS_DISABLED
+ EDITTEXT IDC_PROXYUSER,80,92,86,12,ES_AUTOHSCROLL | WS_DISABLED
+ RTEXT "Password:",IDC_STATIC32,177,94,49,8,WS_DISABLED
+ EDITTEXT IDC_PROXYPASS,232,92,74,12,ES_PASSWORD | ES_AUTOHSCROLL | WS_DISABLED
+ CONTROL "Resolve hostnames through proxy",IDC_PROXYDNS,"Button",BS_3STATE | WS_DISABLED | WS_TABSTOP,20,115,202,10
+ CONTROL "Port range:",IDC_SPECIFYPORTSO,"Button",BS_3STATE | BS_TOP | BS_MULTILINE | WS_TABSTOP,7,130,72,9
+ EDITTEXT IDC_PORTSRANGEO,80,129,142,12,ES_AUTOHSCROLL | WS_DISABLED
+ LTEXT "Example: 1050-1070, 2000-2010, 2500",IDC_STATIC54,80,144,196,8,WS_DISABLED
+ CONTROL "Validate SSL certificates",IDC_VALIDATESSL,"Button",BS_3STATE | BS_TOP | BS_MULTILINE | WS_TABSTOP,7,154,299,9
+ GROUPBOX "Incoming connections",IDC_STATIC43,0,167,313,52
+ CONTROL "Port range:",IDC_SPECIFYPORTS,"Button",BS_3STATE | BS_TOP | BS_MULTILINE | WS_TABSTOP,7,179,72,9
+ EDITTEXT IDC_PORTSRANGE,80,178,142,12,ES_AUTOHSCROLL | WS_DISABLED
+ LTEXT "Example: 1050-1070, 2000-2010, 2500",IDC_STATIC52,80,193,196,8,WS_DISABLED
+ CONTROL "Enable UPnP port mapping",IDC_ENABLEUPNP,"Button",BS_3STATE | BS_TOP | BS_MULTILINE | WS_TABSTOP,7,204,299,9
+ CTEXT "You will need to reconnect for the changes you have made on this page to take effect.",IDC_RECONNECTREQD,4,224,307,8,NOT WS_VISIBLE
+END
+
+IDD_PROFILE_SELECTION DIALOGEX 0, 0, 208, 122
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | DS_CONTROL
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "List1",IDC_PROFILELIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SORTASCENDING | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,0,3,207,117
+END
+
+IDD_PROFILE_NEW DIALOGEX 0, 0, 208, 122
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | DS_CONTROL
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Please complete the following form to create a new user profile",IDC_STATIC,4,3,205,16
+ EDITTEXT IDC_PROFILENAME,61,22,105,12,ES_AUTOHSCROLL
+ COMBOBOX IDC_PROFILEDRIVERS,61,75,105,56,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Profile",IDC_STATIC,20,23,38,9
+ LTEXT "e.g., Workplace",IDC_STATIC,62,35,77,9
+ LTEXT "You can select a different profile driver from the default, it may offer more features or abilities, if in doubt use the default.",IDC_STATIC,4,46,207,24
+ LTEXT "e.g., dbx mmap",IDC_STATIC,62,91,84,11
+ LTEXT "Driver",IDC_STATIC,20,77,37,9
+ LTEXT "Problem: Unable to find any database drivers, this means you cannot create a new profile, you need to get dbx_mmap.dll",IDC_NODBDRIVERS,4,104,203,22,NOT WS_VISIBLE
+END
+
+IDD_OPT_PLUGINS DIALOGEX 0, 0, 315, 252
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "List1",IDC_PLUGLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,4,4,307,110
+ CONTROL "Download more plugins",IDC_GETMOREPLUGINS,"Hyperlink",WS_TABSTOP,4,116,307,8
+ GROUPBOX "",IDC_PLUGININFOFRAME,4,125,307,125,BS_RIGHT
+ RTEXT "Description:",IDC_STATIC,6,136,47,10
+ EDITTEXT IDC_PLUGINLONGINFO,56,136,250,25,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ RTEXT "Author(s):",IDC_STATIC,6,165,47,10
+ EDITTEXT IDC_PLUGINAUTHOR,56,165,250,18,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ RTEXT "E-mail:",IDC_STATIC,6,188,47,10
+ CONTROL "",IDC_PLUGINEMAIL,"Hyperlink",WS_TABSTOP,56,188,250,10
+ RTEXT "Homepage:",IDC_STATIC,6,200,47,9
+ CONTROL "",IDC_PLUGINURL,"Hyperlink",WS_TABSTOP,56,201,250,10
+ RTEXT "Unique ID:",IDC_STATIC,6,214,47,10
+ EDITTEXT IDC_PLUGINPID,56,214,250,12,ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ RTEXT "Copyright:",IDC_STATIC,6,227,47,10
+ EDITTEXT IDC_PLUGINCPYR,56,227,250,12,ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ CTEXT "Please restart Miranda NG for your changes to take effect.",IDC_RESTART,6,240,300,8,NOT WS_VISIBLE
+END
+
+IDD_OPT_FONTS DIALOGEX 0, 0, 316, 251
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Fonts and colors",IDC_STATIC,0,0,315,250
+ CONTROL "Tree1",IDC_FONTGROUP,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | TVS_FULLROWSELECT | WS_BORDER | WS_TABSTOP,4,10,102,235
+ LISTBOX IDC_FONTLIST,109,10,201,186,LBS_OWNERDRAWVARIABLE | LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_BKGCOLOUR,"ColourPicker",WS_TABSTOP,196,199,30,14
+ CONTROL "...",IDC_EFFECT,"MButtonClass",NOT WS_VISIBLE | WS_TABSTOP,196,199,30,14
+ CONTROL "",IDC_FONTCOLOUR,"ColourPicker",WS_TABSTOP,196,215,30,14
+ CONTROL "...",IDC_CHOOSEFONT,"MButtonClass",WS_TABSTOP,196,231,30,14
+ PUSHBUTTON "Undo",IDC_BTN_UNDO,109,199,73,14
+ PUSHBUTTON "Reset",IDC_BTN_RESET,109,215,73,14
+ PUSHBUTTON "Export...",IDC_BTN_EXPORT,109,231,73,14
+ LTEXT "Color/background",IDC_BKGCOLOUR_STATIC,231,199,79,14,SS_CENTERIMAGE
+ LTEXT "Text effect",IDC_EFFECT_STATIC,231,199,79,14,SS_CENTERIMAGE | NOT WS_VISIBLE
+ LTEXT "Text color",IDC_STATIC,231,215,79,14,SS_CENTERIMAGE
+ LTEXT "Choose font",IDC_STATIC,231,231,79,14,SS_CENTERIMAGE
+END
+
+IDD_OPT_ICOLIB DIALOGEX 0, 0, 316, 251
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Icons",IDC_STICONSGROUP,0,0,315,250
+ CONTROL "List1",IDC_PREVIEW,"SysListView32",LVS_AUTOARRANGE | WS_BORDER | WS_TABSTOP,109,10,201,207
+ PUSHBUTTON "&Load icon set...",IDC_LOADICONS,116,220,94,13
+ PUSHBUTTON "&Import icons >>",IDC_IMPORT,215,220,89,13
+ CONTROL "Download more icons",IDC_GETMORE,"Hyperlink",WS_TABSTOP | 0x1,109,237,201,8
+ CONTROL "Tree1",IDC_CATEGORYLIST,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | TVS_FULLROWSELECT | TVS_NOHSCROLL | WS_BORDER | WS_TABSTOP,4,10,102,235
+END
+
+IDD_ICOLIB_IMPORT DIALOGEX 0, 0, 217, 194
+STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+EXSTYLE WS_EX_TOOLWINDOW | WS_EX_CONTROLPARENT
+CAPTION "Icon index"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ RTEXT "Icon library:",IDC_STATIC,5,6,44,8
+ EDITTEXT IDC_ICONSET,53,4,127,12,ES_AUTOHSCROLL
+ PUSHBUTTON "...",IDC_BROWSE,186,5,14,10
+ LTEXT "Drag icons to main list to assign them:",IDC_STATIC,4,18,207,8
+ CONTROL "List1",IDC_PREVIEW,"SysListView32",LVS_SINGLESEL | LVS_AUTOARRANGE | WS_BORDER | WS_TABSTOP,4,27,208,163
+END
+
+IDD_OPT_GENMENU DIALOGEX 0, 0, 316, 254
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Menu objects",IDC_STATIC,5,2,140,94
+ LISTBOX IDC_MENUOBJECTS,11,13,128,79,WS_VSCROLL | WS_TABSTOP
+ GROUPBOX "Menu items",IDC_STATIC,149,2,161,248
+ CONTROL "Tree1",IDC_MENUITEMS,"SysTreeView32",TVS_SHOWSELALWAYS | WS_DISABLED | WS_BORDER | WS_HSCROLL | WS_TABSTOP,156,13,148,212
+ GROUPBOX "Protocol menus",IDC_STATIC,5,100,140,37
+ CONTROL "Move to the main menu",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,11,111,128,8
+ CONTROL "Move to the status bar",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,11,123,130,8
+ LTEXT "Warning!\r\nThis menu object not support user defined options.",IDC_NOTSUPPORTWARNING,10,140,136,32,NOT WS_VISIBLE
+ PUSHBUTTON "Insert separator",IDC_INSERTSEPARATOR,39,180,97,14,WS_DISABLED
+ LTEXT "Service:",IDC_STATIC,4,201,29,8
+ EDITTEXT IDC_GENMENU_SERVICE,36,199,108,14,ES_AUTOHSCROLL | ES_READONLY
+ LTEXT "Name:",IDC_STATIC,4,218,29,8
+ EDITTEXT IDC_GENMENU_CUSTOMNAME,36,215,108,14,ES_AUTOHSCROLL | WS_DISABLED
+ PUSHBUTTON "Default",IDC_GENMENU_DEFAULT,18,234,55,15,WS_DISABLED
+ PUSHBUTTON "Set",IDC_GENMENU_SET,78,234,55,15,WS_DISABLED
+ CONTROL "Enable icons",IDC_DISABLEMENUICONS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,156,234,91,10
+ PUSHBUTTON "Reset",IDC_RESETMENU,254,230,50,14
+END
+
+IDD_OPT_PROTOCOLORDER DIALOGEX 0, 0, 315, 242
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CTEXT "Show accounts in the following order,\ndrag to choose another order:",IDC_STATIC,78,11,150,25
+ GROUPBOX "Account order and visibility",IDC_STATIC,0,0,314,241,0,WS_EX_TRANSPARENT
+ CONTROL "Tree1",IDC_PROTOCOLORDER,"SysTreeView32",TVS_NOTOOLTIPS | TVS_NOHSCROLL | WS_BORDER | WS_TABSTOP,78,37,151,153
+ PUSHBUTTON "Reset",IDC_RESETPROTOCOLDATA,96,193,115,14,BS_NOTIFY
+ CTEXT "Note: Miranda NG will have to be restarted for changes to take effect.",IDC_PROTOCOLORDERWARNING,13,216,280,16
+END
+
+IDD_OPT_KEYBINDINGS DIALOGEX 0, 0, 316, 251
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Key bindings",IDC_STATIC,0,0,315,250
+ CONTROL "Tree1",IDC_CATEGORYLIST,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | TVS_FULLROWSELECT | WS_BORDER | WS_TABSTOP,4,10,102,235
+ LTEXT "Shortcut:",IDC_STATIC,109,10,146,12
+ EDITTEXT IDC_PREVIEW,109,21,146,12
+ CONTROL "Add",IDC_ADD,"MButtonClass",WS_TABSTOP,260,19,50,14
+ LTEXT "",IDC_MESSAGE,109,37,201,41
+ LISTBOX IDC_LIST,109,80,146,52,WS_VSCROLL | WS_TABSTOP
+ CONTROL "Remove",IDC_DELETE,"MButtonClass",WS_TABSTOP,260,80,50,14
+ PUSHBUTTON "Undo changes",IDC_BTN_UNDO,109,140,96,14
+ PUSHBUTTON "Reset to default",IDC_BTN_RESET,214,140,96,14
+END
+
+IDD_OPT_HOTKEYS DIALOGEX 0, 0, 316, 251
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Hotkeys",IDC_STICONSGROUP,0,0,315,250
+ CONTROL "List1",IDC_LV_HOTKEYS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_AUTOARRANGE | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,4,10,306,202
+ EDITTEXT IDC_HOTKEY,192,205,118,14,ES_CENTER | ES_AUTOHSCROLL | NOT WS_VISIBLE
+ CONTROL "",IDC_CANVAS,"Static",SS_OWNERDRAW,4,217,306,14,WS_EX_TRANSPARENT
+ CONTROL "",IDC_CANVAS2,"Static",SS_OWNERDRAW,4,231,306,14,WS_EX_TRANSPARENT
+END
+
+IDD_ACCMGR DIALOGEX 0, 0, 350, 245
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Accounts"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "Configure your IM accounts",IDC_HEADERBAR,
+ "MHeaderbarCtrl",0x0,0,0,350,25
+ LISTBOX IDC_ACCLIST,7,32,130,187,LBS_SORT | LBS_OWNERDRAWVARIABLE | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Account information:",IDC_TXT_ACCOUNT,142,32,201,8
+ LTEXT "",IDC_TXT_INFO,157,45,186,134
+ LTEXT "Additional:",IDC_TXT_ADDITIONAL,142,184,201,8
+ CONTROL "Configure network...",IDC_LNK_NETWORK,"Hyperlink",WS_TABSTOP,157,197,186,11
+ CONTROL "Get more protocols...",IDC_LNK_ADDONS,"Hyperlink",WS_TABSTOP,157,208,186,11
+ CONTROL "&Add...",IDC_ADD,"MButtonClass",WS_TABSTOP,7,224,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Edit",IDC_EDIT,"MButtonClass",WS_TABSTOP,26,224,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Options",IDC_OPTIONS,"MButtonClass",WS_TABSTOP,45,224,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Upgrade",IDC_UPGRADE,"MButtonClass",WS_TABSTOP,64,224,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Remove...",IDC_REMOVE,"MButtonClass",WS_TABSTOP,83,224,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ DEFPUSHBUTTON "OK",IDOK,239,224,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,293,224,50,14
+END
+
+IDD_WAITRESTART DIALOGEX 0, 0, 167, 70
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION
+EXSTYLE WS_EX_TOOLWINDOW
+CAPTION "Miranda NG"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "Miranda NG is being restarted.\nPlease wait...",IDC_STATIC,7,7,153,18
+ CONTROL "",IDC_PROGRESSBAR,"msctls_progress32",WS_BORDER,7,30,153,14
+ PUSHBUTTON "Cancel",IDCANCEL,110,49,50,14
+END
+
+IDD_ERROR_LIST DIALOGEX 0, 0, 312, 209
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Error console"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LISTBOX IDC_LST_ERRORS,5,5,301,198,LBS_OWNERDRAWVARIABLE | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+END
+
+IDD_OPT_ERRORS DIALOGEX 0, 0, 316, 251
+STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Error notifications",IDC_STATIC,0,0,315,250
+ CONTROL "",IDC_LV_ERRORS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,4,10,306,235
+END
+
+IDD_MODERNOPT_FONTS DIALOGEX 0, 0, 260, 210
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Headers:",IDC_TXT_TITLE1,0,0,259,8
+ PUSHBUTTON "...",IDC_CHOOSEFONTHEADER,244,30,16,14
+ LTEXT "This font is used to display main section titles or text elements.",IDC_STATIC,10,11,249,16,SS_CENTERIMAGE
+ LTEXT "Normal text:",IDC_TXT_TITLE2,0,55,259,8
+ PUSHBUTTON "...",IDC_CHOOSEFONTGENERAL,244,84,16,14
+ LTEXT "This font is used to display most text elements or section bodies.",IDC_STATIC,10,66,249,16,SS_CENTERIMAGE
+ LTEXT "Minor notes:",IDC_TXT_TITLE3,0,109,259,8
+ PUSHBUTTON "...",IDC_CHOOSEFONTSMALL,244,139,16,14
+ LTEXT "This font is used to display various additional notes.",IDC_STATIC,10,120,249,16,SS_CENTERIMAGE
+ CONTROL "",IDC_PREVIEWSMALL,"Static",SS_OWNERDRAW,10,139,229,15,WS_EX_STATICEDGE
+ CONTROL "",IDC_PREVIEWGENERAL,"Static",SS_OWNERDRAW,10,84,229,15,WS_EX_STATICEDGE
+ CONTROL "",IDC_PREVIEWHEADER,"Static",SS_OWNERDRAW,10,30,229,15,WS_EX_STATICEDGE
+END
+
+IDD_MODERNOPT_ACCOUNTS DIALOGEX 0, 0, 368, 210
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LISTBOX IDC_ACCLIST,0,0,162,192,LBS_SORT | LBS_OWNERDRAWVARIABLE | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Account information:",IDC_TXT_ACCOUNT,167,0,201,8
+ LTEXT "Welcome to Miranda NG's account manager!\nHere you can set up your IM accounts.\n\nSelect 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.",IDC_TXT_INFO,182,13,186,134
+ CONTROL "&Add...",IDC_ADD,"MButtonClass",WS_TABSTOP,0,196,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Edit",IDC_EDIT,"MButtonClass",WS_TABSTOP,22,196,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Options",IDC_OPTIONS,"MButtonClass",WS_TABSTOP,38,196,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Upgrade",IDC_UPGRADE,"MButtonClass",WS_TABSTOP,54,196,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "&Remove...",IDC_REMOVE,"MButtonClass",WS_TABSTOP,76,196,16,14,WS_EX_NOACTIVATE | 0x10000000L
+ LTEXT "Additional:",IDC_TXT_ADDITIONAL,167,157,201,8
+ CONTROL "Configure network...",IDC_LNK_NETWORK,"Hyperlink",WS_TABSTOP,183,170,186,11
+ CONTROL "Get more protocols...",IDC_LNK_ADDONS,"Hyperlink",WS_TABSTOP,183,181,186,11
+END
+
+IDD_MODERNOPT_MODULES DIALOGEX 0, 0, 369, 210
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ EDITTEXT IDC_PLUGINLONGINFO,65,133,304,25,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ EDITTEXT IDC_PLUGINAUTHOR,65,160,304,12,ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ CONTROL "",IDC_PLUGINEMAIL,"Hyperlink",WS_TABSTOP,65,172,304,10
+ CONTROL "",IDC_PLUGINURL,"Hyperlink",WS_TABSTOP,65,184,304,10
+ EDITTEXT IDC_PLUGINCPYR,65,197,304,12,ES_AUTOHSCROLL | ES_READONLY | ES_WANTRETURN | NOT WS_BORDER
+ RTEXT "Description:",IDC_TXT_TITLE1,-1,133,60,10
+ RTEXT "Author(s):",IDC_TXT_TITLE2,0,160,60,10
+ RTEXT "E-mail:",IDC_TXT_TITLE3,0,172,60,10
+ RTEXT "Homepage:",IDC_TXT_TITLE4,0,184,60,9
+ RTEXT "Copyright:",IDC_TXT_TITLE6,0,197,60,10
+ CONTROL "",IDC_PLUGLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | LVS_AUTOARRANGE | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,0,0,369,128
+END
+
+IDD_MODERNOPT_IGNORE DIALOGEX 0, 0, 369, 210
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "",IDC_LIST,"CListControl",WS_TABSTOP | 0x3da,10,13,358,135,WS_EX_CLIENTEDGE
+ LTEXT "Event icon legend:",IDC_TXT_TITLE2,0,153,119,8
+ LTEXT "Choose events you wish to ignore:",IDC_TXT_TITLE1,0,0,246,8
+ CONTROL "",IDC_LV_LEGEND,"SysListView32",LVS_LIST | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,10,166,358,43
+END
+
+IDD_CHOOSE_FONT_EFFECT DIALOGEX 13, 54, 226, 103
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Font effect"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Effect:",IDC_STATIC,12,12,66,12,SS_CENTERIMAGE
+ COMBOBOX IDC_EFFECT_COMBO,84,12,126,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_BORDER | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Base color:",IDC_STATIC,12,30,66,12,SS_CENTERIMAGE
+ CONTROL "",IDC_EFFECT_COLOUR1,"ColourPicker",WS_TABSTOP,84,30,30,12
+ LTEXT "opacity:",IDC_STATIC,126,30,42,12,SS_CENTERIMAGE
+ EDITTEXT IDC_EFFECT_COLOUR_TEXT1,168,30,42,12,ES_NUMBER
+ CONTROL "",IDC_EFFECT_COLOUR_SPIN1,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_HOTTRACK,200,30,11,12
+ LTEXT "Secondary color:",IDC_STATIC,12,48,66,12,SS_CENTERIMAGE
+ CONTROL "",IDC_EFFECT_COLOUR2,"ColourPicker",WS_TABSTOP,84,48,30,12
+ LTEXT "opacity:",IDC_STATIC,126,48,42,12,SS_CENTERIMAGE
+ EDITTEXT IDC_EFFECT_COLOUR_TEXT2,168,48,42,12,ES_NUMBER
+ CONTROL "",IDC_EFFECT_COLOUR_SPIN2,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_HOTTRACK,200,48,11,12
+ DEFPUSHBUTTON "OK",IDOK,114,78,45,14,WS_GROUP
+ PUSHBUTTON "Cancel",IDCANCEL,168,78,45,14,WS_GROUP
+END
+
+IDD_EI_OPTIONS DIALOGEX 0, 0, 276, 229
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Select the extra icons to be shown in the contact list:",IDC_STATIC,1,9,274,13
+ CONTROL "",IDC_EXTRAORDER,"SysTreeView32",TVS_NOTOOLTIPS | TVS_NOHSCROLL | TVS_CHECKBOXES | TVS_FULLROWSELECT | WS_BORDER | WS_TABSTOP,1,24,274,160
+ LTEXT "*only the first %d icons will be shown",IDC_MAX_ICONS_L,1,190,274,13,NOT WS_VISIBLE
+ LTEXT "You can group/ungroup icons by selecting them (CTRL+left click) and using the popup menu (right click)",IDC_STATIC,1,208,274,20
+END
+
+IDD_ENTER_STRING DIALOGEX 0, 0, 242, 42
+STYLE DS_SETFONT | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ EDITTEXT IDC_TXT_MULTILINE,6,6,230,12,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | NOT WS_VISIBLE | WS_VSCROLL
+ EDITTEXT IDC_TXT_PASSWORD,6,6,230,12,ES_PASSWORD | ES_AUTOHSCROLL | NOT WS_VISIBLE
+ COMBOBOX IDC_TXT_COMBO,6,6,230,92,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_TXT_RICHEDIT,"RichEdit50W",NOT WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP | 0x1004,6,6,230,12
+ DEFPUSHBUTTON "OK",IDOK,131,23,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,186,23,50,14
+END
+
+IDD_METASELECT DIALOGEX 0, 0, 219, 258
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+CAPTION "Add to existing metacontact"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CTEXT "Please select a metacontact:",IDC_STATIC,5,4,207,8
+ LISTBOX IDC_METALIST,4,16,211,222,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Sort alphabetically",IDC_CHK_SRT,"Button",BS_AUTOCHECKBOX | BS_VCENTER | WS_TABSTOP,6,244,104,8
+ DEFPUSHBUTTON "&OK",IDOK,115,241,48,14
+ PUSHBUTTON "&Cancel",IDCANCEL,167,241,48,14
+END
+
+IDD_METAEDIT DIALOGEX 0, 0, 383, 260
+STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_STATICEDGE
+CAPTION "Editing"
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Information",IDC_STATIC,4,4,375,29
+ RTEXT "Name:",IDC_STATIC,70,17,51,8
+ EDITTEXT IDC_ED_NAME,129,15,183,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_TABSTOP
+ GROUPBOX "Contacts",IDC_FRAME,4,33,375,202
+ CONTROL "List1",IDC_LST_CONTACTS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,10,43,363,169,WS_EX_CLIENTEDGE
+ PUSHBUTTON "&Remove",IDC_BTN_REM,87,216,54,14
+ PUSHBUTTON "&Set as default",IDC_BTN_SETDEFAULT,145,216,54,14
+ PUSHBUTTON "Move &up",IDC_BTN_UP,261,216,54,14
+ PUSHBUTTON "Move &down",IDC_BTN_DOWN,319,216,54,14
+ PUSHBUTTON "Send &offline",IDC_BTN_SETOFFLINE,203,216,54,14
+ DEFPUSHBUTTON "&OK",IDOK,220,241,50,14
+ PUSHBUTTON "&Cancel",IDCANCEL,274,241,50,14
+ PUSHBUTTON "&Apply",IDC_VALIDATE,328,241,50,14
+END
+
+IDD_METAOPTIONS DIALOGEX 0, 0, 306, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX "Context menu",IDC_STATIC,4,7,297,109
+ CONTROL "Use contact's unique ID",IDC_RAD_UID,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,16,30,264,10
+ CONTROL "Use contact's display name",IDC_RAD_DID,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,16,43,264,10
+ GROUPBOX "Contact labels",IDC_STATIC,9,16,285,45,WS_GROUP
+ GROUPBOX "When I click on a sub in the popup menu...",IDC_STATIC,9,66,285,45,WS_GROUP
+ CONTROL "Set default and open message window",IDC_RAD_MSG,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,16,76,264,10
+ CONTROL "Show subcontact context menu",IDC_RAD_MENU,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,16,87,264,10
+ CONTROL "Show user information",IDC_RAD_INFO,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,16,97,264,10
+ GROUPBOX "Contact list",IDC_STATIC,4,117,297,47,WS_GROUP
+ CONTROL "Display subcontact nickname",IDC_RAD_NICK,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,9,129,277,10
+ CONTROL "Display subcontact display name",IDC_RAD_NAME,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,9,140,277,10
+ CONTROL "Lock name to first contact",IDC_CHK_LOCKHANDLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,151,278,10
+END
+
+IDD_OPT_LANGUAGES DIALOGEX 0, 0, 301, 191
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ COMBOBOX IDC_LANGUAGES,86,4,210,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ RTEXT "Current language:",IDC_STATIC,5,7,76,8
+ GROUPBOX "",IDC_LANGINFOFRAME,5,18,291,153,WS_GROUP
+ RTEXT "Author(s):",IDC_LANGAUTHORSLABEL,9,29,72,9,SS_NOPREFIX
+ EDITTEXT IDC_LANGAUTHORS,86,27,206,61,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL
+ RTEXT "E-mail:",IDC_LANGEMAILLABEL,9,92,72,8,SS_NOPREFIX
+ CONTROL "",IDC_LANGEMAIL,"Hyperlink",WS_TABSTOP,86,92,206,10
+ RTEXT "Last modified using:",IDC_LANGMODUSINGLABEL,9,105,72,9,SS_NOPREFIX
+ EDITTEXT IDC_LANGMODUSING,86,105,206,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+ RTEXT "Date:",IDC_LANGDATELABEL,9,118,72,9,SS_NOPREFIX
+ EDITTEXT IDC_LANGDATE,86,118,206,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+ RTEXT "Locale:",IDC_STATIC,9,132,72,9,SS_NOPREFIX
+ EDITTEXT IDC_LANGLOCALE,86,132,206,12,ES_READONLY | NOT WS_BORDER
+ PUSHBUTTON "Reload langpack",IDC_RELOAD,169,148,123,17
+ CONTROL "Download more language packs",IDC_MORELANG,"Hyperlink",WS_GROUP | WS_TABSTOP | 0x1,9,173,283,10
+END
+
+IDD_COLORCHOOSER DIALOGEX 0, 0, 198, 150
+STYLE DS_SETFONT | DS_SETFOREGROUND | DS_3DLOOK | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_BORDER
+EXSTYLE WS_EX_TOPMOST
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,172,136,26,14,NOT WS_TABSTOP
+ CTEXT "",IDC_COLORTEXT,0,0,197,12,SS_CENTERIMAGE
+END
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_ACCFORM, DIALOG
+ BEGIN
+ END
+
+ IDD_ADDCONTACT, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 223
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 149
+ END
+
+ IDD_DELETECONTACT, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 286
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 88
+ END
+
+ IDD_OPT_CONTACT, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 306
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 235
+ END
+
+ IDD_FINDADD, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 422
+ VERTGUIDE, 11
+ VERTGUIDE, 42
+ VERTGUIDE, 53
+ VERTGUIDE, 116
+ VERTGUIDE, 122
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 252
+ END
+
+ IDD_OPTIONS, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 424
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 297
+ END
+
+ IDD_OPTIONSPAGE, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 297
+ END
+
+ IDD_INSTALLINI, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 208
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 98
+ END
+
+ IDD_WARNINICHANGE, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 182
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 108
+ END
+
+ IDD_INIIMPORTDONE, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 181
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 68
+ END
+
+ IDD_NETLIBLOGOPTS, DIALOG
+ BEGIN
+ LEFTMARGIN, 6
+ RIGHTMARGIN, 308
+ VERTGUIDE, 14
+ VERTGUIDE, 174
+ VERTGUIDE, 308
+ TOPMARGIN, 5
+ BOTTOMMARGIN, 255
+ END
+
+ IDD_OPT_SOUND, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 281
+ VERTGUIDE, 12
+ VERTGUIDE, 273
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 236
+ END
+
+ IDD_OPT_ICONS, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 253
+ VERTGUIDE, 12
+ VERTGUIDE, 200
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 189
+ END
+
+ IDD_OPT_IGNORE, DIALOG
+ BEGIN
+ VERTGUIDE, 8
+ VERTGUIDE, 96
+ VERTGUIDE, 221
+ VERTGUIDE, 305
+ END
+
+ IDD_OPT_VISIBILITY, DIALOG
+ BEGIN
+ VERTGUIDE, 8
+ VERTGUIDE, 305
+ END
+
+ IDD_ICONINDEX, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 215
+ TOPMARGIN, 4
+ BOTTOMMARGIN, 193
+ END
+
+ IDD_OPT_NETLIB, DIALOG
+ BEGIN
+ VERTGUIDE, 7
+ VERTGUIDE, 20
+ VERTGUIDE, 76
+ VERTGUIDE, 80
+ VERTGUIDE, 166
+ VERTGUIDE, 177
+ VERTGUIDE, 306
+ HORZGUIDE, 80
+ HORZGUIDE, 113
+ END
+
+ IDD_PROFILE_SELECTION, DIALOG
+ BEGIN
+ END
+
+ IDD_PROFILE_NEW, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ TOPMARGIN, 7
+ END
+
+ IDD_OPT_PLUGINS, DIALOG
+ BEGIN
+ VERTGUIDE, 4
+ VERTGUIDE, 6
+ VERTGUIDE, 53
+ VERTGUIDE, 56
+ VERTGUIDE, 306
+ VERTGUIDE, 311
+ TOPMARGIN, 4
+ END
+
+ IDD_CUSTOM_FONT, DIALOG
+ BEGIN
+ END
+
+ IDD_OPT_GENMENU, DIALOG
+ BEGIN
+ END
+
+ IDD_OPT_PROTOCOLORDER, DIALOG
+ BEGIN
+ RIGHTMARGIN, 314
+ BOTTOMMARGIN, 241
+ END
+
+ IDD_OPT_KEYBINDINGS, DIALOG
+ BEGIN
+ END
+
+ IDD_ACCMGR, DIALOG
+ BEGIN
+ VERTGUIDE, 7
+ VERTGUIDE, 23
+ VERTGUIDE, 28
+ VERTGUIDE, 44
+ VERTGUIDE, 60
+ VERTGUIDE, 76
+ VERTGUIDE, 81
+ VERTGUIDE, 137
+ VERTGUIDE, 142
+ VERTGUIDE, 157
+ VERTGUIDE, 343
+ BOTTOMMARGIN, 238
+ HORZGUIDE, 25
+ HORZGUIDE, 32
+ HORZGUIDE, 40
+ HORZGUIDE, 45
+ HORZGUIDE, 179
+ HORZGUIDE, 184
+ HORZGUIDE, 192
+ HORZGUIDE, 197
+ HORZGUIDE, 208
+ HORZGUIDE, 219
+ END
+
+ IDD_WAITRESTART, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 160
+ VERTGUIDE, 83
+ VERTGUIDE, 160
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 63
+ HORZGUIDE, 25
+ HORZGUIDE, 30
+ HORZGUIDE, 44
+ HORZGUIDE, 49
+ END
+
+ IDD_MODERNOPT_FONTS, DIALOG
+ BEGIN
+ VERTGUIDE, 10
+ VERTGUIDE, 239
+ VERTGUIDE, 244
+ HORZGUIDE, 30
+ HORZGUIDE, 45
+ HORZGUIDE, 84
+ HORZGUIDE, 99
+ HORZGUIDE, 139
+ HORZGUIDE, 154
+ END
+
+ IDD_MODERNOPT_ACCOUNTS, DIALOG
+ BEGIN
+ VERTGUIDE, 70
+ VERTGUIDE, 162
+ VERTGUIDE, 167
+ VERTGUIDE, 182
+ VERTGUIDE, 274
+ HORZGUIDE, 147
+ HORZGUIDE, 192
+ HORZGUIDE, 196
+ END
+
+ IDD_MODERNOPT_MODULES, DIALOG
+ BEGIN
+ RIGHTMARGIN, 260
+ VERTGUIDE, 60
+ VERTGUIDE, 65
+ END
+
+ IDD_CHOOSE_FONT_EFFECT, DIALOG
+ BEGIN
+ HORZGUIDE, 18
+ END
+
+ IDD_EI_OPTIONS, DIALOG
+ BEGIN
+ LEFTMARGIN, 1
+ RIGHTMARGIN, 275
+ TOPMARGIN, 1
+ BOTTOMMARGIN, 228
+ END
+
+ IDD_METASELECT, DIALOG
+ BEGIN
+ END
+
+ IDD_METAEDIT, DIALOG
+ BEGIN
+ END
+
+ IDD_METAOPTIONS, DIALOG
+ BEGIN
+ VERTGUIDE, 4
+ VERTGUIDE, 10
+ VERTGUIDE, 16
+ VERTGUIDE, 280
+ VERTGUIDE, 287
+ VERTGUIDE, 293
+ BOTTOMMARGIN, 164
+ END
+
+ IDD_OPT_LANGUAGES, DIALOG
+ BEGIN
+ VERTGUIDE, 5
+ VERTGUIDE, 9
+ VERTGUIDE, 81
+ VERTGUIDE, 86
+ VERTGUIDE, 292
+ VERTGUIDE, 296
+ BOTTOMMARGIN, 186
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include <windows.h>\r\n"
+ "#include <winres.h>\r\n"
+ "#include ""../include/statusmodes.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_MIRANDA ICON "res/miranda_logo.ico"
+IDI_MIRANDAWEBSITE ICON "res/miranda_home.ico"
+IDI_DETAILSLOGO ICON "res/miranda_manager.ico"
+IDI_USERDETAILS ICON "res/contact_view_details.ico"
+IDI_ADDCONTACT ICON "res/contact_add.ico"
+IDI_RENAME ICON "res/contact_rename.ico"
+IDI_DELETE ICON "res/contact_delete.ico"
+IDI_GROUPSHUT ICON "res/group_closed.ico"
+IDI_GROUPOPEN ICON "res/group_opened.ico"
+IDI_USERONLINE ICON "res/status_user_online.ico"
+IDI_ONLINE ICON "res/status_online.ico"
+IDI_AWAY ICON "res/status_away.ico"
+IDI_NA ICON "res/status_NA.ico"
+IDI_OCCUPIED ICON "res/status_occupied.ico"
+IDI_DND ICON "res/status_DND.ico"
+IDI_FREE4CHAT ICON "res/status_free4chat.ico"
+IDI_INVISIBLE ICON "res/status_invisible.ico"
+IDI_ONTHEPHONE ICON "res/status_on_the_phone.ico"
+IDI_OUTTOLUNCH ICON "res/status_out2lunch.ico"
+IDI_OFFLINE ICON "res/status_offline.ico"
+IDI_LOAD ICON "res/icon_connecting.ico"
+IDI_TYPING ICON "res/icon_typing.ico"
+IDI_FINDUSER ICON "res/icon_find_user.ico"
+IDI_SEARCHALL ICON "res/icon_search_all.ico"
+IDI_OPTIONS ICON "res/icon_options.ico"
+IDI_ACCMGR ICON "res/icon_accmgr.ico"
+IDI_MAINMENU ICON "res/icon_mainmenu.ico"
+IDI_HELP ICON "res/icon_help.ico"
+IDI_RECVMSG ICON "res/icon_message.ico"
+IDI_FILE ICON "res/icon_file.ico"
+IDI_HISTORY ICON "res/icon_history.ico"
+IDI_URL ICON "res/icon_url.ico"
+IDI_SMS ICON "res/icon_sms.ico"
+IDI_SENDEMAIL ICON "res/icon_mail.ico"
+IDI_SMALLDOT ICON "res/icon_small_dot.ico"
+IDI_FILLEDBLOB ICON "res/icon_auth_request.ico"
+IDI_EMPTYBLOB ICON "res/icon_all.ico"
+IDI_DOWNARROW ICON "res/icon_down_arrow.ico"
+IDI_NOTICK ICON "res/check_off.ico"
+IDI_TICK ICON "res/check_on.ico"
+IDI_UNICODE ICON "res/icon_unicode.ico"
+IDI_ANSI ICON "res/icon_ansi.ico"
+IDI_LOADED ICON "res/icon_loaded.ico"
+IDI_LOADED_GRAY ICON "res/icon_loaded_gray.ico"
+IDI_NOTLOADED ICON "res/icon_notloaded.ico"
+IDI_NOTLOADED_GRAY ICON "res/icon_notloaded_gray.ico"
+IDI_FRAME ICON "res/icon_frame.ico"
+IDI_MFATAL ICON "res/icon_fatal.ico"
+IDI_MERROR ICON "res/icon_error.ico"
+IDI_MWARNING ICON "res/icon_warning.ico"
+IDI_MINFO ICON "res/icon_notify.ico"
+IDI_BLANK ICON "res/_blank.ico"
+IDI_UNDO ICON "res/icon_undo.ico"
+IDI_WINDOW ICON "res/icon_window.ico"
+IDI_WINDOWS ICON "res/icon_windows.ico"
+IDI_JOINCHAT ICON "res/chat_join.ico"
+IDI_LEAVECHAT ICON "res/chat_leave.ico"
+IDI_STATUS_LOCKED ICON "res/status_locked.ico"
+IDI_SHOWHIDE ICON "res/Icon_show_hide.ico"
+IDI_EXIT ICON "res/Icon_exit.ico"
+IDI_MOVETOGROUP ICON "res/contact_groups.ico"
+IDI_ON ICON "res/On.ico"
+IDI_OFF ICON "res/Off.ico"
+IDI_ALWAYSVIS ICON "res/always_visible.ico"
+IDI_NEVERVIS ICON "res/never_visible.ico"
+IDI_CHAT ICON "res/chat_channel.ico"
+IDI_MALE ICON "res/male.ico"
+IDI_FEMALE ICON "res/female.ico"
+IDI_AUTH_ADD ICON "res/auth_add.ico"
+IDI_AUTH_GRANT ICON "res/auth_grant.ico"
+IDI_AUTH_REQUEST ICON "res/auth_request.ico"
+IDI_AUTH_REVOKE ICON "res/auth_revoke.ico"
+IDI_MCMENU ICON "res/meta_menu.ico"
+IDI_MCMENUOFF ICON "res/meta_menuof.ico"
+IDI_MCEDIT ICON "res/meta_edit.ico"
+IDI_MCREMOVE ICON "res/meta_remove2.ico"
+IDI_MCCONVERT ICON "res/meta_convert.ico"
+IDI_MCADD ICON "res/meta_add.ico"
+IDI_MCSETDEFAULT ICON "res/meta_set_as_default.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Cursor
+//
+
+IDC_HYPERLINKHAND CURSOR "res/cursor_hyperlink.cur"
+IDC_DROP CURSOR "res/cursor_drag_copy.cur"
+IDC_DROPUSER CURSOR "res/cursor_drop_user.cur"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDR_CLISTMENU MENU
+BEGIN
+ MENUITEM SEPARATOR
+END
+
+IDR_CONTEXT MENU
+BEGIN
+ POPUP "Tray"
+ BEGIN
+ MENUITEM "&Hide/Show", ID_TRAY_HIDE
+ MENUITEM SEPARATOR
+ MENUITEM "E&xit", ID_TRAY_EXIT
+ END
+ POPUP "Nowhere"
+ BEGIN
+ MENUITEM "&New group", POPUP_NEWGROUP
+ MENUITEM SEPARATOR
+ MENUITEM "&Hide offline users", POPUP_HIDEOFFLINE
+ MENUITEM "Hide &offline users out here", POPUP_HIDEOFFLINEROOT
+ MENUITEM "Hide &empty groups", POPUP_HIDEEMPTYGROUPS
+ MENUITEM "Disable &groups", POPUP_DISABLEGROUPS
+ MENUITEM SEPARATOR
+ MENUITEM "Hide Miranda", POPUP_HIDEMIRANDA
+ END
+ POPUP "Group"
+ BEGIN
+ MENUITEM "&New subgroup", POPUP_NEWSUBGROUP
+ MENUITEM "&Hide offline users in here", POPUP_GROUPHIDEOFFLINE
+ MENUITEM SEPARATOR
+ MENUITEM "&Rename group", POPUP_RENAMEGROUP
+ MENUITEM "&Delete group", POPUP_DELETEGROUP
+ END
+ POPUP "IconOptions"
+ BEGIN
+ MENUITEM "&Reset to default", ID_RESET
+ END
+ POPUP "find/add"
+ BEGIN
+ MENUITEM "&Add to list", IDC_ADD
+ MENUITEM SEPARATOR
+ MENUITEM "User &details", IDC_DETAILS
+ MENUITEM "Send &message", IDC_SENDMESSAGE
+ END
+ POPUP "Log"
+ BEGIN
+ MENUITEM "&Copy", IDM_COPY
+ MENUITEM "Co&py all", IDM_COPYALL
+ MENUITEM "Select &all", IDM_SELECTALL
+ MENUITEM SEPARATOR
+ MENUITEM "C&lear log", IDM_CLEAR
+ END
+ POPUP "LogLink"
+ BEGIN
+ MENUITEM "Open in &new window", IDM_OPENNEW
+ MENUITEM "&Open in existing window", IDM_OPENEXISTING
+ MENUITEM "&Copy link", IDM_COPYLINK
+ END
+END
+
+IDR_ICOLIB_CONTEXT MENU
+BEGIN
+ POPUP "IconOptions"
+ BEGIN
+ MENUITEM "Cancel change", ID_CANCELCHANGE
+ MENUITEM "&Reset to default", ID_RESET
+ END
+END
+
+IDR_OPT_POPUP MENU
+BEGIN
+ POPUP "Group"
+ BEGIN
+ MENUITEM "Group", ID_GROUP
+ END
+ POPUP "Ungroup"
+ BEGIN
+ MENUITEM "Ungroup", ID_UNGROUP
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/src/mir_app/res/status_DND.ico b/src/mir_app/res/status_DND.ico Binary files differnew file mode 100644 index 0000000000..8183404bb1 --- /dev/null +++ b/src/mir_app/res/status_DND.ico diff --git a/src/mir_app/res/status_NA.ico b/src/mir_app/res/status_NA.ico Binary files differnew file mode 100644 index 0000000000..ccaa4b0d60 --- /dev/null +++ b/src/mir_app/res/status_NA.ico diff --git a/src/mir_app/res/status_away.ico b/src/mir_app/res/status_away.ico Binary files differnew file mode 100644 index 0000000000..f24af50a75 --- /dev/null +++ b/src/mir_app/res/status_away.ico diff --git a/src/mir_app/res/status_free4chat.ico b/src/mir_app/res/status_free4chat.ico Binary files differnew file mode 100644 index 0000000000..c3b0e7d064 --- /dev/null +++ b/src/mir_app/res/status_free4chat.ico diff --git a/src/mir_app/res/status_invisible.ico b/src/mir_app/res/status_invisible.ico Binary files differnew file mode 100644 index 0000000000..c19554b9e8 --- /dev/null +++ b/src/mir_app/res/status_invisible.ico diff --git a/src/mir_app/res/status_locked.ico b/src/mir_app/res/status_locked.ico Binary files differnew file mode 100644 index 0000000000..eb9f58e35f --- /dev/null +++ b/src/mir_app/res/status_locked.ico diff --git a/src/mir_app/res/status_occupied.ico b/src/mir_app/res/status_occupied.ico Binary files differnew file mode 100644 index 0000000000..0f57945ebf --- /dev/null +++ b/src/mir_app/res/status_occupied.ico diff --git a/src/mir_app/res/status_offline.ico b/src/mir_app/res/status_offline.ico Binary files differnew file mode 100644 index 0000000000..018ee8fddf --- /dev/null +++ b/src/mir_app/res/status_offline.ico diff --git a/src/mir_app/res/status_on_the_phone.ico b/src/mir_app/res/status_on_the_phone.ico Binary files differnew file mode 100644 index 0000000000..d0af832234 --- /dev/null +++ b/src/mir_app/res/status_on_the_phone.ico diff --git a/src/mir_app/res/status_online.ico b/src/mir_app/res/status_online.ico Binary files differnew file mode 100644 index 0000000000..226b9c195a --- /dev/null +++ b/src/mir_app/res/status_online.ico diff --git a/src/mir_app/res/status_out2lunch.ico b/src/mir_app/res/status_out2lunch.ico Binary files differnew file mode 100644 index 0000000000..16c5e5a366 --- /dev/null +++ b/src/mir_app/res/status_out2lunch.ico diff --git a/src/mir_app/res/status_user_online.ico b/src/mir_app/res/status_user_online.ico Binary files differnew file mode 100644 index 0000000000..e42e88c44e --- /dev/null +++ b/src/mir_app/res/status_user_online.ico diff --git a/src/mir_app/res/version.rc b/src/mir_app/res/version.rc new file mode 100644 index 0000000000..5343337365 --- /dev/null +++ b/src/mir_app/res/version.rc @@ -0,0 +1,53 @@ +#ifdef APSTUDIO_INVOKED
+#error this file is not editable by Microsoft Visual C++
+#endif //APSTUDIO_INVOKED
+
+#include <windows.h>
+#include <winres.h>
+
+#include "../include/m_version.h"
+
+#ifndef _MAC
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION MIRANDA_VERSION_FILEVERSION
+ PRODUCTVERSION MIRANDA_VERSION_FILEVERSION
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "000004b0"
+ BEGIN
+ VALUE "Comments", "Licensed under the terms of the GNU General Public License\0"
+ VALUE "CompanyName", "Miranda NG Team\0"
+ VALUE "FileDescription", "Miranda NG\0"
+ VALUE "FileVersion", MIRANDA_VERSION_DISPLAY
+ VALUE "InternalName", "miranda32\0"
+ VALUE "LegalCopyright", "Copyright © 2000-11 Miranda IM, 2012-15 Miranda NG project. This software is released under the terms of the GNU General Public License.\0"
+ VALUE "LegalTrademarks", "\0"
+ VALUE "OriginalFilename", "miranda32.exe\0"
+ VALUE "PrivateBuild", "\0"
+ VALUE "ProductName", "Miranda NG\0"
+ VALUE "ProductVersion", MIRANDA_VERSION_DISPLAY
+ VALUE "SpecialBuild", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0, 1200
+ END
+END
+
+#endif // !_MAC
diff --git a/src/mir_app/src/BaseExtraIcon.cpp b/src/mir_app/src/BaseExtraIcon.cpp new file mode 100644 index 0000000000..2de3a6874c --- /dev/null +++ b/src/mir_app/src/BaseExtraIcon.cpp @@ -0,0 +1,80 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+BaseExtraIcon::BaseExtraIcon(int id, const char *name, const TCHAR *description, const char *descIcon, MIRANDAHOOKPARAM OnClick, LPARAM param) :
+ ExtraIcon(name), id(id), OnClick(OnClick), onClickParam(param),
+ tszDescription(mir_tstrdup(description)),
+ szDescIcon(mir_strdup(descIcon))
+{
+}
+
+BaseExtraIcon::~BaseExtraIcon()
+{
+}
+
+void BaseExtraIcon::setOnClick(MIRANDAHOOKPARAM pFunc, LPARAM pParam)
+{
+ OnClick = pFunc;
+ onClickParam = pParam;
+}
+
+int BaseExtraIcon::getID() const
+{
+ return id;
+}
+
+const TCHAR* BaseExtraIcon::getDescription() const
+{
+ return tszDescription;
+}
+
+void BaseExtraIcon::setDescription(const TCHAR *desc)
+{
+ tszDescription = mir_tstrdup(desc);
+}
+
+const char* BaseExtraIcon::getDescIcon() const
+{
+ return szDescIcon;
+}
+
+void BaseExtraIcon::setDescIcon(const char *icon)
+{
+ szDescIcon = mir_strdup(icon);
+}
+
+void BaseExtraIcon::onClick(MCONTACT hContact)
+{
+ if (OnClick != NULL)
+ OnClick(hContact, (LPARAM)ConvertToClistSlot(slot), onClickParam);
+}
+
+int BaseExtraIcon::ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage)
+{
+ ExtraIcon *tmp = extraIconsByHandle[id - 1];
+ if (tmp != this)
+ return tmp->ClistSetExtraIcon(hContact, hImage);
+ return Clist_SetExtraIcon(hContact, slot, hImage);
+}
diff --git a/src/mir_app/src/CallbackExtraIcon.cpp b/src/mir_app/src/CallbackExtraIcon.cpp new file mode 100644 index 0000000000..a4f04b22a7 --- /dev/null +++ b/src/mir_app/src/CallbackExtraIcon.cpp @@ -0,0 +1,75 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+CallbackExtraIcon::CallbackExtraIcon(int _id, const char *_name, const TCHAR *_description, const char *_descIcon,
+ MIRANDAHOOK _RebuildIcons, MIRANDAHOOK _ApplyIcon, MIRANDAHOOKPARAM _OnClick, LPARAM _param) :
+ BaseExtraIcon(_id, _name, _description, _descIcon, _OnClick, _param),
+ RebuildIcons(_RebuildIcons), ApplyIcon(_ApplyIcon), needToRebuild(true)
+{
+}
+
+CallbackExtraIcon::~CallbackExtraIcon()
+{
+}
+
+int CallbackExtraIcon::getType() const
+{
+ return EXTRAICON_TYPE_CALLBACK;
+}
+
+void CallbackExtraIcon::rebuildIcons()
+{
+ if (!isEnabled()) {
+ needToRebuild = true;
+ return;
+ }
+
+ needToRebuild = false;
+ RebuildIcons(0, 0);
+}
+
+void CallbackExtraIcon::applyIcon(MCONTACT hContact)
+{
+ if (!isEnabled() || hContact == NULL)
+ return;
+
+ if (needToRebuild)
+ rebuildIcons();
+
+ ApplyIcon(hContact, 0);
+}
+
+int CallbackExtraIcon::setIcon(int id, MCONTACT hContact, HANDLE icon)
+{
+ if (!isEnabled() || hContact == NULL || id != this->id)
+ return -1;
+
+ return ClistSetExtraIcon(hContact, icon);
+}
+
+int CallbackExtraIcon::setIconByName(int, MCONTACT, const char*)
+{
+ return -1;
+}
diff --git a/src/mir_app/src/DefaultExtraIcons.cpp b/src/mir_app/src/DefaultExtraIcons.cpp new file mode 100644 index 0000000000..0b41f377e3 --- /dev/null +++ b/src/mir_app/src/DefaultExtraIcons.cpp @@ -0,0 +1,326 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "m_cluiframes.h"
+
+#include "ExtraIcon.h"
+#include "extraicons.h"
+
+ExtraIcon* GetExtraIcon(HANDLE id);
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DB extra icons
+
+HANDLE hExtraVisibility, hExtraChat, hExtraGender, hExtraProto;
+
+static void SetVisibility(MCONTACT hContact, int apparentMode, bool clear)
+{
+ if (hContact == NULL)
+ return;
+
+ char *proto = GetContactProto(hContact);
+ if (IsEmpty(proto))
+ return;
+
+ if (apparentMode <= 0)
+ apparentMode = db_get_w(hContact, proto, "ApparentMode", 0);
+
+ HANDLE hExtraIcon, hIcolib = NULL;
+
+ if (db_get_b(hContact, proto, "ChatRoom", 0)) {
+ // Is chat
+ hExtraIcon = hExtraChat;
+ if (apparentMode == ID_STATUS_OFFLINE)
+ hIcolib = Skin_GetIconHandle("ChatActivity");
+ }
+ else {
+ // Not chat
+ hExtraIcon = hExtraVisibility;
+ if (apparentMode == ID_STATUS_OFFLINE)
+ hIcolib = LoadSkinnedIconHandle(SKINICON_OTHER_INVISIBLE_ALL);
+ else if (apparentMode == ID_STATUS_ONLINE)
+ hIcolib = LoadSkinnedIconHandle(SKINICON_OTHER_VISIBLE_ALL);
+ }
+
+ if (hIcolib != NULL || clear) {
+ ExtraIcon *extra = GetExtraIcon(hExtraIcon);
+ if (extra)
+ extra->setIcon((int)hExtraIcon, hContact, hIcolib);
+ }
+}
+
+static void SetGender(MCONTACT hContact, int gender, bool clear)
+{
+ if (hContact == NULL)
+ return;
+
+ char *proto = GetContactProto(hContact);
+ if (IsEmpty(proto))
+ return;
+
+ if (gender <= 0)
+ gender = db_get_b(hContact, proto, "Gender", 0);
+ if (gender <= 0)
+ gender = db_get_b(hContact, "UserInfo", "Gender", 0);
+
+ const char *ico;
+ if (gender == 'M')
+ ico = "gender_male";
+ else if (gender == 'F')
+ ico = "gender_female";
+ else
+ ico = NULL;
+
+ if (ico != NULL || clear) {
+ ExtraIcon *extra = GetExtraIcon(hExtraGender);
+ if (extra)
+ extra->setIconByName((int)hExtraGender, hContact, ico);
+ }
+}
+
+struct Info
+{
+ const char *name;
+ const char *desc;
+ int iSkinIcon;
+ const char *db[8];
+ void(*OnClick)(Info *info, const char *text);
+
+ HANDLE hIcolib, hExtraIcon;
+};
+
+static void EmailOnClick(Info*, const char *text)
+{
+ char cmd[1024];
+ mir_snprintf(cmd, SIZEOF(cmd), "mailto:%s", text);
+ ShellExecuteA(NULL, "open", cmd, NULL, NULL, SW_SHOW);
+}
+
+static void HomepageOnClick(Info*, const char *text)
+{
+ ShellExecuteA(NULL, "open", text, NULL, NULL, SW_SHOW);
+}
+
+static Info infos[] =
+{
+ { "homepage", "Homepage", SKINICON_OTHER_MIRANDAWEB,
+ { NULL, "Homepage", "UserInfo", "Homepage" },
+ &HomepageOnClick },
+ { "sms", "Phone/SMS", SKINICON_OTHER_SMS,
+ { NULL, "Cellular", "UserInfo", "Cellular", "UserInfo", "Phone", "UserInfo", "MyPhone0" },
+ NULL },
+ { "email", "E-mail", SKINICON_OTHER_SENDEMAIL,
+ { NULL, "e-mail", "UserInfo", "e-mail", "UserInfo", "Mye-mail0" },
+ &EmailOnClick },
+};
+
+static void SetExtraIcons(MCONTACT hContact)
+{
+ if (hContact == NULL)
+ return;
+
+ char *proto = GetContactProto(hContact);
+ if ( IsEmpty(proto))
+ return;
+
+ for (unsigned int i = 0; i < SIZEOF(infos); i++) {
+ Info &p = infos[i];
+
+ for (unsigned int j = 0; j < SIZEOF(p.db); j += 2) {
+ if (p.db[j + 1] == NULL)
+ break;
+
+ ptrA szValue(db_get_sa(hContact, p.db[j] == NULL ? proto : p.db[j], p.db[j + 1]));
+ if (!IsEmpty(szValue)) {
+ ExtraIcon_SetIcon(p.hExtraIcon, hContact, p.hIcolib);
+ break;
+ }
+ }
+ }
+}
+
+static int SettingChanged(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact == NULL)
+ return 0;
+
+ char *proto = GetContactProto(hContact);
+ if (IsEmpty(proto))
+ return 0;
+
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;
+ bool isProto = (mir_strcmp(cws->szModule, proto) == 0);
+ if (isProto && mir_strcmp(cws->szSetting, "ApparentMode") == 0) {
+ SetVisibility(hContact, cws->value.type == DBVT_DELETED ? 0 : cws->value.wVal, true);
+ return 0;
+ }
+
+ if (mir_strcmp(cws->szSetting, "Gender") == 0 && (isProto || mir_strcmp(cws->szModule, "UserInfo") == 0)) {
+ SetGender(hContact, cws->value.type == DBVT_DELETED ? 0 : cws->value.bVal, true);
+ return 0;
+ }
+
+ for (int i = 0; i < SIZEOF(infos); i++) {
+ Info &p = infos[i];
+
+ for (int j = 0; j < SIZEOF(p.db); j += 2) {
+ if (p.db[j + 1] == NULL)
+ break;
+ if (p.db[j] == NULL && !isProto)
+ continue;
+ if (p.db[j] != NULL && mir_strcmp(cws->szModule, p.db[j]))
+ continue;
+ if (mir_strcmp(cws->szSetting, p.db[j + 1]))
+ continue;
+
+ bool show = (cws->value.type != DBVT_DELETED && !IsEmpty(cws->value.pszVal));
+ ExtraIcon_SetIcon(p.hExtraIcon, hContact, show ? p.hIcolib : NULL);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int DefaultOnClick(WPARAM hContact, LPARAM, LPARAM param)
+{
+ Info *p = (Info*)param;
+ if (p == NULL)
+ return 0;
+
+ if (hContact == NULL)
+ return 0;
+
+ char *proto = GetContactProto(hContact);
+ if (IsEmpty(proto))
+ return 0;
+
+ bool found = false;
+ for (int j = 0; !found && j < SIZEOF(p->db); j += 2) {
+ if (p->db[j + 1] == NULL)
+ break;
+
+ ptrA szValue(db_get_sa(hContact, p->db[j] == NULL ? proto : p->db[j], p->db[j + 1]));
+ if (!IsEmpty(szValue)) {
+ p->OnClick(p, szValue);
+ found = true;
+ }
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Protocol icon
+
+struct ProtoInfo
+{
+ ProtoInfo(LPCSTR _proto, HANDLE _image) :
+ proto(mir_strdup(_proto)),
+ hImage(_image)
+ {}
+
+ ptrA proto;
+ HANDLE hImage;
+};
+
+static int CompareProtos(const ProtoInfo *p1, const ProtoInfo *p2)
+{ return mir_strcmp(p1->proto, p2->proto);
+}
+
+OBJLIST<ProtoInfo> arProtos(10, CompareProtos);
+
+static int ProtocolRebuildIcons(WPARAM, LPARAM)
+{
+ arProtos.destroy();
+ return 0;
+}
+
+static ProtoInfo* FindProto(const char *proto)
+{
+ ProtoInfo *p = arProtos.find((ProtoInfo*)&proto);
+ if (p)
+ return p;
+
+ HICON hIcon = LoadSkinnedProtoIcon(proto, ID_STATUS_ONLINE);
+ if (hIcon == NULL)
+ return NULL;
+
+ HANDLE hImage = ExtraIcon_Add(hIcon);
+ if (hImage == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ p = new ProtoInfo(proto, hImage);
+ arProtos.insert(p);
+ return p;
+}
+
+static int ProtocolApplyIcon(WPARAM hContact, LPARAM)
+{
+ char *proto = GetContactProto(hContact);
+ if (IsEmpty(proto))
+ return 0;
+
+ HANDLE hImage = INVALID_HANDLE_VALUE;
+ ProtoInfo *pi = FindProto(proto);
+ if (pi != NULL)
+ hImage = pi->hImage;
+
+ ExtraIcon_SetIcon(hExtraProto, hContact, hImage);
+ return 0;
+}
+
+static int ProtocolOnClick(WPARAM wParam, LPARAM, LPARAM)
+{
+ if (wParam)
+ CallService(MS_USERINFO_SHOWDIALOG, wParam, 0);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+void DefaultExtraIcons_Load()
+{
+ hExtraChat = ExtraIcon_Register("chat_activity", LPGEN("Chat activity"), "ChatActivity");
+ hExtraVisibility = ExtraIcon_Register("visibility", "Visibility", LoadSkinnedIconName(SKINICON_OTHER_VISIBLE_ALL));
+ hExtraGender = ExtraIcon_Register("gender", "Gender", "gender_male");
+ hExtraProto = ExtraIcon_Register("protocol", "Account", LoadSkinnedIconName(SKINICON_OTHER_ACCMGR),
+ &ProtocolRebuildIcons, &ProtocolApplyIcon, &ProtocolOnClick);
+
+ for (int i = 0; i < SIZEOF(infos); i++) {
+ Info &p = infos[i];
+ p.hIcolib = LoadSkinnedIconHandle(p.iSkinIcon);
+ if (p.OnClick)
+ p.hExtraIcon = ExtraIcon_Register(p.name, p.desc, LoadSkinnedIconName(p.iSkinIcon), DefaultOnClick, (LPARAM)&p);
+ else
+ p.hExtraIcon = ExtraIcon_Register(p.name, p.desc, LoadSkinnedIconName(p.iSkinIcon));
+ }
+
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ SetExtraIcons(hContact);
+ SetVisibility(hContact, -1, false);
+ SetGender(hContact, -1, false);
+ }
+
+ HookEvent(ME_DB_CONTACT_SETTINGCHANGED, SettingChanged);
+}
diff --git a/src/mir_app/src/Docking.cpp b/src/mir_app/src/Docking.cpp new file mode 100644 index 0000000000..c4b163dda1 --- /dev/null +++ b/src/mir_app/src/Docking.cpp @@ -0,0 +1,363 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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)
+{
+ MONITORINFO monitorInfo;
+ HMONITOR hMonitor = MonitorFromPoint(*pt, MONITOR_DEFAULTTONEAREST); // always returns a valid value
+ monitorInfo.cbSize = sizeof(monitorInfo);
+
+ if (GetMonitorInfo(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 = GetWindowLongPtr(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) {
+ db_set_b(NULL, "CList", "Docked", (BYTE)docked);
+ db_set_dw(NULL, "CList", "DockX", (DWORD)dockPos.x);
+ db_set_dw(NULL, "CList", "DockY", (DWORD)dockPos.y);
+ }
+ else {
+ db_unset(NULL, "CList", "Docked");
+ db_unset(NULL, "CList", "DockX");
+ db_unset(NULL, "CList", "DockY");
+ }
+ }
+
+ if (!docked && msg->message != WM_CREATE && msg->message != WM_MOVING)
+ return 0;
+
+ switch (msg->message) {
+ case WM_CREATE:
+ draggingTitle = 0;
+ docked = db_get_b(NULL, "CLUI", "DockToSides", 1) ?
+ (char)db_get_b(NULL, "CList", "Docked", 0) : 0;
+ dockPos.x = (int)db_get_dw(NULL, "CList", "DockX", 0);
+ dockPos.y = (int)db_get_dw(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)) &&
+ db_get_b(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,
+ db_get_dw(NULL, "CList", "Width", 0),
+ db_get_dw(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/mir_app/src/ExtraIcon.cpp b/src/mir_app/src/ExtraIcon.cpp new file mode 100644 index 0000000000..166f337383 --- /dev/null +++ b/src/mir_app/src/ExtraIcon.cpp @@ -0,0 +1,75 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+ExtraIcon::ExtraIcon(const char *name) :
+ szName(mir_strdup(name)), slot(-1), position(1000), hLangpack(0)
+{
+}
+
+ExtraIcon::~ExtraIcon()
+{
+}
+
+const char *ExtraIcon::getName() const
+{
+ return szName;
+}
+
+int ExtraIcon::getSlot() const
+{
+ return slot;
+}
+
+void ExtraIcon::setSlot(int slot)
+{
+ this->slot = slot;
+}
+
+int ExtraIcon::getPosition() const
+{
+ return position;
+}
+
+void ExtraIcon::setPosition(int position)
+{
+ this->position = position;
+}
+
+bool ExtraIcon::isEnabled() const
+{
+ return slot >= 0;
+}
+
+void ExtraIcon::applyIcons()
+{
+ if (!isEnabled())
+ return;
+
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ // Clear to assert that it will be cleared
+ Clist_SetExtraIcon(hContact, slot, INVALID_HANDLE_VALUE);
+ applyIcon(hContact);
+ }
+}
diff --git a/src/mir_app/src/ExtraIcon.h b/src/mir_app/src/ExtraIcon.h new file mode 100644 index 0000000000..b5c8c1e617 --- /dev/null +++ b/src/mir_app/src/ExtraIcon.h @@ -0,0 +1,180 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#ifndef __EXTRAICON_H__
+#define __EXTRAICON_H__
+
+#define EXTRAICON_TYPE_GROUP -1
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ExtraIcon - base class for all extra icons
+
+class ExtraIcon
+{
+public:
+ ExtraIcon(const char *name);
+ virtual ~ExtraIcon();
+
+ virtual void rebuildIcons() = 0;
+ virtual void applyIcons();
+ virtual void applyIcon(MCONTACT hContact) =0 ;
+ virtual void onClick(MCONTACT hContact) = 0;
+
+ virtual int setIcon(int id, MCONTACT hContact, HANDLE icon) = 0;
+ virtual int setIconByName(int id, MCONTACT hContact, const char* icon) = 0;
+ virtual void storeIcon(MCONTACT, void*) {};
+
+ virtual const char *getName() const;
+ virtual const TCHAR *getDescription() const = 0;
+ virtual const char *getDescIcon() const = 0;
+ virtual int getType() const = 0;
+
+ virtual int getSlot() const;
+ virtual void setSlot(int slot);
+
+ virtual int getPosition() const;
+ virtual void setPosition(int position);
+
+ virtual bool isEnabled() const;
+
+ virtual int ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage) = 0;
+
+ int hLangpack;
+
+protected:
+ ptrA szName;
+
+ int slot;
+ int position;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// BaseExtraIcon - basic class for all 'real' extra icons
+
+class BaseExtraIcon : public ExtraIcon
+{
+public:
+ BaseExtraIcon(int id, const char *name, const TCHAR *description, const char *descIcon, MIRANDAHOOKPARAM OnClick, LPARAM param);
+ virtual ~BaseExtraIcon();
+
+ virtual int getID() const;
+ virtual const TCHAR *getDescription() const;
+ virtual void setDescription(const TCHAR *desc);
+ virtual const char *getDescIcon() const;
+ virtual void setDescIcon(const char *icon);
+ virtual int getType() const =0;
+
+ virtual void onClick(MCONTACT hContact);
+ virtual void setOnClick(MIRANDAHOOKPARAM OnClick, LPARAM param);
+
+ virtual int ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage);
+
+protected:
+ int id;
+ ptrT tszDescription;
+ ptrA szDescIcon;
+ MIRANDAHOOKPARAM OnClick;
+ LPARAM onClickParam;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CallbackExtraIcon - extra icon, implemented using callback functions
+
+class CallbackExtraIcon : public BaseExtraIcon
+{
+public:
+ CallbackExtraIcon(int id, const char *name, const TCHAR *description, const char *descIcon,
+ MIRANDAHOOK RebuildIcons, MIRANDAHOOK ApplyIcon, MIRANDAHOOKPARAM OnClick, LPARAM param);
+ virtual ~CallbackExtraIcon();
+
+ virtual int getType() const;
+
+ virtual void rebuildIcons();
+ virtual void applyIcon(MCONTACT hContact);
+
+ virtual int setIcon(int id, MCONTACT hContact, HANDLE icon);
+ virtual int setIconByName(int id, MCONTACT hContact, const char* icon);
+
+private:
+ int(*RebuildIcons)(WPARAM wParam, LPARAM lParam);
+ int(*ApplyIcon)(WPARAM wParam, LPARAM lParam);
+
+ bool needToRebuild;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcolibExtraIcon - extra icon, implemented using icolib
+
+class IcolibExtraIcon : public BaseExtraIcon
+{
+public:
+ IcolibExtraIcon(int id, const char *name, const TCHAR *description, const char *descIcon, MIRANDAHOOKPARAM OnClick, LPARAM param);
+ virtual ~IcolibExtraIcon();
+
+ virtual int getType() const;
+
+ virtual void rebuildIcons();
+ virtual void applyIcon(MCONTACT hContact);
+
+ virtual int setIcon(int id, MCONTACT hContact, HANDLE icon);
+ virtual int setIconByName(int id, MCONTACT hContact, const char* icon);
+ virtual void storeIcon(MCONTACT hContact, void *icon);
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ExtraIconGroup - joins some slots into one
+
+class ExtraIconGroup : public ExtraIcon
+{
+ int internalSetIcon(int id, MCONTACT hContact, HANDLE icon, bool bByName);
+public:
+ ExtraIconGroup(const char *name);
+ virtual ~ExtraIconGroup();
+
+ virtual void addExtraIcon(BaseExtraIcon *extra);
+
+ virtual void rebuildIcons();
+ virtual void applyIcon(MCONTACT hContact);
+ virtual void onClick(MCONTACT hContact);
+
+ virtual int setIcon(int id, MCONTACT hContact, HANDLE icon);
+ virtual int setIconByName(int id, MCONTACT hContact, const char *icon);
+
+ virtual const TCHAR* getDescription() const;
+ virtual const char* getDescIcon() const;
+ virtual int getType() const;
+
+ virtual int getPosition() const;
+ virtual void setSlot(int slot);
+
+ LIST<BaseExtraIcon> items;
+
+ virtual int ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage);
+
+protected:
+ ptrT tszDescription;
+ bool setValidExtraIcon;
+ bool insideApply;
+
+ virtual ExtraIcon *getCurrentItem(MCONTACT hContact) const;
+};
+
+#endif // __EXTRAICON_H__
diff --git a/src/mir_app/src/ExtraIconGroup.cpp b/src/mir_app/src/ExtraIconGroup.cpp new file mode 100644 index 0000000000..d8e4c0974d --- /dev/null +++ b/src/mir_app/src/ExtraIconGroup.cpp @@ -0,0 +1,217 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+ExtraIconGroup::ExtraIconGroup(const char *_name) :
+ ExtraIcon(_name), setValidExtraIcon(false), insideApply(false),
+ items(1)
+{
+ db_set_resident(MODULE_NAME, _name);
+}
+
+ExtraIconGroup::~ExtraIconGroup()
+{
+}
+
+void ExtraIconGroup::addExtraIcon(BaseExtraIcon *extra)
+{
+ items.insert(extra);
+
+ CMString description;
+ for (int i = 0; i < items.getCount(); i++) {
+ if (i > 0)
+ description += _T(" / ");
+ description += items[i]->getDescription();
+ }
+
+ tszDescription = mir_tstrdup(description);
+}
+
+void ExtraIconGroup::rebuildIcons()
+{
+ for (int i = 0; i < items.getCount(); i++)
+ items[i]->rebuildIcons();
+}
+
+void ExtraIconGroup::applyIcon(MCONTACT hContact)
+{
+ if (!isEnabled() || hContact == NULL)
+ return;
+
+ setValidExtraIcon = false;
+
+ insideApply = true;
+
+ int i;
+ for (i = 0; i < items.getCount(); i++) {
+ items[i]->applyIcon(hContact);
+ if (setValidExtraIcon)
+ break;
+ }
+
+ insideApply = false;
+
+ db_set_dw(hContact, MODULE_NAME, szName, setValidExtraIcon ? items[i]->getID() : 0);
+}
+
+int ExtraIconGroup::getPosition() const
+{
+ int pos = INT_MAX;
+ for (int i = 0; i < items.getCount(); i++)
+ pos = MIN(pos, items[i]->getPosition());
+ return pos;
+}
+
+void ExtraIconGroup::setSlot(int slot)
+{
+ ExtraIcon::setSlot(slot);
+
+ for (int i = 0; i < items.getCount(); i++)
+ items[i]->setSlot(slot);
+}
+
+ExtraIcon * ExtraIconGroup::getCurrentItem(MCONTACT hContact) const
+{
+ int id = (int)db_get_dw(hContact, MODULE_NAME, szName, 0);
+ if (id < 1)
+ return NULL;
+
+ for (int i = 0; i < items.getCount(); i++)
+ if (id == items[i]->getID())
+ return items[i];
+
+ return NULL;
+}
+
+void ExtraIconGroup::onClick(MCONTACT hContact)
+{
+ ExtraIcon *extra = getCurrentItem(hContact);
+ if (extra != NULL)
+ extra->onClick(hContact);
+}
+
+int ExtraIconGroup::setIcon(int id, MCONTACT hContact, HANDLE value)
+{
+ return internalSetIcon(id, hContact, (void*)value, false);
+}
+
+int ExtraIconGroup::setIconByName(int id, MCONTACT hContact, const char *value)
+{
+ return internalSetIcon(id, hContact, (void*)value, true);
+}
+
+int ExtraIconGroup::internalSetIcon(int id, MCONTACT hContact, void *value, bool bByName)
+{
+ if (insideApply) {
+ for (int i=0; i < items.getCount(); i++)
+ if (items[i]->getID() == id) {
+ if (bByName)
+ return items[i]->setIconByName(id, hContact, (const char*)value);
+ return items[i]->setIcon(id, hContact, (HANDLE)value);
+ }
+
+ return -1;
+ }
+
+ ExtraIcon *current = getCurrentItem(hContact);
+ int currentPos = items.getCount();
+ int storePos = items.getCount();
+ for (int i=0; i < items.getCount(); i++) {
+ if (items[i]->getID() == id)
+ storePos = i;
+
+ if (items[i] == current)
+ currentPos = i;
+ }
+
+ if (storePos == items.getCount())
+ return -1;
+
+ if (storePos > currentPos) {
+ items[storePos]->storeIcon(hContact, value);
+ return 0;
+ }
+
+ // Ok, we have to set the icon, but we have to assert it is a valid icon
+
+ setValidExtraIcon = false;
+
+ int ret;
+ if (bByName)
+ ret = items[storePos]->setIconByName(id, hContact, (const char*)value);
+ else
+ ret = items[storePos]->setIcon(id, hContact, (HANDLE)value);
+
+ if (storePos < currentPos) {
+ if (setValidExtraIcon)
+ db_set_dw(hContact, MODULE_NAME, szName, items[storePos]->getID());
+ }
+ else if (storePos == currentPos) {
+ if (!setValidExtraIcon) {
+ db_set_dw(hContact, MODULE_NAME, szName, 0);
+
+ insideApply = true;
+
+ for (++storePos; storePos < items.getCount(); ++storePos) {
+ items[storePos]->applyIcon(hContact);
+ if (setValidExtraIcon)
+ break;
+ }
+
+ insideApply = false;
+
+ if (setValidExtraIcon && storePos < items.getCount())
+ db_set_dw(hContact, MODULE_NAME, szName, items[storePos]->getID());
+ }
+ }
+
+ return ret;
+}
+
+const TCHAR *ExtraIconGroup::getDescription() const
+{
+ return tszDescription;
+}
+
+const char *ExtraIconGroup::getDescIcon() const
+{
+ for (int i = 0; i < items.getCount(); i++)
+ if (!IsEmpty(items[i]->getDescIcon()))
+ return items[i]->getDescIcon();
+
+ return "";
+}
+
+int ExtraIconGroup::getType() const
+{
+ return EXTRAICON_TYPE_GROUP;
+}
+
+int ExtraIconGroup::ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage)
+{
+ if (hImage != INVALID_HANDLE_VALUE)
+ setValidExtraIcon = true;
+
+ return Clist_SetExtraIcon(hContact, slot, hImage);
+}
diff --git a/src/mir_app/src/FontOptions.cpp b/src/mir_app/src/FontOptions.cpp new file mode 100644 index 0000000000..982fb25e2e --- /dev/null +++ b/src/mir_app/src/FontOptions.cpp @@ -0,0 +1,1389 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "FontService.h"
+
+// *_w2 is working copy of list
+// *_w3 is stores initial configuration
+
+static int sttCompareFont(const FontInternal* p1, const FontInternal* p2)
+{
+ int result = mir_tstrcmp(p1->group, p2->group);
+ if (result != 0)
+ return result;
+
+ result = p1->order - p2->order;
+ if (result != 0)
+ return result;
+
+ return mir_tstrcmp(p1->getName(), p2->getName());
+}
+
+OBJLIST<FontInternal> font_id_list(20, sttCompareFont), font_id_list_w2(20, sttCompareFont), font_id_list_w3(20, sttCompareFont);
+
+static int sttCompareColour(const ColourInternal* p1, const ColourInternal* p2)
+{
+ int result = mir_tstrcmp(p1->group, p2->group);
+ if (result != 0)
+ return result;
+
+ result = p1->order - p2->order;
+ if (result != 0)
+ return result;
+
+ return mir_tstrcmp(p1->getName(), p2->getName());
+}
+
+OBJLIST<ColourInternal> colour_id_list(10, sttCompareColour), colour_id_list_w2(10, sttCompareColour), colour_id_list_w3(10, sttCompareColour);
+
+static int sttCompareEffect(const EffectInternal* p1, const EffectInternal* p2)
+{
+ int result = mir_tstrcmp(p1->group, p2->group);
+ if (result != 0)
+ return result;
+
+ result = p1->order - p2->order;
+ if (result != 0)
+ return result;
+
+ return mir_tstrcmp(p1->getName(), p2->getName());
+}
+
+OBJLIST<EffectInternal> effect_id_list(10, sttCompareEffect), effect_id_list_w2(10, sttCompareEffect), effect_id_list_w3(10, sttCompareEffect);
+
+struct DrawTextWithEffectParam
+{
+ 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
+};
+
+#define MS_DRAW_TEXT_WITH_EFFECTA "Modern/SkinEngine/DrawTextWithEffectA"
+#define MS_DRAW_TEXT_WITH_EFFECTW "Modern/SkinEngine/DrawTextWithEffectW"
+
+#define MS_DRAW_TEXT_WITH_EFFECT MS_DRAW_TEXT_WITH_EFFECTW
+
+// 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)
+
+void UpdateFontSettings(FontIDW *font_id, FontSettingsT *fontsettings);
+void UpdateColourSettings(ColourIDW *colour_id, COLORREF *colour);
+void UpdateEffectSettings(EffectIDW *effect_id, FONTEFFECT* effectsettings);
+
+static void WriteLine(FILE *out, const char pszText[])
+{
+ fputs(pszText, out);
+ fputc('\n', out);
+}
+
+static BOOL ExportSettings(HWND hwndDlg, const TCHAR *filename, OBJLIST<FontInternal>& flist, OBJLIST<ColourInternal>& clist, OBJLIST<EffectInternal>& elist)
+{
+ FILE *out = _tfopen(filename, _T("w"));
+ if (out == NULL) {
+ MessageBox(hwndDlg, filename, TranslateT("Failed to create file"), MB_ICONWARNING | MB_OK);
+ return FALSE;
+ }
+
+ char header[512], buff[1024];
+ header[0] = 0;
+
+ fputs("SETTINGS:\n\n", out);
+
+ for (int i = 0; i < flist.getCount(); i++) {
+ FontInternal& F = flist[i];
+
+ mir_snprintf(buff, "\n[%s]", F.dbSettingsGroup);
+ if (mir_strcmp(buff, header) != 0) {
+ strncpy(header, buff, SIZEOF(header));
+ WriteLine(out, buff);
+ }
+
+ fprintf(out, (F.flags & FIDF_APPENDNAME) ? "%sName=s%S\n" : "%s=s%S\n", F.prefix, F.value.szFace);
+
+ int iFontSize;
+ if (F.flags & FIDF_SAVEACTUALHEIGHT) {
+ SIZE size;
+ LOGFONT lf;
+ CreateFromFontSettings(&F.value, &lf);
+ HFONT hFont = CreateFontIndirect(&lf);
+
+ HDC hdc = GetDC(hwndDlg);
+ HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
+ GetTextExtentPoint32(hdc, _T("_W"), 2, &size);
+ ReleaseDC(hwndDlg, hdc);
+ SelectObject(hdc, hOldFont);
+ DeleteObject(hFont);
+
+ iFontSize = size.cy;
+ }
+ else if (F.flags & FIDF_SAVEPOINTSIZE) {
+ HDC hdc = GetDC(hwndDlg);
+ iFontSize = (BYTE)-MulDiv(F.value.size, 72, GetDeviceCaps(hdc, LOGPIXELSY));
+ ReleaseDC(hwndDlg, hdc);
+ }
+ else iFontSize = F.value.size;
+ fprintf(out, "%sSize=b%d\n", F.prefix, iFontSize);
+
+ fprintf(out, "%sSty=b%d\n", F.prefix, F.value.style);
+ fprintf(out, "%sSet=b%d\n", F.prefix, F.value.charset);
+ fprintf(out, "%sCol=d%d\n", F.prefix, F.value.colour);
+
+ if (F.flags & FIDF_NOAS)
+ fprintf(out, "%sAs=w%d\n", F.prefix, 0x00FF);
+
+ fprintf(out, "%sFlags=w%d\n", F.prefix, F.flags);
+ }
+
+ header[0] = 0;
+ for (int i = 0; i < clist.getCount(); i++) {
+ ColourInternal& C = clist[i];
+
+ mir_snprintf(buff, "\n[%s]", C.dbSettingsGroup);
+ if (mir_strcmp(buff, header) != 0) {
+ strncpy_s(header, buff, _TRUNCATE);
+ WriteLine(out, buff);
+ }
+ fprintf(out, "%s=d%d\n", C.setting, (DWORD)C.value);
+ }
+
+ header[0] = 0;
+ for (int i = 0; i < elist.getCount(); i++) {
+ EffectInternal& E = elist[i];
+
+ mir_snprintf(buff, "\n[%s]", E.dbSettingsGroup);
+ if (mir_strcmp(buff, header) != 0) {
+ strncpy_s(header, buff, _TRUNCATE);
+ WriteLine(out, buff);
+ }
+ fprintf(out, "%sEffect=b%d\n", E.setting, E.value.effectIndex);
+ fprintf(out, "%sEffectCol1=d%d\n", E.setting, E.value.baseColour);
+ fprintf(out, "%sEffectCol2=d%d\n", E.setting, E.value.secondaryColour);
+ }
+
+ fclose(out);
+ 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, LPARAM lParam)
+{
+ if (uiMsg == WM_INITDIALOG) {
+ CHOOSEFONT* cf = (CHOOSEFONT *)lParam;
+ if (cf != NULL) {
+ if (cf->lCustData & FIDF_DISABLESTYLES) {
+ EnableWindow(GetDlgItem(hdlg, 1137), FALSE);
+ ShowWindow(GetDlgItem(hdlg, 1137), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, 1095), SW_SHOW);
+ }
+ else if (cf->lCustData & FIDF_ALLOWEFFECTS) {
+ EnableWindow(GetDlgItem(hdlg, 1139), FALSE);
+ ShowWindow(GetDlgItem(hdlg, 1139), SW_HIDE);
+ ShowWindow(GetDlgItem(hdlg, 1091), SW_HIDE);
+ }
+ }
+ }
+
+ 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)
+{
+ BOOL res = FALSE;
+ for (int i = SendMessage(hwndList, LB_GETCOUNT, 0, 0); i--;) {
+ FSUIListItemData *itemData = (FSUIListItemData *)SendMessage(hwndList, LB_GETITEMDATA, i, 0);
+ if (itemData && itemData->font_id >= 0) {
+ FontInternal& F = font_id_list_w2[itemData->font_id];
+
+ if (name && !mir_tstrcmp(F.name, name)) {
+ itemData->colour_id = colourId;
+ res = TRUE;
+ }
+
+ if (backgroundGroup && backgroundName && !mir_tstrcmp(F.backgroundGroup, backgroundGroup) && !mir_tstrcmp(F.backgroundName, backgroundName)) {
+ itemData->colour_id = colourId;
+ res = TRUE;
+ }
+ }
+ }
+
+ return res;
+}
+
+static bool sttFsuiBindEffectIdToFonts(HWND hwndList, const TCHAR *name, int effectId)
+{
+ for (int i = SendMessage(hwndList, LB_GETCOUNT, 0, 0); i--;) {
+ FSUIListItemData *itemData = (FSUIListItemData *)SendMessage(hwndList, LB_GETITEMDATA, i, 0);
+ if (itemData && itemData->font_id >= 0) {
+ FontInternal& F = font_id_list_w2[itemData->font_id];
+
+ if (name && !mir_tstrcmp(F.name, name)) {
+ itemData->effect_id = effectId;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+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 = SIZEOF(str);
+
+ while (tvi.hItem) {
+ TreeView_GetItem(hwndTree, &tvi);
+
+ if (!mir_tstrcmp(tvi.pszText, name))
+ return tvi.hItem;
+
+ tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem);
+ }
+ return NULL;
+}
+
+static void sttFsuiCreateSettingsTreeNode(HWND hwndTree, const TCHAR *groupName, int hLangpack)
+{
+ TCHAR itemName[1024];
+ TCHAR* sectionName;
+ int sectionLevel = 0;
+
+ HTREEITEM hSection = NULL;
+ mir_tstrcpy(itemName, groupName);
+ sectionName = itemName;
+
+ while (sectionName) {
+ // allow multi-level tree
+ TCHAR* pItemName = sectionName;
+ HTREEITEM hItem;
+
+ // one level deeper
+ if (sectionName = _tcschr(sectionName, '/'))
+ *sectionName = 0;
+
+ pItemName = TranslateTH(hLangpack, 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);
+
+ memset(&tvis.item, 0, sizeof(tvis.item));
+ tvis.item.hItem = hItem;
+ tvis.item.mask = TVIF_HANDLE | TVIF_STATE;
+ tvis.item.state = tvis.item.stateMask = db_get_b(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)
+ db_set_b(NULL, "FontServiceUI", treeItem->paramName, TVIS_EXPANDED);
+ else
+ db_set_b(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 count = SendMessage(hList, LB_GETCOUNT, 0, 0);
+ if (count > 0) {
+ for (int idx = 0; idx < count; idx++) {
+ LRESULT res = SendMessage(hList, LB_GETITEMDATA, idx, 0);
+ FSUIListItemData *itemData = (FSUIListItemData *)res;
+ if (itemData && res != LB_ERR)
+ mir_free(itemData);
+ }
+ }
+}
+
+static void 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);
+}
+
+TCHAR* ModernEffectNames[] = { LPGENT("<none>"), LPGENT("Shadow at left"), LPGENT("Shadow at right"), LPGENT("Outline"), LPGENT("Outline smooth"), LPGENT("Smooth bump"), LPGENT("Contour thin"), LPGENT("Contour heavy") };
+
+static INT_PTR CALLBACK ChooseEffectDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static FONTEFFECT * pEffect = NULL;
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ pEffect = (FONTEFFECT*)lParam;
+ {
+ for (int i = 0; i < SIZEOF(ModernEffectNames); i++) {
+ int itemid = SendDlgItemMessage(hwndDlg, IDC_EFFECT_COMBO, CB_ADDSTRING, 0, (LPARAM)TranslateTS(ModernEffectNames[i]));
+ SendDlgItemMessage(hwndDlg, IDC_EFFECT_COMBO, CB_SETITEMDATA, itemid, i);
+ SendDlgItemMessage(hwndDlg, IDC_EFFECT_COMBO, CB_SETCURSEL, 0, 0);
+ }
+
+ int cnt = SendDlgItemMessage(hwndDlg, IDC_EFFECT_COMBO, CB_GETCOUNT, 0, 0);
+ for (int i = 0; i < cnt; i++) {
+ if (SendDlgItemMessage(hwndDlg, IDC_EFFECT_COMBO, CB_GETITEMDATA, i, 0) == pEffect->effectIndex) {
+ 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 void sttSaveFontData(HWND hwndDlg, FontInternal &F)
+{
+ LOGFONT lf;
+ char str[128];
+
+ if (F.flags & FIDF_APPENDNAME)
+ mir_snprintf(str, "%sName", F.prefix);
+ else
+ strncpy_s(str, F.prefix, _TRUNCATE);
+
+ if (db_set_ts(NULL, F.dbSettingsGroup, str, F.value.szFace)) {
+ char buff[1024];
+ WideCharToMultiByte(code_page, 0, F.value.szFace, -1, buff, 1024, 0, 0);
+ db_set_s(NULL, F.dbSettingsGroup, str, buff);
+ }
+
+ mir_snprintf(str, "%sSize", F.prefix);
+ if (F.flags & FIDF_SAVEACTUALHEIGHT) {
+ SIZE size;
+ CreateFromFontSettings(&F.value, &lf);
+ HFONT hFont = CreateFontIndirect(&lf);
+ HDC hdc = GetDC(hwndDlg);
+ HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
+ GetTextExtentPoint32(hdc, _T("_W"), 2, &size);
+ ReleaseDC(hwndDlg, hdc);
+ SelectObject(hdc, hOldFont);
+ DeleteObject(hFont);
+
+ db_set_b(NULL, F.dbSettingsGroup, str, (char)size.cy);
+ }
+ else if (F.flags & FIDF_SAVEPOINTSIZE) {
+ HDC hdc = GetDC(hwndDlg);
+ db_set_b(NULL, F.dbSettingsGroup, str, (BYTE)-MulDiv(F.value.size, 72, GetDeviceCaps(hdc, LOGPIXELSY)));
+ ReleaseDC(hwndDlg, hdc);
+ }
+ else db_set_b(NULL, F.dbSettingsGroup, str, F.value.size);
+
+ mir_snprintf(str, "%sSty", F.prefix);
+ db_set_b(NULL, F.dbSettingsGroup, str, F.value.style);
+ mir_snprintf(str, "%sSet", F.prefix);
+ db_set_b(NULL, F.dbSettingsGroup, str, F.value.charset);
+ mir_snprintf(str, "%sCol", F.prefix);
+ db_set_dw(NULL, F.dbSettingsGroup, str, F.value.colour);
+ if (F.flags & FIDF_NOAS) {
+ mir_snprintf(str, "%sAs", F.prefix);
+ db_set_w(NULL, F.dbSettingsGroup, str, (WORD)0x00FF);
+ }
+ mir_snprintf(str, "%sFlags", F.prefix);
+ db_set_w(NULL, F.dbSettingsGroup, str, (WORD)F.flags);
+}
+
+static INT_PTR CALLBACK DlgProcLogOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ int i, selCount;
+ 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++) {
+ FontInternal& F = font_id_list_w2[i];
+ // sync settings with database
+ UpdateFontSettings(&F, &F.value);
+ sttFsuiCreateSettingsTreeNode(GetDlgItem(hwndDlg, IDC_FONTGROUP), F.group, F.hLangpack);
+ }
+
+ for (i = 0; i < colour_id_list_w2.getCount(); i++) {
+ ColourInternal& C = colour_id_list_w2[i];
+
+ // sync settings with database
+ UpdateColourSettings(&C, &C.value);
+ sttFsuiCreateSettingsTreeNode(GetDlgItem(hwndDlg, IDC_FONTGROUP), C.group, C.hLangpack);
+ }
+
+ for (i = 0; i < effect_id_list_w2.getCount(); i++) {
+ EffectInternal& E = effect_id_list_w2[i];
+
+ // sync settings with database
+ UpdateEffectSettings(&E, &E.value);
+ sttFsuiCreateSettingsTreeNode(GetDlgItem(hwndDlg, IDC_FONTGROUP), E.group, E.hLangpack);
+ }
+
+ SendDlgItemMessage(hwndDlg, IDC_BKGCOLOUR, CPM_SETDEFAULTCOLOUR, 0, (LPARAM)GetSysColor(COLOR_WINDOW));
+ return TRUE;
+
+ case UM_SETFONTGROUP:
+ TreeItem *treeItem;
+ {
+ 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;
+ TCHAR *group_buff = treeItem->groupName;
+
+ sttFreeListItems(GetDlgItem(hwndDlg, IDC_FONTLIST));
+ SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_RESETCONTENT, 0, 0);
+
+ if (group_buff) {
+ BOOL need_restart = FALSE;
+
+ SendDlgItemMessage(hwndDlg, IDC_FONTLIST, WM_SETREDRAW, FALSE, 0);
+
+ for (int fontId = 0; fontId < font_id_list_w2.getCount(); fontId++) {
+ FontInternal &F = font_id_list_w2[fontId];
+ if (!_tcsncmp(F.group, group_buff, 64)) {
+ FSUIListItemData *itemData = (FSUIListItemData*)mir_alloc(sizeof(FSUIListItemData));
+ itemData->colour_id = -1;
+ itemData->effect_id = -1;
+ itemData->font_id = fontId;
+ SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_ADDSTRING, (WPARAM)-1, (LPARAM)itemData);
+ need_restart |= (F.flags & FIDF_NEEDRESTART);
+ }
+ }
+
+ if (hBkgColourBrush) {
+ DeleteObject(hBkgColourBrush);
+ hBkgColourBrush = 0;
+ }
+
+ for (int colourId = 0; colourId < colour_id_list_w2.getCount(); colourId++) {
+ ColourInternal &C = colour_id_list_w2[colourId];
+ if (!_tcsncmp(C.group, group_buff, 64)) {
+ if (!sttFsuiBindColourIdToFonts(GetDlgItem(hwndDlg, IDC_FONTLIST), C.name, C.group, C.name, colourId)) {
+ FSUIListItemData *itemData = (FSUIListItemData*)mir_alloc(sizeof(FSUIListItemData));
+ itemData->colour_id = colourId;
+ itemData->font_id = -1;
+ itemData->effect_id = -1;
+
+ SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_ADDSTRING, (WPARAM)-1, (LPARAM)itemData);
+ }
+
+ if (mir_tstrcmp(C.name, _T("Background")) == 0)
+ hBkgColourBrush = CreateSolidBrush(C.value);
+ }
+ }
+
+ if (!hBkgColourBrush)
+ hBkgColourBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
+
+ for (int effectId = 0; effectId < effect_id_list_w2.getCount(); effectId++) {
+ EffectInternal& E = effect_id_list_w2[effectId];
+ if (!_tcsncmp(E.group, group_buff, 64)) {
+ if (!sttFsuiBindEffectIdToFonts(GetDlgItem(hwndDlg, IDC_FONTLIST), E.name, effectId)) {
+ FSUIListItemData *itemData = (FSUIListItemData*)mir_alloc(sizeof(FSUIListItemData));
+ itemData->effect_id = effectId;
+ itemData->font_id = -1;
+ itemData->colour_id = -1;
+
+ 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;
+ if ((mis->CtlID != IDC_FONTLIST) || (mis->itemID == -1))
+ break;
+
+ FSUIListItemData *itemData = (FSUIListItemData *)mis->itemData;
+ if (!itemData)
+ return FALSE;
+
+ HFONT hFont = NULL, hoFont = NULL;
+ BOOL bIsFont = FALSE;
+ TCHAR *itemName = NULL;
+ if (itemData->font_id >= 0) {
+ int iItem = itemData->font_id;
+ bIsFont = TRUE;
+ CreateFromFontSettings(&font_id_list_w2[iItem].value, &lf);
+ hFont = CreateFontIndirect(&lf);
+ itemName = font_id_list_w2[iItem].getName();
+ }
+
+ if (itemData->colour_id >= 0) {
+ int iItem = itemData->colour_id;
+ if (!itemName)
+ itemName = colour_id_list_w2[iItem].getName();
+ }
+
+ HDC 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));
+
+ SIZE fontSize;
+ GetTextExtentPoint32(hdc, itemName, (int)mir_tstrlen(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:
+ FONTEFFECT Effect;
+ {
+ 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 * 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 = font_id_list_w2[iItem].getName();
+ 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 = colour_id_list_w2[iItem].getName();
+ }
+ }
+
+ 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 = effect_id_list_w2[iItem].getName();
+ }
+
+ 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)mir_tstrlen(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)mir_tstrlen(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) {
+ 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, 0, 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 = bEnableClText = bEnableClBack = bEnableReset = 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:
+ 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);
+
+ FSUIListItemData *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;
+ }
+
+ FontInternal& F = font_id_list_w2[itemData->font_id];
+ CreateFromFontSettings(&F.value, &lf);
+
+ CHOOSEFONT cf = { 0 };
+ cf.lStructSize = sizeof(cf);
+ cf.hwndOwner = hwndDlg;
+ cf.lpLogFont = &lf;
+ cf.lCustData = F.flags;
+ cf.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_ENABLEHOOK;
+ cf.lpfnHook = CFHookProc;
+
+ if (F.flags & FIDF_ALLOWEFFECTS) // enable effects section
+ cf.Flags |= CF_EFFECTS;
+ else if (F.flags & FIDF_DISABLESTYLES) { // mutually exclusive with FIDF_ALLOWEFFECTS
+ cf.Flags |= CF_TTONLY | CF_NOOEMFONTS;
+ lf.lfWeight = FW_NORMAL;
+ lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = FALSE;
+ }
+
+ 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;
+
+ FontInternal& 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;
+ _tcsncpy_s(F1.value.szFace, lf.lfFaceName, _TRUNCATE);
+
+ 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:
+ 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);
+ FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[0], 0);
+ EffectInternal& E = effect_id_list_w2[itemData->effect_id];
+
+ FONTEFFECT es = E.value;
+ if (IDOK == DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_CHOOSE_FONT_EFFECT), hwndDlg, ChooseEffectDlgProc, (LPARAM)&es)) {
+ for (int i = 0; i < selCount; i++) {
+ FSUIListItemData *itemData = (FSUIListItemData *)SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETITEMDATA, selItems[i], 0);
+ if (itemData->effect_id < 0)
+ continue;
+
+ EffectInternal& 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);
+ }
+ return TRUE;
+
+ case IDC_FONTCOLOUR:
+ 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 (int 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:
+ 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 (mir_tstrcmp(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:
+ if (font_id_list_w2.getCount() && (selCount = SendDlgItemMessage(hwndDlg, IDC_FONTLIST, LB_GETSELCOUNT, 0, 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++) {
+ FontInternal& F = font_id_list_w2[i];
+ sttSaveFontData(hwndDlg, F);
+ }
+
+ for (i = 0; i < colour_id_list_w2.getCount(); i++) {
+ ColourInternal& C = colour_id_list_w2[i];
+
+ strncpy_s(str, C.setting, _TRUNCATE);
+ db_set_dw(NULL, C.dbSettingsGroup, str, C.value);
+ }
+
+ for (i = 0; i < effect_id_list_w2.getCount(); i++) {
+ EffectInternal& E = effect_id_list_w2[i];
+
+ mir_snprintf(str, "%sEffect", E.setting);
+ db_set_b(NULL, E.dbSettingsGroup, str, E.value.effectIndex);
+
+ mir_snprintf(str, "%sEffectCol1", E.setting);
+ db_set_dw(NULL, E.dbSettingsGroup, str, E.value.baseColour);
+
+ mir_snprintf(str, "%sEffectCol2", E.setting);
+ db_set_dw(NULL, E.dbSettingsGroup, str, E.value.secondaryColour);
+ }
+
+ OptionsChanged();
+ return TRUE;
+ }
+
+ if (((LPNMHDR)lParam)->idFrom == IDC_FONTGROUP) {
+ switch (((NMHDR*)lParam)->code) {
+ case TVN_SELCHANGED:
+ SendMessage(hwndDlg, UM_SETFONTGROUP, 0, 0);
+ break;
+
+ case TVN_DELETEITEM:
+ 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.position = -790000000;
+ odp.hInstance = g_hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_FONTS);
+ odp.pszTitle = LPGEN("Fonts and colors");
+ odp.pszGroup = LPGEN("Customize");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pfnDlgProc = DlgProcLogOptions;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static FontInternal* sttFindFont(OBJLIST<FontInternal> &fonts, char *module, char *prefix)
+{
+ for (int i = 0; i < fonts.getCount(); i++) {
+ FontInternal& F = fonts[i];
+ if (!mir_strcmp(F.dbSettingsGroup, module) && !mir_strcmp(F.prefix, prefix))
+ return &F;
+ }
+
+ return 0;
+}
+
+static FontInternal fntHeader, fntGeneral, fntSmall;
+
+static INT_PTR CALLBACK DlgProcModernOptions(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ int i;
+ LOGFONT lf;
+
+ 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:
+ {
+ FontInternal *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;
+ 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"), -1, &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:
+ {
+ FontInternal *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);
+
+ CHOOSEFONT cf = { 0 };
+ cf.lStructSize = sizeof(cf);
+ cf.hwndOwner = hwndDlg;
+ cf.lpLogFont = &lf;
+ cf.lCustData = pf->flags;
+ cf.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
+ if (pf->flags & FIDF_ALLOWEFFECTS) {
+ cf.Flags |= CF_EFFECTS | CF_ENABLEHOOK;
+ 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;
+ _tcsncpy_s(pf->value.szFace, lf.lfFaceName, _TRUNCATE);
+
+ 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++) {
+ FontInternal &F = font_id_list[i];
+ if (F.deffontsettings.charset == SYMBOL_CHARSET)
+ continue;
+
+ COLORREF cl = F.value.colour;
+ if (F.isHeader())
+ 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)
+{
+ 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 = g_hInst;
+ 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-ng.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-ng.org/";
+ CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj);
+
+ obj.iSection = MODERNOPT_PAGE_MODULES;
+ obj.iType = MODERNOPT_TYPE_SECTIONPAGE;
+ obj.lpzTemplate = MAKEINTRESOURCEA(IDD_MODERNOPT_MODULES);
+ obj.pfnDlgProc = DlgPluginOpt;
+ obj.iBoldControls = iBoldControls;
+ obj.lpzClassicGroup = NULL;
+ obj.lpzClassicPage = NULL;
+ obj.lpzHelpUrl = "http://wiki.miranda-ng.org/";
+ CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj);
+ return 0;
+}
diff --git a/src/mir_app/src/FontService.cpp b/src/mir_app/src/FontService.cpp new file mode 100644 index 0000000000..417597d8de --- /dev/null +++ b/src/mir_app/src/FontService.cpp @@ -0,0 +1,115 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 = Langpack_GetDefaultCodePage();
+
+ CreateServiceFunction("Font/Register", RegisterFont);
+ CreateServiceFunction("Font/RegisterW", RegisterFontW);
+ CreateServiceFunction(MS_FONT_GET, GetFont);
+ CreateServiceFunction(MS_FONT_GETW, GetFontW);
+
+ CreateServiceFunction("Colour/Register", RegisterColour);
+ CreateServiceFunction("Colour/RegisterW", RegisterColourW);
+ CreateServiceFunction(MS_COLOUR_GET, GetColour);
+ CreateServiceFunction(MS_COLOUR_GETW, GetColourW);
+
+ CreateServiceFunction("Effect/Register", RegisterEffect);
+ CreateServiceFunction("Effect/RegisterW", RegisterEffectW);
+ CreateServiceFunction(MS_EFFECT_GET, GetEffect);
+ CreateServiceFunction(MS_EFFECT_GETW, GetEffectW);
+
+ hFontReloadEvent = CreateHookableEvent(ME_FONT_RELOAD);
+ hColourReloadEvent = CreateHookableEvent(ME_COLOUR_RELOAD);
+
+ // create generic fonts
+ FontIDT fontid = { sizeof(fontid) };
+ strncpy(fontid.dbSettingsGroup, "Fonts", sizeof(fontid.dbSettingsGroup));
+ _tcsncpy_s(fontid.group, LPGENT("General"), _TRUNCATE);
+
+ _tcsncpy_s(fontid.name, LPGENT("Headers"), _TRUNCATE);
+ fontid.flags = FIDF_APPENDNAME | FIDF_NOAS | FIDF_SAVEPOINTSIZE | FIDF_ALLOWEFFECTS | FIDF_CLASSHEADER;
+ strncpy(fontid.prefix, "Header", SIZEOF(fontid.prefix));
+ FontRegisterT(&fontid);
+
+ _tcsncpy_s(fontid.name, LPGENT("Generic text"), _TRUNCATE);
+ fontid.flags = FIDF_APPENDNAME | FIDF_NOAS | FIDF_SAVEPOINTSIZE | FIDF_ALLOWEFFECTS | FIDF_CLASSGENERAL;
+ strncpy(fontid.prefix, "Generic", SIZEOF(fontid.prefix));
+ FontRegisterT(&fontid);
+
+ _tcsncpy_s(fontid.name, LPGENT("Small text"), _TRUNCATE);
+ fontid.flags = FIDF_APPENDNAME | FIDF_NOAS | FIDF_SAVEPOINTSIZE | FIDF_ALLOWEFFECTS | FIDF_CLASSSMALL;
+ strncpy(fontid.prefix, "Small", SIZEOF(fontid.prefix));
+ FontRegisterT(&fontid);
+
+ // do last for silly dyna plugin
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, OnPreShutdown);
+ return 0;
+}
diff --git a/src/mir_app/src/FontService.h b/src/mir_app/src/FontService.h new file mode 100644 index 0000000000..970fd25090 --- /dev/null +++ b/src/mir_app/src/FontService.h @@ -0,0 +1,80 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "m_fontservice.h"
+
+// a font identifier structure - used for registering a font, and getting one out again
+
+struct FontInternal : public FontIDT
+{
+ FontSettingsT value;
+ int hLangpack;
+
+ __inline TCHAR* getName() const { return TranslateTH(hLangpack, name); }
+
+ __inline bool isHeader() const
+ {
+ if ((flags & FIDF_CLASSMASK) == FIDF_CLASSHEADER)
+ return true;
+
+ if ((flags & FIDF_CLASSMASK) == 0)
+ if (_tcsstr(name, _T("Incoming nick")) || _tcsstr(name, _T("Outgoing nick")) || _tcsstr(name, _T("Incoming timestamp")) || _tcsstr(name, _T("Outgoing timestamp")))
+ return true;
+ return false;
+ }
+};
+
+struct ColourInternal : public ColourIDT
+{
+ __inline TCHAR* getName() const { return TranslateTH(hLangpack, name); }
+
+ COLORREF value;
+ int hLangpack;
+};
+
+struct EffectInternal : public EffectIDT
+{
+ __inline TCHAR* getName() const { return TranslateTH(hLangpack, name); }
+
+ int hLangpack;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// global data & functions
+
+typedef struct
+{
+ char *paramName;
+ TCHAR *groupName;
+}
+ TreeItem;
+
+extern OBJLIST<FontInternal> font_id_list;
+extern OBJLIST<ColourInternal> colour_id_list;
+extern OBJLIST<EffectInternal> effect_id_list;
+
+extern int code_page;
+extern HANDLE hFontReloadEvent, hColourReloadEvent;
+
+int CreateFromFontSettings(FontSettingsT *fs, LOGFONT *lf);
diff --git a/src/mir_app/src/IcoLib.h b/src/mir_app/src/IcoLib.h new file mode 100644 index 0000000000..e8bd131b1d --- /dev/null +++ b/src/mir_app/src/IcoLib.h @@ -0,0 +1,108 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#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
+
+struct SectionItem
+{
+ TCHAR* name;
+ int flags;
+ int maxOrder;
+ int ref_count;
+};
+
+struct IconSourceFile
+{
+ TCHAR* file;
+ int ref_count;
+};
+
+struct IconSourceItem
+{
+ IconSourceFile* file;
+ int indx;
+ int cx, cy;
+
+ int ref_count;
+
+ HICON icon;
+ int icon_ref_count;
+
+ BYTE* icon_data;
+ int icon_size;
+};
+
+struct IcolibItem
+{
+ char* name;
+ SectionItem* section;
+ int orderID;
+ TCHAR* description;
+ TCHAR* default_file;
+ int default_indx;
+ int cx, cy;
+ int hLangpack;
+
+ IconSourceItem* source_small;
+ IconSourceItem* source_big;
+ IconSourceItem* default_icon;
+
+ TCHAR* temp_file;
+ HICON temp_icon;
+ BOOL temp_reset;
+
+ __inline TCHAR* getDescr() const { return TranslateTH(hLangpack, description); }
+};
+
+// extracticon.c
+
+UINT _ExtractIconEx(LPCTSTR lpszFile, int iconIndex, int cxIcon, int cyIcon, HICON *phicon, UINT flags);
+
+void __fastcall SAFE_FREE(void** p);
+void __fastcall SafeDestroyIcon(HICON* icon);
+
+int IconSourceItem_Release(IconSourceItem** pitem);
+int IconSourceItem_ReleaseIcon(IconSourceItem* item);
+HICON IconSourceItem_GetIcon(IconSourceItem* item);
+IconSourceItem* GetIconSourceItem(const TCHAR* file, int indx, int cxIcon, int cyIcon);
+
+IcolibItem* IcoLib_FindHIcon(HICON hIcon, bool &big);
+IcolibItem* IcoLib_FindIcon(const char* pszIconName);
+
+HICON IconItem_GetIcon(IcolibItem* item, bool big);
+
+int SkinOptionsInit(WPARAM, LPARAM);
+
+extern mir_cs csIconList;
+extern LIST<IcolibItem> iconList;
+extern LIST<SectionItem> sectionList;
+
+extern BOOL bNeedRebuild;
+extern int iconEventActive;
+extern HICON hIconBlank;
+extern HANDLE hIcons2ChangedEvent, hIconsChangedEvent;
diff --git a/src/mir_app/src/IcolibExtraIcon.cpp b/src/mir_app/src/IcolibExtraIcon.cpp new file mode 100644 index 0000000000..cdcd5a3d4a --- /dev/null +++ b/src/mir_app/src/IcolibExtraIcon.cpp @@ -0,0 +1,119 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+#include "usedIcons.h"
+
+#include "IcoLib.h"
+
+IcolibExtraIcon::IcolibExtraIcon(int _id, const char *_name, const TCHAR *_description, const char *_descIcon,
+ MIRANDAHOOKPARAM _OnClick, LPARAM _param) :
+ BaseExtraIcon(_id, _name, _description, _descIcon, _OnClick, _param)
+{
+ db_set_resident(MODULE_NAME, _name);
+}
+
+IcolibExtraIcon::~IcolibExtraIcon()
+{
+}
+
+int IcolibExtraIcon::getType() const
+{
+ return EXTRAICON_TYPE_ICOLIB;
+}
+
+void IcolibExtraIcon::rebuildIcons()
+{
+}
+
+void IcolibExtraIcon::applyIcon(MCONTACT hContact)
+{
+ if (!isEnabled() || hContact == NULL)
+ return;
+
+ HANDLE hImage = INVALID_HANDLE_VALUE;
+
+ ptrA szIconName(db_get_sa(hContact, MODULE_NAME, szName));
+ if (!IsEmpty(szIconName))
+ hImage = GetIcon(szIconName);
+
+ ClistSetExtraIcon(hContact, hImage);
+}
+
+int IcolibExtraIcon::setIcon(int id, MCONTACT hContact, HANDLE hIcoLib)
+{
+ if (hContact == NULL || id != this->id)
+ return -1;
+
+ if (hIcoLib == INVALID_HANDLE_VALUE)
+ hIcoLib = NULL;
+
+ if (isEnabled()) {
+ ptrA szIconName(db_get_sa(hContact, MODULE_NAME, szName));
+ if (!IsEmpty(szIconName))
+ RemoveIcon(szIconName);
+ }
+
+ IcolibItem *p = (IcolibItem*)hIcoLib;
+ char *szName = (p) ? p->name : NULL;
+ storeIcon(hContact, szName);
+
+ if (isEnabled())
+ return ClistSetExtraIcon(hContact, (hIcoLib == NULL) ? INVALID_HANDLE_VALUE : AddIcon(szName));
+
+ return 0;
+}
+
+int IcolibExtraIcon::setIconByName(int id, MCONTACT hContact, const char *icon)
+{
+ if (hContact == NULL || id != this->id)
+ return -1;
+
+ if (icon == INVALID_HANDLE_VALUE)
+ icon = NULL;
+
+ if (isEnabled()) {
+ ptrA szIconName(db_get_sa(hContact, MODULE_NAME, szName));
+ if (!IsEmpty(szIconName))
+ RemoveIcon(szIconName);
+ }
+
+ storeIcon(hContact, (char*)icon);
+
+ if (isEnabled())
+ return ClistSetExtraIcon(hContact, (IsEmpty(icon)) ? INVALID_HANDLE_VALUE : AddIcon(icon));
+
+ return 0;
+}
+
+void IcolibExtraIcon::storeIcon(MCONTACT hContact, void *icon)
+{
+ if (hContact == NULL)
+ return;
+
+ const char *icolibName = (const char *)icon;
+ if (IsEmpty(icolibName))
+ db_unset(hContact, MODULE_NAME, szName);
+ else
+ db_set_s(hContact, MODULE_NAME, szName, icolibName);
+}
diff --git a/src/mir_app/src/addcontact.cpp b/src/mir_app/src/addcontact.cpp new file mode 100644 index 0000000000..ce8557285e --- /dev/null +++ b/src/mir_app/src/addcontact.cpp @@ -0,0 +1,247 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+class CAddContactDlg : public CDlgBase
+{
+ ADDCONTACTSTRUCT m_acs;
+
+ CCtrlEdit m_authReq, m_myHandle;
+ CCtrlCheck m_chkAdded, m_chkAuth, m_chkOpen;
+ CCtrlButton m_btnOk;
+ CCtrlCombo m_group;
+
+public:
+ CAddContactDlg(ADDCONTACTSTRUCT *acs) :
+ CDlgBase(g_hInst, IDD_ADDCONTACT),
+ m_chkAdded(this, IDC_ADDED),
+ m_chkAuth(this, IDC_AUTH),
+ m_chkOpen(this, IDC_OPEN_WINDOW),
+ m_btnOk(this, IDOK),
+ m_group(this, IDC_GROUP),
+ m_authReq(this, IDC_AUTHREQ),
+ m_myHandle(this, IDC_MYHANDLE)
+ {
+ m_chkAuth.OnChange = Callback(this, &CAddContactDlg::OnAuthClicked);
+ m_chkOpen.OnChange = Callback(this, &CAddContactDlg::OnOpenClicked);
+ m_btnOk.OnClick = Callback(this, &CAddContactDlg::OnOk);
+
+ m_acs = *acs;
+ if (m_acs.psr) {
+ PROTOSEARCHRESULT *psr = (PROTOSEARCHRESULT*)mir_alloc(m_acs.psr->cbSize);
+ memcpy(psr, m_acs.psr, m_acs.psr->cbSize);
+ psr->nick.t = (psr->flags & PSR_UNICODE) ? mir_u2t((wchar_t*)psr->nick.t) : mir_a2t((char*)psr->nick.t);
+ psr->firstName.t = (psr->flags & PSR_UNICODE) ? mir_u2t((wchar_t*)psr->firstName.t) : mir_a2t((char*)psr->firstName.t);
+ psr->lastName.t = (psr->flags & PSR_UNICODE) ? mir_u2t((wchar_t*)psr->lastName.t) : mir_a2t((char*)psr->lastName.t);
+ psr->email.t = (psr->flags & PSR_UNICODE) ? mir_u2t((wchar_t*)psr->email.t) : mir_a2t((char*)psr->email.t);
+ psr->id.t = (psr->flags & PSR_UNICODE) ? mir_u2t((wchar_t*)psr->id.t) : mir_a2t((char*)psr->id.t);
+ psr->flags = psr->flags & ~PSR_UNICODE | PSR_TCHAR;
+ m_acs.psr = psr;
+ }
+ }
+
+ void OnInitDialog()
+ {
+ char szUin[10];
+ Window_SetIcon_IcoLib(m_hwnd, SKINICON_OTHER_ADDCONTACT);
+ if (m_acs.handleType == HANDLE_EVENT) {
+ DWORD dwUin;
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ dbei.cbBlob = sizeof(DWORD);
+ dbei.pBlob = (PBYTE)&dwUin;
+ db_event_get(m_acs.hDbEvent, &dbei);
+ _ltoa(dwUin, szUin, 10);
+ m_acs.szProto = dbei.szModule;
+ }
+
+ MCONTACT hContact;
+ TCHAR *szName = NULL, *tmpStr = NULL;
+ if (m_acs.handleType == HANDLE_CONTACT)
+ szName = cli.pfnGetContactDisplayName(hContact = m_acs.hContact, 0);
+ else {
+ int isSet = 0;
+ hContact = 0;
+
+ if (m_acs.handleType == HANDLE_EVENT) {
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ dbei.cbBlob = db_event_getBlobSize(m_acs.hDbEvent);
+ dbei.pBlob = (PBYTE)mir_alloc(dbei.cbBlob);
+ db_event_get(m_acs.hDbEvent, &dbei);
+ hContact = *(MCONTACT*)(dbei.pBlob + sizeof(DWORD));
+ mir_free(dbei.pBlob);
+ if (hContact != INVALID_CONTACT_ID) {
+ szName = cli.pfnGetContactDisplayName(hContact, 0);
+ isSet = 1;
+ }
+ }
+ if (!isSet) {
+ szName = (m_acs.handleType == HANDLE_EVENT) ? (tmpStr = mir_a2t(szUin)) :
+ (m_acs.psr->id.t ? m_acs.psr->id.t : m_acs.psr->nick.t);
+ }
+ }
+
+ if (szName && szName[0])
+ SetCaption(CMString(FORMAT, TranslateT("Add %s"), szName));
+ else
+ SetCaption(TranslateT("Add contact"));
+ mir_free(tmpStr);
+
+ if (m_acs.handleType == HANDLE_CONTACT && m_acs.hContact)
+ if (m_acs.szProto == NULL || (m_acs.szProto != NULL && *m_acs.szProto == 0))
+ m_acs.szProto = GetContactProto(m_acs.hContact);
+
+ int groupSel = 0;
+ ptrT tszGroup(db_get_tsa(hContact, "CList", "Group"));
+ TCHAR *grpName;
+ for (int groupId = 1; (grpName = cli.pfnGetGroupName(groupId, NULL)) != NULL; groupId++) {
+ int id = m_group.AddString(grpName, groupId);
+ if (!mir_tstrcmpi(tszGroup, grpName))
+ groupSel = id;
+ }
+
+ m_group.InsertString(TranslateT("None"), 0);
+ m_group.SetCurSel(groupSel);
+
+ // By default check both checkboxes
+ m_chkAdded.SetState(true);
+ m_chkAuth.SetState(true);
+
+ // Set last choice
+ if (db_get_b(NULL, "Miranda", "AuthOpenWindow", 1))
+ m_chkOpen.SetState(true);
+
+ DWORD flags = (m_acs.szProto) ? CallProtoServiceInt(NULL, m_acs.szProto, PS_GETCAPS, PFLAGNUM_4, 0) : 0;
+ if (flags & PF4_FORCEADDED) // force you were added requests for this protocol
+ m_chkAdded.Enable(false);
+
+ if (flags & PF4_FORCEAUTH) // force auth requests for this protocol
+ m_chkAuth.Enable(false);
+
+ if (flags & PF4_NOCUSTOMAUTH)
+ m_authReq.Enable(false);
+ else {
+ m_authReq.Enable(m_chkAuth.Enabled());
+ m_authReq.SetText(TranslateT("Please authorize my request and add me to your contact list."));
+ }
+ }
+
+ void OnDestroy()
+ {
+ Window_FreeIcon_IcoLib(m_hwnd);
+
+ if (m_acs.psr) {
+ mir_free(m_acs.psr->nick.t);
+ mir_free(m_acs.psr->firstName.t);
+ mir_free(m_acs.psr->lastName.t);
+ mir_free(m_acs.psr->email.t);
+ mir_free(m_acs.psr);
+ }
+ }
+
+ void OnAuthClicked(CCtrlButton*)
+ {
+ DWORD flags = CallProtoServiceInt(NULL, m_acs.szProto, PS_GETCAPS, PFLAGNUM_4, 0);
+ if (flags & PF4_NOCUSTOMAUTH)
+ m_authReq.Enable(false);
+ else
+ m_authReq.Enable(m_chkAuth.Enabled());
+ }
+
+ void OnOpenClicked(CCtrlButton*)
+ {
+ // Remember this choice
+ db_set_b(NULL, "Miranda", "AuthOpenWindow", m_chkOpen.Enabled());
+ }
+
+ void OnOk(CCtrlButton*)
+ {
+ MCONTACT hContact = INVALID_CONTACT_ID;
+ switch (m_acs.handleType) {
+ case HANDLE_EVENT:
+ {
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ db_event_get(m_acs.hDbEvent, &dbei);
+ hContact = (MCONTACT)CallProtoServiceInt(NULL, dbei.szModule, PS_ADDTOLISTBYEVENT, 0, (LPARAM)m_acs.hDbEvent);
+ }
+ break;
+
+ case HANDLE_SEARCHRESULT:
+ hContact = (MCONTACT)CallProtoServiceInt(NULL, m_acs.szProto, PS_ADDTOLIST, 0, (LPARAM)m_acs.psr);
+ break;
+
+ case HANDLE_CONTACT:
+ hContact = m_acs.hContact;
+ break;
+ }
+
+ if (hContact == NULL)
+ return;
+
+ ptrT szHandle(m_myHandle.GetText());
+ if (mir_tstrlen(szHandle))
+ db_set_ts(hContact, "CList", "MyHandle", szHandle);
+
+ int item = m_group.GetCurSel();
+ if (item > 0)
+ CallService(MS_CLIST_CONTACTCHANGEGROUP, hContact, m_group.GetItemData(item));
+
+ db_unset(hContact, "CList", "NotOnList");
+
+ if (m_chkAdded.GetState())
+ CallContactService(hContact, PSS_ADDED, 0, 0);
+
+ if (m_chkAuth.GetState()) {
+ DWORD flags = CallProtoServiceInt(NULL, m_acs.szProto, PS_GETCAPS, PFLAGNUM_4, 0);
+ if (flags & PF4_NOCUSTOMAUTH)
+ CallContactService(hContact, PSS_AUTHREQUEST, 0, 0);
+ else
+ CallContactService(hContact, PSS_AUTHREQUEST, 0, ptrT(m_authReq.GetText()));
+ }
+
+ if (m_chkOpen.GetState())
+ CallService(MS_CLIST_CONTACTDOUBLECLICKED, hContact, 0);
+ }
+};
+
+INT_PTR AddContactDialog(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam == 0)
+ return 1;
+
+ ADDCONTACTSTRUCT *acs = (ADDCONTACTSTRUCT*)lParam;
+ if (wParam) {
+ CAddContactDlg dlg(acs);
+ dlg.SetParent((HWND)wParam);
+ dlg.DoModal();
+ }
+ else (new CAddContactDlg(acs))->Show();
+ return 0;
+}
+
+int LoadAddContactModule(void)
+{
+ CreateServiceFunction(MS_ADDCONTACT_SHOW, AddContactDialog);
+ return 0;
+}
diff --git a/src/mir_app/src/button.cpp b/src/mir_app/src/button.cpp new file mode 100644 index 0000000000..6b69ff1e2d --- /dev/null +++ b/src/mir_app/src/button.cpp @@ -0,0 +1,629 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include <m_button_int.h>
+
+#include <initguid.h>
+#include <oleacc.h>
+
+struct TTooltips
+{
+ DWORD ThreadId;
+ HWND hwnd;
+};
+
+static LIST<TTooltips> lToolTips(1, NumericKeySortT);
+static mir_cs csTips;
+static BOOL bModuleInitialized = FALSE;
+
+// Used for our own cheap TrackMouseEvent
+#define BUTTON_POLLID 100
+#define BUTTON_POLLDELAY 50
+
+static void DestroyTheme(MButtonCtrl *ctl)
+{
+ if (ctl->hThemeButton) {
+ CloseThemeData(ctl->hThemeButton);
+ ctl->hThemeButton = NULL;
+ }
+ if (ctl->hThemeToolbar) {
+ CloseThemeData(ctl->hThemeToolbar);
+ ctl->hThemeToolbar = NULL;
+ }
+ ctl->bIsThemed = false;
+}
+
+static void LoadTheme(MButtonCtrl *ctl)
+{
+ DestroyTheme(ctl);
+ ctl->hThemeButton = OpenThemeData(ctl->hwnd, L"BUTTON");
+ ctl->hThemeToolbar = OpenThemeData(ctl->hwnd, L"TOOLBAR");
+ ctl->bIsThemed = true;
+}
+
+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
+
+///////////////////////////////////////////////////////////////////////////////
+// Button painter
+
+static void PaintWorker(MButtonCtrl *ctl, HDC hdcPaint)
+{
+ if (!hdcPaint)
+ return;
+
+ RECT rcClient;
+ GetClientRect(ctl->hwnd, &rcClient);
+
+ HDC hdcMem = CreateCompatibleDC(hdcPaint);
+ HBITMAP hbmMem = CreateCompatibleBitmap(hdcPaint, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top);
+ HDC hOld = (HDC)SelectObject(hdcMem, hbmMem);
+
+ // If its a push button, check to see if it should stay pressed
+ if (ctl->bIsPushBtn && ctl->bIsPushed)
+ ctl->stateId = PBS_PRESSED;
+
+ // Draw the flat button
+ if (ctl->bIsFlat) {
+ if (ctl->hThemeToolbar && ctl->bIsThemed) {
+ int state = IsWindowEnabled(ctl->hwnd)?(ctl->stateId == PBS_NORMAL && ctl->bIsDefault ? 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);
+ if (ctl->stateId == PBS_HOT || ctl->focus) {
+ if (ctl->bIsPushed)
+ 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 && ctl->bIsThemed) {
+ int state = IsWindowEnabled(ctl->hwnd)?(ctl->stateId == PBS_NORMAL && ctl->bIsDefault ? 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->bIsDefault && 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
+ int textLen = GetWindowTextLength(ctl->hwnd);
+
+ if (ctl->hIcon) {
+ LONG g_cxsmIcon = GetSystemMetrics(SM_CXSMICON);
+ LONG g_cysmIcon = GetSystemMetrics(SM_CYSMICON);
+ int ix = (rcClient.right-rcClient.left)/2 - (g_cxsmIcon/2);
+ int iy = (rcClient.bottom-rcClient.top)/2 - (g_cysmIcon/2);
+ if (ctl->stateId == PBS_PRESSED) {
+ ix++;
+ iy++;
+ }
+
+ HIMAGELIST hImageList = ImageList_Create(g_cxsmIcon, g_cysmIcon, ILC_MASK | ILC_COLOR32, 1, 0);
+ ImageList_AddIcon(hImageList, ctl->hIcon);
+ HICON hIconNew = ImageList_GetIcon(hImageList, 0, ILD_NORMAL);
+ DrawState(hdcMem, NULL, NULL, (LPARAM) hIconNew, 0, ix, iy, g_cxsmIcon, g_cysmIcon, DST_ICON | (IsWindowEnabled(ctl->hwnd) ? DSS_NORMAL : 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 (textLen > 0) {
+ // Draw the text and optinally the arrow
+ SetBkMode(hdcMem, TRANSPARENT);
+ HFONT hOldFont = (HFONT)SelectObject(hdcMem, ctl->hFont);
+
+ SIZE sz;
+ TCHAR szText[MAX_PATH];
+ GetWindowText(ctl->hwnd, szText, SIZEOF(szText));
+ GetTextExtentPoint32(hdcMem, szText, (int)mir_tstrlen(szText), &sz);
+ int xOffset = (rcClient.right - rcClient.left - sz.cx)/2;
+ int yOffset = (rcClient.bottom - rcClient.top - sz.cy)/2;
+
+ // 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));
+ //!! move it up, to text extent points?
+ 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,
+ xOffset+(!ctl->hThemeButton && ctl->stateId == PBS_PRESSED?1:0),
+ ctl->hThemeButton ? yOffset : yOffset - (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);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Button's window procedure
+
+static LRESULT CALLBACK MButtonWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ MButtonCtrl *bct = (MButtonCtrl *)GetWindowLongPtr(hwnd, 0);
+
+ switch(msg) {
+ case WM_NCCREATE:
+ SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_OWNERDRAW);
+ bct = (MButtonCtrl*)mir_calloc(sizeof(MButtonCtrl));
+ if (bct == NULL)
+ return FALSE;
+ bct->hwnd = hwnd;
+ bct->stateId = PBS_NORMAL;
+ bct->fnPainter = PaintWorker;
+ bct->hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+ LoadTheme(bct);
+
+ // Annotating the Role of this object to be PushButton
+ if (SUCCEEDED(CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, IID_IAccPropServices, (void**)&bct->pAccPropServices)))
+ SetHwndPropInt(bct, OBJID_CLIENT, CHILDID_SELF, PROPID_ACC_ROLE, ROLE_SYSTEM_PUSHBUTTON);
+ else
+ bct->pAccPropServices = NULL;
+
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)bct);
+ if (((CREATESTRUCT *)lParam)->lpszName)
+ SetWindowText(hwnd, ((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) {
+ TTooltips tt;
+ tt.ThreadId = GetCurrentThreadId();
+
+ mir_cslock lck(csTips);
+ int idx = lToolTips.getIndex(&tt);
+ if (idx != -1) {
+ mir_free(lToolTips[idx]);
+ lToolTips.remove(idx);
+ DestroyWindow(bct->hwndToolTips);
+ }
+ bct->hwndToolTips = NULL;
+ }
+ }
+ if (bct->arrow) IcoLib_ReleaseIcon(bct->arrow, 0);
+ DestroyTheme(bct);
+ }
+ break;
+
+ case WM_NCDESTROY:
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
+ 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 && !(GetKeyState(VK_CONTROL) & 0x8000) && !(GetKeyState(VK_SHIFT) & 0x8000)) {
+ if (bct->bIsPushBtn) {
+ if (bct->bIsPushed) {
+ bct->bIsPushed = 0;
+ bct->stateId = PBS_NORMAL;
+ }
+ else {
+ bct->bIsPushed = 1;
+ bct->stateId = PBS_PRESSED;
+ }
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ }
+ SendMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwnd), BN_CLICKED), (LPARAM)hwnd);
+ return 0;
+ }
+ break;
+
+ case WM_SYSKEYUP:
+ if (bct->stateId != PBS_DISABLED && !bct->bSendOnDown && bct->cHot && bct->cHot == tolower((int)wParam)) {
+ if (bct->bIsPushBtn) {
+ if (bct->bIsPushed) {
+ bct->bIsPushed = false;
+ bct->stateId = PBS_NORMAL;
+ }
+ else {
+ bct->bIsPushed = true;
+ bct->stateId = PBS_PRESSED;
+ }
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ }
+ SendMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwnd), BN_CLICKED), (LPARAM)hwnd);
+ return 0;
+ }
+ break;
+
+ case WM_THEMECHANGED:
+ // themed changed, reload theme object
+ if (bct->bIsThemed)
+ 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 = BeginPaint(hwnd, &ps);
+ if (hdcPaint) {
+ bct->fnPainter(bct, hdcPaint);
+ EndPaint(hwnd, &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->bIsPushBtn) break;
+ if (wParam == BST_CHECKED) {
+ bct->bIsPushed = 1;
+ bct->stateId = PBS_PRESSED;
+ }
+ else if (wParam == BST_UNCHECKED) {
+ bct->bIsPushed = 0;
+ bct->stateId = PBS_NORMAL;
+ }
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ break;
+
+ case BM_GETCHECK:
+ if (bct->bIsPushBtn)
+ return bct->bIsPushed ? 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) {
+ IcoLib_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->bIsDefault = (wParam != 0);
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ break;
+
+ case BUTTONSETASPUSHBTN:
+ bct->bIsPushBtn = (wParam != 0);
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ break;
+
+ case BUTTONSETASFLATBTN:
+ bct->bIsFlat = (wParam != 0);
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ break;
+
+ case BUTTONSETASTHEMEDBTN:
+ if ((bct->bIsThemed = (wParam != 0)) != 0)
+ bct->bIsSkinned = false;
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ break;
+
+ case BUTTONADDTOOLTIP:
+ if (wParam) {
+ if (!bct->hwndToolTips) {
+ TTooltips tt;
+ tt.ThreadId = GetCurrentThreadId();
+
+ mir_cslock lck(csTips);
+ int idx = lToolTips.getIndex(&tt);
+ if (idx != -1)
+ bct->hwndToolTips = lToolTips[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, g_hInst, NULL);
+ lToolTips.insert(ptt);
+ bct->hwndToolTips = ptt->hwnd;
+ }
+ }
+ 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);
+ ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
+ ti.uId = (UINT_PTR)bct->hwnd;
+ if (lParam & BATF_UNICODE)
+ ti.lpszText = mir_wstrdup(TranslateW((WCHAR*)wParam));
+ else
+ ti.lpszText = Langpack_PcharToTchar((char*)wParam);
+ 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);
+ mir_free(ti.lpszText);
+ }
+ break;
+
+ case BUTTONSETCUSTOMPAINT:
+ if (wParam > sizeof(MButtonCtrl)) {
+ bct = (MButtonCtrl*)mir_realloc(bct, wParam);
+ memset(bct+1, 0, wParam - sizeof(MButtonCtrl));
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)bct);
+ }
+ if (lParam)
+ bct->fnPainter = pfnPainterFunc(lParam);
+ break;
+
+ case BUTTONSETSENDONDOWN:
+ bct->bSendOnDown = (wParam != 0);
+ 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);
+ if (bct->bSendOnDown) {
+ SendMessage( GetParent(hwnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwnd), BN_CLICKED), (LPARAM)hwnd);
+ bct->stateId = PBS_NORMAL;
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ }
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ {
+ int showClick = 0;
+ if (bct->bIsPushBtn)
+ bct->bIsPushed = !bct->bIsPushed;
+
+ if (bct->stateId != PBS_DISABLED) { // don't change states if disabled
+ if (bct->stateId == PBS_PRESSED)
+ showClick = 1;
+ bct->stateId = PBS_HOT;
+ InvalidateRect(bct->hwnd, NULL, TRUE);
+ }
+ if (showClick && !bct->bSendOnDown) // Tell your daddy you got clicked.
+ SendMessage( GetParent(hwnd), WM_COMMAND, MAKELONG(GetDlgCtrlID(hwnd), BN_CLICKED), (LPARAM)hwnd);
+ }
+ 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(hwnd, 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;
+ GetWindowRect(hwnd, &rc);
+
+ POINT pt;
+ GetCursorPos(&pt);
+ if (!PtInRect(&rc, pt)) { // mouse must be gone, trigger mouse leave
+ PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L);
+ KillTimer(hwnd, BUTTON_POLLID);
+ } }
+ break;
+
+ case WM_ERASEBKGND:
+ return 1;
+ }
+
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Module load
+
+static INT_PTR GetButtonProc(WPARAM, LPARAM)
+{
+ return (INT_PTR)MButtonWndProc;
+}
+
+int LoadButtonModule(void)
+{
+ if (bModuleInitialized)
+ return 0;
+
+ bModuleInitialized = true;
+
+ WNDCLASSEX wc = { 0 };
+ 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);
+
+ CreateServiceFunction("Button/GetWindowProc", GetButtonProc);
+ return 0;
+}
diff --git a/src/mir_app/src/chat.h b/src/mir_app/src/chat.h new file mode 100644 index 0000000000..a84ed5cf54 --- /dev/null +++ b/src/mir_app/src/chat.h @@ -0,0 +1,110 @@ +/*
+
+Chat module plugin for Miranda IM
+
+Copyright (C) 2003 Jörgen Persson
+
+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_smileyadd.h>
+#include <m_popup.h>
+#include <m_fontservice.h>
+
+struct MODULEINFO : public GCModuleInfoBase {};
+struct SESSION_INFO : public GCSessionInfoBase {};
+struct LOGSTREAMDATA : public GCLogStreamDataBase {};
+
+// special service for tweaking performance
+#define MS_GC_GETEVENTPTR "GChat/GetNewEventPtr"
+typedef INT_PTR(*GETEVENTFUNC)(WPARAM wParam, LPARAM lParam);
+struct GCPTRS
+{
+ GETEVENTFUNC pfnAddEvent;
+};
+
+extern HGENMENU hJoinMenuItem, hLeaveMenuItem;
+extern GlobalLogSettingsBase *g_Settings;
+extern int g_cbSession, g_cbModuleInfo, g_iFontMode, g_iChatLang;
+extern TCHAR *g_szFontGroup;
+extern mir_cs cs;
+
+extern char* pLogIconBmpBits[14];
+extern size_t logIconBmpSize[14];
+
+// log.c
+void LoadMsgLogBitmaps(void);
+void FreeMsgLogBitmaps(void);
+void ValidateFilename (TCHAR *filename);
+TCHAR* MakeTimeStamp(TCHAR *pszStamp, time_t time);
+TCHAR* GetChatLogsFilename(SESSION_INFO *si, time_t tTime);
+char* Log_CreateRtfHeader(MODULEINFO *mi);
+char* Log_CreateRTF(LOGSTREAMDATA *streamData);
+char* Log_SetStyle(int style);
+
+// clist.c
+BOOL AddEvent(MCONTACT hContact, HICON hIcon, MEVENT hEvent, int type, TCHAR* fmt, ...);
+MCONTACT AddRoom(const char *pszModule, const TCHAR *pszRoom, const TCHAR *pszDisplayName, int iType);
+MCONTACT FindRoom(const char *pszModule, const TCHAR *pszRoom);
+BOOL SetAllOffline(BOOL bHide, const char *pszModule);
+BOOL SetOffline(MCONTACT hContact, BOOL bHide);
+
+int RoomDoubleclicked(WPARAM wParam,LPARAM lParam);
+INT_PTR EventDoubleclicked(WPARAM wParam,LPARAM lParam);
+INT_PTR JoinChat(WPARAM wParam, LPARAM lParam);
+INT_PTR LeaveChat(WPARAM wParam, LPARAM lParam);
+int PrebuildContactMenu(WPARAM wParam, LPARAM lParam);
+INT_PTR PrebuildContactMenuSvc(WPARAM wParam, LPARAM lParam);
+
+// colorchooser.c
+void ColorChooser(SESSION_INFO *si, BOOL bFG, HWND hwndDlg, HWND hwndTarget, HWND hwndChooser);
+
+// options.c
+int OptionsInit(void);
+int OptionsUnInit(void);
+void LoadMsgDlgFont(int i, LOGFONT * lf, COLORREF * colour);
+void LoadGlobalSettings(void);
+HICON LoadIconEx(char* pszIcoLibName, BOOL big);
+void LoadLogFonts(void);
+void SetIndentSize(void);
+void RegisterFonts(void);
+
+// services.c
+void LoadChatIcons(void);
+int LoadChatModule(void);
+void UnloadChatModule(void);
+
+// tools.c
+int DoRtfToTags(CMString &pszText, int iNumColors, COLORREF *pColors); +int GetTextPixelSize(TCHAR* pszText, HFONT hFont, BOOL bWidth);
+TCHAR *RemoveFormatting(const TCHAR* pszText);
+BOOL DoSoundsFlashPopupTrayStuff(SESSION_INFO *si, GCEVENT *gce, BOOL bHighlight, int bManyFix);
+int GetColorIndex(const char *pszModule, COLORREF cr);
+void CheckColorsInModule(const char *pszModule);
+int GetRichTextLength(HWND hwnd);
+BOOL IsHighlighted(SESSION_INFO *si, GCEVENT *pszText);
+UINT CreateGCMenu(HWND hwndDlg, HMENU *hMenu, int iIndex, POINT pt, SESSION_INFO *si, TCHAR* pszUID, TCHAR* pszWordText);
+void DestroyGCMenu(HMENU *hMenu, int iIndex);
+BOOL DoEventHookAsync(HWND hwnd, const TCHAR *pszID, const char *pszModule, int iType, const TCHAR* pszUID, const TCHAR* pszText, INT_PTR dwItem);
+BOOL DoEventHook(const TCHAR *pszID, const char *pszModule, int iType, const TCHAR *pszUID, const TCHAR* pszText, INT_PTR dwItem);
+BOOL IsEventSupported(int eventType);
+BOOL LogToFile(SESSION_INFO *si, GCEVENT *gce);
+BOOL DoTrayIcon(SESSION_INFO *si, GCEVENT *gce);
+BOOL DoPopup(SESSION_INFO *si, GCEVENT *gce);
+int ShowPopup(MCONTACT hContact, SESSION_INFO *si, HICON hIcon, char* pszProtoName, TCHAR* pszRoomName, COLORREF crBkg, const TCHAR* fmt, ...);
+
+const TCHAR* my_strstri(const TCHAR* s1, const TCHAR* s2);
+
+#pragma comment(lib,"comctl32.lib")
diff --git a/src/mir_app/src/chat_clist.cpp b/src/mir_app/src/chat_clist.cpp new file mode 100644 index 0000000000..26dbd2ed74 --- /dev/null +++ b/src/mir_app/src/chat_clist.cpp @@ -0,0 +1,250 @@ +/*
+Chat module plugin for Miranda IM
+
+Copyright (C) 2003 Jörgen Persson
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "chat.h"
+
+MCONTACT AddRoom(const char *pszModule, const TCHAR *pszRoom, const TCHAR *pszDisplayName, int iType)
+{
+ TCHAR pszGroup[50]; *pszGroup = '\0';
+ ptrT groupName(db_get_tsa(NULL, CHAT_MODULE, "AddToGroup"));
+ if (groupName)
+ _tcsncpy_s(pszGroup, groupName, _TRUNCATE);
+ else
+ mir_tstrcpy(pszGroup, _T("Chat rooms"));
+
+ if (pszGroup[0]) {
+ HANDLE hGroup = Clist_GroupExists(pszGroup);
+ if (hGroup == 0) {
+ hGroup = Clist_CreateGroup(0, pszGroup);
+ if (hGroup) {
+ CallService(MS_CLUI_GROUPADDED, (WPARAM)hGroup, 0);
+ CallService(MS_CLIST_GROUPSETEXPANDED, (WPARAM)hGroup, 1);
+ }
+ }
+ }
+
+ MCONTACT hContact = ci.FindRoom(pszModule, pszRoom);
+ if (hContact) { //contact exist, make sure it is in the right group
+ if (pszGroup[0]) {
+ ptrT grpName(db_get_tsa(hContact, "CList", "Group"));
+ if (!mir_tstrcmp(pszGroup, grpName))
+ db_set_ts(hContact, "CList", "Group", pszGroup);
+ }
+
+ db_set_w(hContact, pszModule, "Status", ID_STATUS_OFFLINE);
+ db_set_ts(hContact, pszModule, "Nick", pszDisplayName);
+ return hContact;
+ }
+
+ // here we create a new one since no one is to be found
+ if ((hContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0)) == NULL)
+ return NULL;
+
+ CallService(MS_PROTO_ADDTOCONTACT, hContact, (LPARAM)pszModule);
+ if (pszGroup[0])
+ db_set_ts(hContact, "CList", "Group", pszGroup);
+ else
+ db_unset(hContact, "CList", "Group");
+ db_set_ts(hContact, pszModule, "Nick", pszDisplayName);
+ db_set_ts(hContact, pszModule, "ChatRoomID", pszRoom);
+ db_set_b(hContact, pszModule, "ChatRoom", (BYTE)iType);
+ db_set_w(hContact, pszModule, "Status", ID_STATUS_OFFLINE);
+ return hContact;
+}
+
+BOOL SetOffline(MCONTACT hContact, BOOL)
+{
+ if (hContact) {
+ char *szProto = GetContactProto(hContact);
+ db_set_w(hContact, szProto, "ApparentMode", 0);
+ db_set_w(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL SetAllOffline(BOOL, const char *pszModule)
+{
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ char *szProto = GetContactProto(hContact);
+ if (!ci.MM_FindModule(szProto))
+ continue;
+ if (pszModule && mir_strcmp(pszModule, szProto))
+ continue;
+ int i = db_get_b(hContact, szProto, "ChatRoom", 0);
+ if (i != 0) {
+ db_set_w(hContact, szProto, "ApparentMode", 0);
+ db_set_w(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+ }
+ }
+
+ return TRUE;
+}
+
+int RoomDoubleclicked(WPARAM hContact, LPARAM)
+{
+ if (!hContact)
+ return 0;
+
+ char *szProto = GetContactProto(hContact);
+ if (ci.MM_FindModule(szProto) == NULL)
+ return 0;
+ if (db_get_b(hContact, szProto, "ChatRoom", 0) == 0)
+ return 0;
+
+ ptrT roomid(db_get_tsa(hContact, szProto, "ChatRoomID"));
+ if (roomid == NULL)
+ return 0;
+
+ SESSION_INFO *si = ci.SM_FindSession(roomid, szProto);
+ if (si) {
+ // is the "toggle visibility option set, so we need to close the window?
+ if (si->hWnd != NULL &&
+ db_get_b(NULL, CHAT_MODULE, "ToggleVisibility", 0) == 1 &&
+ !CallService(MS_CLIST_GETEVENT, hContact, 0) &&
+ IsWindowVisible(si->hWnd) && !IsIconic(si->hWnd))
+ {
+ if (ci.OnDblClickSession)
+ ci.OnDblClickSession(si);
+ return 1;
+ }
+ ci.ShowRoom(si, WINDOW_VISIBLE, TRUE);
+ }
+ return 1;
+}
+
+INT_PTR EventDoubleclicked(WPARAM,LPARAM lParam)
+{
+ return RoomDoubleclicked((WPARAM)((CLISTEVENT*)lParam)->hContact, 0);
+}
+
+INT_PTR JoinChat(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact) {
+ char *szProto = GetContactProto(hContact);
+ if (szProto) {
+ if (db_get_w(hContact, szProto, "Status", 0) == ID_STATUS_OFFLINE)
+ CallProtoService(szProto, PS_JOINCHAT, hContact, lParam);
+ else
+ RoomDoubleclicked(hContact, 0);
+ }
+ }
+
+ return 0;
+}
+
+INT_PTR LeaveChat(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact) {
+ char *szProto = GetContactProto(hContact);
+ if (szProto)
+ CallProtoService(szProto, PS_LEAVECHAT, hContact, lParam);
+ }
+ return 0;
+}
+
+int PrebuildContactMenu(WPARAM hContact, LPARAM)
+{
+ if (hContact == 0)
+ return 0;
+
+ bool bEnabledJoin = false, bEnabledLeave = false;
+ char *szProto = GetContactProto(hContact);
+ if (szProto) {
+ // display this menu item only for chats
+ if (db_get_b(hContact, szProto, "ChatRoom", 0)) {
+ // still hide it for offline protos
+ if (CallProtoService(szProto, PS_GETSTATUS, 0, 0) != ID_STATUS_OFFLINE) {
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIM_NAME;
+ if (db_get_w(hContact, szProto, "Status", 0) == ID_STATUS_OFFLINE) {
+ if (ProtoServiceExists(szProto, PS_JOINCHAT)) {
+ bEnabledJoin = true;
+ mi.pszName = LPGEN("&Join chat");
+ }
+ }
+ else {
+ bEnabledJoin = true;
+ mi.pszName = LPGEN("&Open chat window");
+ }
+ Menu_ModifyItem(hJoinMenuItem, &mi);
+ }
+ bEnabledLeave = ProtoServiceExists(szProto, PS_LEAVECHAT) != 0;
+ }
+ }
+
+ Menu_ShowItem(hJoinMenuItem, bEnabledJoin);
+ Menu_ShowItem(hLeaveMenuItem, bEnabledLeave);
+ return 0;
+}
+
+INT_PTR PrebuildContactMenuSvc(WPARAM wParam, LPARAM lParam)
+{
+ return PrebuildContactMenu(wParam, lParam);
+}
+
+BOOL AddEvent(MCONTACT hContact, HICON hIcon, MEVENT hEvent, int type, TCHAR* fmt, ...)
+{
+ TCHAR szBuf[4096];
+
+ if (!fmt || !fmt[0] || mir_tstrlen(fmt) > 2000)
+ return FALSE;
+
+ va_list marker;
+ va_start(marker, fmt);
+ mir_vsntprintf(szBuf, SIZEOF(szBuf), fmt, marker);
+ va_end(marker);
+
+ CLISTEVENT cle = { 0 };
+ cle.cbSize = sizeof(cle);
+ cle.hContact = hContact;
+ cle.hDbEvent = hEvent;
+ cle.flags = type | CLEF_TCHAR;
+ cle.hIcon = hIcon;
+ cle.pszService = "GChat/DblClickEvent" ;
+ cle.ptszTooltip = TranslateTS(szBuf);
+ if (type) {
+ if (!CallService(MS_CLIST_GETEVENT, hContact, 0))
+ CallService(MS_CLIST_ADDEVENT, hContact, (LPARAM)&cle);
+ }
+ else {
+ if (CallService(MS_CLIST_GETEVENT, hContact, 0))
+ CallService(MS_CLIST_REMOVEEVENT, hContact, (LPARAM)GC_FAKE_EVENT);
+ CallService(MS_CLIST_ADDEVENT, hContact, (LPARAM)&cle);
+ }
+ return TRUE;
+}
+
+MCONTACT FindRoom(const char *pszModule, const TCHAR *pszRoom)
+{
+ for (MCONTACT hContact = db_find_first(pszModule); hContact; hContact = db_find_next(hContact, pszModule)) {
+ if (!db_get_b(hContact, pszModule, "ChatRoom", 0))
+ continue;
+
+ ptrT roomid(db_get_tsa(hContact, pszModule, "ChatRoomID"));
+ if (roomid != NULL && !mir_tstrcmpi(roomid, pszRoom))
+ return hContact;
+ }
+
+ return 0;
+}
diff --git a/src/mir_app/src/chat_log.cpp b/src/mir_app/src/chat_log.cpp new file mode 100644 index 0000000000..79b608953c --- /dev/null +++ b/src/mir_app/src/chat_log.cpp @@ -0,0 +1,542 @@ +/*
+Chat module plugin for Miranda IM
+
+Copyright (C) 2003 Jörgen Persson
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "chat.h"
+
+// The code for streaming the text is to a large extent copied from
+// the srmm module and then modified to fit the chat module.
+
+char* pLogIconBmpBits[14];
+size_t logIconBmpSize[ SIZEOF(pLogIconBmpBits) ];
+
+#define RTFCACHELINESIZE 128
+static char CHAT_rtfFontsGlobal[OPTIONS_FONTCOUNT][RTFCACHELINESIZE];
+
+static int EventToIndex(LOGINFO *lin)
+{
+ switch (lin->iType) {
+ case GC_EVENT_MESSAGE:
+ if (lin->bIsMe)
+ return 10;
+ else
+ return 9;
+
+ case GC_EVENT_JOIN: return 3;
+ case GC_EVENT_PART: return 4;
+ case GC_EVENT_QUIT: return 5;
+ case GC_EVENT_NICK: return 7;
+ case GC_EVENT_KICK: return 6;
+ case GC_EVENT_NOTICE: return 8;
+ case GC_EVENT_TOPIC: return 11;
+ case GC_EVENT_INFORMATION:return 12;
+ case GC_EVENT_ADDSTATUS: return 13;
+ case GC_EVENT_REMOVESTATUS: return 14;
+ case GC_EVENT_ACTION: return 15;
+ }
+ return 0;
+}
+
+static int EventToIcon(LOGINFO *lin)
+{
+ switch (lin->iType) {
+ case GC_EVENT_MESSAGE:
+ if (lin->bIsMe)
+ return ICON_MESSAGEOUT;
+ else
+ return ICON_MESSAGE;
+
+ case GC_EVENT_JOIN: return ICON_JOIN;
+ case GC_EVENT_PART: return ICON_PART;
+ case GC_EVENT_QUIT: return ICON_QUIT;
+ case GC_EVENT_NICK: return ICON_NICK;
+ case GC_EVENT_KICK: return ICON_KICK;
+ case GC_EVENT_NOTICE: return ICON_NOTICE;
+ case GC_EVENT_TOPIC: return ICON_TOPIC;
+ case GC_EVENT_INFORMATION:return ICON_INFO;
+ case GC_EVENT_ADDSTATUS: return ICON_ADDSTATUS;
+ case GC_EVENT_REMOVESTATUS: return ICON_REMSTATUS;
+ case GC_EVENT_ACTION: return ICON_ACTION;
+ }
+ return 0;
+}
+
+char* Log_SetStyle(int style)
+{
+ if (style < OPTIONS_FONTCOUNT)
+ return CHAT_rtfFontsGlobal[style];
+
+ return "";
+}
+
+static void Log_Append(char *&buffer, size_t &cbBufferEnd, size_t &cbBufferAlloced, const char *fmt, ...)
+{
+ va_list va;
+ int charsDone = 0;
+
+ va_start(va, fmt);
+ for (;;) {
+ charsDone = mir_vsnprintf(buffer + cbBufferEnd, cbBufferAlloced - cbBufferEnd, fmt, va);
+ if (charsDone >= 0)
+ break;
+ cbBufferAlloced += 4096;
+ buffer = (char*)mir_realloc(buffer, cbBufferAlloced);
+ }
+ va_end(va);
+ cbBufferEnd += charsDone;
+}
+
+static int Log_AppendRTF(LOGSTREAMDATA *streamData, BOOL simpleMode, char *&buffer, size_t &cbBufferEnd, size_t &cbBufferAlloced, const TCHAR *fmt, ...)
+{
+ va_list va;
+ int lineLen, textCharsCount = 0;
+ TCHAR* line = (TCHAR*)alloca(8001 * sizeof(TCHAR));
+
+ va_start(va, fmt);
+ lineLen = mir_vsntprintf(line, 8000, fmt, va);
+ if (lineLen < 0) lineLen = 8000;
+ line[lineLen] = 0;
+ va_end(va);
+
+ lineLen = lineLen * 20 + 8;
+ if (cbBufferEnd + lineLen > cbBufferAlloced) {
+ cbBufferAlloced += (lineLen + 1024 - lineLen % 1024);
+ buffer = (char *)mir_realloc(buffer, cbBufferAlloced);
+ }
+
+ char *d = buffer + cbBufferEnd;
+
+ for (; *line; line++, textCharsCount++) {
+ if (*line == '\r' && line[1] == '\n') {
+ memcpy(d, "\\par ", 5);
+ line++;
+ d += 5;
+ }
+ else if (*line == '\n') {
+ memcpy(d, "\\line ", 6);
+ d += 6;
+ }
+ else if (*line == '%' && !simpleMode) {
+ char szTemp[200];
+
+ szTemp[0] = '\0';
+ switch (*++line) {
+ case '\0':
+ case '%':
+ *d++ = '%';
+ break;
+
+ case 'c':
+ case 'f':
+ if (g_Settings->bStripFormat || streamData->bStripFormat)
+ line += 2;
+
+ else if (line[1] != '\0' && line[2] != '\0') {
+ TCHAR szTemp3[3], c = *line;
+ int col;
+ szTemp3[0] = line[1];
+ szTemp3[1] = line[2];
+ szTemp3[2] = '\0';
+ line += 2;
+
+ col = _ttoi(szTemp3);
+ col += (OPTIONS_FONTCOUNT + 1);
+ mir_snprintf(szTemp, (c == 'c') ? "\\cf%u " : "\\highlight%u ", col);
+ }
+ break;
+ case 'C':
+ case 'F':
+ if (!g_Settings->bStripFormat && !streamData->bStripFormat) {
+ int j = streamData->lin->bIsHighlighted ? 16 : EventToIndex(streamData->lin);
+ if (*line == 'C')
+ mir_snprintf(szTemp, "\\cf%u ", j + 1);
+ else
+ mir_snprintf(szTemp, "\\highlight0 ");
+ }
+ break;
+ case 'b':
+ case 'u':
+ case 'i':
+ if (!streamData->bStripFormat)
+ mir_snprintf(szTemp, (*line == 'u') ? "\\%cl " : "\\%c ", *line);
+ break;
+
+ case 'B':
+ case 'U':
+ case 'I':
+ if (!streamData->bStripFormat) {
+ mir_snprintf(szTemp, (*line == 'U') ? "\\%cl0 " : "\\%c0 ", *line);
+ CharLowerA(szTemp);
+ }
+ break;
+
+ case 'r':
+ if (!streamData->bStripFormat) {
+ int index = EventToIndex(streamData->lin);
+ mir_snprintf(szTemp, "%s ", Log_SetStyle(index));
+ }
+ break;
+ }
+
+ if (szTemp[0]) {
+ size_t iLen = mir_strlen(szTemp);
+ memcpy(d, szTemp, iLen);
+ d += iLen;
+ }
+ }
+ else if (*line == '\t' && !streamData->bStripFormat) {
+ memcpy(d, "\\tab ", 5);
+ d += 5;
+ }
+ else if ((*line == '\\' || *line == '{' || *line == '}') && !streamData->bStripFormat) {
+ *d++ = '\\';
+ *d++ = (char)*line;
+ }
+ else if (*line > 0 && *line < 128) {
+ *d++ = (char)*line;
+ }
+ else d += sprintf(d, "\\u%u ?", (WORD)*line); //!!!!!!!!!!!
+ }
+
+ cbBufferEnd = (int)(d - buffer);
+ return textCharsCount;
+}
+
+static void AddEventToBuffer(char *&buffer, size_t &bufferEnd, size_t &bufferAlloced, LOGSTREAMDATA *streamData)
+{
+ TCHAR szTemp[512], szTemp2[512];
+ TCHAR* pszNick = NULL;
+ if (streamData->lin->ptszNick) {
+ if (g_Settings->bLogLimitNames && mir_tstrlen(streamData->lin->ptszNick) > 20) {
+ mir_tstrncpy(szTemp2, streamData->lin->ptszNick, 20);
+ mir_tstrncpy(szTemp2 + 20, _T("..."), 4);
+ }
+ else mir_tstrncpy(szTemp2, streamData->lin->ptszNick, 511);
+
+ if (streamData->lin->ptszUserInfo)
+ mir_sntprintf(szTemp, _T("%s (%s)"), szTemp2, streamData->lin->ptszUserInfo);
+ else
+ _tcsncpy_s(szTemp, szTemp2, _TRUNCATE);
+ pszNick = szTemp;
+ }
+
+ switch (streamData->lin->iType) {
+ case GC_EVENT_MESSAGE:
+ if (streamData->lin->ptszText)
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T("%s"), streamData->lin->ptszText);
+ break;
+ case GC_EVENT_ACTION:
+ if (streamData->lin->ptszNick && streamData->lin->ptszText) {
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, _T("%s "), streamData->lin->ptszNick);
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T("%s"), streamData->lin->ptszText);
+ }
+ break;
+ case GC_EVENT_JOIN:
+ if (pszNick) {
+ if (!streamData->lin->bIsMe)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has joined"), pszNick);
+ else
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, TranslateT("You have joined %s"), streamData->si->ptszName);
+ }
+ break;
+ case GC_EVENT_PART:
+ if (pszNick)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has left"), pszNick);
+ if (streamData->lin->ptszText)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, _T(": %s"), streamData->lin->ptszText);
+ break;
+ case GC_EVENT_QUIT:
+ if (pszNick)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has disconnected"), pszNick);
+ if (streamData->lin->ptszText)
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T(": %s"), streamData->lin->ptszText);
+ break;
+ case GC_EVENT_NICK:
+ if (pszNick && streamData->lin->ptszText) {
+ if (!streamData->lin->bIsMe)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s is now known as %s"), pszNick, streamData->lin->ptszText);
+ else
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("You are now known as %s"), streamData->lin->ptszText);
+ }
+ break;
+ case GC_EVENT_KICK:
+ if (streamData->lin->ptszNick && streamData->lin->ptszStatus)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s kicked %s"), streamData->lin->ptszStatus, streamData->lin->ptszNick);
+ if (streamData->lin->ptszText)
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T(": %s"), streamData->lin->ptszText);
+ break;
+ case GC_EVENT_NOTICE:
+ if (pszNick && streamData->lin->ptszText) {
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("Notice from %s: "), pszNick);
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T("%s"), streamData->lin->ptszText);
+ }
+ break;
+ case GC_EVENT_TOPIC:
+ if (streamData->lin->ptszText)
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, TranslateT("The topic is '%s%s'"), streamData->lin->ptszText, _T("%r"));
+ if (streamData->lin->ptszNick)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced,
+ streamData->lin->ptszUserInfo ? TranslateT(" (set by %s on %s)") : TranslateT(" (set by %s)"),
+ streamData->lin->ptszNick, streamData->lin->ptszUserInfo);
+ break;
+ case GC_EVENT_INFORMATION:
+ if (streamData->lin->ptszText)
+ Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, (streamData->lin->bIsMe) ? _T("--> %s") : _T("%s"), streamData->lin->ptszText);
+ break;
+ case GC_EVENT_ADDSTATUS:
+ if (streamData->lin->ptszNick && streamData->lin->ptszText && streamData->lin->ptszStatus)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s enables '%s' status for %s"), streamData->lin->ptszText, streamData->lin->ptszStatus, streamData->lin->ptszNick);
+ break;
+ case GC_EVENT_REMOVESTATUS:
+ if (streamData->lin->ptszNick && streamData->lin->ptszText && streamData->lin->ptszStatus)
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s disables '%s' status for %s"), streamData->lin->ptszText, streamData->lin->ptszStatus, streamData->lin->ptszNick);
+ break;
+ }
+}
+
+TCHAR* MakeTimeStamp(TCHAR *pszStamp, time_t time)
+{
+ static TCHAR szTime[30];
+ if (!_tcsftime(szTime, SIZEOF(szTime)-1, pszStamp, localtime(&time)))
+ _tcsncpy_s(szTime, TranslateT("<invalid>"), _TRUNCATE);
+ return szTime;
+}
+
+char* Log_CreateRTF(LOGSTREAMDATA *streamData)
+{
+ MODULEINFO *mi = ci.MM_FindModule(streamData->si->pszModule);
+
+ // guesstimate amount of memory for the RTF
+ size_t bufferEnd = 0, bufferAlloced = streamData->bRedraw ? 1024 * (streamData->si->iEventCount + 2) : 2048;
+ char *buffer = (char *)mir_alloc(bufferAlloced);
+ buffer[0] = '\0';
+
+ // ### RTF HEADER
+ char *header = mi->pszHeader;
+ if (header)
+ Log_Append(buffer, bufferEnd, bufferAlloced, header);
+
+ // ### RTF BODY (one iteration per event that should be streamed in)
+ for (LOGINFO *lin = streamData->lin; lin; lin = lin->prev) {
+ // filter
+ if (streamData->si->iType == GCW_CHATROOM || streamData->si->iType == GCW_PRIVMESS)
+ if (streamData->si->bFilterEnabled && (streamData->si->iLogFilterFlags & lin->iType) == 0)
+ continue;
+
+ // create new line, and set font and color
+ if (lin->next != NULL)
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\par ");
+ Log_Append(buffer, bufferEnd, bufferAlloced, "%s ", Log_SetStyle(0));
+
+ // Insert icon
+ if ((lin->iType & g_Settings->dwIconFlags) || lin->bIsHighlighted && (g_Settings->dwIconFlags & GC_EVENT_HIGHLIGHT)) {
+ int iIndex = (lin->bIsHighlighted && g_Settings->dwIconFlags & GC_EVENT_HIGHLIGHT) ? ICON_HIGHLIGHT : EventToIcon(lin);
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\f0\\fs14");
+ while (bufferAlloced - bufferEnd < logIconBmpSize[0])
+ bufferAlloced += 4096;
+ buffer = (char *)mir_realloc(buffer, bufferAlloced);
+ memcpy(buffer + bufferEnd, pLogIconBmpBits[iIndex], logIconBmpSize[iIndex]);
+ bufferEnd += logIconBmpSize[iIndex];
+ }
+
+ if (g_Settings->bTimeStampEventColour) {
+ LOGFONT &lf = ci.aFonts[0].lf;
+
+ // colored timestamps
+ static char szStyle[256];
+ if (lin->ptszNick && lin->iType == GC_EVENT_MESSAGE) {
+ int iii = lin->bIsHighlighted ? 16 : (lin->bIsMe ? 2 : 1);
+ mir_snprintf(szStyle, SIZEOF(szStyle), "\\f0\\cf%u\\ul0\\highlight0\\b%d\\i%d\\fs%u", iii + 1, lf.lfWeight >= FW_BOLD ? 1 : 0, lf.lfItalic, 2 * abs(lf.lfHeight) * 74 / ci.logPixelSY);
+ Log_Append(buffer, bufferEnd, bufferAlloced, "%s ", szStyle);
+ }
+ else {
+ int iii = lin->bIsHighlighted ? 16 : EventToIndex(lin);
+ mir_snprintf(szStyle, SIZEOF(szStyle), "\\f0\\cf%u\\ul0\\highlight0\\b%d\\i%d\\fs%u", iii + 1, lf.lfWeight >= FW_BOLD ? 1 : 0, lf.lfItalic, 2 * abs(lf.lfHeight) * 74 / ci.logPixelSY);
+ Log_Append(buffer, bufferEnd, bufferAlloced, "%s ", szStyle);
+ }
+ }
+ else Log_Append(buffer, bufferEnd, bufferAlloced, "%s ", Log_SetStyle(0));
+
+ if (g_Settings->dwIconFlags)
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\tab ");
+
+ //insert timestamp
+ if (g_Settings->bShowTime) {
+ TCHAR szTimeStamp[30], szOldTimeStamp[30];
+
+ mir_tstrncpy(szTimeStamp, MakeTimeStamp(g_Settings->pszTimeStamp, lin->time), 30);
+ mir_tstrncpy(szOldTimeStamp, MakeTimeStamp(g_Settings->pszTimeStamp, streamData->si->LastTime), 30);
+ if (!g_Settings->bShowTimeIfChanged || streamData->si->LastTime == 0 || mir_tstrcmp(szTimeStamp, szOldTimeStamp)) {
+ streamData->si->LastTime = lin->time;
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, _T("%s"), szTimeStamp);
+ }
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\tab ");
+ }
+
+ // Insert the nick
+ if (lin->ptszNick && lin->iType == GC_EVENT_MESSAGE) {
+ TCHAR pszTemp[300], *p1;
+
+ Log_Append(buffer, bufferEnd, bufferAlloced, "%s ", Log_SetStyle(lin->bIsMe ? 2 : 1));
+ mir_tstrncpy(pszTemp, lin->bIsMe ? g_Settings->pszOutgoingNick : g_Settings->pszIncomingNick, 299);
+ p1 = _tcsstr(pszTemp, _T("%n"));
+ if (p1)
+ p1[1] = 's';
+
+ Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, pszTemp, lin->ptszNick);
+ Log_Append(buffer, bufferEnd, bufferAlloced, " ");
+ }
+
+ // Insert the message
+ Log_Append(buffer, bufferEnd, bufferAlloced, "%s ", Log_SetStyle(lin->bIsHighlighted ? 16 : EventToIndex(lin)));
+ streamData->lin = lin;
+ AddEventToBuffer(buffer, bufferEnd, bufferAlloced, streamData);
+ }
+
+ // ### RTF END
+ if (streamData->bRedraw)
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\par}");
+ else
+ Log_Append(buffer, bufferEnd, bufferAlloced, "}");
+ return buffer;
+}
+
+char* Log_CreateRtfHeader(MODULEINFO *mi)
+{
+ int i;
+
+ // guesstimate amount of memory for the RTF header
+ size_t bufferEnd = 0, bufferAlloced = 4096;
+ char *buffer = (char *)mir_realloc(mi->pszHeader, bufferAlloced);
+ buffer[0] = '\0';
+
+ // get the number of pixels per logical inch
+ HDC hdc = GetDC(NULL);
+ ci.logPixelSY = GetDeviceCaps(hdc, LOGPIXELSY);
+ ci.logPixelSX = GetDeviceCaps(hdc, LOGPIXELSX);
+ ReleaseDC(NULL, hdc);
+
+ // ### RTF HEADER
+
+ // font table
+ Log_Append(buffer, bufferEnd, bufferAlloced, "{\\rtf1\\ansi\\deff0{\\fonttbl");
+ for (i = 0; i < OPTIONS_FONTCOUNT; i++)
+ Log_Append(buffer, bufferEnd, bufferAlloced, "{\\f%u\\fnil\\fcharset%u%S;}", i, ci.aFonts[i].lf.lfCharSet, ci.aFonts[i].lf.lfFaceName);
+
+ // colour table
+ Log_Append(buffer, bufferEnd, bufferAlloced, "}{\\colortbl ;");
+
+ for (i = 0; i < OPTIONS_FONTCOUNT; i++)
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\red%u\\green%u\\blue%u;", GetRValue(ci.aFonts[i].color), GetGValue(ci.aFonts[i].color), GetBValue(ci.aFonts[i].color));
+
+ for (i = 0; i < mi->nColorCount; i++)
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\red%u\\green%u\\blue%u;", GetRValue(mi->crColors[i]), GetGValue(mi->crColors[i]), GetBValue(mi->crColors[i]));
+
+ // new paragraph
+ Log_Append(buffer, bufferEnd, bufferAlloced, "}\\pard");
+
+ // set tabs and indents
+ int iIndent = 0;
+
+ if (g_Settings->dwIconFlags) {
+ iIndent += (14 * 1440) / ci.logPixelSX;
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\tx%u", iIndent);
+ }
+ if (g_Settings->bShowTime) {
+ int iSize = (g_Settings->LogTextIndent * 1440) / ci.logPixelSX;
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\tx%u", iIndent + iSize);
+ if (g_Settings->bLogIndentEnabled)
+ iIndent += iSize;
+ }
+
+ Log_Append(buffer, bufferEnd, bufferAlloced, "\\fi-%u\\li%u", iIndent, iIndent);
+ return buffer;
+}
+
+#define RTFPICTHEADERMAXSIZE 78
+
+void LoadMsgLogBitmaps(void)
+{
+ HBRUSH hBkgBrush = CreateSolidBrush(g_Settings->crLogBackground);
+
+ BITMAPINFOHEADER bih = { 0 };
+ bih.biSize = sizeof(bih);
+ bih.biBitCount = 24;
+ bih.biCompression = BI_RGB;
+ bih.biHeight = bih.biWidth = g_Settings->LogIconSize;
+ bih.biPlanes = 1;
+ int widthBytes = ((bih.biWidth * bih.biBitCount + 31) >> 5) * 4;
+
+ RECT rc;
+ rc.top = rc.left = 0;
+ rc.right = bih.biWidth;
+ rc.bottom = bih.biHeight;
+
+ HDC hdc = GetDC(NULL);
+ HBITMAP hBmp = CreateCompatibleBitmap(hdc, bih.biWidth, bih.biHeight);
+ HDC hdcMem = CreateCompatibleDC(hdc);
+ PBYTE pBmpBits = (PBYTE)mir_alloc(widthBytes * bih.biHeight);
+ for (int i = 0; i < SIZEOF(pLogIconBmpBits); i++) {
+ size_t size = RTFPICTHEADERMAXSIZE + (bih.biSize + widthBytes * bih.biHeight) * 2;
+ pLogIconBmpBits[i] = (char*)mir_alloc(size);
+ size_t rtfHeaderSize = mir_snprintf((char *)pLogIconBmpBits[i], size, "{\\pict\\dibitmap0\\wbmbitspixel%u\\wbmplanes1\\wbmwidthbytes%u\\picw%u\\pich%u ", bih.biBitCount, widthBytes, bih.biWidth, bih.biHeight);
+
+ HICON hIcon = ci.hIcons[i];
+ HBITMAP hoBmp = (HBITMAP)SelectObject(hdcMem, hBmp);
+ FillRect(hdcMem, &rc, hBkgBrush);
+ DrawIconEx(hdcMem, 0, 0, hIcon, bih.biWidth, bih.biHeight, 0, NULL, DI_NORMAL);
+ SelectObject(hdcMem, hoBmp);
+ GetDIBits(hdc, hBmp, 0, bih.biHeight, pBmpBits, (BITMAPINFO *)& bih, DIB_RGB_COLORS);
+
+ char *szDest = pLogIconBmpBits[i] + rtfHeaderSize;
+ bin2hex(&bih, sizeof(bih), szDest); szDest += sizeof(bih) * 2;
+ bin2hex(pBmpBits, widthBytes * bih.biHeight, szDest); szDest += widthBytes * bih.biHeight * 2;
+ mir_strcpy(szDest, "}");
+
+ logIconBmpSize[i] = size_t(szDest - pLogIconBmpBits[i]) + 1;
+ }
+ mir_free(pBmpBits);
+ DeleteDC(hdcMem);
+ DeleteObject(hBmp);
+ ReleaseDC(NULL, hdc);
+ DeleteObject(hBkgBrush);
+
+ if (ci.logPixelSY == 0) {
+ HDC hdc;
+ hdc = GetDC(NULL);
+ ci.logPixelSY = GetDeviceCaps(hdc, LOGPIXELSY);
+ ci.logPixelSX = GetDeviceCaps(hdc, LOGPIXELSX);
+ ReleaseDC(NULL, hdc);
+ }
+
+ for (int i = 0; i < OPTIONS_FONTCOUNT; i++) {
+ LOGFONT &F = ci.aFonts[i].lf;
+ mir_snprintf(CHAT_rtfFontsGlobal[i], RTFCACHELINESIZE,
+ "\\f%u\\cf%u\\ul0\\highlight0\\b%d\\i%d\\ul%d\\fs%u", i, i + 1,
+ F.lfWeight >= FW_BOLD ? 1 : 0, F.lfItalic, F.lfUnderline, 2 * abs(F.lfHeight) * 74 / ci.logPixelSY);
+ }
+}
+
+void FreeMsgLogBitmaps(void)
+{
+ for (int i = 0; i < SIZEOF(pLogIconBmpBits); i++)
+ mir_free(pLogIconBmpBits[i]);
+}
diff --git a/src/mir_app/src/chat_manager.cpp b/src/mir_app/src/chat_manager.cpp new file mode 100644 index 0000000000..cde5bc8437 --- /dev/null +++ b/src/mir_app/src/chat_manager.cpp @@ -0,0 +1,1331 @@ +/*
+Chat module plugin for Miranda IM
+
+Copyright 2000-12 Miranda IM, 2012-15 Miranda NG 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 "stdafx.h"
+
+#include "chat.h"
+
+#define WINDOWS_COMMANDS_MAX 30
+
+CHAT_MANAGER ci;
+
+MODULEINFO *m_ModList = 0;
+
+static void SetActiveSessionEx(SESSION_INFO *si)
+{
+ if (si) {
+ replaceStrT(ci.szActiveWndID, si->ptszID);
+ replaceStr(ci.szActiveWndModule, si->pszModule);
+ }
+}
+
+static void SetActiveSession(const TCHAR *pszID, const char *pszModule)
+{
+ SESSION_INFO *si = ci.SM_FindSession(pszID, pszModule);
+ if (si)
+ SetActiveSessionEx(si);
+}
+
+static SESSION_INFO* GetActiveSession(void)
+{
+ SESSION_INFO *si = ci.SM_FindSession(ci.szActiveWndID, ci.szActiveWndModule);
+ if (si)
+ return si;
+
+ return ci.wndList;
+}
+
+//---------------------------------------------------
+// Session Manager functions
+//
+// Keeps track of all sessions and its windows
+//---------------------------------------------------
+
+static SESSION_INFO* SM_AddSession(const TCHAR *pszID, const char *pszModule)
+{
+ if (!pszID || !pszModule)
+ return NULL;
+
+ if (ci.SM_FindSession(pszID, pszModule))
+ return NULL;
+
+ SESSION_INFO *node = (SESSION_INFO*)mir_calloc(g_cbSession);
+ node->ptszID = mir_tstrdup(pszID);
+ node->pszModule = mir_strdup(pszModule);
+
+ if (ci.wndList == NULL) { // list is empty
+ ci.wndList = node;
+ node->next = NULL;
+ }
+ else {
+ node->next = ci.wndList;
+ ci.wndList = node;
+ }
+ return node;
+}
+
+static void SM_FreeSession(SESSION_INFO *si)
+{
+ // contact may have been deleted here already, since function may be called after deleting
+ // contact so the handle may be invalid, therefore db_get_b shall return 0
+ if (si->hContact && db_get_b(si->hContact, si->pszModule, "ChatRoom", 0) != 0) {
+ ci.SetOffline(si->hContact, (si->iType == GCW_CHATROOM || si->iType == GCW_PRIVMESS) ? TRUE : FALSE);
+ db_set_s(si->hContact, si->pszModule, "Topic", "");
+ db_set_s(si->hContact, si->pszModule, "StatusBar", "");
+ db_unset(si->hContact, "CList", "StatusMsg");
+ }
+
+ ci.UM_RemoveAll(&si->pUsers);
+ ci.TM_RemoveAll(&si->pStatuses);
+ ci.LM_RemoveAll(&si->pLog, &si->pLogEnd);
+
+ si->iStatusCount = 0;
+ si->nUsersInNicklist = 0;
+
+ mir_free(si->pszModule);
+ mir_free(si->ptszID);
+ mir_free(si->ptszName);
+ mir_free(si->ptszStatusbarText);
+ mir_free(si->ptszTopic);
+
+ while (si->lpCommands != NULL) {
+ COMMANDINFO *pNext = si->lpCommands->next;
+ mir_free(si->lpCommands->lpCommand);
+ mir_free(si->lpCommands);
+ si->lpCommands = pNext;
+ }
+
+ mir_free(si);
+}
+
+static int SM_RemoveSession(const TCHAR *pszID, const char *pszModule, BOOL removeContact)
+{
+ if (!pszModule)
+ return FALSE;
+
+ SESSION_INFO *pTemp = ci.wndList, *pLast = NULL;
+ while (pTemp != NULL) {
+ // match
+ if ((!pszID && pTemp->iType != GCW_SERVER || !mir_tstrcmpi(pTemp->ptszID, pszID)) && !mir_strcmpi(pTemp->pszModule, pszModule)) {
+ DWORD dw = pTemp->dwItemData;
+
+ if (ci.OnRemoveSession)
+ ci.OnRemoveSession(pTemp);
+ DoEventHook(pTemp->ptszID, pTemp->pszModule, GC_SESSION_TERMINATE, NULL, NULL, (DWORD)pTemp->dwItemData);
+
+ if (pLast == NULL)
+ ci.wndList = pTemp->next;
+ else
+ pLast->next = pTemp->next;
+
+ // contact may have been deleted here already, since function may be called after deleting
+ // contact so the handle may be invalid, therefore db_get_b shall return 0
+ if (pTemp->hContact && removeContact)
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)pTemp->hContact, 0);
+
+ SM_FreeSession(pTemp);
+
+ if (pszID)
+ return (int)dw;
+ if (pLast)
+ pTemp = pLast->next;
+ else
+ pTemp = ci.wndList;
+ }
+ else {
+ pLast = pTemp;
+ pTemp = pTemp->next;
+ }
+ }
+ return FALSE;
+}
+
+static SESSION_INFO* SM_FindSession(const TCHAR *pszID, const char *pszModule)
+{
+ if (!pszID || !pszModule)
+ return NULL;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next)
+ if (!mir_tstrcmpi(si->ptszID, pszID) && !mir_strcmpi(si->pszModule, pszModule))
+ return si;
+
+ return NULL;
+}
+
+static BOOL SM_SetOffline(const TCHAR *pszID, const char *pszModule)
+{
+ if (!pszModule)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ ci.UM_RemoveAll(&si->pUsers);
+ si->nUsersInNicklist = 0;
+ if (si->iType != GCW_SERVER)
+ si->bInitDone = FALSE;
+ if (ci.OnOfflineSession)
+ ci.OnOfflineSession(si);
+ if (pszID)
+ return TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL SM_SetStatusEx(const TCHAR *pszID, const char *pszModule, const TCHAR* pszText, int flags)
+{
+ if (!pszModule)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ ci.UM_SetStatusEx(si->pUsers, pszText, flags);
+ if (si->hWnd)
+ RedrawWindow(GetDlgItem(si->hWnd, IDC_LIST), NULL, NULL, RDW_INVALIDATE);
+ if (pszID)
+ return TRUE;
+ }
+ return TRUE;
+}
+
+static HICON SM_GetStatusIcon(SESSION_INFO *si, USERINFO * ui)
+{
+ if (!ui || !si)
+ return NULL;
+
+ STATUSINFO *ti = ci.TM_FindStatus(si->pStatuses, ci.TM_WordToString(si->pStatuses, ui->Status));
+ if (ti != NULL) {
+ if ((UINT_PTR)ti->hIcon >= STATUSICONCOUNT)
+ return ti->hIcon;
+
+ return ci.hIcons[ICON_STATUS0 + (int)ti->hIcon];
+ }
+ return ci.hIcons[ICON_STATUS0];
+}
+
+static BOOL SM_AddEventToAllMatchingUID(GCEVENT *gce)
+{
+ int bManyFix = 0;
+
+ for (SESSION_INFO *p = ci.wndList; p != NULL; p = p->next) {
+ if (!p->bInitDone || mir_strcmpi(p->pszModule, gce->pDest->pszModule))
+ continue;
+
+ if (!ci.UM_FindUser(p->pUsers, gce->ptszUID))
+ continue;
+
+ if (ci.OnEventBroadcast)
+ ci.OnEventBroadcast(p, gce);
+
+ if (!(gce->dwFlags & GCEF_NOTNOTIFY))
+ ci.DoSoundsFlashPopupTrayStuff(p, gce, FALSE, bManyFix);
+
+ bManyFix++;
+ if ((gce->dwFlags & GCEF_ADDTOLOG) && g_Settings->bLoggingEnabled)
+ ci.LogToFile(p, gce);
+ }
+
+ return 0;
+}
+
+static BOOL SM_AddEvent(const TCHAR *pszID, const char *pszModule, GCEVENT *gce, BOOL bIsHighlighted)
+{
+ SESSION_INFO *p = SM_FindSession(pszID, pszModule);
+ if (p == NULL)
+ return TRUE;
+
+ LOGINFO *li = ci.LM_AddEvent(&p->pLog, &p->pLogEnd);
+ p->iEventCount += 1;
+
+ li->iType = gce->pDest->iType;
+ li->ptszNick = mir_tstrdup(gce->ptszNick);
+ li->ptszText = mir_tstrdup(gce->ptszText);
+ li->ptszStatus = mir_tstrdup(gce->ptszStatus);
+ li->ptszUserInfo = mir_tstrdup(gce->ptszUserInfo);
+
+ li->bIsMe = gce->bIsMe;
+ li->time = gce->time;
+ li->bIsHighlighted = bIsHighlighted;
+
+ if (g_Settings->iEventLimit > 0 && p->iEventCount > g_Settings->iEventLimit + 20) {
+ ci.LM_TrimLog(&p->pLog, &p->pLogEnd, p->iEventCount - g_Settings->iEventLimit);
+ p->bTrimmed = true;
+ p->iEventCount = g_Settings->iEventLimit;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static USERINFO* SM_AddUser(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID, const TCHAR *pszNick, WORD wStatus)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return NULL;
+
+ USERINFO *p = ci.UM_AddUser(si->pStatuses, &si->pUsers, pszUID, pszNick, wStatus);
+ si->nUsersInNicklist++;
+ if (ci.OnAddUser)
+ ci.OnAddUser(si, p);
+ return p;
+}
+
+static BOOL SM_MoveUser(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID)
+{
+ if (!pszUID)
+ return FALSE;
+
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ ci.UM_SortUser(&si->pUsers, pszUID);
+ return TRUE;
+}
+
+static BOOL SM_RemoveUser(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID)
+{
+ if (!pszModule || !pszUID)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ USERINFO *ui = ci.UM_FindUser(si->pUsers, pszUID);
+ if (ui) {
+ si->nUsersInNicklist--;
+ if (ci.OnRemoveUser)
+ ci.OnRemoveUser(si, ui);
+
+ ci.UM_RemoveUser(&si->pUsers, pszUID);
+
+ if (si->hWnd)
+ SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
+
+ if (pszID)
+ return TRUE;
+ }
+ }
+
+ return 0;
+}
+
+static USERINFO* SM_GetUserFromIndex(const TCHAR *pszID, const char *pszModule, int index)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ return (si == NULL) ? NULL : ci.UM_FindUserFromIndex(si->pUsers, index);
+}
+
+STATUSINFO* SM_AddStatus(const TCHAR *pszID, const char *pszModule, const TCHAR *pszStatus)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return NULL;
+
+ STATUSINFO *ti = ci.TM_AddStatus(&si->pStatuses, pszStatus, &si->iStatusCount);
+ if (ti)
+ si->iStatusCount++;
+ if (ci.OnAddStatus)
+ ci.OnAddStatus(si, ti);
+ return ti;
+}
+
+static BOOL SM_GiveStatus(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID, const TCHAR *pszStatus)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ USERINFO *ui = ci.UM_GiveStatus(si->pUsers, pszUID, ci.TM_StringToWord(si->pStatuses, pszStatus));
+ if (ui) {
+ SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
+ if (si->hWnd)
+ SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
+ }
+ return TRUE;
+}
+
+static BOOL SM_SetContactStatus(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID, WORD wStatus)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ USERINFO *ui = ci.UM_SetContactStatus(si->pUsers, pszUID, wStatus);
+ if (ui) {
+ SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
+ if (si->hWnd)
+ SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
+ }
+ return TRUE;
+}
+
+static BOOL SM_TakeStatus(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID, const TCHAR *pszStatus)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ USERINFO *ui = ci.UM_TakeStatus(si->pUsers, pszUID, ci.TM_StringToWord(si->pStatuses, pszStatus));
+ if (ui) {
+ SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
+ if (si->hWnd)
+ SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
+ }
+ return TRUE;
+}
+
+static LRESULT SM_SendMessage(const TCHAR *pszID, const char *pszModule, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (pszModule == NULL)
+ return 0;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ if (si->hWnd) {
+ LRESULT i = SendMessage(si->hWnd, msg, wParam, lParam);
+ if (pszID)
+ return i;
+ }
+ if (pszID)
+ return 0;
+ }
+ return 0;
+}
+
+static BOOL SM_PostMessage(const TCHAR *pszID, const char *pszModule, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ if (si->hWnd)
+ return PostMessage(si->hWnd, msg, wParam, lParam);
+ return FALSE;
+}
+
+static BOOL SM_BroadcastMessage(const char *pszModule, UINT msg, WPARAM wParam, LPARAM lParam, BOOL bAsync)
+{
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if (pszModule && _strcmpi(si->pszModule, pszModule))
+ continue;
+
+ if (si->hWnd) {
+ if (bAsync)
+ PostMessage(si->hWnd, msg, wParam, lParam);
+ else
+ SendMessage(si->hWnd, msg, wParam, lParam);
+ }
+ }
+ return TRUE;
+}
+
+static BOOL SM_SetStatus(const TCHAR *pszID, const char *pszModule, int wStatus)
+{
+ if (!pszModule)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ si->wStatus = wStatus;
+ if (si->hContact) {
+ if (si->iType != GCW_SERVER && wStatus != ID_STATUS_OFFLINE)
+ db_unset(si->hContact, "CList", "Hidden");
+
+ db_set_w(si->hContact, si->pszModule, "Status", (WORD)wStatus);
+ }
+
+ if (ci.OnSetStatus)
+ ci.OnSetStatus(si, wStatus);
+
+ if (pszID)
+ return TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL SM_SendUserMessage(const TCHAR *pszID, const char *pszModule, const TCHAR* pszText)
+{
+ if (!pszModule || !pszText)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ if (si->iType == GCW_CHATROOM || si->iType == GCW_PRIVMESS)
+ DoEventHook(si->ptszID, si->pszModule, GC_USER_MESSAGE, NULL, pszText, 0);
+ if (pszID)
+ return TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL SM_ChangeUID(const TCHAR *pszID, const char *pszModule, const TCHAR *pszUID, const TCHAR* pszNewUID)
+{
+ if (!pszModule)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((pszID && mir_tstrcmpi(si->ptszID, pszID)) || mir_strcmpi(si->pszModule, pszModule))
+ continue;
+
+ USERINFO* ui = ci.UM_FindUser(si->pUsers, pszUID);
+ if (ui)
+ replaceStrT(ui->pszUID, pszNewUID);
+ if (pszID)
+ return TRUE;
+ }
+ return TRUE;
+}
+
+static BOOL SM_ChangeNick(const TCHAR *pszID, const char *pszModule, GCEVENT *gce)
+{
+ if (!pszModule)
+ return FALSE;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if ((!pszID || !mir_tstrcmpi(si->ptszID, pszID)) && !mir_strcmpi(si->pszModule, pszModule)) {
+ USERINFO *ui = ci.UM_FindUser(si->pUsers, gce->ptszUID);
+ if (ui) {
+ replaceStrT(ui->pszNick, gce->ptszText);
+ SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
+ if (si->hWnd)
+ SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
+ if (ci.OnChangeNick)
+ ci.OnChangeNick(si);
+ }
+
+ if (pszID)
+ return TRUE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL SM_SetTabbedWindowHwnd(SESSION_INFO *si, HWND hwnd)
+{
+ for (SESSION_INFO *p = ci.wndList; p != NULL; p = p->next) {
+ if (si && si == p)
+ p->hWnd = hwnd;
+ else
+ p->hWnd = NULL;
+ }
+ return TRUE;
+}
+
+static BOOL SM_RemoveAll(void)
+{
+ while (ci.wndList) {
+ SESSION_INFO *pLast = ci.wndList->next;
+
+ if (ci.wndList->hWnd)
+ SendMessage(ci.wndList->hWnd, GC_EVENT_CONTROL + WM_USER + 500, SESSION_TERMINATE, 0);
+ DoEventHook(ci.wndList->ptszID, ci.wndList->pszModule, GC_SESSION_TERMINATE, NULL, NULL, (DWORD)ci.wndList->dwItemData);
+
+ SM_FreeSession(ci.wndList);
+ ci.wndList = pLast;
+ }
+ ci.wndList = NULL;
+ return TRUE;
+}
+
+static void SM_AddCommand(const TCHAR *pszID, const char *pszModule, const char* lpNewCommand)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return;
+
+ COMMANDINFO *node = (COMMANDINFO *)mir_alloc(sizeof(COMMANDINFO));
+ node->lpCommand = mir_strdup(lpNewCommand);
+ node->last = NULL; // always added at beginning!
+
+ // new commands are added at start
+ if (si->lpCommands == NULL) {
+ node->next = NULL;
+ si->lpCommands = node;
+ }
+ else {
+ node->next = si->lpCommands;
+ si->lpCommands->last = node; // hmm, weird
+ si->lpCommands = node;
+ }
+ si->lpCurrentCommand = NULL; // current command
+ si->wCommandsNum++;
+
+ if (si->wCommandsNum > WINDOWS_COMMANDS_MAX) {
+ COMMANDINFO *pCurComm = si->lpCommands;
+ while (pCurComm->next != NULL) { pCurComm = pCurComm->next; }
+ COMMANDINFO *pLast = pCurComm->last;
+ mir_free(pCurComm->lpCommand);
+ mir_free(pCurComm);
+ pLast->next = NULL;
+ // done
+ si->wCommandsNum--;
+ }
+}
+
+static char* SM_GetPrevCommand(const TCHAR *pszID, const char *pszModule) // get previous command. returns NULL if previous command does not exist. current command remains as it was.
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return NULL;
+
+ COMMANDINFO *pPrevCmd = NULL;
+ if (si->lpCurrentCommand != NULL) {
+ if (si->lpCurrentCommand->next != NULL) // not NULL
+ pPrevCmd = si->lpCurrentCommand->next; // next command (newest at beginning)
+ else
+ pPrevCmd = si->lpCurrentCommand;
+ }
+ else pPrevCmd = si->lpCommands;
+
+ si->lpCurrentCommand = pPrevCmd; // make it the new command
+ return (pPrevCmd) ? pPrevCmd->lpCommand : NULL;
+}
+
+static char* SM_GetNextCommand(const TCHAR *pszID, const char *pszModule) // get next command. returns NULL if next command does not exist. current command becomes NULL (a prev command after this one will get you the last command)
+{
+ SESSION_INFO *si = SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return NULL;
+
+ COMMANDINFO *pNextCmd = NULL;
+ if (si->lpCurrentCommand != NULL)
+ pNextCmd = si->lpCurrentCommand->last; // last command (newest at beginning)
+
+ si->lpCurrentCommand = pNextCmd; // make it the new command
+ return (pNextCmd) ? pNextCmd->lpCommand : NULL;
+}
+
+static int SM_GetCount(const char *pszModule)
+{
+ int count = 0;
+
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next)
+ if (!mir_strcmpi(pszModule, si->pszModule))
+ count++;
+
+ return count;
+}
+
+static SESSION_INFO* SM_FindSessionByIndex(const char *pszModule, int iItem)
+{
+ int count = 0;
+ for (SESSION_INFO *si = ci.wndList; si != NULL; si = si->next) {
+ if (!mir_strcmpi(pszModule, si->pszModule)) {
+ if (iItem == count)
+ return si;
+
+ count++;
+ }
+ }
+ return NULL;
+
+}
+
+static char* SM_GetUsers(SESSION_INFO *si)
+{
+ if (si == NULL)
+ return NULL;
+
+ USERINFO *utemp = NULL;
+ for (SESSION_INFO *p = ci.wndList; p != NULL; p = p->next) {
+ if (si == p) {
+ if ((utemp = p->pUsers) == NULL)
+ return NULL;
+
+ break;
+ }
+ }
+ if (utemp == NULL)
+ return NULL;
+
+ char* p = NULL;
+ size_t alloced = 0;
+ do {
+ size_t pLen = mir_strlen(p), nameLen = mir_tstrlen(utemp->pszUID);
+ if (pLen + nameLen + 2 > alloced)
+ p = (char*)mir_realloc(p, alloced += 4096);
+
+ WideCharToMultiByte(CP_ACP, 0, utemp->pszUID, -1, p + pLen, (int)nameLen + 1, 0, 0);
+ mir_strcpy(p + pLen + nameLen, " ");
+ utemp = utemp->next;
+ }
+ while (utemp != NULL);
+ return p;
+}
+
+static void SM_InvalidateLogDirectories()
+{
+ for (SESSION_INFO *si = ci.wndList; si; si = si->next)
+ si->pszLogFileName[0] = si->pszLogFileName[1] = 0;
+}
+
+//---------------------------------------------------
+// Module Manager functions
+//
+// Necessary to keep track of all modules
+// that has registered with the plugin
+//---------------------------------------------------
+
+static MODULEINFO* MM_AddModule(const char *pszModule)
+{
+ if (pszModule == NULL)
+ return NULL;
+
+ if (ci.MM_FindModule(pszModule))
+ return NULL;
+
+ MODULEINFO *node = (MODULEINFO*)mir_calloc(g_cbModuleInfo);
+ replaceStr(node->pszModule, pszModule);
+ if (ci.OnCreateModule)
+ ci.OnCreateModule(node);
+
+ if (m_ModList == NULL) { // list is empty
+ m_ModList = node;
+ node->next = NULL;
+ }
+ else {
+ node->next = m_ModList;
+ m_ModList = node;
+ }
+ return node;
+}
+
+static void MM_IconsChanged()
+{
+ LoadChatIcons();
+
+ for (MODULEINFO *mi = m_ModList; mi != NULL; mi = mi->next) {
+ Safe_DestroyIcon(mi->hOnlineIcon);
+ Safe_DestroyIcon(mi->hOfflineIcon);
+ Safe_DestroyIcon(mi->hOnlineTalkIcon);
+ Safe_DestroyIcon(mi->hOfflineTalkIcon);
+
+ if (ci.OnCreateModule) // recreate icons
+ ci.OnCreateModule(mi);
+ }
+}
+
+static void MM_FontsChanged()
+{
+ for (MODULEINFO *mi = m_ModList; mi != NULL; mi = mi->next)
+ mi->pszHeader = ci.Log_CreateRtfHeader(mi);
+}
+
+static MODULEINFO* MM_FindModule(const char *pszModule)
+{
+ if (!pszModule)
+ return NULL;
+
+ for (MODULEINFO *mi = m_ModList; mi != NULL; mi = mi->next)
+ if (mir_strcmpi(mi->pszModule, pszModule) == 0)
+ return mi;
+
+ return NULL;
+}
+
+// stupid thing..
+static void MM_FixColors()
+{
+ for (MODULEINFO *mi = m_ModList; mi != NULL; mi = mi->next)
+ CheckColorsInModule(mi->pszModule);
+}
+
+static BOOL MM_RemoveAll(void)
+{
+ while (m_ModList != NULL) {
+ MODULEINFO *pLast = m_ModList->next;
+ mir_free(m_ModList->pszModule);
+ mir_free(m_ModList->ptszModDispName);
+ mir_free(m_ModList->pszHeader);
+ mir_free(m_ModList->crColors);
+
+ Safe_DestroyIcon(m_ModList->hOnlineIcon);
+ Safe_DestroyIcon(m_ModList->hOfflineIcon);
+ Safe_DestroyIcon(m_ModList->hOnlineTalkIcon);
+ Safe_DestroyIcon(m_ModList->hOfflineTalkIcon);
+
+ mir_free(m_ModList);
+ m_ModList = pLast;
+ }
+ m_ModList = NULL;
+ return TRUE;
+}
+
+//---------------------------------------------------
+// Status manager functions
+//
+// Necessary to keep track of what user statuses
+// per window nicklist that is available
+//---------------------------------------------------
+
+static STATUSINFO* TM_AddStatus(STATUSINFO **ppStatusList, const TCHAR *pszStatus, int *iCount)
+{
+ if (!ppStatusList || !pszStatus)
+ return NULL;
+
+ if (!ci.TM_FindStatus(*ppStatusList, pszStatus)) {
+ STATUSINFO *node = (STATUSINFO*)mir_alloc(sizeof(STATUSINFO));
+ memset(node, 0, sizeof(STATUSINFO));
+ replaceStrT(node->pszGroup, pszStatus);
+ node->hIcon = (HICON)(*iCount);
+ while ((int)node->hIcon > STATUSICONCOUNT - 1)
+ node->hIcon--;
+
+ if (*ppStatusList == NULL) { // list is empty
+ node->Status = 1;
+ *ppStatusList = node;
+ node->next = NULL;
+ }
+ else {
+ node->Status = ppStatusList[0]->Status * 2;
+ node->next = *ppStatusList;
+ *ppStatusList = node;
+ }
+ return node;
+
+ }
+ return FALSE;
+}
+
+static STATUSINFO* TM_FindStatus(STATUSINFO *pStatusList, const TCHAR *pszStatus)
+{
+ if (!pStatusList || !pszStatus)
+ return NULL;
+
+ for (STATUSINFO *pTemp = pStatusList; pTemp != NULL; pTemp = pTemp->next)
+ if (mir_tstrcmpi(pTemp->pszGroup, pszStatus) == 0)
+ return pTemp;
+
+ return 0;
+}
+
+static WORD TM_StringToWord(STATUSINFO *pStatusList, const TCHAR *pszStatus)
+{
+ if (!pStatusList || !pszStatus)
+ return 0;
+
+ for (STATUSINFO *pTemp = pStatusList; pTemp != NULL; pTemp = pTemp->next) {
+ if (mir_tstrcmpi(pTemp->pszGroup, pszStatus) == 0)
+ return pTemp->Status;
+
+ if (pTemp->next == NULL)
+ return pStatusList->Status;
+ }
+ return 0;
+}
+
+static TCHAR* TM_WordToString(STATUSINFO *pStatusList, WORD Status)
+{
+ if (!pStatusList)
+ return NULL;
+
+ for (STATUSINFO *pTemp = pStatusList; pTemp != NULL; pTemp = pTemp->next) {
+ if (pTemp->Status & Status) {
+ Status -= pTemp->Status;
+ if (Status == 0)
+ return pTemp->pszGroup;
+ }
+ }
+ return 0;
+}
+
+static BOOL TM_RemoveAll(STATUSINFO **ppStatusList)
+{
+ if (!ppStatusList)
+ return FALSE;
+
+ while (*ppStatusList != NULL) {
+ STATUSINFO *pLast = ppStatusList[0]->next;
+ mir_free(ppStatusList[0]->pszGroup);
+ if ((int)ppStatusList[0]->hIcon > 10)
+ DestroyIcon(ppStatusList[0]->hIcon);
+ mir_free(*ppStatusList);
+ *ppStatusList = pLast;
+ }
+ *ppStatusList = NULL;
+ return TRUE;
+}
+
+//---------------------------------------------------
+// User manager functions
+//
+// Necessary to keep track of the users
+// in a window nicklist
+//---------------------------------------------------
+
+static int UM_CompareItem(USERINFO *u1, const TCHAR *pszNick, WORD wStatus)
+{
+ WORD dw1 = u1->Status;
+ WORD dw2 = wStatus;
+
+ for (int i = 0; i < 8; i++) {
+ if ((dw1 & 1) && !(dw2 & 1))
+ return -1;
+ if ((dw2 & 1) && !(dw1 & 1))
+ return 1;
+ if ((dw1 & 1) && (dw2 & 1))
+ return mir_tstrcmpi(u1->pszNick, pszNick);
+
+ dw1 = dw1 >> 1;
+ dw2 = dw2 >> 1;
+ }
+ return mir_tstrcmpi(u1->pszNick, pszNick);
+}
+
+static USERINFO* UM_SortUser(USERINFO **ppUserList, const TCHAR *pszUID)
+{
+ USERINFO *ui = *ppUserList, *pLast = NULL;
+ if (!ui || !pszUID)
+ return NULL;
+
+ while (ui && mir_tstrcmpi(ui->pszUID, pszUID)) {
+ pLast = ui;
+ ui = ui->next;
+ }
+
+ if (ui == NULL)
+ return NULL;
+
+ USERINFO *node = ui;
+ if (pLast)
+ pLast->next = ui->next;
+ else
+ *ppUserList = ui->next;
+ ui = *ppUserList;
+
+ pLast = NULL;
+
+ while (ui && ci.UM_CompareItem(ui, node->pszNick, node->Status) <= 0) {
+ pLast = ui;
+ ui = ui->next;
+ }
+
+ if (*ppUserList == NULL) { // list is empty
+ *ppUserList = node;
+ node->next = NULL;
+ }
+ else {
+ if (pLast) {
+ node->next = ui;
+ pLast->next = node;
+ }
+ else {
+ node->next = *ppUserList;
+ *ppUserList = node;
+ }
+ }
+
+ return node;
+}
+
+USERINFO* UM_AddUser(STATUSINFO *pStatusList, USERINFO **ppUserList, const TCHAR *pszUID, const TCHAR *pszNick, WORD wStatus)
+{
+ if (pStatusList == NULL || ppUserList == NULL || pszNick == NULL)
+ return NULL;
+
+ USERINFO *ui = *ppUserList, *pLast = NULL;
+ while (ui && ci.UM_CompareItem(ui, pszNick, wStatus) <= 0) {
+ pLast = ui;
+ ui = ui->next;
+ }
+
+ // if (!UM_FindUser(*ppUserList, pszUI, wStatus)
+ USERINFO *node = (USERINFO*)mir_calloc(sizeof(USERINFO));
+ replaceStrT(node->pszUID, pszUID);
+
+ if (*ppUserList == NULL) { // list is empty
+ *ppUserList = node;
+ node->next = NULL;
+ }
+ else {
+ if (pLast) {
+ node->next = ui;
+ pLast->next = node;
+ }
+ else {
+ node->next = *ppUserList;
+ *ppUserList = node;
+ }
+ }
+
+ return node;
+}
+
+static USERINFO* UM_FindUser(USERINFO *pUserList, const TCHAR *pszUID)
+{
+ if (!pUserList || !pszUID)
+ return NULL;
+
+ for (USERINFO *ui = pUserList; ui != NULL; ui = ui->next)
+ if (!mir_tstrcmpi(ui->pszUID, pszUID))
+ return ui;
+
+ return NULL;
+}
+
+static USERINFO* UM_FindUserFromIndex(USERINFO *pUserList, int index)
+{
+ if (!pUserList)
+ return NULL;
+
+ int i = 0;
+ for (USERINFO *ui = pUserList; ui != NULL; ui = ui->next) {
+ if (i == index)
+ return ui;
+ i++;
+ }
+ return NULL;
+}
+
+static USERINFO* UM_GiveStatus(USERINFO *pUserList, const TCHAR *pszUID, WORD status)
+{
+ USERINFO *ui = UM_FindUser(pUserList, pszUID);
+ if (ui == NULL)
+ return NULL;
+
+ ui->Status |= status;
+ return ui;
+}
+
+static USERINFO* UM_SetContactStatus(USERINFO *pUserList, const TCHAR *pszUID, WORD status)
+{
+ USERINFO *ui = UM_FindUser(pUserList, pszUID);
+ if (ui == NULL)
+ return NULL;
+
+ ui->ContactStatus = status;
+ return ui;
+}
+
+static BOOL UM_SetStatusEx(USERINFO *pUserList, const TCHAR* pszText, int flags)
+{
+ int bOnlyMe = (flags & GC_SSE_ONLYLISTED) != 0, bSetStatus = (flags & GC_SSE_ONLINE) != 0;
+ char cDelimiter = (flags & GC_SSE_TABDELIMITED) ? '\t' : ' ';
+
+ for (USERINFO *ui = pUserList; ui != NULL; ui = ui->next) {
+ if (!bOnlyMe)
+ ui->iStatusEx = 0;
+
+ if (pszText != NULL) {
+ TCHAR *s = (TCHAR *)_tcsstr(pszText, ui->pszUID);
+ if (s) {
+ ui->iStatusEx = 0;
+ if (s == pszText || s[-1] == cDelimiter) {
+ size_t len = mir_tstrlen(ui->pszUID);
+ if (s[len] == cDelimiter || s[len] == '\0')
+ ui->iStatusEx = (!bOnlyMe || bSetStatus) ? 1 : 0;
+ }
+ }
+ }
+ }
+ return TRUE;
+}
+
+static USERINFO* UM_TakeStatus(USERINFO *pUserList, const TCHAR *pszUID, WORD status)
+{
+ USERINFO *ui = UM_FindUser(pUserList, pszUID);
+ if (ui == NULL)
+ return NULL;
+
+ ui->Status &= ~status;
+ return ui;
+}
+
+static TCHAR* UM_FindUserAutoComplete(USERINFO *pUserList, const TCHAR* pszOriginal, const TCHAR* pszCurrent)
+{
+ if (!pUserList || !pszOriginal || !pszCurrent)
+ return NULL;
+
+ TCHAR *pszName = NULL;
+ for (USERINFO *ui = pUserList; ui != NULL; ui = ui->next)
+ if (ui->pszNick && my_strstri(ui->pszNick, pszOriginal) == ui->pszNick)
+ if (mir_tstrcmpi(ui->pszNick, pszCurrent) > 0 && (!pszName || mir_tstrcmpi(ui->pszNick, pszName) < 0))
+ pszName = ui->pszNick;
+
+ return pszName;
+}
+
+static BOOL UM_RemoveUser(USERINFO **ppUserList, const TCHAR *pszUID)
+{
+ if (!ppUserList || !pszUID)
+ return FALSE;
+
+ USERINFO *ui = *ppUserList, *pLast = NULL;
+ while (ui != NULL) {
+ if (!mir_tstrcmpi(ui->pszUID, pszUID)) {
+ if (pLast == NULL)
+ *ppUserList = ui->next;
+ else
+ pLast->next = ui->next;
+ mir_free(ui->pszNick);
+ mir_free(ui->pszUID);
+ mir_free(ui);
+ return TRUE;
+ }
+ pLast = ui;
+ ui = ui->next;
+ }
+ return FALSE;
+}
+
+static BOOL UM_RemoveAll(USERINFO **ppUserList)
+{
+ if (!ppUserList)
+ return FALSE;
+
+ while (*ppUserList != NULL) {
+ USERINFO *pLast = ppUserList[0]->next;
+ mir_free(ppUserList[0]->pszUID);
+ mir_free(ppUserList[0]->pszNick);
+ mir_free(*ppUserList);
+ *ppUserList = pLast;
+ }
+ *ppUserList = NULL;
+ return TRUE;
+}
+
+//---------------------------------------------------
+// Log manager functions
+//
+// Necessary to keep track of events
+// in a window log
+//---------------------------------------------------
+
+static LOGINFO* LM_AddEvent(LOGINFO **ppLogListStart, LOGINFO** ppLogListEnd)
+{
+ if (!ppLogListStart || !ppLogListEnd)
+ return NULL;
+
+ LOGINFO *node = (LOGINFO*)mir_calloc(sizeof(LOGINFO));
+ if (*ppLogListStart == NULL) { // list is empty
+ *ppLogListStart = node;
+ *ppLogListEnd = node;
+ node->next = NULL;
+ node->prev = NULL;
+ }
+ else {
+ ppLogListStart[0]->prev = node;
+ node->next = *ppLogListStart;
+ *ppLogListStart = node;
+ ppLogListStart[0]->prev = NULL;
+ }
+
+ return node;
+}
+
+static BOOL LM_TrimLog(LOGINFO **ppLogListStart, LOGINFO **ppLogListEnd, int iCount)
+{
+ LOGINFO *pTemp = *ppLogListEnd;
+ while (pTemp != NULL && iCount > 0) {
+ *ppLogListEnd = pTemp->prev;
+ if (*ppLogListEnd == NULL)
+ *ppLogListStart = NULL;
+
+ mir_free(pTemp->ptszNick);
+ mir_free(pTemp->ptszUserInfo);
+ mir_free(pTemp->ptszText);
+ mir_free(pTemp->ptszStatus);
+ mir_free(pTemp);
+ pTemp = *ppLogListEnd;
+ iCount--;
+ }
+ ppLogListEnd[0]->next = NULL;
+
+ return TRUE;
+}
+
+static BOOL LM_RemoveAll(LOGINFO **ppLogListStart, LOGINFO **ppLogListEnd)
+{
+ while (*ppLogListStart != NULL) {
+ LOGINFO *pLast = ppLogListStart[0]->next;
+ mir_free(ppLogListStart[0]->ptszText);
+ mir_free(ppLogListStart[0]->ptszNick);
+ mir_free(ppLogListStart[0]->ptszStatus);
+ mir_free(ppLogListStart[0]->ptszUserInfo);
+ mir_free(*ppLogListStart);
+ *ppLogListStart = pLast;
+ }
+ *ppLogListStart = NULL;
+ *ppLogListEnd = NULL;
+ return TRUE;
+}
+
+INT_PTR SvcGetChatManager(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam == NULL)
+ return (INT_PTR)&ci;
+
+ // wipe out old junk
+ memset(PBYTE(&ci) + offsetof(CHAT_MANAGER, OnCreateModule), 0, sizeof(CHAT_MANAGER)-offsetof(CHAT_MANAGER, OnCreateModule));
+
+ CHAT_MANAGER_INITDATA *pInit = (CHAT_MANAGER_INITDATA*)lParam;
+ if (g_cbSession) { // reallocate old sessions
+ mir_cslock lck(cs);
+ SESSION_INFO *pPrev = NULL;
+ for (SESSION_INFO *p = ci.wndList; p; p = p->next) {
+ SESSION_INFO *p1 = (SESSION_INFO*)mir_realloc(p, pInit->cbSession);
+ memset(PBYTE(p1) + sizeof(GCSessionInfoBase), 0, pInit->cbSession - sizeof(GCSessionInfoBase));
+ if (p1 != p) { // realloc could change a pointer, reinsert a structure
+ if (ci.wndList == p)
+ ci.wndList = p1;
+ if (pPrev != NULL)
+ pPrev->next = p1;
+ p = p1;
+ }
+ pPrev = p;
+ }
+ }
+ if (g_cbModuleInfo) { // reallocate old modules
+ mir_cslock lck(cs);
+ MODULEINFO *pPrev = NULL;
+ for (MODULEINFO *p = m_ModList; p; p = p->next) {
+ MODULEINFO *p1 = (MODULEINFO*)mir_realloc(p, pInit->cbModuleInfo);
+ memset(PBYTE(p1) + sizeof(GCModuleInfoBase), 0, pInit->cbModuleInfo - sizeof(GCModuleInfoBase));
+ if (p1 != p) { // realloc could change a pointer, reinsert a structure
+ if (m_ModList == p)
+ m_ModList = p1;
+ if (pPrev != NULL)
+ pPrev->next = p1;
+ p = p1;
+ }
+ pPrev = p;
+ }
+ }
+ g_Settings = pInit->pSettings;
+ g_szFontGroup = pInit->szFontGroup;
+ g_cbSession = pInit->cbSession;
+ g_cbModuleInfo = pInit->cbModuleInfo;
+ g_iFontMode = pInit->iFontMode;
+ g_iChatLang = (int)wParam;
+
+ ci.SetActiveSession = SetActiveSession;
+ ci.SetActiveSessionEx = SetActiveSessionEx;
+ ci.GetActiveSession = GetActiveSession;
+ ci.SM_AddSession = SM_AddSession;
+ ci.SM_RemoveSession = SM_RemoveSession;
+ ci.SM_FindSession = SM_FindSession;
+ ci.SM_AddUser = SM_AddUser;
+ ci.SM_ChangeUID = SM_ChangeUID;
+ ci.SM_ChangeNick = SM_ChangeNick;
+ ci.SM_RemoveUser = SM_RemoveUser;
+ ci.SM_SetOffline = SM_SetOffline;
+ ci.SM_SetTabbedWindowHwnd = SM_SetTabbedWindowHwnd;
+ ci.SM_GetStatusIcon = SM_GetStatusIcon;
+ ci.SM_SetStatus = SM_SetStatus;
+ ci.SM_SetStatusEx = SM_SetStatusEx;
+ ci.SM_SendUserMessage = SM_SendUserMessage;
+ ci.SM_AddStatus = SM_AddStatus;
+ ci.SM_AddEventToAllMatchingUID = SM_AddEventToAllMatchingUID;
+ ci.SM_AddEvent = SM_AddEvent;
+ ci.SM_SendMessage = SM_SendMessage;
+ ci.SM_PostMessage = SM_PostMessage;
+ ci.SM_BroadcastMessage = SM_BroadcastMessage;
+ ci.SM_RemoveAll = SM_RemoveAll;
+ ci.SM_GiveStatus = SM_GiveStatus;
+ ci.SM_SetContactStatus = SM_SetContactStatus;
+ ci.SM_TakeStatus = SM_TakeStatus;
+ ci.SM_MoveUser = SM_MoveUser;
+ ci.SM_AddCommand = SM_AddCommand;
+ ci.SM_GetPrevCommand = SM_GetPrevCommand;
+ ci.SM_GetNextCommand = SM_GetNextCommand;
+ ci.SM_GetCount = SM_GetCount;
+ ci.SM_FindSessionByIndex = SM_FindSessionByIndex;
+ ci.SM_GetUsers = SM_GetUsers;
+ ci.SM_GetUserFromIndex = SM_GetUserFromIndex;
+ ci.SM_InvalidateLogDirectories = SM_InvalidateLogDirectories;
+
+ ci.MM_AddModule = MM_AddModule;
+ ci.MM_FindModule = MM_FindModule;
+ ci.MM_FixColors = MM_FixColors;
+ ci.MM_FontsChanged = MM_FontsChanged;
+ ci.MM_IconsChanged = MM_IconsChanged;
+ ci.MM_RemoveAll = MM_RemoveAll;
+
+ ci.TM_AddStatus = TM_AddStatus;
+ ci.TM_FindStatus = TM_FindStatus;
+ ci.TM_StringToWord = TM_StringToWord;
+ ci.TM_WordToString = TM_WordToString;
+ ci.TM_RemoveAll = TM_RemoveAll;
+
+ ci.UM_SetStatusEx = UM_SetStatusEx;
+ ci.UM_AddUser = UM_AddUser;
+ ci.UM_SortUser = UM_SortUser;
+ ci.UM_FindUser = UM_FindUser;
+ ci.UM_FindUserFromIndex = UM_FindUserFromIndex;
+ ci.UM_GiveStatus = UM_GiveStatus;
+ ci.UM_SetContactStatus = UM_SetContactStatus;
+ ci.UM_TakeStatus = UM_TakeStatus;
+ ci.UM_FindUserAutoComplete = UM_FindUserAutoComplete;
+ ci.UM_RemoveUser = UM_RemoveUser;
+ ci.UM_RemoveAll = UM_RemoveAll;
+ ci.UM_CompareItem = UM_CompareItem;
+
+ ci.LM_AddEvent = LM_AddEvent;
+ ci.LM_TrimLog = LM_TrimLog;
+ ci.LM_RemoveAll = LM_RemoveAll;
+
+ ci.AddRoom = AddRoom;
+ ci.SetOffline = SetOffline;
+ ci.SetAllOffline = SetAllOffline;
+ ci.AddEvent = AddEvent;
+ ci.FindRoom = FindRoom;
+ ci.DoRtfToTags = DoRtfToTags;
+
+ ci.Log_CreateRTF = Log_CreateRTF;
+ ci.Log_CreateRtfHeader = Log_CreateRtfHeader;
+ ci.LoadMsgDlgFont = LoadMsgDlgFont;
+ ci.MakeTimeStamp = MakeTimeStamp;
+
+ ci.DoEventHook = DoEventHook;
+ ci.DoEventHookAsync = DoEventHookAsync;
+
+ ci.DoSoundsFlashPopupTrayStuff = DoSoundsFlashPopupTrayStuff;
+ ci.DoTrayIcon = DoTrayIcon;
+ ci.DoPopup = DoPopup;
+ ci.ShowPopup = ShowPopup;
+ ci.LogToFile = LogToFile;
+ ci.GetChatLogsFilename = GetChatLogsFilename;
+ ci.GetColorIndex = GetColorIndex;
+ ci.Log_SetStyle = Log_SetStyle;
+
+ ci.IsHighlighted = IsHighlighted;
+ ci.RemoveFormatting = RemoveFormatting;
+ ci.ReloadSettings = LoadGlobalSettings;
+ ci.ColorChooser = ColorChooser;
+
+ ci.pLogIconBmpBits = pLogIconBmpBits;
+ ci.logIconBmpSize = logIconBmpSize;
+
+ RegisterFonts();
+ OptionsInit();
+ return (INT_PTR)&ci;
+}
diff --git a/src/mir_app/src/chat_opts.cpp b/src/mir_app/src/chat_opts.cpp new file mode 100644 index 0000000000..3497d5b22b --- /dev/null +++ b/src/mir_app/src/chat_opts.cpp @@ -0,0 +1,351 @@ +/*
+Chat module plugin for Miranda IM
+
+Copyright (C) 2003 Jörgen Persson
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "chat.h"
+
+#include <m_fontservice.h>
+
+extern SESSION_INFO g_TabSession;
+
+GlobalLogSettingsBase *g_Settings;
+int g_cbSession, g_cbModuleInfo, g_iFontMode, g_iChatLang;
+TCHAR *g_szFontGroup;
+
+#define FONTF_BOLD 1
+#define FONTF_ITALIC 2
+struct FontOptionsList
+{
+ LPCTSTR szDescr;
+ COLORREF defColour;
+ LPCTSTR szDefFace;
+ BYTE defCharset, defStyle;
+ char defSize;
+};
+
+//remeber to put these in the Translate( ) template file too
+
+static LOGFONT lfDefault;
+
+static FontOptionsList fontOptionsList[] =
+{
+ { LPGENT("Timestamp"), RGB(50, 50, 240), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Others nicknames"), RGB(0, 0, 192), lfDefault.lfFaceName, DEFAULT_CHARSET, FONTF_BOLD, -12 },
+ { LPGENT("Your nickname"), RGB(0, 0, 192), lfDefault.lfFaceName, DEFAULT_CHARSET, FONTF_BOLD, -12 },
+ { LPGENT("User has joined"), RGB(90, 160, 90), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("User has left"), RGB(160, 160, 90), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("User has disconnected"), RGB(160, 90, 90), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("User kicked ..."), RGB(100, 100, 100), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("User is now known as ..."), RGB(90, 90, 160), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Notice from user"), RGB(160, 130, 60), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Incoming message"), RGB(90, 90, 90), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Outgoing message"), RGB(90, 90, 90), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("The topic is ..."), RGB(70, 70, 160), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Information messages"), RGB(130, 130, 195), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("User enables status for ..."), RGB(70, 150, 70), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("User disables status for ..."), RGB(150, 70, 70), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Action message"), RGB(160, 90, 160), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Highlighted message"), RGB(180, 150, 80), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { _T(""), 0, lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Nick list members (online)"), RGB(0, 0, 0), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 },
+ { LPGENT("Nick list members (away)"), RGB(170, 170, 170), lfDefault.lfFaceName, DEFAULT_CHARSET, 0, -12 }
+};
+
+static void LoadColors()
+{
+ g_Settings->crUserListBGColor = db_get_dw(NULL, CHAT_MODULE, "ColorNicklistBG", GetSysColor(COLOR_WINDOW));
+ g_Settings->crUserListSelectedBGColor = db_get_dw(NULL, CHAT_MODULE, "ColorNicklistSelectedBG", GetSysColor(COLOR_HIGHLIGHT));
+}
+
+void LoadLogFonts(void)
+{
+ for (int i=0; i < OPTIONS_FONTCOUNT; i++)
+ LoadMsgDlgFont(i, &ci.aFonts[i].lf, &ci.aFonts[i].color);
+ LoadColors();
+
+ if (ci.hListBkgBrush != NULL)
+ DeleteObject(ci.hListBkgBrush);
+ ci.hListBkgBrush = CreateSolidBrush(g_Settings->crUserListBGColor);
+
+ if (ci.hListSelectedBkgBrush != NULL)
+ DeleteObject(ci.hListSelectedBkgBrush);
+ ci.hListSelectedBkgBrush = CreateSolidBrush(g_Settings->crUserListSelectedBGColor);
+}
+
+void LoadMsgDlgFont(int i, LOGFONT *lf, COLORREF *colour)
+{
+ char str[32];
+ int style;
+ FontOptionsList &FO = fontOptionsList[i];
+
+ if (colour) {
+ mir_snprintf(str, "Font%dCol", i);
+ *colour = db_get_dw(NULL, CHATFONT_MODULE, str, FO.defColour);
+ }
+ if (lf) {
+ mir_snprintf(str, "Font%dSize", i);
+ lf->lfHeight = (char)db_get_b(NULL, CHATFONT_MODULE, str, FO.defSize);
+ lf->lfWidth = 0;
+ lf->lfEscapement = 0;
+ lf->lfOrientation = 0;
+ mir_snprintf(str, "Font%dSty", i);
+ style = db_get_b(NULL, CHATFONT_MODULE, str, FO.defStyle);
+ lf->lfWeight = style & FONTF_BOLD ? FW_BOLD : FW_NORMAL;
+ lf->lfItalic = style & FONTF_ITALIC ? 1 : 0;
+ lf->lfUnderline = 0;
+ lf->lfStrikeOut = 0;
+ mir_snprintf(str, "Font%dSet", i);
+ lf->lfCharSet = db_get_b(NULL, CHATFONT_MODULE, str, FO.defCharset);
+ lf->lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf->lfQuality = DEFAULT_QUALITY;
+ lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
+ mir_snprintf(str, "Font%d", i);
+
+ ptrT tszFace(db_get_tsa(NULL, CHATFONT_MODULE, str));
+ if (tszFace == NULL)
+ mir_tstrcpy(lf->lfFaceName, FO.szDefFace);
+ else
+ _tcsncpy_s(lf->lfFaceName, tszFace, _TRUNCATE);
+ }
+}
+
+void RegisterFonts(void)
+{
+ int index = 0;
+
+ SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfDefault), &lfDefault, FALSE);
+
+ FontIDT fontid = { sizeof(fontid) };
+ fontid.flags = FIDF_ALLOWREREGISTER | FIDF_DEFAULTVALID | FIDF_NEEDRESTART;
+ _tcsncpy_s(fontid.backgroundGroup, g_szFontGroup, _TRUNCATE);
+ _tcsncpy_s(fontid.group, g_szFontGroup, _TRUNCATE);
+
+ for (int i = 0; i < SIZEOF(fontOptionsList); i++, index++) {
+ FontOptionsList &FO = fontOptionsList[i];
+ strncpy_s(fontid.dbSettingsGroup, CHATFONT_MODULE, _TRUNCATE);
+ _tcsncpy_s(fontid.name, FO.szDescr, _TRUNCATE);
+
+ mir_snprintf(fontid.prefix, SIZEOF(fontid.prefix), "Font%d", index);
+ fontid.order = index;
+
+ switch (i) {
+ case 18:
+ case 19:
+ _tcsncpy_s(fontid.backgroundName, LPGENT("Nick list background"), _TRUNCATE);
+ break;
+ case 17:
+ if (g_iFontMode == FONTMODE_SKIP)
+ continue;
+ if (g_iFontMode == FONTMODE_USE) {
+ _tcsncpy_s(fontid.name, LPGENT("Message typing area"), _TRUNCATE);
+ _tcsncpy_s(fontid.backgroundName, LPGENT("Message background"), _TRUNCATE);
+ FO.defColour = RGB(0, 0, 40);
+ break;
+ }
+
+ _tcsncpy_s(fontid.name, LPGENT("Chat log symbols (Webdings)"), _TRUNCATE);
+ FO.szDefFace = _T("Webdings");
+ FO.defColour = RGB(170, 170, 170);
+ FO.defCharset = SYMBOL_CHARSET;
+ // fall through
+ default:
+ _tcsncpy_s(fontid.backgroundName, LPGENT("Group chat log background"), _TRUNCATE);
+ break;
+ }
+ _tcsncpy_s(fontid.deffontsettings.szFace, FO.szDefFace, _TRUNCATE);
+ fontid.deffontsettings.charset = FO.defCharset;
+ fontid.deffontsettings.colour = FO.defColour;
+ fontid.deffontsettings.size = FO.defSize;
+ fontid.deffontsettings.style = FO.defStyle;
+ CallService("Font/RegisterW", (WPARAM)&fontid, g_iChatLang);
+ }
+}
+
+// load icons from the skinning module if available
+
+HICON LoadIconEx(char* pszIcoLibName, BOOL big)
+{
+ char szTemp[256];
+ mir_snprintf(szTemp, "chat_%s", pszIcoLibName);
+ return Skin_GetIcon(szTemp, big);
+}
+
+static void InitSetting(TCHAR** ppPointer, char* pszSetting, TCHAR* pszDefault)
+{
+ DBVARIANT dbv;
+ if (!db_get_ts(NULL, CHAT_MODULE, pszSetting, &dbv)) {
+ replaceStrT(*ppPointer, dbv.ptszVal);
+ db_free(&dbv);
+ }
+ else replaceStrT(*ppPointer, pszDefault);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void LoadGlobalSettings(void)
+{
+ g_Settings->LogIconSize = 10;
+ g_Settings->bLogLimitNames = db_get_b(NULL, CHAT_MODULE, "LogLimitNames", 1) != 0;
+ g_Settings->bShowTime = db_get_b(NULL, CHAT_MODULE, "ShowTimeStamp", 1) != 0;
+ g_Settings->bSoundsFocus = db_get_b(NULL, CHAT_MODULE, "SoundsFocus", 0) != 0;
+ g_Settings->bShowTimeIfChanged = (BOOL)db_get_b(NULL, CHAT_MODULE, "ShowTimeStampIfChanged", 0) != 0;
+ g_Settings->bTimeStampEventColour = (BOOL)db_get_b(NULL, CHAT_MODULE, "TimeStampEventColour", 0) != 0;
+ g_Settings->iEventLimit = db_get_w(NULL, CHAT_MODULE, "LogLimit", 100);
+ g_Settings->dwIconFlags = db_get_dw(NULL, CHAT_MODULE, "IconFlags", 0x0000);
+ g_Settings->dwTrayIconFlags = db_get_dw(NULL, CHAT_MODULE, "TrayIconFlags", 0x1000);
+ g_Settings->dwPopupFlags = db_get_dw(NULL, CHAT_MODULE, "PopupFlags", 0x0000);
+ g_Settings->LoggingLimit = db_get_w(NULL, CHAT_MODULE, "LoggingLimit", 100);
+ g_Settings->bLoggingEnabled = (BOOL)db_get_b(NULL, CHAT_MODULE, "LoggingEnabled", 0) != 0;
+ g_Settings->bFlashWindow = (BOOL)db_get_b(NULL, CHAT_MODULE, "FlashWindow", 0) != 0;
+ g_Settings->bFlashWindowHighlight = (BOOL)db_get_b(NULL, CHAT_MODULE, "FlashWindowHighlight", false) != 0;
+ g_Settings->bHighlightEnabled = (BOOL)db_get_b(NULL, CHAT_MODULE, "HighlightEnabled", 1) != 0;
+ g_Settings->crLogBackground = db_get_dw(NULL, CHAT_MODULE, "ColorLogBG", GetSysColor(COLOR_WINDOW));
+ g_Settings->crUserListColor = db_get_dw(NULL, CHATFONT_MODULE, "Font18Col", RGB(0, 0, 0));
+ g_Settings->crUserListHeadingsColor = db_get_dw(NULL, CHATFONT_MODULE, "Font19Col", RGB(170, 170, 170));
+ g_Settings->bStripFormat = (BOOL)db_get_b(NULL, CHAT_MODULE, "StripFormatting", 0) != 0;
+ g_Settings->bTrayIconInactiveOnly = (BOOL)db_get_b(NULL, CHAT_MODULE, "TrayIconInactiveOnly", 1) != 0;
+ g_Settings->bPopupInactiveOnly = (BOOL)db_get_b(NULL, CHAT_MODULE, "PopupInactiveOnly", 1) != 0;
+ g_Settings->bAddColonToAutoComplete = (BOOL)db_get_b(NULL, CHAT_MODULE, "AddColonToAutoComplete", 1) != 0;
+ g_Settings->iPopupStyle = db_get_b(NULL, CHAT_MODULE, "PopupStyle", 1);
+ g_Settings->iPopupTimeout = db_get_w(NULL, CHAT_MODULE, "PopupTimeout", 3);
+ g_Settings->crPUBkgColour = db_get_dw(NULL, CHAT_MODULE, "PopupColorBG", GetSysColor(COLOR_WINDOW));
+ g_Settings->crPUTextColour = db_get_dw(NULL, CHAT_MODULE, "PopupColorText", 0);
+ g_Settings->bShowContactStatus = db_get_b(NULL, CHAT_MODULE, "ShowContactStatus", 0) != 0;
+ g_Settings->bContactStatusFirst = db_get_b(NULL, CHAT_MODULE, "ContactStatusFirst", 0) != 0;
+
+ LoadColors();
+
+ if (ci.OnLoadSettings)
+ ci.OnLoadSettings();
+
+ InitSetting(&g_Settings->pszTimeStamp, "HeaderTime", _T("[%H:%M]"));
+ InitSetting(&g_Settings->pszTimeStampLog, "LogTimestamp", _T("[%d %b %y %H:%M]"));
+ InitSetting(&g_Settings->pszIncomingNick, "HeaderIncoming", _T("%n:"));
+ InitSetting(&g_Settings->pszOutgoingNick, "HeaderOutgoing", _T("%n:"));
+ InitSetting(&g_Settings->pszHighlightWords, "HighlightWords", _T("%m"));
+
+ InitSetting(&g_Settings->pszLogDir, "LogDirectory", _T("%miranda_logpath%\\%proto%\\%userid%.log"));
+ g_Settings->bLogIndentEnabled = db_get_b(NULL, CHAT_MODULE, "LogIndentEnabled", 1) != 0;
+
+ LOGFONT lf;
+ if (g_Settings->UserListFont)
+ DeleteObject(g_Settings->UserListFont);
+ LoadMsgDlgFont(18, &lf, NULL);
+ g_Settings->UserListFont = CreateFontIndirect(&lf);
+
+ if (g_Settings->UserListHeadingsFont)
+ DeleteObject(g_Settings->UserListHeadingsFont);
+ LoadMsgDlgFont(19, &lf, NULL);
+ g_Settings->UserListHeadingsFont = CreateFontIndirect(&lf);
+
+ SetIndentSize();
+}
+
+static void FreeGlobalSettings(void)
+{
+ if (g_Settings == NULL)
+ return;
+
+ mir_free(g_Settings->pszTimeStamp);
+ mir_free(g_Settings->pszTimeStampLog);
+ mir_free(g_Settings->pszIncomingNick);
+ mir_free(g_Settings->pszOutgoingNick);
+ mir_free(g_Settings->pszHighlightWords);
+ mir_free(g_Settings->pszLogDir);
+ if (g_Settings->UserListFont)
+ DeleteObject(g_Settings->UserListFont);
+ if (g_Settings->UserListHeadingsFont)
+ DeleteObject(g_Settings->UserListHeadingsFont);
+ if (g_Settings->NameFont)
+ DeleteObject(g_Settings->NameFont);
+}
+
+void SetIndentSize()
+{
+ if (g_Settings->bShowTime) {
+ LOGFONT lf;
+ LoadMsgDlgFont(0, &lf, NULL);
+ HFONT hFont = CreateFontIndirect(&lf);
+ int iText = GetTextPixelSize(MakeTimeStamp(g_Settings->pszTimeStamp, time(NULL)), hFont, TRUE);
+ DeleteObject(hFont);
+ g_Settings->LogTextIndent = iText * 12 / 10;
+ }
+ else g_Settings->LogTextIndent = 0;
+}
+
+int GetTextPixelSize(TCHAR* pszText, HFONT hFont, BOOL bWidth)
+{
+ if (!pszText || !hFont)
+ return 0;
+
+ HDC hdc = GetDC(NULL);
+ HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
+
+ RECT rc = { 0 };
+ DrawText(hdc, pszText, -1, &rc, DT_CALCRECT);
+ SelectObject(hdc, hOldFont);
+ ReleaseDC(NULL, hdc);
+ return bWidth ? rc.right - rc.left : rc.bottom - rc.top;
+}
+
+int OptionsInit(void)
+{
+ LoadLogFonts();
+
+ LOGFONT lf;
+ LoadMsgDlgFont(18, &lf, NULL);
+ mir_tstrcpy(lf.lfFaceName, _T("MS Shell Dlg"));
+ lf.lfUnderline = lf.lfItalic = lf.lfStrikeOut = 0;
+ lf.lfHeight = -17;
+ lf.lfWeight = FW_BOLD;
+ g_Settings->NameFont = CreateFontIndirect(&lf);
+ g_Settings->UserListFont = NULL;
+ g_Settings->UserListHeadingsFont = NULL;
+ g_Settings->iWidth = db_get_dw(NULL, CHAT_MODULE, "roomwidth", -1);
+ g_Settings->iHeight = db_get_dw(NULL, CHAT_MODULE, "roomheight", -1);
+
+ g_Settings->iSplitterX = db_get_w(NULL, CHAT_MODULE, "SplitterX", 105);
+ if (g_Settings->iSplitterX <= 50)
+ g_Settings->iSplitterX = 105;
+ g_Settings->iSplitterY = db_get_w(NULL, CHAT_MODULE, "SplitterY", 90);
+ if (g_Settings->iSplitterY <= 65)
+ g_Settings->iSplitterY = 90;
+
+ SkinAddNewSoundEx("ChatMessage", LPGEN("Group chats"), LPGEN("Incoming message"));
+ SkinAddNewSoundEx("ChatHighlight", LPGEN("Group chats"), LPGEN("Message is highlighted"));
+ SkinAddNewSoundEx("ChatAction", LPGEN("Group chats"), LPGEN("User has performed an action"));
+ SkinAddNewSoundEx("ChatJoin", LPGEN("Group chats"), LPGEN("User has joined"));
+ SkinAddNewSoundEx("ChatPart", LPGEN("Group chats"), LPGEN("User has left"));
+ SkinAddNewSoundEx("ChatKick", LPGEN("Group chats"), LPGEN("User has kicked some other user"));
+ SkinAddNewSoundEx("ChatMode", LPGEN("Group chats"), LPGEN("User's status was changed"));
+ SkinAddNewSoundEx("ChatNick", LPGEN("Group chats"), LPGEN("User has changed name"));
+ SkinAddNewSoundEx("ChatNotice", LPGEN("Group chats"), LPGEN("User has sent a notice"));
+ SkinAddNewSoundEx("ChatQuit", LPGEN("Group chats"), LPGEN("User has disconnected"));
+ SkinAddNewSoundEx("ChatTopic", LPGEN("Group chats"), LPGEN("The topic has been changed"));
+ return 0;
+}
+
+int OptionsUnInit(void)
+{
+ FreeGlobalSettings();
+ return 0;
+}
diff --git a/src/mir_app/src/chat_rtf.cpp b/src/mir_app/src/chat_rtf.cpp new file mode 100644 index 0000000000..d7ce42dbf3 --- /dev/null +++ b/src/mir_app/src/chat_rtf.cpp @@ -0,0 +1,204 @@ +/* +Chat module plugin for Miranda IM + +Copyright 2000-12 Miranda IM, 2012-15 Miranda NG 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 "stdafx.h" + +#include "chat.h" + +///////////////////////////////////////////////////////////////////////////////////////// +// convert rich edit code to bbcode (if wanted). Otherwise, strip all RTF formatting +// tags and return plain text + +static TCHAR tszRtfBreaks[] = _T(" \\\n\r"); + +static void CreateColorMap(CMString &Text, int iCount, COLORREF *pSrc, int *pDst) +{ + const TCHAR *pszText = Text; + int iIndex = 1, i = 0; + + static const TCHAR *lpszFmt = _T("\\red%[^ \x5b\\]\\green%[^ \x5b\\]\\blue%[^ \x5b;];"); + TCHAR szRed[10], szGreen[10], szBlue[10]; + + const TCHAR *p1 = _tcsstr(pszText, _T("\\colortbl")); + if (!p1) + return; + + const TCHAR *pEnd = _tcschr(p1, '}'); + + const TCHAR *p2 = _tcsstr(p1, _T("\\red")); + + for (i = 0; i < iCount; i++) + pDst[i] = -1; + + while (p2 && p2 < pEnd) { + if (_stscanf(p2, lpszFmt, &szRed, &szGreen, &szBlue) > 0) { + for (int i = 0; i < iCount; i++) { + if (pSrc[i] == RGB(_ttoi(szRed), _ttoi(szGreen), _ttoi(szBlue))) + pDst[i] = iIndex; + } + } + iIndex++; + p1 = p2; + p1++; + + p2 = _tcsstr(p1, _T("\\red")); + } +} + +static int GetRtfIndex(int iCol, int iCount, int *pIndex) +{ + for (int i = 0; i < iCount; i++) + if (pIndex[i] == iCol) + return i; + + return -1; +} + +int DoRtfToTags(CMString &pszText, int iNumColors, COLORREF *pColors) +{ + if (pszText.IsEmpty()) + return FALSE; + + // create an index of colors in the module and map them to + // corresponding colors in the RTF color table + int *pIndex = (int*)_alloca(iNumColors * sizeof(int)); + CreateColorMap(pszText, iNumColors, pColors, pIndex); + + // scan the file for rtf commands and remove or parse them + int idx = pszText.Find(_T("\\pard")); + if (idx == -1) { + if ((idx = pszText.Find(_T("\\ltrpar"))) == -1) + return FALSE; + idx += 7; + } + else idx += 5; + + bool bInsideColor = false, bInsideUl = false; + CMString res; + + // iterate through all characters, if rtf control character found then take action + for (const TCHAR *p = pszText.GetString() + idx; *p;) { + switch (*p) { + case '\\': + if (p[1] == '\\' || p[1] == '{' || p[1] == '}') { // escaped characters + res.AppendChar(p[1]); + p += 2; break; + } + if (p[1] == '~') { // non-breaking space + res.AppendChar(0xA0); + p += 2; break; + } + + if (!_tcsncmp(p, _T("\\cf"), 3)) { // foreground color + int iCol = _ttoi(p + 3); + int iInd = GetRtfIndex(iCol, iNumColors, pIndex); + bInsideColor = iInd > 0; + } + else if (!_tcsncmp(p, _T("\\highlight"), 10)) { //background color + TCHAR szTemp[20]; + int iCol = _ttoi(p + 10); + mir_sntprintf(szTemp, _T("%d"), iCol); + } + else if (!_tcsncmp(p, _T("\\line"), 5)) { // soft line break; + res.AppendChar('\n'); + } + else if (!_tcsncmp(p, _T("\\endash"), 7)) { + res.AppendChar(0x2013); + } + else if (!_tcsncmp(p, _T("\\emdash"), 7)) { + res.AppendChar(0x2014); + } + else if (!_tcsncmp(p, _T("\\bullet"), 7)) { + res.AppendChar(0x2022); + } + else if (!_tcsncmp(p, _T("\\ldblquote"), 10)) { + res.AppendChar(0x201C); + } + else if (!_tcsncmp(p, _T("\\rdblquote"), 10)) { + res.AppendChar(0x201D); + } + else if (!_tcsncmp(p, _T("\\lquote"), 7)) { + res.AppendChar(0x2018); + } + else if (!_tcsncmp(p, _T("\\rquote"), 7)) { + res.AppendChar(0x2019); + } + else if (!_tcsncmp(p, _T("\\b"), 2)) { //bold + res.Append((p[2] != '0') ? _T("[b]") : _T("[/b]")); + } + else if (!_tcsncmp(p, _T("\\i"), 2)) { // italics + res.Append((p[2] != '0') ? _T("[i]") : _T("[/i]")); + } + else if (!_tcsncmp(p, _T("\\strike"), 7)) { // strike-out + res.Append((p[7] != '0') ? _T("[s]") : _T("[/s]")); + } + else if (!_tcsncmp(p, _T("\\ul"), 3)) { // underlined + if (p[3] == 0 || _tcschr(tszRtfBreaks, p[3])) { + res.Append(_T("[u]")); + bInsideUl = true; + } + else if (!_tcsnccmp(p + 3, _T("none"), 4)) { + if (bInsideUl) + res.Append(_T("[/u]")); + bInsideUl = false; + } + } + else if (!_tcsncmp(p, _T("\\tab"), 4)) { // tab + res.AppendChar('\t'); + } + else if (p[1] == '\'') { // special character + if (p[2] != ' ' && p[2] != '\\') { + TCHAR tmp[10], *t = tmp; + *t++ = p[2]; + if (p[3] != ' ' && p[3] != '\\') + *t++ = p[3]; + *t = 0; + + // convert string containing char in hex format to int. + TCHAR *stoppedHere; + res.AppendChar(_tcstol(tmp, &stoppedHere, 16)); + } + } + + p++; // skip initial slash + p += _tcscspn(p, tszRtfBreaks); + if (*p == ' ') + p++; + break; + + case '{': // other RTF control characters + case '}': + p++; + break; + + default: // other text that should not be touched + res.AppendChar(*p++); + break; + } + } + + if (bInsideUl) + res.Append(_T("[/u]")); + + pszText = res; + return TRUE; +} diff --git a/src/mir_app/src/chat_svc.cpp b/src/mir_app/src/chat_svc.cpp new file mode 100644 index 0000000000..5d56b3729b --- /dev/null +++ b/src/mir_app/src/chat_svc.cpp @@ -0,0 +1,631 @@ +/* +Chat module plugin for Miranda IM + +Copyright 2000-12 Miranda IM, 2012-15 Miranda NG 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 "stdafx.h" + +INT_PTR SvcGetChatManager(WPARAM, LPARAM); + +#include "chat.h" + +HGENMENU hJoinMenuItem, hLeaveMenuItem; +mir_cs cs; + +static HANDLE + hServiceRegister = NULL, + hServiceNewChat = NULL, + hServiceAddEvent = NULL, + hServiceGetAddEventPtr = NULL, + hServiceGetInfo = NULL, + hServiceGetCount = NULL, + hEventPrebuildMenu = NULL, + hEventDoubleclicked = NULL, + hEventJoinChat = NULL, + hEventLeaveChat = NULL, + hHookEvent = NULL; + +///////////////////////////////////////////////////////////////////////////////////////// +// Post-load event hooks + +void LoadChatIcons(void) +{ + ci.hIcons[ICON_ACTION] = LoadIconEx("log_action", FALSE); + ci.hIcons[ICON_ADDSTATUS] = LoadIconEx("log_addstatus", FALSE); + ci.hIcons[ICON_HIGHLIGHT] = LoadIconEx("log_highlight", FALSE); + ci.hIcons[ICON_INFO] = LoadIconEx("log_info", FALSE); + ci.hIcons[ICON_JOIN] = LoadIconEx("log_join", FALSE); + ci.hIcons[ICON_KICK] = LoadIconEx("log_kick", FALSE); + ci.hIcons[ICON_MESSAGE] = LoadIconEx("log_message_in", FALSE); + ci.hIcons[ICON_MESSAGEOUT] = LoadIconEx("log_message_out", FALSE); + ci.hIcons[ICON_NICK] = LoadIconEx("log_nick", FALSE); + ci.hIcons[ICON_NOTICE] = LoadIconEx("log_notice", FALSE); + ci.hIcons[ICON_PART] = LoadIconEx("log_part", FALSE); + ci.hIcons[ICON_QUIT] = LoadIconEx("log_quit", FALSE); + ci.hIcons[ICON_REMSTATUS] = LoadIconEx("log_removestatus", FALSE); + ci.hIcons[ICON_TOPIC] = LoadIconEx("log_topic", FALSE); + ci.hIcons[ICON_STATUS0] = LoadIconEx("status0", FALSE); + ci.hIcons[ICON_STATUS1] = LoadIconEx("status1", FALSE); + ci.hIcons[ICON_STATUS2] = LoadIconEx("status2", FALSE); + ci.hIcons[ICON_STATUS3] = LoadIconEx("status3", FALSE); + ci.hIcons[ICON_STATUS4] = LoadIconEx("status4", FALSE); + ci.hIcons[ICON_STATUS5] = LoadIconEx("status5", FALSE); + + FreeMsgLogBitmaps(); + LoadMsgLogBitmaps(); +} + +static int FontsChanged(WPARAM, LPARAM) +{ + LoadGlobalSettings(); + LoadLogFonts(); + + FreeMsgLogBitmaps(); + LoadMsgLogBitmaps(); + + SetIndentSize(); + g_Settings->bLogIndentEnabled = (db_get_b(NULL, CHAT_MODULE, "LogIndentEnabled", 1) != 0) ? TRUE : FALSE; + + ci.MM_FontsChanged(); + ci.MM_FixColors(); + ci.SM_BroadcastMessage(NULL, GC_SETWNDPROPS, 0, 0, TRUE); + return 0; +} + +static int IconsChanged(WPARAM, LPARAM) +{ + FreeMsgLogBitmaps(); + LoadMsgLogBitmaps(); + + ci.MM_IconsChanged(); + ci.SM_BroadcastMessage(NULL, GC_SETWNDPROPS, 0, 0, FALSE); + return 0; +} + +static int PreShutdown(WPARAM, LPARAM) +{ + if (g_Settings != NULL) { + ci.SM_BroadcastMessage(NULL, GC_CLOSEWINDOW, 0, 1, FALSE); + + ci.SM_RemoveAll(); + ci.MM_RemoveAll(); + + DeleteObject(ci.hListBkgBrush); + DeleteObject(ci.hListSelectedBkgBrush); + } + return 0; +} + +static int SmileyOptionsChanged(WPARAM, LPARAM) +{ + ci.SM_BroadcastMessage(NULL, GC_REDRAWLOG, 0, 1, FALSE); + return 0; +} + +static INT_PTR Service_GetCount(WPARAM, LPARAM lParam) +{ + if (!lParam) + return -1; + + mir_cslock lck(cs); + return ci.SM_GetCount((char *)lParam); +} + +static INT_PTR Service_GetInfo(WPARAM, LPARAM lParam) +{ + GC_INFO *gci = (GC_INFO *)lParam; + if (!gci || !gci->pszModule) + return 1; + + mir_cslock lck(cs); + + SESSION_INFO *si; + if (gci->Flags & GCF_BYINDEX) + si = ci.SM_FindSessionByIndex(gci->pszModule, gci->iItem); + else + si = ci.SM_FindSession(gci->pszID, gci->pszModule); + if (si == NULL) + return 1; + + if (gci->Flags & GCF_DATA) gci->dwItemData = si->dwItemData; + if (gci->Flags & GCF_HCONTACT) gci->hContact = si->hContact; + if (gci->Flags & GCF_TYPE) gci->iType = si->iType; + if (gci->Flags & GCF_COUNT) gci->iCount = si->nUsersInNicklist; + if (gci->Flags & GCF_USERS) gci->pszUsers = ci.SM_GetUsers(si); + if (gci->Flags & GCF_ID) gci->pszID = si->ptszID; + if (gci->Flags & GCF_NAME) gci->pszName = si->ptszName; + return 0; +} + +static INT_PTR Service_Register(WPARAM, LPARAM lParam) +{ + GCREGISTER *gcr = (GCREGISTER *)lParam; + if (gcr == NULL) + return GC_REGISTER_ERROR; + + if (gcr->cbSize != sizeof(GCREGISTER)) + return GC_REGISTER_WRONGVER; + + mir_cslock lck(cs); + MODULEINFO *mi = ci.MM_AddModule(gcr->pszModule); + if (mi == NULL) + return GC_REGISTER_ERROR; + + mi->ptszModDispName = mir_tstrdup(gcr->ptszDispName); + mi->bBold = (gcr->dwFlags & GC_BOLD) != 0; + mi->bUnderline = (gcr->dwFlags & GC_UNDERLINE) != 0; + mi->bItalics = (gcr->dwFlags & GC_ITALICS) != 0; + mi->bColor = (gcr->dwFlags & GC_COLOR) != 0; + mi->bBkgColor = (gcr->dwFlags & GC_BKGCOLOR) != 0; + mi->bAckMsg = (gcr->dwFlags & GC_ACKMSG) != 0; + mi->bChanMgr = (gcr->dwFlags & GC_CHANMGR) != 0; + mi->bSingleFormat = (gcr->dwFlags & GC_SINGLEFORMAT) != 0; + mi->bFontSize = (gcr->dwFlags & GC_FONTSIZE) != 0; + mi->iMaxText = gcr->iMaxText; + mi->nColorCount = gcr->nColors; + if (gcr->nColors > 0) { + mi->crColors = (COLORREF *)mir_alloc(sizeof(COLORREF)* gcr->nColors); + memcpy(mi->crColors, gcr->pColors, sizeof(COLORREF)* gcr->nColors); + } + + mi->pszHeader = ci.Log_CreateRtfHeader(mi); + + CheckColorsInModule((char*)gcr->pszModule); + ci.SetAllOffline(TRUE, gcr->pszModule); + return 0; +} + +static INT_PTR Service_NewChat(WPARAM, LPARAM lParam) +{ + GCSESSION *gcw = (GCSESSION *)lParam; + if (gcw == NULL) + return GC_NEWSESSION_ERROR; + + if (gcw->cbSize != sizeof(GCSESSION)) + return GC_NEWSESSION_WRONGVER; + + mir_cslock lck(cs); + MODULEINFO *mi = ci.MM_FindModule(gcw->pszModule); + if (mi == NULL) + return GC_NEWSESSION_ERROR; + + // create a new session and set the defaults + SESSION_INFO *si = ci.SM_AddSession(gcw->ptszID, gcw->pszModule); + if (si != NULL) { + si->dwItemData = gcw->dwItemData; + if (gcw->iType != GCW_SERVER) + si->wStatus = ID_STATUS_ONLINE; + si->iType = gcw->iType; + si->dwFlags = gcw->dwFlags; + si->ptszName = mir_tstrdup(gcw->ptszName); + si->ptszStatusbarText = mir_tstrdup(gcw->ptszStatusbarText); + si->iSplitterX = g_Settings->iSplitterX; + si->iSplitterY = g_Settings->iSplitterY; + si->iLogFilterFlags = db_get_dw(NULL, CHAT_MODULE, "FilterFlags", 0x03E0); + si->bFilterEnabled = db_get_b(NULL, CHAT_MODULE, "FilterEnabled", 0); + si->bNicklistEnabled = db_get_b(NULL, CHAT_MODULE, "ShowNicklist", 1); + + if (mi->bColor) { + si->iFG = 4; + si->bFGSet = TRUE; + } + if (mi->bBkgColor) { + si->iBG = 2; + si->bBGSet = TRUE; + } + + TCHAR szTemp[256]; + if (si->iType == GCW_SERVER) + mir_sntprintf(szTemp, _T("Server: %s"), si->ptszName); + else + _tcsncpy_s(szTemp, si->ptszName, _TRUNCATE); + si->hContact = ci.AddRoom(gcw->pszModule, gcw->ptszID, szTemp, si->iType); + db_set_s(si->hContact, si->pszModule, "Topic", ""); + db_unset(si->hContact, "CList", "StatusMsg"); + if (si->ptszStatusbarText) + db_set_ts(si->hContact, si->pszModule, "StatusBar", si->ptszStatusbarText); + else + db_set_s(si->hContact, si->pszModule, "StatusBar", ""); + + if (ci.OnCreateSession) + ci.OnCreateSession(si, mi); + } + else if (si = ci.SM_FindSession(gcw->ptszID, gcw->pszModule)) { + ci.UM_RemoveAll(&si->pUsers); + ci.TM_RemoveAll(&si->pStatuses); + + si->iStatusCount = 0; + si->nUsersInNicklist = 0; + + if (ci.OnReplaceSession) + ci.OnReplaceSession(si); + } + + return 0; +} + +static void SetInitDone(SESSION_INFO *si) +{ + if (si->bInitDone) + return; + + si->bInitDone = true; + for (STATUSINFO *p = si->pStatuses; p; p = p->next) + if ((UINT_PTR)p->hIcon < STATUSICONCOUNT) + p->hIcon = HICON(si->iStatusCount - (int)p->hIcon - 1); +} + +static int DoControl(GCEVENT *gce, WPARAM wp) +{ + SESSION_INFO *si; + + if (gce->pDest->iType == GC_EVENT_CONTROL) { + switch (wp) { + case WINDOW_HIDDEN: + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) { + SetInitDone(si); + ci.SetActiveSession(si->ptszID, si->pszModule); + if (si->hWnd) + ci.ShowRoom(si, wp, FALSE); + } + return 0; + + case WINDOW_MINIMIZE: + case WINDOW_MAXIMIZE: + case WINDOW_VISIBLE: + case SESSION_INITDONE: + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) { + SetInitDone(si); + if (wp != SESSION_INITDONE || db_get_b(NULL, CHAT_MODULE, "PopupOnJoin", 0) == 0) + ci.ShowRoom(si, wp, TRUE); + return 0; + } + break; + + case SESSION_OFFLINE: + ci.SM_SetOffline(gce->pDest->ptszID, gce->pDest->pszModule); + // fall through + + case SESSION_ONLINE: + ci.SM_SetStatus(gce->pDest->ptszID, gce->pDest->pszModule, wp == SESSION_ONLINE ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE); + break; + + case WINDOW_CLEARLOG: + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) { + ci.LM_RemoveAll(&si->pLog, &si->pLogEnd); + if (ci.OnClearLog) + ci.OnClearLog(si); + si->iEventCount = 0; + si->LastTime = 0; + } + break; + + case SESSION_TERMINATE: + return ci.SM_RemoveSession(gce->pDest->ptszID, gce->pDest->pszModule, (gce->dwFlags & GCEF_REMOVECONTACT) != 0); + } + ci.SM_SendMessage(gce->pDest->ptszID, gce->pDest->pszModule, GC_EVENT_CONTROL + WM_USER + 500, wp, 0); + } + + else if (gce->pDest->iType == GC_EVENT_CHUID && gce->ptszText) { + ci.SM_ChangeUID(gce->pDest->ptszID, gce->pDest->pszModule, gce->ptszNick, gce->ptszText); + } + + else if (gce->pDest->iType == GC_EVENT_CHANGESESSIONAME && gce->ptszText) { + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) { + replaceStrT(si->ptszName, gce->ptszText); + if (si->hWnd) + SendMessage(si->hWnd, GC_UPDATETITLE, 0, 0); + if (ci.OnRenameSession) + ci.OnRenameSession(si); + } + } + + else if (gce->pDest->iType == GC_EVENT_SETITEMDATA) { + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) + si->dwItemData = gce->dwItemData; + } + + else if (gce->pDest->iType == GC_EVENT_GETITEMDATA) { + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) { + gce->dwItemData = si->dwItemData; + return si->dwItemData; + } + return 0; + } + else if (gce->pDest->iType == GC_EVENT_SETSBTEXT) { + if (si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule)) { + replaceStrT(si->ptszStatusbarText, gce->ptszText); + if (si->ptszStatusbarText) + db_set_ts(si->hContact, si->pszModule, "StatusBar", si->ptszStatusbarText); + else + db_set_s(si->hContact, si->pszModule, "StatusBar", ""); + + if (ci.OnSetStatusBar) + ci.OnSetStatusBar(si); + } + } + else if (gce->pDest->iType == GC_EVENT_ACK) { + ci.SM_SendMessage(gce->pDest->ptszID, gce->pDest->pszModule, GC_ACKMESSAGE, 0, 0); + } + else if (gce->pDest->iType == GC_EVENT_SENDMESSAGE && gce->ptszText) { + ci.SM_SendUserMessage(gce->pDest->ptszID, gce->pDest->pszModule, gce->ptszText); + } + else if (gce->pDest->iType == GC_EVENT_SETSTATUSEX) { + ci.SM_SetStatusEx(gce->pDest->ptszID, gce->pDest->pszModule, gce->ptszText, gce->dwItemData); + } + else return 1; + + return 0; +} + +static void AddUser(GCEVENT *gce) +{ + SESSION_INFO *si = ci.SM_FindSession(gce->pDest->ptszID, gce->pDest->pszModule); + if (si == NULL) return; + + WORD status = ci.TM_StringToWord(si->pStatuses, gce->ptszStatus); + USERINFO *ui = ci.SM_AddUser(gce->pDest->ptszID, gce->pDest->pszModule, gce->ptszUID, gce->ptszNick, status); + if (ui == NULL) return; + + ui->pszNick = mir_tstrdup(gce->ptszNick); + if (gce->bIsMe) + si->pMe = ui; + ui->Status = status; + ui->Status |= si->pStatuses->Status; + + if (ci.OnNewUser) + ci.OnNewUser(si, ui); +} + +static INT_PTR Service_AddEvent(WPARAM wParam, LPARAM lParam) +{ + GCEVENT *gce = (GCEVENT*)lParam; + BOOL bIsHighlighted = FALSE; + BOOL bRemoveFlag = FALSE; + + if (gce == NULL) + return GC_EVENT_ERROR; + + GCDEST *gcd = gce->pDest; + if (gcd == NULL) + return GC_EVENT_ERROR; + + if (gce->cbSize != sizeof(GCEVENT)) + return GC_EVENT_WRONGVER; + + if (!IsEventSupported(gcd->iType)) + return GC_EVENT_ERROR; + + NotifyEventHooks(hHookEvent, wParam, lParam); + + SESSION_INFO *si; + mir_cslock lck(cs); + + // Do different things according to type of event + switch (gcd->iType) { + case GC_EVENT_ADDGROUP: + { + STATUSINFO *si = ci.SM_AddStatus(gcd->ptszID, gcd->pszModule, gce->ptszStatus); + if (si && gce->dwItemData) + si->hIcon = CopyIcon((HICON)gce->dwItemData); + } + return 0; + + case GC_EVENT_CHUID: + case GC_EVENT_CHANGESESSIONAME: + case GC_EVENT_SETITEMDATA: + case GC_EVENT_GETITEMDATA: + case GC_EVENT_CONTROL: + case GC_EVENT_SETSBTEXT: + case GC_EVENT_ACK: + case GC_EVENT_SENDMESSAGE: + case GC_EVENT_SETSTATUSEX: + return DoControl(gce, wParam); + + case GC_EVENT_SETCONTACTSTATUS: + return ci.SM_SetContactStatus(gcd->ptszID, gcd->pszModule, gce->ptszUID, (WORD)gce->dwItemData); + + case GC_EVENT_TOPIC: + if (si = ci.SM_FindSession(gcd->ptszID, gcd->pszModule)) { + if (gce->ptszText) { + replaceStrT(si->ptszTopic, RemoveFormatting(gce->ptszText)); + db_set_ts(si->hContact, si->pszModule, "Topic", si->ptszTopic); + if (ci.OnSetTopic) + ci.OnSetTopic(si); + if (db_get_b(NULL, CHAT_MODULE, "TopicOnClist", 0)) + db_set_ts(si->hContact, "CList", "StatusMsg", si->ptszTopic); + } + } + break; + + case GC_EVENT_ADDSTATUS: + ci.SM_GiveStatus(gcd->ptszID, gcd->pszModule, gce->ptszUID, gce->ptszStatus); + bIsHighlighted = ci.IsHighlighted(NULL, gce); + break; + + case GC_EVENT_REMOVESTATUS: + ci.SM_TakeStatus(gcd->ptszID, gcd->pszModule, gce->ptszUID, gce->ptszStatus); + bIsHighlighted = ci.IsHighlighted(NULL, gce); + break; + + case GC_EVENT_MESSAGE: + case GC_EVENT_ACTION: + if (!gce->bIsMe && gcd->ptszID && gce->ptszText) { + si = ci.SM_FindSession(gcd->ptszID, gcd->pszModule); + bIsHighlighted = ci.IsHighlighted(si, gce); + } + break; + + case GC_EVENT_NICK: + ci.SM_ChangeNick(gcd->ptszID, gcd->pszModule, gce); + bIsHighlighted = ci.IsHighlighted(NULL, gce); + break; + + case GC_EVENT_JOIN: + AddUser(gce); + bIsHighlighted = ci.IsHighlighted(NULL, gce); + break; + + case GC_EVENT_PART: + case GC_EVENT_QUIT: + case GC_EVENT_KICK: + bRemoveFlag = TRUE; + bIsHighlighted = ci.IsHighlighted(NULL, gce); + break; + } + + // Decide which window (log) should have the event + LPCTSTR pWnd = NULL; + LPCSTR pMod = NULL; + if (gcd->ptszID) { + pWnd = gcd->ptszID; + pMod = gcd->pszModule; + } + else if (gcd->iType == GC_EVENT_NOTICE || gcd->iType == GC_EVENT_INFORMATION) { + si = ci.GetActiveSession(); + if (si && !mir_strcmp(si->pszModule, gcd->pszModule)) { + pWnd = si->ptszID; + pMod = si->pszModule; + } + else return 0; + } + else { + // Send the event to all windows with a user pszUID. Used for broadcasting QUIT etc + ci.SM_AddEventToAllMatchingUID(gce); + if (!bRemoveFlag) + return 0; + } + + // add to log + if (pWnd) { + si = ci.SM_FindSession(pWnd, pMod); + + // fix for IRC's old stuyle mode notifications. Should not affect any other protocol + if ((gcd->iType == GC_EVENT_ADDSTATUS || gcd->iType == GC_EVENT_REMOVESTATUS) && !(gce->dwFlags & GCEF_ADDTOLOG)) + return 0; + + if (gcd->iType == GC_EVENT_JOIN && gce->time == 0) + return 0; + + if (si && (si->bInitDone || gcd->iType == GC_EVENT_TOPIC || (gcd->iType == GC_EVENT_JOIN && gce->bIsMe))) { + int isOk = ci.SM_AddEvent(pWnd, pMod, gce, bIsHighlighted); + if (ci.OnAddLog) + ci.OnAddLog(si, isOk); + if (!(gce->dwFlags & GCEF_NOTNOTIFY)) + ci.DoSoundsFlashPopupTrayStuff(si, gce, bIsHighlighted, 0); + if ((gce->dwFlags & GCEF_ADDTOLOG) && g_Settings->bLoggingEnabled) + ci.LogToFile(si, gce); + } + + if (!bRemoveFlag) + return 0; + } + + if (bRemoveFlag) + return ci.SM_RemoveUser(gcd->ptszID, gcd->pszModule, gce->ptszUID) == 0; + + return GC_EVENT_ERROR; +} + +static INT_PTR Service_GetAddEventPtr(WPARAM, LPARAM lParam) +{ + GCPTRS *gp = (GCPTRS *)lParam; + + mir_cslock lck(cs); + gp->pfnAddEvent = Service_AddEvent; + return 0; +} + +static int ModulesLoaded(WPARAM, LPARAM) +{ + LoadChatIcons(); + + HookEvent(ME_SMILEYADD_OPTIONSCHANGED, SmileyOptionsChanged); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, PrebuildContactMenu); + + CLISTMENUITEM mi = { sizeof(mi) }; + mi.position = -2000090001; + mi.flags = CMIF_DEFAULT; + mi.icolibItem = LoadSkinnedIconHandle(SKINICON_CHAT_JOIN); + mi.pszName = LPGEN("&Join chat"); + mi.pszService = "GChat/JoinChat"; + hJoinMenuItem = Menu_AddContactMenuItem(&mi); + + mi.position = -2000090000; + mi.icolibItem = LoadSkinnedIconHandle(SKINICON_CHAT_LEAVE); + mi.flags = CMIF_NOTOFFLINE; + mi.pszName = LPGEN("&Leave chat"); + mi.pszService = "GChat/LeaveChat"; + hLeaveMenuItem = Menu_AddContactMenuItem(&mi); + + ci.SetAllOffline(TRUE, NULL); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Service creation + +static bool bInited = false; + +int LoadChatModule(void) +{ + HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); + HookEvent(ME_SYSTEM_PRESHUTDOWN, PreShutdown); + HookEvent(ME_SKIN_ICONSCHANGED, IconsChanged); + + CreateServiceFunction(MS_GC_REGISTER, Service_Register); + CreateServiceFunction(MS_GC_NEWSESSION, Service_NewChat); + CreateServiceFunction(MS_GC_EVENT, Service_AddEvent); + CreateServiceFunction(MS_GC_GETEVENTPTR, Service_GetAddEventPtr); + CreateServiceFunction(MS_GC_GETINFO, Service_GetInfo); + CreateServiceFunction(MS_GC_GETSESSIONCOUNT, Service_GetCount); + + CreateServiceFunction("GChat/DblClickEvent", EventDoubleclicked); + CreateServiceFunction("GChat/PrebuildMenuEvent", PrebuildContactMenuSvc); + CreateServiceFunction("GChat/JoinChat", JoinChat); + CreateServiceFunction("GChat/LeaveChat", LeaveChat); + CreateServiceFunction("GChat/GetInterface", SvcGetChatManager); + + ci.hSendEvent = CreateHookableEvent(ME_GC_EVENT); + ci.hBuildMenuEvent = CreateHookableEvent(ME_GC_BUILDMENU); + hHookEvent = CreateHookableEvent(ME_GC_HOOK_EVENT); + + HookEvent(ME_FONT_RELOAD, FontsChanged); + HookEvent(ME_SKIN2_ICONSCHANGED, IconsChanged); + + bInited = true; + return 0; +} + +void UnloadChatModule(void) +{ + if (!bInited) + return; + + mir_free(ci.szActiveWndID); + mir_free(ci.szActiveWndModule); + + FreeMsgLogBitmaps(); + OptionsUnInit(); + + DestroyHookableEvent(ci.hSendEvent); + DestroyHookableEvent(ci.hBuildMenuEvent); + DestroyHookableEvent(hHookEvent); +} diff --git a/src/mir_app/src/chat_tools.cpp b/src/mir_app/src/chat_tools.cpp new file mode 100644 index 0000000000..a19bff68d9 --- /dev/null +++ b/src/mir_app/src/chat_tools.cpp @@ -0,0 +1,770 @@ +/*
+Chat module plugin for Miranda IM
+
+Copyright 2000-12 Miranda IM, 2012-15 Miranda NG 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 "stdafx.h"
+
+#include "chat.h"
+
+#pragma comment(lib, "Shlwapi.lib")
+
+int GetRichTextLength(HWND hwnd)
+{
+ GETTEXTLENGTHEX gtl;
+ gtl.flags = GTL_PRECISE;
+ gtl.codepage = CP_ACP;
+ return (int)SendMessage(hwnd, EM_GETTEXTLENGTHEX, (WPARAM)>l, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static TCHAR szTemp[10000];
+
+TCHAR* RemoveFormatting(const TCHAR *pszWord)
+{
+ if (pszWord == NULL)
+ return NULL;
+
+ TCHAR *d = szTemp;
+ size_t cbLen = mir_tstrlen(pszWord);
+ if (cbLen > SIZEOF(szTemp))
+ cbLen = SIZEOF(szTemp)-1;
+
+ for (size_t i = 0; i < cbLen;) {
+ if (pszWord[i] == '%') {
+ switch (pszWord[i+1]) {
+ case '%':
+ *d++ = '%';
+
+ case 'b':
+ case 'u':
+ case 'i':
+ case 'B':
+ case 'U':
+ case 'I':
+ case 'r':
+ case 'C':
+ case 'F':
+ i += 2;
+ continue;
+
+ case 'c':
+ case 'f':
+ i += 4;
+ continue;
+ }
+ }
+
+ *d++ = pszWord[i++];
+ }
+ *d = 0;
+
+ return szTemp;
+}
+
+BOOL DoTrayIcon(SESSION_INFO *si, GCEVENT *gce)
+{
+ switch (gce->pDest->iType) {
+ case GC_EVENT_MESSAGE | GC_EVENT_HIGHLIGHT:
+ case GC_EVENT_ACTION | GC_EVENT_HIGHLIGHT:
+ ci.AddEvent(si->hContact, LoadSkinnedIcon(SKINICON_EVENT_MESSAGE), GC_FAKE_EVENT, 0, TranslateT("%s wants your attention in %s"), gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_MESSAGE:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_MESSAGE], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s speaks in %s"), gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_ACTION:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_ACTION], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s speaks in %s"), gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_JOIN:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_JOIN], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s has joined %s"), gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_PART:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_PART], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s has left %s"), gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_QUIT:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_QUIT], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s has disconnected"), gce->ptszNick);
+ break;
+ case GC_EVENT_NICK:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_NICK], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s is now known as %s"), gce->ptszNick, gce->ptszText);
+ break;
+ case GC_EVENT_KICK:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_KICK], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s kicked %s from %s"), gce->ptszStatus, gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_NOTICE:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_NOTICE], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("Notice from %s"), gce->ptszNick);
+ break;
+ case GC_EVENT_TOPIC:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_TOPIC], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("Topic change in %s"), si->ptszName);
+ break;
+ case GC_EVENT_INFORMATION:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_INFO], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("Information in %s"), si->ptszName);
+ break;
+ case GC_EVENT_ADDSTATUS:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_ADDSTATUS], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s enables '%s' status for %s in %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick, si->ptszName);
+ break;
+ case GC_EVENT_REMOVESTATUS:
+ ci.AddEvent(si->hContact, ci.hIcons[ICON_REMSTATUS], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s disables '%s' status for %s in %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick, si->ptszName);
+ break;
+ }
+
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void __stdcall ShowRoomFromPopup(void *pi)
+{
+ SESSION_INFO *si = (SESSION_INFO*)pi;
+ ci.ShowRoom(si, WINDOW_VISIBLE, TRUE);
+}
+
+static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_COMMAND:
+ if (HIWORD(wParam) == STN_CLICKED) {
+ SESSION_INFO *si = (SESSION_INFO*)PUGetPluginData(hWnd);
+ CallFunctionAsync(ShowRoomFromPopup, si);
+
+ PUDeletePopup(hWnd);
+ return TRUE;
+ }
+ break;
+ case WM_CONTEXTMENU:
+ SESSION_INFO *si = (SESSION_INFO*)PUGetPluginData(hWnd);
+ if (si->hContact)
+ if (CallService(MS_CLIST_GETEVENT, (WPARAM)si->hContact, 0))
+ CallService(MS_CLIST_REMOVEEVENT, (WPARAM)si->hContact, (LPARAM)GC_FAKE_EVENT);
+
+ if (si->hWnd && KillTimer(si->hWnd, TIMERID_FLASHWND))
+ FlashWindow(si->hWnd, FALSE);
+
+ PUDeletePopup(hWnd);
+ break;
+ }
+ return DefWindowProc(hWnd, message, wParam, lParam);
+}
+
+int ShowPopup(MCONTACT hContact, SESSION_INFO *si, HICON hIcon, char *pszProtoName, TCHAR*, COLORREF crBkg, const TCHAR *fmt, ...)
+{
+ static TCHAR szBuf[4 * 1024];
+
+ if (!fmt || fmt[0] == 0 || mir_tstrlen(fmt) > 2000)
+ return 0;
+
+ va_list marker;
+ va_start(marker, fmt);
+ mir_vsntprintf(szBuf, 4096, fmt, marker);
+ va_end(marker);
+
+ POPUPDATAT pd = { 0 };
+ pd.lchContact = hContact;
+
+ if (hIcon)
+ pd.lchIcon = hIcon;
+ else
+ pd.lchIcon = LoadIconEx("window", FALSE);
+
+ PROTOACCOUNT *pa = ProtoGetAccount(pszProtoName);
+ mir_sntprintf(pd.lptzContactName, SIZEOF(pd.lptzContactName), _T("%s - %s"),
+ (pa == NULL) ? _A2T(pszProtoName) : pa->tszAccountName,
+ cli.pfnGetContactDisplayName(hContact, 0));
+
+ mir_tstrncpy(pd.lptzText, TranslateTS(szBuf), SIZEOF(pd.lptzText));
+ pd.iSeconds = g_Settings->iPopupTimeout;
+
+ if (g_Settings->iPopupStyle == 2) {
+ pd.colorBack = 0;
+ pd.colorText = 0;
+ }
+ else if (g_Settings->iPopupStyle == 3) {
+ pd.colorBack = g_Settings->crPUBkgColour;
+ pd.colorText = g_Settings->crPUTextColour;
+ }
+ else {
+ pd.colorBack = g_Settings->crLogBackground;
+ pd.colorText = crBkg;
+ }
+
+ pd.PluginWindowProc = PopupDlgProc;
+ pd.PluginData = si;
+ return PUAddPopupT(&pd);
+}
+
+BOOL DoPopup(SESSION_INFO *si, GCEVENT *gce)
+{
+ switch (gce->pDest->iType) {
+ case GC_EVENT_MESSAGE | GC_EVENT_HIGHLIGHT:
+ ci.ShowPopup(si->hContact, si, LoadSkinnedIcon(SKINICON_EVENT_MESSAGE), si->pszModule, si->ptszName, ci.aFonts[16].color, TranslateT("%s says: %s"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_ACTION | GC_EVENT_HIGHLIGHT:
+ ci.ShowPopup(si->hContact, si, LoadSkinnedIcon(SKINICON_EVENT_MESSAGE), si->pszModule, si->ptszName, ci.aFonts[16].color, _T("%s %s"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_MESSAGE:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_MESSAGE], si->pszModule, si->ptszName, ci.aFonts[9].color, TranslateT("%s says: %s"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_ACTION:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_ACTION], si->pszModule, si->ptszName, ci.aFonts[15].color, _T("%s %s"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_JOIN:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_JOIN], si->pszModule, si->ptszName, ci.aFonts[3].color, TranslateT("%s has joined"), gce->ptszNick);
+ break;
+ case GC_EVENT_PART:
+ if (!gce->ptszText)
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_PART], si->pszModule, si->ptszName, ci.aFonts[4].color, TranslateT("%s has left"), gce->ptszNick);
+ else
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_PART], si->pszModule, si->ptszName, ci.aFonts[4].color, TranslateT("%s has left (%s)"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_QUIT:
+ if (!gce->ptszText)
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_QUIT], si->pszModule, si->ptszName, ci.aFonts[5].color, TranslateT("%s has disconnected"), gce->ptszNick);
+ else
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_QUIT], si->pszModule, si->ptszName, ci.aFonts[5].color, TranslateT("%s has disconnected (%s)"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_NICK:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_NICK], si->pszModule, si->ptszName, ci.aFonts[7].color, TranslateT("%s is now known as %s"), gce->ptszNick, gce->ptszText);
+ break;
+ case GC_EVENT_KICK:
+ if (!gce->ptszText)
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_KICK], si->pszModule, si->ptszName, ci.aFonts[6].color, TranslateT("%s kicked %s"), (char *)gce->ptszStatus, gce->ptszNick);
+ else
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_KICK], si->pszModule, si->ptszName, ci.aFonts[6].color, TranslateT("%s kicked %s (%s)"), (char *)gce->ptszStatus, gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_NOTICE:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_NOTICE], si->pszModule, si->ptszName, ci.aFonts[8].color, TranslateT("Notice from %s: %s"), gce->ptszNick, RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_TOPIC:
+ if (!gce->ptszNick)
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_TOPIC], si->pszModule, si->ptszName, ci.aFonts[11].color, TranslateT("The topic is '%s'"), RemoveFormatting(gce->ptszText));
+ else
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_TOPIC], si->pszModule, si->ptszName, ci.aFonts[11].color, TranslateT("The topic is '%s' (set by %s)"), RemoveFormatting(gce->ptszText), gce->ptszNick);
+ break;
+ case GC_EVENT_INFORMATION:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_INFO], si->pszModule, si->ptszName, ci.aFonts[12].color, _T("%s"), RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_ADDSTATUS:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_ADDSTATUS], si->pszModule, si->ptszName, ci.aFonts[13].color, TranslateT("%s enables '%s' status for %s"), gce->ptszText, (char *)gce->ptszStatus, gce->ptszNick);
+ break;
+ case GC_EVENT_REMOVESTATUS:
+ ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_REMSTATUS], si->pszModule, si->ptszName, ci.aFonts[14].color, TranslateT("%s disables '%s' status for %s"), gce->ptszText, (char *)gce->ptszStatus, gce->ptszNick);
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL DoSoundsFlashPopupTrayStuff(SESSION_INFO *si, GCEVENT *gce, BOOL bHighlight, int bManyFix)
+{
+ if (!gce || !si || gce->bIsMe || si->iType == GCW_SERVER)
+ return FALSE;
+
+ BOOL bInactive = si->hWnd == NULL || GetForegroundWindow() != si->hWnd;
+
+ int iEvent = gce->pDest->iType;
+
+ if (bHighlight) {
+ gce->pDest->iType |= GC_EVENT_HIGHLIGHT;
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatHighlight");
+ if (db_get_b(si->hContact, "CList", "Hidden", 0) != 0)
+ db_unset(si->hContact, "CList", "Hidden");
+ if (bInactive)
+ ci.DoTrayIcon(si, gce);
+ if (bInactive || !g_Settings->bPopupInactiveOnly)
+ ci.DoPopup(si, gce);
+ if (ci.OnFlashHighlight)
+ ci.OnFlashHighlight(si, bInactive);
+ return TRUE;
+ }
+
+ // do blinking icons in tray
+ if (bInactive || !g_Settings->bTrayIconInactiveOnly)
+ ci.DoTrayIcon(si, gce);
+
+ // stupid thing to not create multiple popups for a QUIT event for instance
+ if (bManyFix == 0) {
+ // do popups
+ if (bInactive || !g_Settings->bPopupInactiveOnly)
+ ci.DoPopup(si, gce);
+
+ // do sounds and flashing
+ switch (iEvent) {
+ case GC_EVENT_JOIN:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatJoin");
+ break;
+ case GC_EVENT_PART:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatPart");
+ break;
+ case GC_EVENT_QUIT:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatQuit");
+ break;
+ case GC_EVENT_ADDSTATUS:
+ case GC_EVENT_REMOVESTATUS:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatMode");
+ break;
+ case GC_EVENT_KICK:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatKick");
+ break;
+ case GC_EVENT_MESSAGE:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatMessage");
+
+ if (bInactive && !(si->wState & STATE_TALK)) {
+ si->wState |= STATE_TALK;
+ db_set_w(si->hContact, si->pszModule, "ApparentMode", ID_STATUS_OFFLINE);
+ }
+ if (ci.OnFlashWindow)
+ ci.OnFlashWindow(si, bInactive);
+ break;
+ case GC_EVENT_ACTION:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatAction");
+ break;
+ case GC_EVENT_NICK:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatNick");
+ break;
+ case GC_EVENT_NOTICE:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatNotice");
+ break;
+ case GC_EVENT_TOPIC:
+ if (bInactive || !g_Settings->bSoundsFocus)
+ SkinPlaySound("ChatTopic");
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+int GetColorIndex(const char *pszModule, COLORREF cr)
+{
+ MODULEINFO *pMod = ci.MM_FindModule(pszModule);
+ int i = 0;
+
+ if (!pMod || pMod->nColorCount == 0)
+ return -1;
+
+ for (i = 0; i < pMod->nColorCount; i++)
+ if (pMod->crColors[i] == cr)
+ return i;
+
+ return -1;
+}
+
+// obscure function that is used to make sure that any of the colors
+// passed by the protocol is used as fore- or background color
+// in the messagebox. THis is to vvercome limitations in the richedit
+// that I do not know currently how to fix
+
+void CheckColorsInModule(const char *pszModule)
+{
+ MODULEINFO *pMod = ci.MM_FindModule(pszModule);
+ int i = 0;
+ COLORREF crFG;
+ COLORREF crBG = (COLORREF)db_get_dw(NULL, CHAT_MODULE, "ColorMessageBG", GetSysColor(COLOR_WINDOW));
+
+ LoadMsgDlgFont(17, NULL, &crFG);
+
+ if (!pMod)
+ return;
+
+ for (i = 0; i < pMod->nColorCount; i++) {
+ if (pMod->crColors[i] == crFG || pMod->crColors[i] == crBG) {
+ if (pMod->crColors[i] == RGB(255, 255, 255))
+ pMod->crColors[i]--;
+ else
+ pMod->crColors[i]++;
+ }
+ }
+}
+
+const TCHAR* my_strstri(const TCHAR* s1, const TCHAR* s2)
+{
+ int i, j, k;
+ for (i = 0; s1[i]; i++)
+ for (j = i, k = 0; _totlower(s1[j]) == _totlower(s2[k]); j++, k++)
+ if (!s2[k + 1])
+ return s1 + i;
+
+ return NULL;
+}
+
+static TCHAR szTrimString[] = _T(":,.!?;\'>)");
+
+BOOL IsHighlighted(SESSION_INFO *si, GCEVENT *gce)
+{
+ if (!g_Settings->bHighlightEnabled || !g_Settings->pszHighlightWords || !gce || !si || !si->pMe)
+ return FALSE;
+
+ if (gce->ptszText == NULL)
+ return FALSE;
+
+ TCHAR *buf = RemoveFormatting(NEWTSTR_ALLOCA(gce->ptszText));
+
+ int iStart = 0;
+ CMString tszHighlightWords(g_Settings->pszHighlightWords);
+
+ while (true) {
+ CMString tszToken = tszHighlightWords.Tokenize(_T("\t "), iStart);
+ if (iStart == -1)
+ break;
+
+ // replace %m with the users nickname
+ if (tszToken == _T("%m"))
+ tszToken = si->pMe->pszNick;
+
+ if (tszToken.Find('*') == -1)
+ tszToken = '*' + tszToken + '*';
+
+ // time to get the next/first word in the incoming text string
+ for (const TCHAR *p = buf; *p != '\0'; p += _tcscspn(p, _T(" "))) {
+ p += _tcsspn(p, _T(" "));
+
+ // compare the words, using wildcards
+ if (wildcmpit(RemoveFormatting(p), tszToken))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL LogToFile(SESSION_INFO *si, GCEVENT *gce)
+{
+ TCHAR szBuffer[4096];
+ TCHAR szLine[4096];
+ TCHAR p = '\0';
+ szBuffer[0] = '\0';
+
+ GetChatLogsFilename(si, gce->time);
+ BOOL bFileJustCreated = !PathFileExists(si->pszLogFileName);
+
+ TCHAR tszFolder[MAX_PATH];
+ _tcsncpy_s(tszFolder, si->pszLogFileName, _TRUNCATE);
+ PathRemoveFileSpec(tszFolder);
+ if (!PathIsDirectory(tszFolder))
+ CreateDirectoryTreeT(tszFolder);
+
+ TCHAR szTime[100];
+ mir_tstrncpy(szTime, ci.MakeTimeStamp(g_Settings->pszTimeStampLog, gce->time), 99);
+
+ FILE *hFile = _tfopen(si->pszLogFileName, _T("ab+"));
+ if (hFile == NULL)
+ return FALSE;
+
+ TCHAR szTemp[512], szTemp2[512];
+ TCHAR* pszNick = NULL;
+ if (bFileJustCreated)
+ fputws((const wchar_t*)"\377\376", hFile); //UTF-16 LE BOM == FF FE
+ if (gce->ptszNick) {
+ if (g_Settings->bLogLimitNames && mir_tstrlen(gce->ptszNick) > 20) {
+ mir_tstrncpy(szTemp2, gce->ptszNick, 20);
+ mir_tstrncpy(szTemp2 + 20, _T("..."), 4);
+ }
+ else mir_tstrncpy(szTemp2, gce->ptszNick, 511);
+
+ if (gce->ptszUserInfo)
+ mir_sntprintf(szTemp, _T("%s (%s)"), szTemp2, gce->ptszUserInfo);
+ else
+ _tcsncpy_s(szTemp, szTemp2, _TRUNCATE);
+ pszNick = szTemp;
+ }
+
+ switch (gce->pDest->iType) {
+ case GC_EVENT_MESSAGE:
+ case GC_EVENT_MESSAGE | GC_EVENT_HIGHLIGHT:
+ p = '*';
+ mir_sntprintf(szBuffer, _T("%s: %s"), gce->ptszNick, ci.RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_ACTION:
+ case GC_EVENT_ACTION | GC_EVENT_HIGHLIGHT:
+ p = '*';
+ mir_sntprintf(szBuffer, _T("%s %s"), gce->ptszNick, ci.RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_JOIN:
+ p = '>';
+ mir_sntprintf(szBuffer, TranslateT("%s has joined"), pszNick);
+ break;
+ case GC_EVENT_PART:
+ p = '<';
+ if (!gce->ptszText)
+ mir_sntprintf(szBuffer, TranslateT("%s has left"), pszNick);
+ else
+ mir_sntprintf(szBuffer, TranslateT("%s has left (%s)"), pszNick, ci.RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_QUIT:
+ p = '<';
+ if (!gce->ptszText)
+ mir_sntprintf(szBuffer, TranslateT("%s has disconnected"), pszNick);
+ else
+ mir_sntprintf(szBuffer, TranslateT("%s has disconnected (%s)"), pszNick, ci.RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_NICK:
+ p = '^';
+ mir_sntprintf(szBuffer, TranslateT("%s is now known as %s"), gce->ptszNick, gce->ptszText);
+ break;
+ case GC_EVENT_KICK:
+ p = '~';
+ if (!gce->ptszText)
+ mir_sntprintf(szBuffer, TranslateT("%s kicked %s"), gce->ptszStatus, gce->ptszNick);
+ else
+ mir_sntprintf(szBuffer, TranslateT("%s kicked %s (%s)"), gce->ptszStatus, gce->ptszNick, ci.RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_NOTICE:
+ p = 'o';
+ mir_sntprintf(szBuffer, TranslateT("Notice from %s: %s"), gce->ptszNick, ci.RemoveFormatting(gce->ptszText));
+ break;
+ case GC_EVENT_TOPIC:
+ p = '#';
+ if (!gce->ptszNick)
+ mir_sntprintf(szBuffer, TranslateT("The topic is '%s'"), ci.RemoveFormatting(gce->ptszText));
+ else
+ mir_sntprintf(szBuffer, TranslateT("The topic is '%s' (set by %s)"), ci.RemoveFormatting(gce->ptszText), gce->ptszNick);
+ break;
+ case GC_EVENT_INFORMATION:
+ p = '!';
+ _tcsncpy_s(szBuffer, ci.RemoveFormatting(gce->ptszText), _TRUNCATE);
+ break;
+ case GC_EVENT_ADDSTATUS:
+ p = '+';
+ mir_sntprintf(szBuffer, TranslateT("%s enables '%s' status for %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick);
+ break;
+ case GC_EVENT_REMOVESTATUS:
+ p = '-';
+ mir_sntprintf(szBuffer, TranslateT("%s disables '%s' status for %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick);
+ break;
+ }
+
+ // formatting strings don't need to be translatable - changing them via language pack would
+ // only screw up the log format.
+ if (p)
+ mir_sntprintf(szLine, SIZEOF(szLine), _T("%s %c %s\r\n"), szTime, p, szBuffer);
+ else
+ mir_sntprintf(szLine, SIZEOF(szLine), _T("%s %s\r\n"), szTime, szBuffer);
+
+ if (szLine[0]) {
+ _fputts(szLine, hFile);
+
+ if (g_Settings->LoggingLimit > 0) {
+ fseek(hFile, 0, SEEK_END);
+ long dwSize = ftell(hFile);
+ rewind(hFile);
+
+ long trimlimit = g_Settings->LoggingLimit * 1024;
+ if (dwSize > trimlimit) {
+ time_t now = time(0);
+
+ TCHAR tszTimestamp[20];
+ _tcsftime(tszTimestamp, 20, _T("%Y%m%d-%H%M%S"), _localtime32((__time32_t *)&now));
+ tszTimestamp[19] = 0;
+
+ // max size reached, rotate the log
+ // move old logs to /archived sub folder just inside the log root folder.
+ // add a time stamp to the file name.
+ TCHAR tszDrive[_MAX_DRIVE], tszDir[_MAX_DIR], tszName[_MAX_FNAME], tszExt[_MAX_EXT];
+ _tsplitpath(si->pszLogFileName, tszDrive, tszDir, tszName, tszExt);
+
+ TCHAR tszNewPath[_MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 20];
+ mir_sntprintf(tszNewPath, SIZEOF(tszNewPath), _T("%s%sarchived\\"), tszDrive, tszDir);
+ CreateDirectoryTreeT(tszNewPath);
+
+ TCHAR tszNewName[_MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 20];
+ mir_sntprintf(tszNewName, SIZEOF(tszNewName), _T("%s%s-%s%s"), tszNewPath, tszName, tszTimestamp, tszExt);
+ fclose(hFile);
+ hFile = 0;
+ if (!PathFileExists(tszNewName))
+ CopyFile(si->pszLogFileName, tszNewName, TRUE);
+ DeleteFile(si->pszLogFileName);
+ }
+ }
+ }
+
+ if (hFile)
+ fclose(hFile);
+ return TRUE;
+}
+
+BOOL DoEventHookAsync(HWND hwnd, const TCHAR *pszID, const char *pszModule, int iType, const TCHAR* pszUID, const TCHAR* pszText, INT_PTR dwItem)
+{
+ SESSION_INFO *si = ci.SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ GCDEST *gcd = (GCDEST*)mir_calloc(sizeof(GCDEST));
+ gcd->pszModule = mir_strdup(pszModule);
+ gcd->ptszID = mir_tstrdup(pszID);
+ gcd->iType = iType;
+
+ GCHOOK *gch = (GCHOOK*)mir_calloc(sizeof(GCHOOK));
+ gch->ptszUID = mir_tstrdup(pszUID);
+ gch->ptszText = mir_tstrdup(pszText);
+ gch->dwData = dwItem;
+ gch->pDest = gcd;
+ PostMessage(hwnd, GC_FIREHOOK, 0, (LPARAM)gch);
+ return TRUE;
+}
+
+BOOL DoEventHook(const TCHAR *pszID, const char *pszModule, int iType, const TCHAR *pszUID, const TCHAR* pszText, INT_PTR dwItem)
+{
+ SESSION_INFO *si = ci.SM_FindSession(pszID, pszModule);
+ if (si == NULL)
+ return FALSE;
+
+ GCDEST gcd = { (char*)pszModule, pszID, iType };
+ GCHOOK gch = { 0 };
+ gch.ptszUID = (LPTSTR)pszUID;
+ gch.ptszText = (LPTSTR)pszText;
+ gch.dwData = dwItem;
+ gch.pDest = &gcd;
+ NotifyEventHooks(ci.hSendEvent, 0, (WPARAM)&gch);
+ return TRUE;
+}
+
+BOOL IsEventSupported(int eventType)
+{
+ // Supported events
+ switch (eventType) {
+ case GC_EVENT_JOIN:
+ case GC_EVENT_PART:
+ case GC_EVENT_QUIT:
+ case GC_EVENT_KICK:
+ case GC_EVENT_NICK:
+ case GC_EVENT_NOTICE:
+ case GC_EVENT_MESSAGE:
+ case GC_EVENT_TOPIC:
+ case GC_EVENT_INFORMATION:
+ case GC_EVENT_ACTION:
+ case GC_EVENT_ADDSTATUS:
+ case GC_EVENT_REMOVESTATUS:
+ case GC_EVENT_CHUID:
+ case GC_EVENT_CHANGESESSIONAME:
+ case GC_EVENT_ADDGROUP:
+ case GC_EVENT_SETITEMDATA:
+ case GC_EVENT_GETITEMDATA:
+ case GC_EVENT_SETSBTEXT:
+ case GC_EVENT_ACK:
+ case GC_EVENT_SENDMESSAGE:
+ case GC_EVENT_SETSTATUSEX:
+ case GC_EVENT_CONTROL:
+ case GC_EVENT_SETCONTACTSTATUS:
+ return TRUE;
+ }
+
+ // Other events
+ return FALSE;
+}
+
+void ValidateFilename(TCHAR *filename)
+{
+ TCHAR *p1 = filename;
+ TCHAR szForbidden[] = _T("\\/:*?\"<>|");
+ while (*p1 != '\0') {
+ if (_tcschr(szForbidden, *p1))
+ *p1 = '_';
+ p1 += 1;
+ }
+}
+
+static TCHAR tszOldTimeStamp[30];
+
+TCHAR* GetChatLogsFilename(SESSION_INFO *si, time_t tTime)
+{
+ if (!tTime)
+ time(&tTime);
+
+ // check whether relevant parts of the timestamp have changed and
+ // we have to reparse the filename
+ TCHAR *tszNow = ci.MakeTimeStamp(_T("%a%d%m%Y"), tTime); // once a day
+ if (mir_tstrcmp(tszOldTimeStamp, tszNow)) {
+ _tcsncpy_s(tszOldTimeStamp, tszNow, _TRUNCATE);
+ *si->pszLogFileName = 0;
+ }
+
+ if (si->pszLogFileName[0] == 0) {
+ REPLACEVARSARRAY rva[11];
+ rva[0].lptzKey = _T("d");
+ rva[0].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%#d"), tTime));
+ // day 01-31
+ rva[1].lptzKey = _T("dd");
+ rva[1].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%d"), tTime));
+ // month 1-12
+ rva[2].lptzKey = _T("m");
+ rva[2].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%#m"), tTime));
+ // month 01-12
+ rva[3].lptzKey = _T("mm");
+ rva[3].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%m"), tTime));
+ // month text short
+ rva[4].lptzKey = _T("mon");
+ rva[4].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%b"), tTime));
+ // month text
+ rva[5].lptzKey = _T("month");
+ rva[5].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%B"), tTime));
+ // year 01-99
+ rva[6].lptzKey = _T("yy");
+ rva[6].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%y"), tTime));
+ // year 1901-9999
+ rva[7].lptzKey = _T("yyyy");
+ rva[7].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%Y"), tTime));
+ // weekday short
+ rva[8].lptzKey = _T("wday");
+ rva[8].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%a"), tTime));
+ // weekday
+ rva[9].lptzKey = _T("weekday");
+ rva[9].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%A"), tTime));
+ // end of array
+ rva[10].lptzKey = NULL;
+ rva[10].lptzValue = NULL;
+
+ TCHAR tszTemp[MAX_PATH], *ptszVarPath;
+ if (g_Settings->pszLogDir[mir_tstrlen(g_Settings->pszLogDir) - 1] == '\\') {
+ mir_sntprintf(tszTemp, SIZEOF(tszTemp), _T("%s%s"), g_Settings->pszLogDir, _T("%userid%.log"));
+ ptszVarPath = tszTemp;
+ }
+ else ptszVarPath = g_Settings->pszLogDir;
+
+ REPLACEVARSDATA dat = { sizeof(dat) };
+ dat.dwFlags = RVF_TCHAR;
+ dat.hContact = si->hContact;
+ dat.variables = rva;
+ TCHAR *tszParsedName = (TCHAR*)CallService(MS_UTILS_REPLACEVARS, (WPARAM)ptszVarPath, (LPARAM)&dat);
+ if (ci.OnGetLogName)
+ ci.OnGetLogName(si, tszParsedName);
+ else
+ PathToAbsoluteT(tszParsedName, si->pszLogFileName);
+ mir_free(tszParsedName);
+
+ for (int i = 0; i < SIZEOF(rva); i++)
+ mir_free(rva[i].lptzValue);
+
+ for (TCHAR *p = si->pszLogFileName + 2; *p; ++p)
+ if (*p == ':' || *p == '*' || *p == '?' || *p == '"' || *p == '<' || *p == '>' || *p == '|')
+ *p = _T('_');
+ }
+
+ return si->pszLogFileName;
+}
diff --git a/src/mir_app/src/clc.cpp b/src/mir_app/src/clc.cpp new file mode 100644 index 0000000000..6863694d05 --- /dev/null +++ b/src/mir_app/src/clc.cpp @@ -0,0 +1,1334 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 LIST<void> arEvents(10);
+
+int g_IconWidth, g_IconHeight;
+
+void FreeDisplayNameCache(void);
+
+void fnInitAutoRebuild(HWND hWnd)
+{
+ if (!cli.bAutoRebuild && hWnd) {
+ cli.bAutoRebuild = true;
+ SendMessage(hWnd, CLM_AUTOREBUILD, 0, 0);
+ }
+}
+
+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(ClcGroup* group)
+{
+ HMENU hMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT));
+ HMENU hGroupMenu = GetSubMenu(hMenu, 2);
+ RemoveMenu(hMenu, 2, MF_BYPOSITION);
+ DestroyMenu(hMenu);
+ TranslateMenu(hGroupMenu);
+
+ CheckMenuItem(hGroupMenu, POPUP_GROUPHIDEOFFLINE, group->hideOffline ? MF_CHECKED : MF_UNCHECKED);
+ return hGroupMenu;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// standard CLC services
+
+static int ClcSettingChanged(WPARAM hContact, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+ if (hContact == NULL) {
+ if (!mir_strcmp(cws->szModule, "CListGroups"))
+ cli.pfnClcBroadcast(INTM_GROUPSCHANGED, hContact, lParam);
+ return 0;
+ }
+
+ if (!mir_strcmp(cws->szModule, "CList")) {
+ if (!mir_strcmp(cws->szSetting, "MyHandle")) {
+ cli.pfnInvalidateDisplayNameCacheEntry(hContact);
+ cli.pfnClcBroadcast(INTM_NAMECHANGED, hContact, lParam);
+ }
+ else if (!mir_strcmp(cws->szSetting, "Group"))
+ cli.pfnClcBroadcast(INTM_GROUPCHANGED, hContact, lParam);
+ else if (!mir_strcmp(cws->szSetting, "Hidden"))
+ cli.pfnClcBroadcast(INTM_HIDDENCHANGED, hContact, lParam);
+ else if (!mir_strcmp(cws->szSetting, "NotOnList"))
+ cli.pfnClcBroadcast(INTM_NOTONLISTCHANGED, hContact, lParam);
+ else if (!mir_strcmp(cws->szSetting, "Status"))
+ cli.pfnClcBroadcast(INTM_INVALIDATE, 0, 0);
+ else if (!mir_strcmp(cws->szSetting, "NameOrder"))
+ cli.pfnClcBroadcast(INTM_NAMEORDERCHANGED, 0, 0);
+ }
+ else {
+ char *szProto = GetContactProto(hContact);
+ if (szProto != NULL) {
+ if (!mir_strcmp(cws->szModule, "Protocol") && !mir_strcmp(cws->szSetting, "p"))
+ cli.pfnClcBroadcast(INTM_PROTOCHANGED, hContact, lParam);
+
+ // something is being written to a protocol module
+ if (!mir_strcmp(szProto, cws->szModule)) {
+ // was a unique setting key written?
+ char *id = (char *) CallProtoServiceInt(NULL,szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
+ if ((INT_PTR)id != CALLSERVICE_NOTFOUND && id != NULL && !mir_strcmp(id, cws->szSetting))
+ cli.pfnClcBroadcast(INTM_PROTOCHANGED, hContact, lParam);
+ }
+ }
+ if (szProto == NULL || mir_strcmp(szProto, cws->szModule))
+ return 0;
+ if (!mir_strcmp(cws->szSetting, "Nick") || !mir_strcmp(cws->szSetting, "FirstName") || !mir_strcmp(cws->szSetting, "e-mail")
+ || !mir_strcmp(cws->szSetting, "LastName") || !mir_strcmp(cws->szSetting, "UIN"))
+ cli.pfnClcBroadcast(INTM_NAMECHANGED, hContact, lParam);
+ else if (!mir_strcmp(cws->szSetting, "ApparentMode"))
+ cli.pfnClcBroadcast(INTM_APPARENTMODECHANGED, hContact, lParam);
+ else if (!mir_strcmp(cws->szSetting, "IdleTS"))
+ cli.pfnClcBroadcast(INTM_IDLECHANGED, hContact, 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 = CallProtoServiceInt(NULL,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;
+ if (ack->type == ACKTYPE_STATUS) {
+ WindowList_BroadcastAsync(hClcWindowList, INTM_INVALIDATE, 0, 0);
+ if (ack->result == ACKRESULT_SUCCESS) {
+ for (int i=0; i < cli.hClcProtoCount; i++) {
+ if (!mir_strcmp(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)
+{
+ db_set_w(NULL, "CLC", "InfoTipHoverTime", (WORD) wParam);
+ cli.pfnClcBroadcast(INTM_SETINFOTIPHOVERTIME, wParam, 0);
+ return 0;
+}
+
+static INT_PTR GetInfoTipHoverTime(WPARAM, LPARAM)
+{
+ return db_get_w(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 = WindowList_Create();
+ hShowInfoTipEvent = CreateHookableEvent(ME_CLC_SHOWINFOTIP);
+ hHideInfoTipEvent = CreateHookableEvent(ME_CLC_HIDEINFOTIP);
+ CreateServiceFunction(MS_CLC_SETINFOTIPHOVERTIME, SetInfoTipHoverTime);
+ CreateServiceFunction(MS_CLC_GETINFOTIPHOVERTIME, GetInfoTipHoverTime);
+
+ InitFileDropping();
+
+ arEvents.insert(HookEvent(ME_SYSTEM_MODULESLOADED, ClcModulesLoaded));
+ arEvents.insert(HookEvent(ME_PROTO_ACCLISTCHANGED, ClcAccountsChanged));
+ arEvents.insert(HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ClcSettingChanged));
+ arEvents.insert(HookEvent(ME_DB_CONTACT_ADDED, ClcContactAdded));
+ arEvents.insert(HookEvent(ME_DB_CONTACT_DELETED, ClcContactDeleted));
+ arEvents.insert(HookEvent(ME_CLIST_CONTACTICONCHANGED, ClcContactIconChanged));
+ arEvents.insert(HookEvent(ME_SKIN_ICONSCHANGED, ClcIconsChanged));
+ arEvents.insert(HookEvent(ME_PROTO_ACK, ClcProtoAck));
+
+ InitCustomMenus();
+ return 0;
+}
+
+void UnloadClcModule()
+{
+ if (!bModuleInitialized)
+ return;
+
+ for (int i = 0; i < arEvents.getCount(); i++)
+ UnhookEvent(arEvents[i]);
+
+ mir_free(cli.clcProto);
+ WindowList_Destroy(hClcWindowList); hClcWindowList = NULL;
+
+ FreeDisplayNameCache();
+
+ UninitCustomMenus();
+ UnitGenMenu();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default contact list control window procedure
+
+LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ ClcGroup *group;
+ ClcContact *contact;
+ DWORD hitFlags;
+ int hit;
+
+ ClcData *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);
+ }
+ {
+ for (int 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 = db_get_w(NULL, "CLC", "InfoTipHoverTime", 750);
+ dat->extraColumnSpacing = 20;
+ dat->list.cl.increment = 30;
+ dat->needsResort = 1;
+ cli.pfnLoadClcOptions(hwnd, dat, TRUE);
+ 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, FALSE);
+ 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
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+ if (rc.right == 0)
+ break;
+
+ rc.bottom = dat->rowHeight;
+ HDC hdc = GetDC(hwnd);
+ int depth = GetDeviceCaps(hdc, BITSPIXEL);
+ if (depth < 16)
+ depth = 16;
+ HBITMAP hBmp = CreateBitmap(rc.right, rc.bottom, 1, depth, NULL);
+ HBITMAP hBmpMask = CreateBitmap(rc.right, rc.bottom, 1, 1, NULL);
+ HDC hdcMem = CreateCompatibleDC(hdc);
+ HBITMAP hoBmp = (HBITMAP) SelectObject(hdcMem, hBmp);
+ HBRUSH hBrush = CreateSolidBrush(dat->useWindowsColours ? GetSysColor(COLOR_HIGHLIGHT) : dat->selBkColour);
+ FillRect(hdcMem, &rc, hBrush);
+ DeleteObject(hBrush);
+
+ HBITMAP 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, ILC_COLOR32 | 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;
+ TCHAR szFullName[512];
+ int i, eq;
+ //check name of group and ignore message if just being expanded/collapsed
+ if (cli.pfnFindItem(hwnd, dat, groupId | HCONTACT_ISGROUP, &contact, &group, NULL)) {
+ mir_tstrcpy(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;
+ size_t nameLen = mir_tstrlen(group->cl.items[i]->szText);
+ if (mir_tstrlen(szFullName) + 1 + nameLen > SIZEOF(szFullName)) {
+ szFullName[0] = '\0';
+ break;
+ }
+ memmove(szFullName + 1 + nameLen, szFullName, sizeof(TCHAR)*(mir_tstrlen(szFullName) + 1));
+ memcpy(szFullName, group->cl.items[i]->szText, sizeof(TCHAR)*nameLen);
+ szFullName[nameLen] = '\\';
+ }
+
+ if (dbcws->value.type == DBVT_ASCIIZ) {
+ WCHAR* wszGrpName = mir_a2u(dbcws->value.pszVal+1);
+ eq = !mir_tstrcmp(szFullName, wszGrpName);
+ mir_free(wszGrpName);
+ }
+ else {
+ char* szGrpName = NEWSTR_ALLOCA(dbcws->value.pszVal+1);
+ WCHAR* wszGrpName;
+ Utf8Decode(szGrpName, &wszGrpName);
+ eq = !mir_tstrcmp(szFullName, wszGrpName);
+ mir_free(wszGrpName);
+ }
+ 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:
+ cli.pfnInitAutoRebuild(hwnd);
+ break;
+
+ case INTM_CONTACTADDED:
+ cli.pfnAddContactToTree(hwnd, dat, wParam, 1, 1);
+ cli.pfnNotifyNewContact(hwnd, wParam);
+ SortClcByTimer(hwnd);
+ break;
+
+ case INTM_CONTACTDELETED:
+ cli.pfnDeleteItemFromTree(hwnd, 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, wParam, NULL, NULL, NULL))
+ break;
+ cli.pfnAddContactToTree(hwnd, dat, wParam, 1, 1);
+ cli.pfnNotifyNewContact(hwnd, wParam);
+ }
+ else cli.pfnDeleteItemFromTree(hwnd, wParam);
+
+ dat->needsResort = 1;
+ SortClcByTimer(hwnd);
+ }
+ break;
+
+ case INTM_GROUPCHANGED:
+ {
+ WORD iExtraImage[EXTRA_ICON_COUNT];
+ BYTE flags = 0;
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ memset(iExtraImage, 0xFF, sizeof(iExtraImage));
+ else {
+ memcpy(iExtraImage, contact->iExtraImage, sizeof(iExtraImage));
+ flags = contact->flags;
+ }
+ cli.pfnDeleteItemFromTree(hwnd, wParam);
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN || !db_get_b(wParam, "CList", "Hidden", 0)) {
+ NMCLISTCONTROL nm;
+ cli.pfnAddContactToTree(hwnd, dat, wParam, 1, 1);
+ if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) {
+ memcpy(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:
+ {
+ int recalcScrollBar = 0, shouldShow;
+ WORD status;
+ MCONTACT hSelItem = NULL;
+ ClcContact *selcontact = NULL;
+
+ char *szProto = GetContactProto(wParam);
+ if (szProto == NULL)
+ status = ID_STATUS_OFFLINE;
+ else
+ status = db_get_w(wParam, szProto, "Status", ID_STATUS_OFFLINE);
+
+ // this means an offline msg is flashing, so the contact should be shown
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ shouldShow = (style & CLS_SHOWHIDDEN || !db_get_b(wParam, "CList", "Hidden", 0))
+ && (!cli.pfnIsHiddenMode(dat, status) || CallService(MS_CLIST_GETCONTACTICON, wParam, 0) != lParam);
+
+ contact = NULL;
+ group = NULL;
+ if (!cli.pfnFindItem(hwnd, dat, 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 = (MCONTACT)cli.pfnContactToHItem(selcontact);
+ cli.pfnAddContactToTree(hwnd, dat, wParam, (style & CLS_CONTACTLIST) == 0, 0);
+ recalcScrollBar = 1;
+ cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL);
+ if (contact) {
+ contact->iImage = (WORD) lParam;
+ cli.pfnNotifyNewContact(hwnd, 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 = (MCONTACT)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) {
+ 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:
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ break;
+
+ mir_tstrncpy(contact->szText, cli.pfnGetContactDisplayName(wParam, 0), SIZEOF(contact->szText));
+ dat->needsResort = 1;
+ SortClcByTimer(hwnd);
+ break;
+
+ case INTM_PROTOCHANGED:
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ break;
+
+ contact->proto = GetContactProto(wParam);
+ cli.pfnInvalidateDisplayNameCacheEntry(wParam);
+ mir_tstrncpy(contact->szText, cli.pfnGetContactDisplayName(wParam, 0), SIZEOF(contact->szText));
+ SortClcByTimer(hwnd);
+ break;
+
+ case INTM_NOTONLISTCHANGED:
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ break;
+
+ if (contact->type == CLCIT_CONTACT) {
+ DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam;
+ 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:
+ if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) {
+ char *szProto = GetContactProto(wParam);
+ if (szProto == NULL)
+ break;
+
+ WORD apparentMode = db_get_w(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:
+ if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) {
+ char *szProto = GetContactProto(wParam);
+ if (szProto == NULL)
+ break;
+ contact->flags &= ~CONTACTF_IDLE;
+ if (db_get_dw(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) {
+ 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:
+ {
+ PAINTSTRUCT ps;
+ HDC 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:
+ cli.pfnEndRename(hwnd, dat, 1);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ {
+ int desty = dat->yScroll, noSmooth = 0;
+
+ RECT clRect;
+ 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:
+ cli.pfnEndRename(hwnd, dat, 1);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ {
+ UINT scrollLines;
+ 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);
+ dat->szQuickSearch[0] = 0;
+ if (dat->filterSearch)
+ cli.pfnSaveStateAndRebuildList(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) {
+ if (!dat->filterSearch)
+ 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) {
+ if (!dat->filterSearch)
+ 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[mir_tstrlen(dat->szQuickSearch) - 1] = '\0';
+ else if (wParam < ' ')
+ break;
+ else if (wParam == ' ' && dat->szQuickSearch[0] == '\0' && GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CHECKBOXES) {
+ 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 (mir_tstrlen(dat->szQuickSearch) >= SIZEOF(dat->szQuickSearch) - 1) {
+ MessageBeep(MB_OK);
+ break;
+ }
+ mir_tstrcat(dat->szQuickSearch, szNew);
+ }
+
+ if (dat->filterSearch)
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+
+ 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[ mir_tstrlen(dat->szQuickSearch) - 1] = '\0';
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+ }
+ 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;
+ 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 : (HANDLE)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:
+ 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);
+ if (!dat->filterSearch)
+ 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) {
+ ClcGroup *selgroup;
+ 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) {
+
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ NMCLISTCONTROL nm;
+ 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;
+
+ 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:
+ 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 = { LOWORD(lParam), HIWORD(lParam) };
+ int target = cli.pfnGetDropTargetInformation(hwnd, dat, pt);
+ switch (target) {
+ case DROPTARGET_ONSELF:
+ case DROPTARGET_ONCONTACT:
+ break;
+
+ case DROPTARGET_ONGROUP:
+ {
+ 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];
+ mir_sntprintf(szNewName, SIZEOF(szNewName), _T("%s\\%s"), cli.pfnGetGroupName(contactn->groupId, NULL), contacto->szText);
+ cli.pfnRenameGroup(contacto->groupId, szNewName);
+ }
+ }
+ break;
+
+ case DROPTARGET_INSERTION:
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ {
+ ClcContact *destcontact;
+ ClcGroup *destgroup;
+ 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;
+ CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, destgroup->groupId);
+ }
+ }
+ break;
+ case DROPTARGET_OUTSIDE:
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ {
+ NMCLISTCONTROL nm;
+ 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:
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, &group);
+ if (!group->parent)
+ break;
+ if (contact->type == CLCIT_GROUP) { //dropee is a group
+ TCHAR szNewName[120];
+ mir_tstrncpy(szNewName, contact->szText, SIZEOF(szNewName));
+ cli.pfnRenameGroup(contact->groupId, szNewName);
+ }
+ else if (contact->type == CLCIT_CONTACT) //dropee is a contact
+ CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contact->hContact, 0);
+ }
+ }
+
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ dat->iDragItem = -1;
+ dat->iInsertionMark = -1;
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ ReleaseCapture();
+ dat->iHotTrack = -1;
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_RENAME);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+
+ 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);
+ dat->szQuickSearch[0] = 0;
+ if (dat->filterSearch)
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+ break;
+
+ case WM_CONTEXTMENU:
+ 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;
+ if (!dat->filterSearch)
+ dat->szQuickSearch[0] = 0;
+ {
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(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);
+
+ HMENU hMenu = NULL;
+ 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;
+ }
+ 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:
+ 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);
+
+ for (int 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->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/mir_app/src/clc.h b/src/mir_app/src/clc.h new file mode 100644 index 0000000000..3a79db0804 --- /dev/null +++ b/src/mir_app/src/clc.h @@ -0,0 +1,207 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+struct ClcContact : public ClcContactBase
+{
+};
+
+struct ClcData : public ClcDataBase
+{
+};
+
+struct ClcCacheEntry : public ClcCacheEntryBase
+{
+};
+
+/* clc.c */
+extern int g_IconWidth, g_IconHeight;
+
+void fnClcOptionsChanged(void);
+void fnClcBroadcast(int msg, WPARAM wParam, LPARAM lParam);
+HMENU fnBuildGroupPopupMenu(ClcGroup* group);
+void fnInitAutoRebuild(HWND hWnd);
+
+LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+/* clcidents.c */
+int fnGetRowsPriorTo(ClcGroup *group, ClcGroup *subgroup, int contactIndex);
+int fnFindItem(HWND hwnd, struct ClcData *dat, DWORD dwItem, ClcContact **contact, ClcGroup **subgroup, int *isVisible);
+int fnGetRowByIndex(struct ClcData *dat, int testindex, ClcContact **contact, ClcGroup **subgroup);
+HANDLE fnContactToHItem(ClcContact *contact);
+HANDLE fnContactToItemHandle(ClcContact *contact, DWORD *nmFlags);
+
+/* clcitems.c */
+ClcGroup* fnAddGroup(HWND hwnd, struct ClcData *dat, const TCHAR *szName, DWORD flags, int groupId, int calcTotalMembers);
+ClcGroup* fnRemoveItemFromGroup(HWND hwnd, ClcGroup *group, ClcContact *contact, int updateTotalCount);
+
+void fnFreeContact(ClcContact *p);
+void fnFreeGroup(ClcGroup *group);
+int fnAddInfoItemToGroup(ClcGroup *group, int flags, const TCHAR *pszText);
+int fnAddItemToGroup(ClcGroup *group, int iAboveItem);
+void fnAddContactToTree(HWND hwnd, struct ClcData *dat, MCONTACT hContact, int updateTotalCount, int checkHideOffline);
+int fnAddContactToGroup(struct ClcData *dat, ClcGroup *group, MCONTACT hContact);
+void fnDeleteItemFromTree(HWND hwnd, MCONTACT hItem);
+void fnRebuildEntireList(HWND hwnd, struct ClcData *dat);
+int fnGetGroupContentsCount(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 */
+TCHAR* fnGetGroupCountsText(struct ClcData *dat, ClcContact *contact);
+int fnHitTest(HWND hwnd, struct ClcData *dat, int testx, int testy, ClcContact **contact, 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, 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, ClcContact *contact, 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, MCONTACT 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, BOOL bFirst);
+void fnRecalculateGroupCheckboxes(HWND hwnd, struct ClcData *dat);
+void fnSetGroupChildCheckboxes(ClcGroup *group, int checked);
+void fnSetContactCheckboxes(ClcContact *cc, 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);
+void GetFontSetting(int i, LOGFONTA *lf, COLORREF *colour);
+
+/* clistmenus.c */
+HGENMENU fnGetProtocolMenu(const char*);
+int fnGetProtocolVisibility(const char* accName);
+int fnConvertMenu(CLISTMENUITEM*, TMO_MenuItem*);
+int fnGetAverageMode(int *pNetProtoCount);
+
+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(MCONTACT hContact, int mode);
+void fnGetDefaultFontSetting(int i, LOGFONT* lf, COLORREF * colour);
+void fnInvalidateDisplayNameCacheEntry(MCONTACT hContact);
+
+ClcCacheEntry* fnGetCacheEntry(MCONTACT hContact);
+ClcCacheEntry* fnCreateCacheItem (MCONTACT hContact);
+void fnCheckCacheItem(ClcCacheEntry *p);
+void fnFreeCacheItem(ClcCacheEntry *p);
+
+/* clcfiledrop.c */
+void InitFileDropping(void);
+
+void fnRegisterFileDropping (HWND hwnd);
+void fnUnregisterFileDropping (HWND hwnd);
+
+/* clistevents.c */
+struct CListEvent* fnAddEvent(CLISTEVENT *cle);
+CLISTEVENT* fnGetEvent(MCONTACT hContact, int idx);
+
+struct CListEvent* fnCreateEvent(void);
+void fnFreeEvent(struct CListEvent* p);
+
+int fnEventsProcessContactDoubleClick(MCONTACT hContact);
+int fnEventsProcessTrayDoubleClick(int);
+int fnGetImlIconIndex(HICON hIcon);
+int fnRemoveEvent(MCONTACT hContact, MEVENT dbEvent);
+
+/* clistmod.c */
+int fnGetContactIcon(MCONTACT hContact);
+int fnIconFromStatusMode(const char *szProto, int status, MCONTACT hContact);
+int fnShowHide(WPARAM wParam, LPARAM lParam);
+HICON fnGetIconFromStatusMode(MCONTACT hContact, const char *szProto, int status);
+TCHAR* fnGetStatusModeDescription(int wParam, int lParam);
+int fnGetWindowVisibleState(HWND hWnd, int iStepX, int iStepY);
+
+/* clisttray.c */
+extern mir_cs trayLockCS;
+
+void fnInitTray(void);
+void fnUninitTray(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);
+int fnTrayCalcChanged(const char *szChangedProto, int averageMode, int netProtoCount);
+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 (MCONTACT hContact, int iIcon, int add);
+void fnLoadContactTree (void);
+int fnCompareContacts (const ClcContact *contact1, const 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/mir_app/src/clcfiledrop.cpp b/src/mir_app/src/clcfiledrop.cpp new file mode 100644 index 0000000000..da3ab82e47 --- /dev/null +++ b/src/mir_app/src/clcfiledrop.cpp @@ -0,0 +1,277 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+#include <shlobj.h>
+
+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 MCONTACT HContactFromPoint(HWND hwnd, struct ClcData *dat, int x, int y, int *hitLine)
+{
+ DWORD hitFlags;
+ ClcContact *contact;
+ int hit = cli.pfnHitTest(hwnd, dat, x, y, &contact, NULL, &hitFlags);
+ if (hit == -1 || !(hitFlags & (CLCHT_ONITEMLABEL | CLCHT_ONITEMICON)) || contact->type != CLCIT_CONTACT)
+ return NULL;
+
+ char *szProto = GetContactProto(contact->hContact);
+ if (szProto == NULL)
+ return NULL;
+
+ DWORD protoCaps = CallProtoServiceInt(NULL,szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (!(protoCaps & PF1_FILESEND))
+ return NULL;
+ if (ID_STATUS_OFFLINE == db_get_w(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;
+ MCONTACT hContact;
+
+ if (pDropTargetHelper && hwndCurrentDrag)
+ pDropTargetHelper->DragOver((POINT*)&pt, *pdwEffect);
+
+ *pdwEffect = 0;
+ if (hwndCurrentDrag == NULL) {
+ *pdwEffect = DROPEFFECT_NONE;
+ return S_OK;
+ }
+ cli.pfnTrayIconPauseAutoHide(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 (!mir_tstrcmp(szWindowClass, _T(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];
+ mir_tstrcpy(szPath, szFilename);
+ mir_tstrcat(szPath, _T("\\*"));
+ if (hFind = FindFirstFile(szPath, &fd)) {
+ do {
+ if (!mir_tstrcmp(fd.cFileName, _T(".")) || !mir_tstrcmp(fd.cFileName, _T("..")))
+ continue;
+ mir_tstrcpy(szPath, szFilename);
+ mir_tstrcat(szPath, _T("\\"));
+ mir_tstrcat(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;
+
+ 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);
+ MCONTACT 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, 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/mir_app/src/clcidents.cpp b/src/mir_app/src/clcidents.cpp new file mode 100644 index 0000000000..20669f4e54 --- /dev/null +++ b/src/mir_app/src/clcidents.cpp @@ -0,0 +1,210 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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) ClcContact/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(ClcGroup *group, 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++;
+
+ ClcContact *cc = group->cl.items[group->scanIndex];
+ if (cc->type == CLCIT_GROUP) {
+ if (cc->group == subgroup && contactIndex == -1)
+ return count - 1;
+ if (cc->group->expanded) {
+ group = cc->group;
+ group->scanIndex = 0;
+ continue;
+ }
+ }
+ group->scanIndex++;
+ }
+ return -1;
+}
+
+int fnFindItem(HWND hwnd, struct ClcData *dat, DWORD dwItem, ClcContact **contact, ClcGroup **subgroup, int *isVisible)
+{
+ int index = 0;
+ int nowVisible = 1;
+ ClcGroup *group = &dat->list;
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ 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++;
+
+ ClcContact *cc = group->cl.items[group->scanIndex];
+ if ((IsHContactGroup(dwItem) && cc->type == CLCIT_GROUP && (dwItem & ~HCONTACT_ISGROUP) == cc->groupId) ||
+ (IsHContactContact(dwItem) && cc->type == CLCIT_CONTACT && cc->hContact == dwItem) ||
+ (IsHContactInfo(dwItem) && cc->type == CLCIT_INFO && cc->hContact == (dwItem & ~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 = cc;
+ if (subgroup)
+ *subgroup = group;
+ return 1;
+ }
+ if (cc->type == CLCIT_GROUP) {
+ group = cc->group;
+ group->scanIndex = 0;
+ nowVisible &= group->expanded;
+ continue;
+ }
+ group->scanIndex++;
+ }
+
+ if (isVisible) *isVisible = FALSE;
+ if (contact) *contact = NULL;
+ if (subgroup) *subgroup = NULL;
+ return 0;
+}
+
+int fnGetRowByIndex(struct ClcData *dat, int testindex, ClcContact **contact, ClcGroup **subgroup)
+{
+ int index = 0;
+ 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;
+ }
+
+ ClcContact *cc = group->cl.items[group->scanIndex];
+ if (testindex == index) {
+ if (contact)
+ *contact = cc;
+ if (subgroup)
+ *subgroup = group;
+ return index;
+ }
+ index++;
+ if (cc->type == CLCIT_GROUP && cc->group->expanded) {
+ group = cc->group;
+ group->scanIndex = 0;
+ continue;
+ }
+ group->scanIndex++;
+ }
+ return -1;
+}
+
+HANDLE fnContactToHItem(ClcContact *contact)
+{
+ switch (contact->type) {
+ case CLCIT_CONTACT:
+ return (HANDLE)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(ClcContact *contact, DWORD *nmFlags)
+{
+ switch (contact->type) {
+ case CLCIT_CONTACT:
+ return (HANDLE)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/mir_app/src/clcitems.cpp b/src/mir_app/src/clcitems.cpp new file mode 100644 index 0000000000..533c45aa8b --- /dev/null +++ b/src/mir_app/src/clcitems.cpp @@ -0,0 +1,705 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.h" +#include "clc.h" + +//routines for managing adding/removal of items in the list, including sorting + +int fnAddItemToGroup(ClcGroup *group, int iAboveItem) +{ + 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; +} + +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) ]; + 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) { + mir_tstrncpy(szThisField, pNextField, SIZEOF(szThisField)); + pNextField = NULL; + } + else { + mir_tstrncpy(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 = mir_tstrcmp(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; + mir_tstrncpy(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 = (ClcGroup *) mir_alloc(sizeof(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); + for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) { + ClcCacheEntry *cache = cli.pfnGetCacheEntry(hContact); + if (!mir_tstrcmp(cache->tszGroup, szName) && (style & CLS_SHOWHIDDEN || !cache->bIsHidden)) + group->totalMembers++; + } + } + } + } + while (pNextField); + return group; +} + +void fnFreeContact(ClcContact* p) +{ + if (p->type == CLCIT_GROUP) { + cli.pfnFreeGroup(p->group); + mir_free(p->group); + p->group = NULL; + } +} + +void fnFreeGroup(ClcGroup *group) +{ + if (!group) + return; + if (group->cl.items) { + for (int i=0; i < group->cl.count; i++) { + cli.pfnFreeContact(group->cl.items[i]); + mir_free(group->cl.items[i]); + } + mir_free(group->cl.items); + group->cl.items = NULL; + } + group->cl.limit = group->cl.count = 0; +} + +static int iInfoItemUniqueHandle = 0; +int fnAddInfoItemToGroup(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 = LOWORD(iInfoItemUniqueHandle+1); + if (iInfoItemUniqueHandle == 0) + ++iInfoItemUniqueHandle; + group->cl.items[i]->type = CLCIT_INFO; + group->cl.items[i]->flags = (BYTE) flags; + group->cl.items[i]->hContact = (MCONTACT)++iInfoItemUniqueHandle; + mir_tstrncpy(group->cl.items[i]->szText, pszText, SIZEOF(group->cl.items[i]->szText)); + return i; +} + +int fnAddContactToGroup(struct ClcData *dat, ClcGroup *group, MCONTACT hContact) +{ + 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); + char *szProto = GetContactProto(hContact); + group->cl.items[i]->type = CLCIT_CONTACT; + group->cl.items[i]->iImage = CallService(MS_CLIST_GETCONTACTICON, hContact, 0); + group->cl.items[i]->hContact = hContact; + group->cl.items[i]->proto = szProto; + if (szProto != NULL && !cli.pfnIsHiddenMode(dat, db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE))) + group->cl.items[i]->flags |= CONTACTF_ONLINE; + WORD apparentMode = szProto != NULL ? db_get_w(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 (db_get_b(hContact, "CList", "NotOnList", 0)) + group->cl.items[i]->flags |= CONTACTF_NOTONLIST; + DWORD idleMode = szProto != NULL ? db_get_dw(hContact, szProto, "IdleTS", 0) : 0; + if (idleMode) + group->cl.items[i]->flags |= CONTACTF_IDLE; + mir_tstrncpy(group->cl.items[i]->szText, cli.pfnGetContactDisplayName(hContact, 0), SIZEOF(group->cl.items[i]->szText)); + + ClcCacheEntry *p = cli.pfnGetCacheEntry(hContact); + if (p != NULL) + replaceStrT(p->tszGroup, NULL); + + return i; +} + +void fnAddContactToTree(HWND hwnd, struct ClcData *dat, MCONTACT hContact, int updateTotalCount, int checkHideOffline) +{ + ClcGroup *group; + DBVARIANT dbv; + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + WORD status = ID_STATUS_OFFLINE; + char *szProto = GetContactProto(hContact); + + dat->needsResort = 1; + if (style & CLS_NOHIDEOFFLINE) + checkHideOffline = 0; + if (checkHideOffline) + if (szProto != NULL) + status = db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE); + + if (db_get_ts(hContact, "CList", "Group", &dbv)) + group = &dat->list; + else { + group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, (DWORD) - 1, 0, 0); + if (group == NULL) { + int i; + 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; + } + if (!mir_tstrcmp(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; + } + if (!mir_tstrcmp(szGroupName, dbv.ptszVal)) + break; + size_t len = mir_tstrlen(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++; +} + +ClcGroup* fnRemoveItemFromGroup(HWND hwnd, ClcGroup *group, ClcContact *contact, int updateTotalCount) +{ + int iContact; + if ((iContact = List_IndexOf((SortedList*)&group->cl, contact)) == -1) + return group; + + if (contact->type == CLCIT_CONTACT) { + if (updateTotalCount) + group->totalMembers--; + + ClcCacheEntry *p = cli.pfnGetCacheEntry(contact->hContact); + if (p != NULL) + replaceStrT(p->tszGroup, 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 && group->parent != NULL) + for (int 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) + return cli.pfnRemoveItemFromGroup(hwnd, group->parent, group->parent->cl.items[i], 0); + + return group; +} + +void fnDeleteItemFromTree(HWND hwnd, MCONTACT hItem) +{ + ClcContact *contact; + 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 (db_get_ts(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) { + size_t len = mir_tstrlen(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) +{ + DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); + ClcGroup *group; + + dat->list.expanded = 1; + dat->list.hideOffline = db_get_b(NULL, "CLC", "HideOfflineRoot", 0) && style&CLS_USEGROUPS; + dat->list.cl.count = dat->list.cl.limit = 0; + dat->selection = -1; + + for (int i = 1;; i++) { + DWORD groupFlags; + TCHAR *szGroupName = cli.pfnGetGroupName(i, &groupFlags); + if (szGroupName == NULL) + break; + cli.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 0); + } + + for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) { + if (style & CLS_SHOWHIDDEN || !db_get_b(hContact, "CList", "Hidden", 0)) { + DBVARIANT dbv; + if (db_get_ts(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 (dat->filterSearch && dat->szQuickSearch[0] != '\0') { + TCHAR *name = cli.pfnGetContactDisplayName(hContact, 0); + TCHAR *lowered_name = CharLowerW(NEWTSTR_ALLOCA(name)); + TCHAR *lowered_search = CharLowerW(NEWTSTR_ALLOCA(dat->szQuickSearch)); + + if (_tcsstr(lowered_name, lowered_search)) + cli.pfnAddContactToGroup(dat, group, hContact); + } + else if (!(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { + char *szProto = GetContactProto(hContact); + if (szProto == NULL) { + if (!cli.pfnIsHiddenMode(dat, ID_STATUS_OFFLINE)) + cli.pfnAddContactToGroup(dat, group, hContact); + } + else if (!cli.pfnIsHiddenMode(dat, db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE))) + cli.pfnAddContactToGroup(dat, group, hContact); + } + else cli.pfnAddContactToGroup(dat, group, hContact); + } + } + } + + 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); + cli.pfnSetAllExtraIcons(0); +} + +int fnGetGroupContentsCount(ClcGroup *group, int visibleOnly) +{ + int count = group->cl.count; + 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 mir_tstrcmpi(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(ClcContact **pContactArray, int nArray, int (*CompareProc) (const void *, const void *)) +{ + int i, j; + 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, 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; + mir_tstrcpy(group->cl.items[i]->szText, TranslateT("Offline")); + } + break; + } + } + } +} + +void fnSortCLC(HWND hwnd, struct ClcData *dat, int useInsertionSort) +{ + ClcContact *selcontact; + ClcGroup *group = &dat->list, *selgroup; + MCONTACT hSelItem; + + if (dat->needsResort) { + if (cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) == -1) + hSelItem = NULL; + else + hSelItem = (MCONTACT)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 +{ + MCONTACT hContact; + WORD iExtraImage[EXTRA_ICON_COUNT]; + 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<SavedContactState_t> saveContact(10, NumericKeySortT); + OBJLIST<SavedGroupState_t> saveGroup(100, NumericKeySortT); + OBJLIST<SavedInfoState_t> 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; + memcpy(p->iExtraImage, group->cl.items[group->scanIndex]->iExtraImage, sizeof(p->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) { + memcpy(group->cl.items[group->scanIndex]->iExtraImage, p->iExtraImage, sizeof(p->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, 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/mir_app/src/clcmsgs.cpp b/src/mir_app/src/clcmsgs.cpp new file mode 100644 index 0000000000..2706c6780b --- /dev/null +++ b/src/mir_app/src/clcmsgs.cpp @@ -0,0 +1,474 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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, 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 NULL;
+ if (cii->hParentGroup == NULL)
+ group = &dat->list;
+ else {
+ if (!cli.pfnFindItem(hwnd, dat, int(cii->hParentGroup) | HCONTACT_ISGROUP, &groupContact, NULL, NULL))
+ return NULL;
+ group = groupContact->group;
+ }
+ 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);
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ return (LRESULT)group->cl.items[i]->hContact | HCONTACT_ISINFO;
+ }
+
+ case CLM_AUTOREBUILD:
+ KillTimer(hwnd, TIMERID_REBUILDAFTER);
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+ cli.bAutoRebuild = false;
+ break;
+
+ case CLM_DELETEITEM:
+ cli.pfnDeleteItemFromTree(hwnd, 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, 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, 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, wParam, NULL, NULL, NULL))
+ return NULL;
+ return wParam;
+
+ case CLM_FINDGROUP:
+ if (!cli.pfnFindItem(hwnd, dat, wParam | HCONTACT_ISGROUP, NULL, NULL, NULL))
+ return NULL;
+ return wParam | HCONTACT_ISGROUP;
+
+ case CLM_GETBKCOLOR:
+ return dat->bkColour;
+
+ case CLM_GETCHECKMARK:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, 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, wParam, &contact, NULL, NULL))
+ return CLE_INVALID;
+ if (contact->type != CLCIT_GROUP)
+ return CLE_INVALID;
+ return contact->group->expanded;
+ }
+
+ case CLM_SETEXTRASPACE:
+ dat->extraColumnSpacing = (int)wParam;
+ cli.pfnInvalidateRect(hwnd,NULL,FALSE);
+ return 0;
+
+ case CLM_GETEXTRACOLUMNS:
+ return dat->extraColumnsCount;
+
+ case CLM_GETEXTRAIMAGE:
+ if (LOWORD(lParam) < dat->extraColumnsCount) {
+ ClcContact *contact;
+ if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ return contact->iExtraImage[LOWORD(lParam)];
+ }
+ return EMPTY_EXTRA_ICON;
+
+ case CLM_GETEXTRAIMAGELIST:
+ return (LRESULT)dat->himlExtraColumns;
+
+ case CLM_GETFONT:
+ if (wParam > FONTID_MAX)
+ return 0;
+ return (LRESULT)dat->fontInfo[wParam].hFont;
+
+ case CLM_GETHIDEOFFLINEROOT:
+ return db_get_b(NULL, "CLC", "HideOfflineRoot", 0);
+
+ case CLM_GETINDENT:
+ return dat->groupIndent;
+
+ case CLM_GETISEARCHSTRING:
+ mir_tstrcpy((TCHAR*) lParam, dat->szQuickSearch);
+ return mir_tstrlen(dat->szQuickSearch);
+
+ case CLM_GETITEMTEXT:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ return 0;
+ mir_tstrcpy((TCHAR*) lParam, contact->szText);
+ return mir_tstrlen(contact->szText);
+ }
+
+ case CLM_GETITEMTYPE:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, 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, 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 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 > FONTID_MAX)
+ return 0;
+ return (LRESULT)dat->fontInfo[wParam].colour;
+
+ case CLM_HITTEST:
+ {
+ ClcContact *contact;
+ DWORD hitFlags;
+ int 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, 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->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->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, 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 > EXTRA_ICON_COUNT)
+ return 0;
+
+ dat->extraColumnsCount = wParam;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETEXTRAIMAGE:
+ if ( LOWORD(lParam) < dat->extraColumnsCount) {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ return 0;
+
+ contact->iExtraImage[LOWORD(lParam)] = 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) > 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);
+ cli.pfnInitAutoRebuild(hwnd);
+ break;
+
+ case CLM_SETHIDEOFFLINEROOT:
+ db_set_b(NULL, "CLC", "HideOfflineRoot", (BYTE) wParam);
+ cli.pfnInitAutoRebuild(hwnd);
+ break;
+
+ case CLM_SETINDENT:
+ dat->groupIndent = wParam;
+ cli.pfnInitAutoRebuild(hwnd);
+ break;
+
+ case CLM_SETITEMTEXT:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL))
+ break;
+ mir_tstrncpy(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;
+ cli.pfnInitAutoRebuild(hwnd);
+ break;
+
+ case CLM_SETSCROLLTIME:
+ dat->scrollTime = wParam;
+ break;
+
+ case CLM_SETTEXTCOLOR:
+ if (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);
+ cli.pfnInitAutoRebuild(hwnd);
+ break;
+ }
+ return 0;
+}
diff --git a/src/mir_app/src/clcutils.cpp b/src/mir_app/src/clcutils.cpp new file mode 100644 index 0000000000..fa0c1ad919 --- /dev/null +++ b/src/mir_app/src/clcutils.cpp @@ -0,0 +1,877 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+
+//loads of stuff that didn't really fit anywhere else
+
+extern HANDLE hHideInfoTipEvent;
+
+TCHAR* fnGetGroupCountsText(struct ClcData *dat, ClcContact *contact)
+{
+ if (contact->type != CLCIT_GROUP || !(dat->exStyle & CLS_EX_SHOWGROUPCOUNTS))
+ return _T("");
+
+ ClcGroup *group = contact->group, *topgroup = group;
+ int onlineCount = 0;
+ int 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 _T("");
+
+ static TCHAR szName[32];
+ mir_sntprintf(szName, _T("(%u/%u)"), onlineCount, totalCount);
+ return szName;
+}
+
+int fnHitTest(HWND hwnd, struct ClcData *dat, int testx, int testy, ClcContact **contact, ClcGroup **group, DWORD * flags)
+{
+ ClcContact *hitcontact = NULL;
+ ClcGroup *hitgroup = NULL;
+ int indent, i;
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+
+ if (flags)
+ *flags = 0;
+
+ POINT pt;
+ 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 || !(GetWindowLongPtr(hwndTemp, GWL_STYLE) & BS_GROUPBOX))
+ return -1;
+ }
+ while (hwndParent);
+
+ RECT clRect;
+ 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;
+ }
+ int 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;
+ }
+ int 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;
+ }
+
+ int eiOffset = 0;
+ for (i = dat->extraColumnsCount-1; i >= 0; i--) {
+ if (hitcontact->iExtraImage[i] == EMPTY_EXTRA_ICON)
+ continue;
+
+ eiOffset += dat->extraColumnSpacing;
+ if (testx >= clRect.right - eiOffset && testx < clRect.right - eiOffset + g_IconWidth) {
+ if (flags)
+ *flags |= CLCHT_ONITEMEXTRA | (i << 24);
+ return hit;
+ }
+ }
+
+ HDC hdc = GetDC(hwnd);
+ HFONT hFont = (HFONT)SelectObject(hdc, dat->fontInfo[hitcontact->type == CLCIT_GROUP ? FONTID_GROUPS : FONTID_CONTACTS].hFont);
+
+ SIZE textSize;
+ GetTextExtentPoint32(hdc, hitcontact->szText, (int)mir_tstrlen(hitcontact->szText), &textSize);
+ int width = textSize.cx;
+ if (hitcontact->type == CLCIT_GROUP) {
+ TCHAR *szCounts;
+ szCounts = cli.pfnGetGroupCountsText(dat, hitcontact);
+ if (szCounts[0]) {
+ GetTextExtentPoint32(hdc, _T(" "), 1, &textSize);
+ width += textSize.cx;
+ SelectObject(hdc, dat->fontInfo[FONTID_GROUPCOUNTS].hFont);
+ GetTextExtentPoint32(hdc, szCounts, (int)mir_tstrlen(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, 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)
+{
+ 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)
+{
+ ClcGroup *group = &dat->list;
+ size_t testlen = mir_tstrlen(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) {
+ bool show;
+ if (dat->filterSearch) {
+ TCHAR *lowered_szText = CharLowerW(NEWTSTR_ALLOCA(group->cl.items[group->scanIndex]->szText));
+ TCHAR *lowered_text = CharLowerW(NEWTSTR_ALLOCA(text));
+ show = _tcsstr(lowered_szText, lowered_text) != NULL;
+ }
+ else show = ((prefixOk && !_tcsnicmp(text, group->cl.items[group->scanIndex]->szText, testlen)) || (!prefixOk && !mir_tstrcmpi(text, group->cl.items[group->scanIndex]->szText)));
+
+ if (show) {
+ 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 (mir_tstrcmp(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] || !mir_tstrcmp(otherName, text))
+ db_unset(contact->hContact, "CList", "MyHandle");
+ else
+ db_set_ts(contact->hContact, "CList", "MyHandle", text);
+ mir_free(otherName);
+ }
+ }
+ }
+ }
+ DestroyWindow(hwndEdit);
+}
+
+void fnDeleteFromContactList(HWND hwnd, struct ClcData *dat)
+{
+ 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 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 mir_callNextSubclass(hwnd, RenameEditSubclassProc, msg, wParam, lParam);
+}
+
+void fnBeginRenameSelection(HWND hwnd, struct ClcData *dat)
+{
+ ClcContact *contact;
+ ClcGroup *group;
+ POINT pt;
+
+ 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;
+
+ RECT clRect;
+ GetClientRect(hwnd, &clRect);
+ cli.pfnCalcEipPosition(dat, contact, group, &pt);
+ int 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);
+ mir_subclassWindow(dat->hwndRenameEdit, 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, ClcContact *, 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;
+ GetClientRect(hwnd, &clRect);
+ dat->selection = dat->iDragItem;
+ dat->iInsertionMark = -1;
+ if (!PtInRect(&clRect, pt))
+ return DROPTARGET_OUTSIDE;
+
+ ClcContact *contact, *movecontact;
+ ClcGroup *group, *movegroup;
+ DWORD hitFlags;
+ int 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) {
+ ClcContact *bottomcontact = NULL, *topcontact = NULL;
+ 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)
+{
+ if (dat->hInfoTipItem == NULL)
+ return;
+
+ CLCINFOTIP it = { 0 };
+ 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, MCONTACT hContact)
+{
+ NMCLISTCONTROL nm;
+ nm.hdr.code = CLN_NEWCONTACT;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = (HANDLE)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)
+{
+ cli.pfnGetDefaultFontSetting(i, lf, colour);
+
+ char idstr[20];
+ mir_snprintf(idstr, "Font%dName", i);
+ ptrT tszFace(db_get_tsa(NULL, "CLC", idstr));
+ if (tszFace)
+ mir_tstrcpy(lf->lfFaceName, tszFace);
+
+ mir_snprintf(idstr, "Font%dCol", i);
+ *colour = db_get_dw(NULL, "CLC", idstr, *colour);
+
+ mir_snprintf(idstr, "Font%dSize", i);
+ lf->lfHeight = (char)db_get_b(NULL, "CLC", idstr, lf->lfHeight);
+
+ mir_snprintf(idstr, "Font%dSty", i);
+ BYTE style = (BYTE)db_get_b(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, "Font%dSet", i);
+ lf->lfCharSet = db_get_b(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, BOOL bFirst)
+{
+ dat->rowHeight = db_get_b(NULL, "CLC", "RowHeight", CLCDEFAULT_ROWHEIGHT);
+
+ dat->leftMargin = db_get_b(NULL, "CLC", "LeftMargin", CLCDEFAULT_LEFTMARGIN);
+ dat->exStyle = db_get_dw(NULL, "CLC", "ExStyle", cli.pfnGetDefaultExStyle());
+ dat->scrollTime = db_get_w(NULL, "CLC", "ScrollTime", CLCDEFAULT_SCROLLTIME);
+ dat->groupIndent = db_get_b(NULL, "CLC", "GroupIndent", CLCDEFAULT_GROUPINDENT);
+ dat->gammaCorrection = db_get_b(NULL, "CLC", "GammaCorrect", CLCDEFAULT_GAMMACORRECT);
+ dat->showIdle = db_get_b(NULL, "CLC", "ShowIdle", CLCDEFAULT_SHOWIDLE);
+ dat->noVScrollbar = db_get_b(NULL, "CLC", "NoVScrollBar", 0);
+ dat->filterSearch = db_get_b(NULL, "CLC", "FilterSearch", 1);
+ SendMessage(hwnd, INTM_SCROLLBARCHANGED, 0, 0);
+
+ dat->greyoutFlags = db_get_dw(NULL, "CLC", "GreyoutFlags", CLCDEFAULT_GREYOUTFLAGS);
+ dat->offlineModes = db_get_dw(NULL, "CLC", "OfflineModes", CLCDEFAULT_OFFLINEMODES);
+ dat->selBkColour = db_get_dw(NULL, "CLC", "SelBkColour", CLCDEFAULT_SELBKCOLOUR);
+ dat->selTextColour = db_get_dw(NULL, "CLC", "SelTextColour", CLCDEFAULT_SELTEXTCOLOUR);
+ dat->hotTextColour = db_get_dw(NULL, "CLC", "HotTextColour", CLCDEFAULT_HOTTEXTCOLOUR);
+ dat->quickSearchColour = db_get_dw(NULL, "CLC", "QuickSearchColour", CLCDEFAULT_QUICKSEARCHCOLOUR);
+ dat->useWindowsColours = db_get_b(NULL, "CLC", "UseWinColours", CLCDEFAULT_USEWINDOWSCOLOURS);
+
+ if (cli.hwndContactTree != NULL && hwnd != cli.hwndContactTree) {
+ dat->bkChanged = true; // block custom background
+ dat->bkColour = GetSysColor(COLOR_WINDOW);
+ if (dat->hBmpBackground) {
+ DeleteObject(dat->hBmpBackground);
+ dat->hBmpBackground = NULL;
+ }
+
+ dat->greyoutFlags = 0;
+ dat->leftMargin = 4;
+ dat->groupIndent = 10;
+
+ LPARAM dwColor = GetSysColor(COLOR_WINDOWTEXT);
+ for (int i=0; i <= FONTID_MAX; i++)
+ SendMessage(hwnd, CLM_SETTEXTCOLOR, i, dwColor);
+ }
+
+ if (!dat->bkChanged) {
+ dat->bkColour = db_get_dw(NULL, "CLC", "BkColour", CLCDEFAULT_BKCOLOUR);
+ if (dat->hBmpBackground) {
+ DeleteObject(dat->hBmpBackground);
+ dat->hBmpBackground = NULL;
+ }
+ if (db_get_b(NULL, "CLC", "UseBitmap", CLCDEFAULT_USEBITMAP)) {
+ ptrT tszBitmap(db_get_tsa(NULL, "CLC", "BkBitmap"));
+ if (tszBitmap)
+ dat->hBmpBackground = Bitmap_Load(tszBitmap);
+ }
+ dat->backgroundBmpUse = db_get_w(NULL, "CLC", "BkBmpUse", CLCDEFAULT_BKBMPUSE);
+ }
+
+ NMHDR hdr;
+ hdr.code = CLN_OPTIONSCHANGED;
+ hdr.hwndFrom = hwnd;
+ hdr.idFrom = (bFirst) ? 0 : 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)
+{
+ ClcGroup *group = &dat->list;
+ group->scanIndex = GSIF_ALLCHECKED;
+ for (;;) {
+ if ((group->scanIndex & GSIF_INDEXMASK) == group->cl.count) {
+ int 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 fnSetContactCheckboxes(ClcContact *cc, int checked)
+{
+ if (checked)
+ cc->flags |= CONTACTF_CHECKED;
+ else
+ cc->flags &= ~CONTACTF_CHECKED;
+}
+
+void fnSetGroupChildCheckboxes(ClcGroup *group, int checked)
+{
+ for (int i=0; i < group->cl.count; i++) {
+ ClcContact *cc = group->cl.items[i];
+ if (cc->type == CLCIT_GROUP) {
+ cli.pfnSetGroupChildCheckboxes(cc->group, checked);
+ cli.pfnSetContactCheckboxes(cc, checked);
+ }
+ else if (cc->type == CLCIT_CONTACT)
+ cli.pfnSetContactCheckboxes(cc, checked);
+ }
+}
+
+void fnInvalidateItem(HWND hwnd, struct ClcData *dat, int iItem)
+{
+ if (iItem == -1)
+ return;
+
+ RECT rc;
+ 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/mir_app/src/clistcore.cpp b/src/mir_app/src/clistcore.cpp new file mode 100644 index 0000000000..b4f5442bf9 --- /dev/null +++ b/src/mir_app/src/clistcore.cpp @@ -0,0 +1,232 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+#include "genmenu.h"
+#include "extraicons.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 ClcContact* fnCreateClcContact(void)
+{
+ return (ClcContact*)mir_calloc(sizeof(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 (db_get_b(NULL, "CList", "MoveProtoMenus", TRUE))
+ BuildProtoMenus();
+ cli.pfnCluiProtocolStatusChanged(0, 0);
+}
+
+static INT_PTR srvRetrieveInterface(WPARAM, 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.pfnSetContactCheckboxes = fnSetContactCheckboxes;
+ 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.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.pfnTrayCalcChanged = fnTrayCalcChanged;
+ 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.pfnConvertMenu = fnConvertMenu;
+
+ cli.pfnReloadExtraIcons = fnReloadExtraIcons;
+ cli.pfnSetAllExtraIcons = fnSetAllExtraIcons;
+
+ cli.pfnGetContactIcon = fnGetContactIcon;
+ cli.pfnGetAverageMode = fnGetAverageMode;
+ cli.pfnInitAutoRebuild = fnInitAutoRebuild;
+
+ 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/mir_app/src/clistevents.cpp b/src/mir_app/src/clistevents.cpp new file mode 100644 index 0000000000..29b71a2a5a --- /dev/null +++ b/src/mir_app/src/clistevents.cpp @@ -0,0 +1,423 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 && idx >= cli.events.count)
+ return NULL;
+
+ CListEvent *ev = cli.events.items[idx];
+ if (ev->cle.hContact != NULL)
+ return GetContactProto(ev->cle.hContact);
+
+ return (ev->cle.flags & CLEF_PROTOCOLGLOBAL) ? ev->cle.lpszProtocol : 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 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
+ mir_cslock lck(trayLockCS);
+ char **pTrayProtos = (char**)_alloca(sizeof(char*)*cli.trayIconCount);
+ int nTrayProtoCnt = 0;
+ for (int i = 0; i < cli.trayIconCount; i++)
+ if (cli.trayIcon[i].id != 0 && cli.trayIcon[i].szProto)
+ pTrayProtos[nTrayProtoCnt++] = cli.trayIcon[i].szProto;
+
+ for (int i = 0; i < cli.events.count; i++) {
+ char *iEventProto = GetEventProtocol(i);
+
+ int j;
+ for (j = 0; j < nTrayProtoCnt; j++)
+ if (iEventProto && pTrayProtos[j] && !mir_strcmp(pTrayProtos[j], iEventProto))
+ break;
+ if (j >= nTrayProtoCnt) // event was not found so assume first icon
+ j = 0;
+ if (pTrayProtos[j]) // if not already set
+ ShowOneEventInTray(i); // show it
+ pTrayProtos[j] = NULL; // and clear slot
+ }
+}
+
+static VOID CALLBACK IconFlashTimer(HWND, UINT, UINT_PTR idEvent, DWORD)
+{
+ ShowEventsInTray();
+
+ for (int i=0; i < cli.events.count; i++) {
+ int j;
+ 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;
+}
+
+CListEvent* fnAddEvent(CLISTEVENT *cle)
+{
+ int i;
+
+ 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;
+
+ CListEvent *p = cli.pfnCreateEvent();
+ if (p == 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 (p->cle.flags & CLEF_UNICODE)
+ p->cle.ptszTooltip = mir_tstrdup(p->cle.ptszTooltip);
+ else
+ p->cle.ptszTooltip = mir_a2u(p->cle.pszTooltip); //if no flag defined it handled as unicode
+ if (cli.events.count == 1) {
+ char *szProto;
+ if (cle->hContact == NULL) {
+ if (cle->flags & CLEF_PROTOCOLGLOBAL)
+ szProto = cle->lpszProtocol;
+ else
+ szProto = NULL;
+ }
+ else szProto = GetContactProto(cle->hContact);
+
+ iconsOn = 1;
+ flashTimerId = SetTimer(NULL, 0, db_get_w(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(MCONTACT hContact, MEVENT dbEvent)
+{
+ // Find the event that should be removed
+ int i;
+ 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
+ char *szProto = GetContactProto(hContact);
+ 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
+ int nSameProto = 0;
+ char *szEventProto;
+ for (int i = 0; i < cli.events.count; i++) {
+ if (cli.events.items[i]->cle.hContact)
+ szEventProto = GetContactProto((cli.events.items[i]->cle.hContact));
+ else if (cli.events.items[i]->cle.flags & CLEF_PROTOCOLGLOBAL)
+ szEventProto = (char *)cli.events.items[i]->cle.lpszProtocol;
+ else
+ szEventProto = NULL;
+ if (szEventProto && szProto && !mir_strcmp(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 = GetContactProto(cli.events.items[0]->cle.hContact);
+ cli.pfnTrayIconUpdateWithImageList(iconsOn ? cli.events.items[0]->imlIconIndex : 0, cli.events.items[0]->cle.ptszTooltip, szProto);
+ }
+
+ return 0;
+}
+
+CLISTEVENT* fnGetEvent(MCONTACT hContact, int idx)
+{
+ if (hContact == INVALID_CONTACT_ID) {
+ 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(MCONTACT hContact)
+{
+ for (int i = 0; i < cli.events.count; i++) {
+ if (cli.events.items[i]->cle.hContact == hContact) {
+ MEVENT 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 == 0)
+ return 1;
+
+ int eventIndex = 0;
+
+ mir_cslockfull lck(trayLockCS);
+ if (cli.trayIconCount > 1 && index > 0) {
+ int i;
+ char *szProto = NULL;
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == index) {
+ szProto = cli.trayIcon[i].szProto;
+ if (i == 0)
+ click_in_first_icon = TRUE;
+ break;
+ }
+ }
+ if (szProto) {
+ for (i = 0; i < cli.events.count; i++) {
+ char *eventProto = NULL;
+ if (cli.events.items[i]->cle.hContact)
+ eventProto = GetContactProto(cli.events.items[i]->cle.hContact);
+ if (!eventProto)
+ eventProto = cli.events.items[i]->cle.lpszProtocol;
+
+ if (!eventProto || !_strcmpi(eventProto, szProto)) {
+ eventIndex = i;
+ break;
+ }
+ }
+
+ // let's process backward try to find first event without desired proto in tray
+ if (i == cli.events.count) {
+ if (click_in_first_icon) {
+ for (i = 0; i < cli.events.count; i++) {
+ char *eventProto = NULL;
+ if (cli.events.items[i]->cle.hContact)
+ eventProto = GetContactProto(cli.events.items[i]->cle.hContact);
+ if (!eventProto)
+ eventProto = cli.events.items[i]->cle.lpszProtocol;
+ if (!eventProto)
+ continue;
+
+ int j;
+ for (j = 0; j < cli.trayIconCount; j++)
+ if (cli.trayIcon[j].szProto && !_strcmpi(eventProto, cli.trayIcon[j].szProto))
+ break;
+
+ if (j == cli.trayIconCount) {
+ eventIndex = i;
+ break;
+ }
+ }
+ }
+ if (i == cli.events.count) //not found
+ return 1; //continue processing to show contact list
+ }
+ }
+ }
+ lck.unlock();
+
+ MCONTACT hContact = cli.events.items[eventIndex]->cle.hContact;
+ MEVENT hDbEvent = cli.events.items[eventIndex]->cle.hDbEvent;
+ // ; may be better to show send msg?
+ CallService(cli.events.items[eventIndex]->cle.pszService, 0, (LPARAM)& cli.events.items[eventIndex]->cle);
+ cli.pfnRemoveEvent(hContact, hDbEvent);
+ return 0;
+}
+
+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 == wParam) {
+ cli.pfnRemoveEvent(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 hContact, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;
+ if (hContact == NULL && cws && cws->szModule && cws->szSetting && mir_strcmp(cws->szModule, "CList") == 0) {
+ if (mir_strcmp(cws->szSetting, "DisableTrayFlash") == 0)
+ disableTrayFlash = (int)cws->value.bVal;
+ else if (mir_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(wParam, lParam); }
+INT_PTR GetEventStub(WPARAM wParam, LPARAM lParam) { return (INT_PTR)cli.pfnGetEvent(wParam, (int)lParam); }
+
+int InitCListEvents(void)
+{
+ memset(&cli.events, 0, sizeof(cli.events));
+ cli.events.increment = 10;
+
+ disableTrayFlash = db_get_b(NULL, "CList", "DisableTrayFlash", 0);
+ disableIconFlash = db_get_b(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;
+}
+
+CListEvent* fnCreateEvent(void)
+{
+ return (CListEvent*)mir_calloc(sizeof(CListEvent));
+}
+
+void fnFreeEvent(CListEvent *p)
+{
+ mir_free(p->cle.pszService);
+ mir_free(p->cle.pszTooltip);
+ mir_free(p);
+}
+
+void UninitCListEvents(void)
+{
+ if (cli.events.count)
+ KillTimer(NULL, flashTimerId);
+
+ for (int i=0; i < cli.events.count; i++)
+ cli.pfnFreeEvent((CListEvent*)cli.events.items[i]);
+ List_Destroy((SortedList*)&cli.events);
+
+ if (imlIcon != NULL)
+ mir_free(imlIcon);
+}
diff --git a/src/mir_app/src/clistmenus.cpp b/src/mir_app/src/clistmenus.cpp new file mode 100644 index 0000000000..78eb3a8433 --- /dev/null +++ b/src/mir_app/src/clistmenus.cpp @@ -0,0 +1,1351 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 HMENU hMainMenu, hStatusMenu = 0;
+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
+};
+
+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)
+struct MainMenuExecParam
+{
+ char *szServiceName;
+ TCHAR *szMenuName;
+ int Param1;
+};
+
+//contactmenu exec param(ownerdata)
+//also used in checkservice
+struct ContactMenuExecParam
+{
+ char *szServiceName;
+ char *pszContactOwner;//for check proc
+ int param;
+};
+
+struct BuildContactParam
+{
+ char *szProto;
+ int isOnList;
+ int isOnline;
+};
+
+struct StatusMenuExecParam
+{
+ char *proto; //This is unique protoname
+ int protoindex;
+ int status;
+
+ BOOL custom;
+ char *svc;
+ HANDLE hMenuItem;
+};
+
+struct MenuItemData
+{
+ HMENU OwnerMenu;
+ int position;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// service functions
+
+void FreeMenuProtos(void)
+{
+ if (cli.menuProtos) {
+ for (int i = 0; i < cli.menuProtoCount; i++)
+ mir_free(cli.menuProtos[i].szProto);
+ mir_free(cli.menuProtos);
+ cli.menuProtos = NULL;
+ }
+ cli.menuProtoCount = 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+int fnGetAverageMode(int *pNetProtoCount)
+{
+ int netProtoCount = 0, averageMode = 0;
+
+ for (int i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *pa = accounts[i];
+ if (cli.pfnGetProtocolVisibility(pa->szModuleName) == 0 || Proto_IsAccountLocked(pa))
+ continue;
+
+ netProtoCount++;
+
+ if (averageMode == 0)
+ averageMode = CallProtoServiceInt(NULL, pa->szModuleName, PS_GETSTATUS, 0, 0);
+ else if (averageMode > 0 && averageMode != CallProtoServiceInt(NULL, pa->szModuleName, PS_GETSTATUS, 0, 0)) {
+ averageMode = -1;
+ if (pNetProtoCount == NULL)
+ break;
+ }
+ }
+
+ if (pNetProtoCount) *pNetProtoCount = netProtoCount;
+ return averageMode;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MAIN MENU
+
+static INT_PTR BuildMainMenu(WPARAM, LPARAM)
+{
+ ListParam param = { 0 };
+ param.MenuObjectHandle = hMainMenuObject;
+
+ NotifyEventHooks(hPreBuildMainMenuEvent, 0, 0);
+
+ CallService(MO_BUILDMENU, (WPARAM)hMainMenu, (LPARAM)¶m);
+ DrawMenuBar((HWND)CallService("CLUI/GetHwnd", 0, 0));
+ return (INT_PTR)hMainMenu;
+}
+
+static INT_PTR AddMainMenuItem(WPARAM, LPARAM lParam)
+{
+ TMO_MenuItem tmi;
+ CLISTMENUITEM *mi = (CLISTMENUITEM*)lParam;
+ if (!cli.pfnConvertMenu(mi, &tmi))
+ return 0;
+
+ MainMenuExecParam *mmep = (MainMenuExecParam*)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)
+{
+ MainMenuExecParam *mmep = (MainMenuExecParam*)wParam;
+ if (mmep != NULL) {
+ // bug in help.c, it used wparam as parent window handle without reason.
+ if (!mir_strcmp(mmep->szServiceName, "Help/AboutCommand"))
+ mmep->Param1 = 0;
+
+ CallService(mmep->szServiceName, mmep->Param1, lParam);
+ }
+ return 1;
+}
+
+INT_PTR FreeOwnerDataMainMenu(WPARAM, LPARAM lParam)
+{
+ MainMenuExecParam *mmep = (MainMenuExecParam*)lParam;
+ if (mmep != NULL) {
+ FreeAndNil((void**)&mmep->szServiceName);
+ FreeAndNil((void**)&mmep);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CONTACT MENU
+
+static INT_PTR AddContactMenuItem(WPARAM, LPARAM lParam)
+{
+ TMO_MenuItem tmi;
+ CLISTMENUITEM *mi = (CLISTMENUITEM*)lParam;
+ if (!cli.pfnConvertMenu(mi, &tmi))
+ return 0;
+
+ if (!(mi->flags & CMIF_ROOTHANDLE)) {
+ //old system
+ tmi.flags |= CMIF_ROOTHANDLE;
+ tmi.root = NULL;
+ }
+
+ //owner data
+ ContactMenuExecParam *cmep = (ContactMenuExecParam*)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, "%s/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", (mi->pszService) ? mi->pszService : "");
+ else if (mi->ptszName) {
+ if (tmi.flags & CMIF_UNICODE)
+ mir_snprintf(buf, "%s/NoService/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", _T2A(mi->ptszName));
+ else
+ mir_snprintf(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 hContact, LPARAM)
+{
+ NotifyEventHooks(hPreBuildContactMenuEvent, hContact, 0);
+
+ char *szProto = GetContactProto(hContact);
+
+ BuildContactParam bcp;
+ bcp.szProto = szProto;
+ bcp.isOnList = (db_get_b(hContact, "CList", "NotOnList", 0) == 0);
+ bcp.isOnline = (szProto != NULL && ID_STATUS_OFFLINE != db_get_w(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) {
+ ContactMenuExecParam *cmep = (ContactMenuExecParam*)wParam;
+ //call with wParam = (MCONTACT)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;
+ if (pcpp == NULL)
+ return FALSE;
+
+ BuildContactParam *bcp = (BuildContactParam*)pcpp->wParam;
+ if (bcp == NULL)
+ return FALSE;
+
+ ContactMenuExecParam *cmep = (ContactMenuExecParam*)pcpp->MenuItemOwnerData;
+ if (cmep == NULL) //this is root...build it
+ return TRUE;
+
+ if (cmep->pszContactOwner != NULL) {
+ if (bcp->szProto == NULL) return FALSE;
+ if (mir_strcmp(cmep->pszContactOwner, bcp->szProto)) return FALSE;
+ }
+
+ TMO_MenuItem mi;
+ 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)
+{
+ ContactMenuExecParam *cmep = (ContactMenuExecParam*)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)
+{
+ if (!itdat)
+ return FALSE;
+
+ MENUITEMINFO mii = { sizeof(mii) };
+ 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)
+ continue;
+
+ BOOL inSub = FALSE;
+ if (mii.hSubMenu)
+ inSub = FindMenuHandleByGlobalID(mii.hSubMenu, id, itdat);
+ if (inSub)
+ return inSub;
+
+ PMO_IntMenuItem 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;
+ CUSTOM_STATUS cs = { sizeof(cs) };
+ cs.flags = CSSF_MASK_STATUS;
+ cs.status = &XStatus;
+ if (CallProtoServiceInt(NULL, smep->proto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&cs) != 0)
+ XStatus = 0;
+
+ char buf[255];
+ mir_snprintf(buf, "*XStatus%d", XStatus);
+
+ bool check = wildcmp(smep->svc, buf) != 0;
+ bool reset = wildcmp(smep->svc, "*XStatus0") != 0;
+
+ 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 = { sizeof(mi2) };
+ mi2.flags = CMIM_NAME | CMIF_TCHAR;
+ mi2.ptszName = TranslateTH(timi->mi.hLangpack, timi->mi.hIcon ? timi->mi.ptszName : LPGENT("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);
+
+ 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);
+ }
+
+ Menu_ModifyItem(timi->mi.root, &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) ? CallProtoServiceInt(NULL, smep->proto, PS_GETSTATUS, 0, 0) : cli.pfnGetAverageMode(NULL);
+ 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 {
+ char *prn = mir_u2a(timi->mi.ptszName);
+ prot = NEWSTR_ALLOCA(prn);
+ if (prn) mir_free(prn);
+ }
+ if (Proto_GetAccount(prot) == NULL)
+ return TRUE;
+
+ if ((curProtoStatus = CallProtoServiceInt(NULL, 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)CallProtoServiceInt(NULL, 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 IcoLib_ReleaseIcon(timi->mi.hIcon, 0);
+ }
+ }
+
+ return TRUE;
+}
+
+INT_PTR StatusMenuExecService(WPARAM wParam, LPARAM)
+{
+ StatusMenuExecParam *smep = (StatusMenuExecParam*)wParam;
+ if (smep == NULL)
+ return 0;
+
+ if (smep->custom) {
+ if (smep->svc && *smep->svc)
+ CallService(smep->svc, 0, (LPARAM)smep->hMenuItem);
+ return 0;
+ }
+
+ if (smep->status == 0 && smep->protoindex != 0 && smep->proto != NULL) {
+ char *prot = smep->proto;
+ char szHumanName[64] = { 0 };
+ PROTOACCOUNT *acc = Proto_GetAccount(smep->proto);
+ bool bIsLocked = !Proto_IsAccountLocked(acc);
+ db_set_b(NULL, prot, "LockMainStatus", bIsLocked);
+
+ CallProtoServiceInt(NULL, smep->proto, PS_GETNAME, (WPARAM)SIZEOF(szHumanName), (LPARAM)szHumanName);
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)smep->protoindex);
+ if (pimi == NULL)
+ return 0;
+
+ PMO_IntMenuItem root = (PMO_IntMenuItem)pimi->mi.root;
+ TCHAR buf[256], *ptszName;
+ if (bIsLocked) {
+ pimi->mi.flags |= CMIF_CHECKED;
+ if (cli.bDisplayLocked) {
+ mir_sntprintf(buf, TranslateT("%s (locked)"), acc->tszAccountName);
+ ptszName = buf;
+ }
+ else ptszName = acc->tszAccountName;
+ }
+ else {
+ ptszName = acc->tszAccountName;
+ pimi->mi.flags &= ~CMIF_CHECKED;
+ }
+ replaceStrT(pimi->mi.ptszName, ptszName);
+ replaceStrT(root->mi.ptszName, ptszName);
+
+ if (cli.hwndStatus)
+ InvalidateRect(cli.hwndStatus, NULL, TRUE);
+ return 0;
+ }
+
+ if (smep->proto != NULL) {
+ Proto_SetStatus(smep->proto, smep->status);
+ NotifyEventHooks(hStatusModeChangeEvent, smep->status, (LPARAM)smep->proto);
+ return 0;
+ }
+
+ 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);
+ db_set_w(NULL, "CList", "Status", (WORD)cli.currentDesiredStatusMode);
+ return 1;
+}
+
+INT_PTR FreeOwnerDataStatusMenu(WPARAM, LPARAM lParam)
+{
+ StatusMenuExecParam *smep = (StatusMenuExecParam*)lParam;
+ if (smep != NULL) {
+ FreeAndNil((void**)&smep->proto);
+ FreeAndNil((void**)&smep->svc);
+ FreeAndNil((void**)&smep);
+ }
+
+ return (0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Other menu functions
+
+static INT_PTR ShowHideMenuItem(WPARAM wParam, LPARAM lParam)
+{
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)wParam);
+ if (pimi == NULL)
+ return 1;
+
+ TMO_MenuItem tmi = { sizeof(tmi) };
+ tmi.flags = CMIM_FLAGS + pimi->mi.flags;
+ if (lParam)
+ tmi.flags &= ~CMIF_HIDDEN;
+ else
+ tmi.flags |= CMIF_HIDDEN;
+
+ return MO_ModifyMenuItem((PMO_IntMenuItem)wParam, &tmi);
+}
+
+//wparam MenuItemHandle
+static INT_PTR ModifyCustomMenuItem(WPARAM wParam, LPARAM lParam)
+{
+ TMO_MenuItem tmi;
+ CLISTMENUITEM *mi = (CLISTMENUITEM*)lParam;
+ if (!cli.pfnConvertMenu(mi, &tmi))
+ return 0;
+
+ 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)
+{
+ if (!itdat)
+ return FALSE;
+
+ BOOL inSub = FALSE;
+
+ MENUITEMINFO mii = { sizeof(mii) };
+ 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)
+ continue;
+
+ if (mii.hSubMenu)
+ inSub = FindMenuHanleByGlobalID(mii.hSubMenu, id, itdat);
+ if (inSub)
+ return inSub;
+
+ PMO_IntMenuItem 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);
+ if (pa && pa->bIsVisible && Proto_IsAccountEnabled(pa) && pa->ppro) {
+ PROTOCOLDESCRIPTOR *pd = Proto_IsProtocolLoaded(pa->szProtoName);
+ if (pd == NULL || pd->type != PROTOTYPE_PROTOCOL)
+ return FALSE;
+
+ return (pa->ppro->GetCaps(PFLAGNUM_2, 0) & ~pa->ppro->GetCaps(PFLAGNUM_5, 0));
+ }
+ }
+
+ return FALSE;
+}
+
+int fnGetProtoIndexByPos(PROTOCOLDESCRIPTOR **proto, int protoCnt, int Pos)
+{
+ char buf[10];
+ _itoa(Pos, buf, 10);
+
+ DBVARIANT dbv;
+ if (!db_get_s(NULL, "Protocols", buf, &dbv)) {
+ for (int p = 0; p < protoCnt; p++) {
+ if (mir_strcmp(proto[p]->szName, dbv.pszVal) == 0) {
+ db_free(&dbv);
+ return p;
+ }
+ }
+
+ db_free(&dbv);
+ }
+
+ return -1;
+}
+
+int fnGetAccountIndexByPos(int Pos)
+{
+ for (int i = 0; i < accounts.getCount(); i++)
+ if (accounts[i]->iOrder == Pos)
+ return i;
+
+ return -1;
+}
+
+void RebuildMenuOrder(void)
+{
+ BYTE bHideStatusMenu = db_get_b(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);
+ }
+
+ hStatusMenuObject = MO_CreateMenuObject("StatusMenu", LPGEN("Status menu"), "StatusMenuCheckService", "StatusMenuExecService");
+ 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 (int s = 0; s < accounts.getCount(); s++) {
+ int i = cli.pfnGetAccountIndexByPos(s);
+ if (i == -1)
+ continue;
+
+ PROTOACCOUNT *pa = accounts[i];
+ int pos = 0;
+ if (!bHideStatusMenu && !cli.pfnGetProtocolVisibility(pa->szModuleName))
+ continue;
+
+ DWORD flags = pa->ppro->GetCaps(PFLAGNUM_2, 0) & ~pa->ppro->GetCaps(PFLAGNUM_5, 0);
+ 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)CallProtoServiceInt(NULL, 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
+ StatusMenuExecParam *smep = (StatusMenuExecParam*)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
+ smep = (StatusMenuExecParam*)mir_calloc(sizeof(StatusMenuExecParam));
+ 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);
+ ((StatusMenuExecParam*)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, "RootProtocolIcon_%s", pa->szModuleName);
+ MO_SetOptionsMenuItem(menuHandle, OPT_MENUITEMSETUNIQNAME, (INT_PTR)buf);
+
+ DestroyIcon(ic);
+ pos += 500000;
+
+ for (int 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
+ StatusMenuExecParam *smep = (StatusMenuExecParam*)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, "ProtocolIcon_%s_%s", pa->szModuleName, tmi.pszName);
+ MO_SetOptionsMenuItem(hStatusMenuHandles[i].menuhandle[j], OPT_MENUITEMSETUNIQNAME, (INT_PTR)buf);
+
+ IcoLib_ReleaseIcon(tmi.hIcon, 0);
+ }
+ }
+
+ NotifyEventHooks(cli.hPreBuildStatusMenuEvent, 0, 0);
+ int pos = 200000;
+
+ // add to root menu
+ for (int j = 0; j < SIZEOF(statusModeList); j++) {
+ for (int i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *pa = accounts[i];
+ if (!bHideStatusMenu && !cli.pfnGetProtocolVisibility(pa->szModuleName))
+ continue;
+
+ DWORD flags = pa->ppro->GetCaps(PFLAGNUM_2, 0) & ~pa->ppro->GetCaps(PFLAGNUM_5, 0);
+ if (!(flags & statusModePf2List[j]))
+ continue;
+
+ TMO_MenuItem tmi = { 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
+ StatusMenuExecParam *smep = (StatusMenuExecParam*)mir_calloc(sizeof(StatusMenuExecParam));
+ smep->status = statusModeList[j];
+ tmi.ownerdata = smep;
+ {
+ TCHAR buf[256], hotkeyName[100];
+ WORD hotKey = GetHotkeyValue(statusHotkeys[j]);
+ HotkeyToName(hotkeyName, SIZEOF(hotkeyName), HIBYTE(hotKey), LOBYTE(hotKey));
+ mir_sntprintf(buf, _T("%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, "Root2ProtocolIcon_%s_%s", pa->szModuleName, tmi.pszName);
+ MO_SetOptionsMenuItem(hStatusMainMenuHandles[j], OPT_MENUITEMSETUNIQNAME, (INT_PTR)buf);
+
+ IcoLib_ReleaseIcon(tmi.hIcon, 0);
+ break;
+ }
+ }
+
+ BuildStatusMenu(0, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int sttRebuildHotkeys(WPARAM, LPARAM)
+{
+ TMO_MenuItem tmi = { 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, _T("%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)
+{
+ for (int j = 0; j < SIZEOF(statusModeList); j++)
+ if (status == statusModeList[j])
+ return j;
+
+ return -1;
+}
+
+static int MenuProtoAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA*)lParam;
+ 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;
+
+ int overallStatus = cli.pfnGetAverageMode(NULL);
+
+ TMO_MenuItem tmi = { sizeof(tmi) };
+ if (overallStatus >= ID_STATUS_OFFLINE) {
+ int pos = statustopos(cli.currentStatusMenuItem);
+ if (pos == -1)
+ pos = 0;
+
+ // reset all current possible checked statuses
+ for (int pos2 = 0; pos2 < hStatusMainMenuHandlesCnt; pos2++) {
+ if (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);
+ }
+ }
+ 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);
+ }
+
+ cli.currentStatusMenuItem = 0;
+ }
+
+ for (int i = 0; i < accounts.getCount(); i++) {
+ if (!mir_strcmp(accounts[i]->szModuleName, ack->szModule)) {
+ 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;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int fnConvertMenu(CLISTMENUITEM *mi, TMO_MenuItem *pmi)
+{
+ if (mi == NULL || pmi == NULL)
+ return FALSE;
+
+ if (mi->cbSize != sizeof(CLISTMENUITEM))
+ return FALSE;
+
+ memset(pmi, 0, sizeof(TMO_MenuItem));
+ pmi->cbSize = sizeof(TMO_MenuItem);
+ pmi->root = mi->hParentMenu;
+ pmi->flags = mi->flags;
+ pmi->hIcon = mi->hIcon;
+ pmi->hotKey = mi->hotKey;
+ pmi->pszName = mi->pszName;
+ pmi->position = mi->position;
+ pmi->hLangpack = mi->hLangpack;
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static MenuProto* FindProtocolMenu(const char *proto)
+{
+ for (int i = 0; i < cli.menuProtoCount; i++)
+ if (cli.menuProtos[i].pMenu && !mir_strcmpi(cli.menuProtos[i].szProto, proto))
+ return &cli.menuProtos[i];
+
+ if (cli.menuProtoCount == 1)
+ if (!mir_strcmpi(cli.menuProtos[0].szProto, proto))
+ return &cli.menuProtos[0];
+
+ return NULL;
+}
+
+HGENMENU fnGetProtocolMenu(const char* proto)
+{
+ MenuProto *mp = FindProtocolMenu(proto);
+ return (mp) ? mp->pMenu : NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR AddStatusMenuItem(WPARAM wParam, LPARAM lParam)
+{
+ CLISTMENUITEM *mi = (CLISTMENUITEM*)lParam;
+
+ TMO_MenuItem tmi;
+ if (!cli.pfnConvertMenu(mi, &tmi))
+ return 0;
+
+ // for new style menus the pszPopupName contains the root menu handle
+ PMO_IntMenuItem pRoot = NULL;
+ 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) {
+ 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);
+ }
+ 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
+ StatusMenuExecParam *smep = NULL;
+ if (mi->pszService) {
+ smep = (StatusMenuExecParam*)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];
+ char *p = (pRoot) ? mir_t2a(pRoot->mi.ptszName) : NULL;
+ mir_snprintf(buf, "%s/%s", (p) ? p : "", mi->pszService ? mi->pszService : "");
+ mir_free(p);
+
+ MO_SetOptionsMenuItem(menuHandle, OPT_MENUITEMSETUNIQNAME, (INT_PTR)buf);
+ return (INT_PTR)menuHandle;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR HotkeySetStatus(WPARAM, LPARAM lParam)
+{
+ return SetStatusMode(lParam, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// PROTOCOL MENU
+
+static INT_PTR AddProtoMenuItem(WPARAM wParam, LPARAM lParam)
+{
+ if (db_get_b(NULL, "CList", "MoveProtoMenus", TRUE))
+ 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("CList/AddMainMenuItem", AddMainMenuItem);
+ CreateServiceFunction("CList/AddStatusMenuItem", AddStatusMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUGETMAIN, MenuGetMain);
+ CreateServiceFunction(MS_CLIST_MENUBUILDMAIN, BuildMainMenu);
+
+ CreateServiceFunction("CList/AddContactMenuItem", AddContactMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUBUILDCONTACT, BuildContactMenu);
+
+ CreateServiceFunction(MS_CLIST_SHOWHIDEMENUITEM, ShowHideMenuItem);
+ 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("CList/AddProtoMenuItem", AddProtoMenuItem);
+
+ hPreBuildContactMenuEvent = CreateHookableEvent(ME_CLIST_PREBUILDCONTACTMENU);
+ hPreBuildMainMenuEvent = CreateHookableEvent(ME_CLIST_PREBUILDMAINMENU);
+ cli.hPreBuildStatusMenuEvent = CreateHookableEvent(ME_CLIST_PREBUILDSTATUSMENU);
+ hStatusModeChangeEvent = CreateHookableEvent(ME_CLIST_STATUSMODECHANGE);
+
+ HookEvent(ME_PROTO_ACK, MenuProtoAck);
+
+ hMainMenu = CreatePopupMenu();
+ hStatusMenu = CreatePopupMenu();
+
+ hStatusMainMenuHandles = NULL;
+ hStatusMainMenuHandlesCnt = 0;
+
+ hStatusMenuHandles = NULL;
+ hStatusMenuHandlesCnt = 0;
+
+ // new menu sys
+ InitGenMenu();
+
+ // main menu
+ hMainMenuObject = MO_CreateMenuObject("MainMenu", LPGEN("Main menu"), 0, "MainMenuExecService");
+ MO_SetOptionsMenuObject(hMainMenuObject, OPT_USERDEFINEDITEMS, TRUE);
+ MO_SetOptionsMenuObject(hMainMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataMainMenu");
+
+ // contact menu
+ hContactMenuObject = MO_CreateMenuObject("ContactMenu", LPGEN("Contact menu"), "ContactMenuCheckService", "ContactMenuExecService");
+ 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 = { 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] = Hotkey_Register(&hkd);
+ }
+
+ HookEvent(ME_HOTKEYS_CHANGED, sttRebuildHotkeys);
+
+ // add exit command to menu
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.position = 0x7fffffff;
+ 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;
+
+ 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);
+}
diff --git a/src/mir_app/src/clistmod.cpp b/src/mir_app/src/clistmod.cpp new file mode 100644 index 0000000000..a2c1aae495 --- /dev/null +++ b/src/mir_app/src/clistmod.cpp @@ -0,0 +1,540 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+
+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);
+void LoadCluiServices();
+INT_PTR Docking_IsDocked(WPARAM wParam, LPARAM lParam);
+int LoadCLUIModule(void);
+int InitClistHotKeys(void);
+
+HANDLE hContactDoubleClicked, hContactIconChangedEvent;
+HIMAGELIST hCListImages;
+
+extern BYTE nameOrder[];
+
+struct ProtoIconIndex
+{
+ char *szProto;
+ int iIconBase;
+};
+
+OBJLIST<ProtoIconIndex> protoIconIndex(5);
+
+TCHAR* fnGetStatusModeDescription(int mode, int flags)
+{
+ static TCHAR szMode[64];
+ TCHAR* descr;
+ int noPrefixReqd = 0;
+ switch (mode) {
+ case ID_STATUS_OFFLINE:
+ descr = LPGENT("Offline");
+ noPrefixReqd = 1;
+ break;
+ case ID_STATUS_CONNECTING:
+ descr = LPGENT("Connecting");
+ noPrefixReqd = 1;
+ break;
+ case ID_STATUS_ONLINE:
+ descr = LPGENT("Online");
+ noPrefixReqd = 1;
+ break;
+ case ID_STATUS_AWAY:
+ descr = LPGENT("Away");
+ break;
+ case ID_STATUS_DND:
+ descr = LPGENT("DND");
+ break;
+ case ID_STATUS_NA:
+ descr = LPGENT("NA");
+ break;
+ case ID_STATUS_OCCUPIED:
+ descr = LPGENT("Occupied");
+ break;
+ case ID_STATUS_FREECHAT:
+ descr = LPGENT("Free for chat");
+ break;
+ case ID_STATUS_INVISIBLE:
+ descr = LPGENT("Invisible");
+ break;
+ case ID_STATUS_OUTTOLUNCH:
+ descr = LPGENT("Out to lunch");
+ break;
+ case ID_STATUS_ONTHEPHONE:
+ descr = LPGENT("On the phone");
+ break;
+ case ID_STATUS_IDLE:
+ descr = LPGENT("Idle");
+ break;
+ default:
+ if (IsStatusConnecting(mode)) {
+ const TCHAR* connFmt = LPGENT("Connecting (attempt %d)");
+ mir_sntprintf(szMode, SIZEOF(szMode), (flags & GSMDF_UNTRANSLATED) ? connFmt : TranslateTS(connFmt), mode - ID_STATUS_CONNECTING + 1);
+ return szMode;
+ }
+ return NULL;
+ }
+
+ return (flags & GSMDF_UNTRANSLATED) ? descr : TranslateTS(descr);
+}
+
+static INT_PTR GetStatusModeDescription(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR *buf1 = cli.pfnGetStatusModeDescription(wParam, lParam);
+
+ if (!(lParam & GSMDF_TCHAR)) {
+ static char szMode[64];
+ char *buf2 = mir_u2a(buf1);
+ strncpy_s(szMode, buf2, _TRUNCATE);
+ mir_free(buf2);
+ return (INT_PTR)szMode;
+ }
+
+ return (INT_PTR)buf1;
+}
+
+static int ProtocolAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA *) lParam;
+ if (ack->type != ACKTYPE_STATUS)
+ return 0;
+
+ cli.pfnCluiProtocolStatusChanged(lParam, ack->szModule);
+
+ if ((int)ack->hProcess < ID_STATUS_ONLINE && ack->lParam >= ID_STATUS_ONLINE) {
+ DWORD caps = (DWORD)CallProtoServiceInt(NULL,ack->szModule, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (caps & PF1_SERVERCLIST) {
+ for (MCONTACT hContact = db_find_first(ack->szModule); hContact; ) {
+ MCONTACT hNext = db_find_next(hContact, ack->szModule);
+ if (db_get_b(hContact, "CList", "Delete", 0))
+ CallService(MS_DB_CONTACT_DELETE, hContact, 0);
+ hContact = hNext;
+ }
+ }
+ }
+
+ cli.pfnTrayIconUpdateBase(ack->szModule);
+ return 0;
+}
+
+HICON fnGetIconFromStatusMode(MCONTACT hContact, const char *szProto, int status)
+{
+ return ImageList_GetIcon(hCListImages, cli.pfnIconFromStatusMode(szProto, status, hContact), ILD_NORMAL);
+}
+
+int fnIconFromStatusMode(const char *szProto, int status, MCONTACT)
+{
+ 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 (mir_strcmp(szProto, protoIconIndex[i].szProto) == 0)
+ return protoIconIndex[i].iIconBase + index;
+ }
+ return 1;
+}
+
+int fnGetContactIcon(MCONTACT hContact)
+{
+ char *szProto = GetContactProto(hContact);
+ return cli.pfnIconFromStatusMode(szProto,
+ szProto == NULL ? ID_STATUS_OFFLINE : db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE), hContact);
+}
+
+static INT_PTR GetContactIcon(WPARAM wParam, LPARAM)
+{
+ return cli.pfnGetContactIcon(wParam);
+}
+
+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 (mir_strcmp(protoIconIndex[i].szProto, pa->szModuleName) == 0) {
+ protoIconIndex.remove(i);
+ break;
+ }
+}
+
+static int ContactListModulesLoaded(WPARAM, LPARAM)
+{
+ 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(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(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;
+
+ if (CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0))
+ return GWVS_VISIBLE;
+
+ GetWindowRect(hWnd, &rcWin);
+
+ SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, FALSE);
+ HMONITOR hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO mi;
+ mi.cbSize = sizeof(mi);
+ if (GetMonitorInfo(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;
+
+ if (iNotCoveredDots == 0) //They're all covered!
+ return GWVS_COVERED;
+
+ //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 (!db_get_b(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 (!db_get_b(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);
+ db_set_b(NULL, "CList", "State", SETTING_STATE_NORMAL);
+
+ //this forces the window onto the visible screen
+ GetWindowRect(cli.hwndContactList, &rcWindow);
+ if (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 (db_get_b(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ||
+ db_get_b(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT)) {
+ ShowWindow(cli.hwndContactList, SW_HIDE);
+ db_set_b(NULL, "CList", "State", SETTING_STATE_HIDDEN);
+ }
+ else {
+ ShowWindow(cli.hwndContactList, SW_MINIMIZE);
+ db_set_b(NULL, "CList", "State", SETTING_STATE_MINIMIZED);
+ }
+
+ if (db_get_b(NULL, "CList", "DisableWorkingSet", 1))
+ SetProcessWorkingSetSize(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)
+{
+ MCONTACT a = wParam, b = lParam;
+ TCHAR namea[128], *nameb;
+ int statusa, statusb;
+ char *szProto1, *szProto2;
+ int rc;
+
+ szProto1 = GetContactProto(a);
+ szProto2 = GetContactProto(b);
+ statusa = db_get_w(a, SAFESTRING(szProto1), "Status", ID_STATUS_OFFLINE);
+ statusb = db_get_w(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 = mir_strcmp(SAFESTRING(szProto1), SAFESTRING(szProto2)); /* mir_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_s(namea, nameb, _TRUNCATE);
+ namea[ SIZEOF(namea)-1 ] = 0;
+ nameb = cli.pfnGetContactDisplayName(b, 0);
+
+ //otherwise just compare names
+ return mir_tstrcmpi(namea, nameb);
+}
+
+/***************************************************************************************/
+
+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);
+ HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChanged);
+ HookEvent(ME_DB_CONTACT_ADDED, ContactAdded);
+ HookEvent(ME_DB_CONTACT_DELETED, ContactDeleted);
+ HookEvent(ME_PROTO_ACK, ProtocolAck);
+
+ hContactDoubleClicked = CreateHookableEvent(ME_CLIST_DOUBLECLICKED);
+ hContactIconChangedEvent = CreateHookableEvent(ME_CLIST_CONTACTICONCHANGED);
+
+ LoadCluiServices();
+
+ 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_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);
+
+ InitCListEvents();
+ InitGroupServices();
+ cli.pfnInitTray();
+
+ hCListImages = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 13, 0);
+ HookEvent(ME_SKIN_ICONSCHANGED, CListIconsChanged);
+ CreateServiceFunction(MS_CLIST_GETICONSIMAGELIST, GetIconsImageList);
+
+ ImageList_AddIcon_NotShared(hCListImages, MAKEINTRESOURCE(IDI_BLANK));
+
+ //now all core skin icons are loaded via icon lib. so lets release them
+ for (int 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)
+ return;
+
+ //remove transitory contacts
+ for (MCONTACT hContact = db_find_first(); hContact != NULL; ) {
+ MCONTACT hNext = db_find_next(hContact);
+ if (db_get_b(hContact, "CList", "NotOnList", 0))
+ CallService(MS_DB_CONTACT_DELETE, hContact, 0);
+ hContact = hNext;
+ }
+ ImageList_Destroy(hCListImages);
+ UninitCListEvents();
+ DestroyHookableEvent(hContactDoubleClicked);
+}
diff --git a/src/mir_app/src/clistsettings.cpp b/src/mir_app/src/clistsettings.cpp new file mode 100644 index 0000000000..66792adb9a --- /dev/null +++ b/src/mir_app/src/clistsettings.cpp @@ -0,0 +1,285 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+
+static LIST<ClcCacheEntry> clistCache(50, NumericKeySortT);
+
+void FreeDisplayNameCache(void)
+{
+ for (int i=0; i < clistCache.getCount(); i++) {
+ cli.pfnFreeCacheItem(clistCache[i]);
+ mir_free(clistCache[i]);
+ }
+
+ clistCache.destroy();
+}
+
+// default handlers for the cache item creation and destruction
+
+ClcCacheEntry* fnCreateCacheItem(MCONTACT hContact)
+{
+ ClcCacheEntry* p = (ClcCacheEntry*)mir_calloc(sizeof(ClcCacheEntry));
+ if (p == NULL)
+ return NULL;
+
+ p->hContact = hContact;
+ return p;
+}
+
+void fnCheckCacheItem(ClcCacheEntry *p)
+{
+ DBVARIANT dbv;
+ if (p->tszGroup == NULL) {
+ if (!db_get_ts(p->hContact, "CList", "Group", &dbv)) {
+ p->tszGroup = mir_tstrdup(dbv.ptszVal);
+ mir_free(dbv.ptszVal);
+ }
+ else p->tszGroup = mir_tstrdup(_T(""));
+ }
+
+ if (p->bIsHidden == -1)
+ p->bIsHidden = db_get_b(p->hContact, "CList", "Hidden", 0);
+}
+
+void fnFreeCacheItem(ClcCacheEntry *p)
+{
+ if (p->tszName) { mir_free(p->tszName); p->tszName = NULL; }
+ if (p->tszGroup) { mir_free(p->tszGroup); p->tszGroup = NULL; }
+ p->bIsHidden = -1;
+}
+
+ClcCacheEntry* fnGetCacheEntry(MCONTACT hContact)
+{
+ ClcCacheEntry *p;
+ int idx = clistCache.getIndex((ClcCacheEntry*)&hContact);
+ if (idx == -1) {
+ if ((p = cli.pfnCreateCacheItem(hContact)) != NULL) {
+ clistCache.insert(p);
+ cli.pfnInvalidateDisplayNameCacheEntry(hContact);
+ }
+ }
+ else p = clistCache[idx];
+
+ cli.pfnCheckCacheItem(p);
+ return p;
+}
+
+void fnInvalidateDisplayNameCacheEntry(MCONTACT hContact)
+{
+ if (hContact == INVALID_CONTACT_ID) {
+ FreeDisplayNameCache();
+ cli.pfnInitAutoRebuild(cli.hwndContactTree);
+ }
+ else {
+ int idx = clistCache.getIndex((ClcCacheEntry*)&hContact);
+ if (idx != -1)
+ cli.pfnFreeCacheItem(clistCache[idx]);
+ }
+}
+
+TCHAR* fnGetContactDisplayName(MCONTACT hContact, int mode)
+{
+ ClcCacheEntry *cacheEntry = NULL;
+
+ if (mode & GCDNF_NOCACHE)
+ mode &= ~GCDNF_NOCACHE;
+ else if (mode != GCDNF_NOMYHANDLE) {
+ cacheEntry = cli.pfnGetCacheEntry(hContact);
+ if (cacheEntry->tszName)
+ return cacheEntry->tszName;
+ }
+
+ CONTACTINFO ci;
+ memset(&ci, 0, 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)
+ cacheEntry->tszName = ci.pszVal;
+ return ci.pszVal;
+ }
+
+ if (ci.type == CNFT_DWORD) {
+ TCHAR *buffer = (TCHAR*) mir_alloc(15 * sizeof(TCHAR));
+ _ltot(ci.dVal, buffer, 10);
+ if (cacheEntry != NULL)
+ cacheEntry->tszName = buffer;
+ return buffer;
+ }
+ }
+
+ CallContactService(hContact, PSS_GETINFO, SGIF_MINIMAL, 0);
+ TCHAR *buffer = TranslateT("(Unknown contact)");
+ return (cacheEntry == NULL) ? mir_tstrdup(buffer) : buffer;
+}
+
+INT_PTR GetContactDisplayName(WPARAM hContact, LPARAM lParam)
+{
+ static char retVal[200];
+ ClcCacheEntry *cacheEntry = NULL;
+
+ if (lParam & GCDNF_UNICODE)
+ return (INT_PTR)cli.pfnGetContactDisplayName(hContact, lParam & ~GCDNF_UNICODE);
+
+ if (lParam & GCDNF_NOCACHE)
+ lParam &= ~GCDNF_NOCACHE;
+ else if (lParam != GCDNF_NOMYHANDLE) {
+ cacheEntry = cli.pfnGetCacheEntry(hContact);
+ if (cacheEntry->tszName) {
+ strncpy_s(retVal, _T2A(cacheEntry->tszName), _TRUNCATE);
+ return (INT_PTR)retVal;
+ }
+ }
+
+ CONTACTINFO ci = { 0 };
+ ci.cbSize = sizeof(ci);
+ ci.hContact = hContact;
+ if (ci.hContact == NULL) // killme !!!!!!!!!!
+ 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) {
+ strncpy_s(retVal, _T2A(ci.pszVal), _TRUNCATE);
+ if (cacheEntry == NULL) {
+ mir_free(ci.pszVal);
+ return (INT_PTR)mir_strdup(retVal);
+ }
+
+ cacheEntry->tszName = ci.pszVal;
+ return (INT_PTR)retVal;
+ }
+ if (ci.type == CNFT_DWORD) {
+ _ltoa(ci.dVal, retVal, 10);
+ if (cacheEntry == NULL)
+ return (INT_PTR)mir_strdup(retVal);
+
+ cacheEntry->tszName = mir_a2u(retVal);
+ return (INT_PTR)retVal;
+ }
+ }
+
+ CallContactService(hContact, PSS_GETINFO, SGIF_MINIMAL, 0);
+ char* result = Translate("(Unknown contact)");
+ return (INT_PTR)((cacheEntry == NULL) ? mir_strdup(result) : result);
+}
+
+INT_PTR InvalidateDisplayName(WPARAM wParam, LPARAM)
+{
+ cli.pfnInvalidateDisplayNameCacheEntry(wParam);
+ return 0;
+}
+
+int ContactAdded(WPARAM wParam, LPARAM)
+{
+ cli.pfnChangeContactIcon(wParam, cli.pfnIconFromStatusMode(GetContactProto(wParam), 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 hContact, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+
+ // Early exit
+ if (hContact == NULL)
+ return 0;
+
+ DBVARIANT dbv;
+ dbv.pszVal = NULL;
+ if (!db_get(hContact, "Protocol", "p", &dbv)) {
+ if (!mir_strcmp(cws->szModule, dbv.pszVal)) {
+ cli.pfnInvalidateDisplayNameCacheEntry(hContact);
+ if (!mir_strcmp(cws->szSetting, "UIN") || !mir_strcmp(cws->szSetting, "Nick") || !mir_strcmp(cws->szSetting, "FirstName")
+ || !mir_strcmp(cws->szSetting, "LastName") || !mir_strcmp(cws->szSetting, "e-mail"))
+ {
+ CallService(MS_CLUI_CONTACTRENAMED, hContact, 0);
+ }
+ else if (!mir_strcmp(cws->szSetting, "Status")) {
+ if (!db_get_b(hContact, "CList", "Hidden", 0)) {
+ if (db_get_b(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, hContact, 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 (!mir_strcmp(cws->szModule, "CList")) {
+ if (!mir_strcmp(cws->szSetting, "Hidden")) {
+ if (cws->value.type == DBVT_DELETED || cws->value.bVal == 0) {
+ char *szProto = GetContactProto(hContact);
+ cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(szProto, szProto == NULL ? ID_STATUS_OFFLINE : db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE), hContact), 1);
+ }
+ else
+ CallService(MS_CLUI_CONTACTDELETED, hContact, 0);
+ }
+ if (!mir_strcmp(cws->szSetting, "MyHandle"))
+ cli.pfnInvalidateDisplayNameCacheEntry(hContact);
+ }
+
+ if (!mir_strcmp(cws->szModule, "Protocol")) {
+ if (!mir_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 : db_get_w(hContact, szProto, "Status",
+ ID_STATUS_OFFLINE), hContact), 0);
+ }
+ }
+
+ // Clean up
+ if (dbv.pszVal)
+ mir_free(dbv.pszVal);
+
+ return 0;
+}
diff --git a/src/mir_app/src/clisttray.cpp b/src/mir_app/src/clisttray.cpp new file mode 100644 index 0000000000..93bde38516 --- /dev/null +++ b/src/mir_app/src/clisttray.cpp @@ -0,0 +1,877 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+
+#define TOOLTIP_TOLERANCE 5
+
+extern HIMAGELIST hCListImages;
+
+static UINT WM_TASKBARCREATED;
+static UINT WM_TASKBARBUTTONCREATED;
+static UINT_PTR RefreshTimerId = 0; /////by FYR
+
+mir_cs trayLockCS;
+
+static bool hasTips()
+{
+ return ServiceExists("mToolTip/ShowTip") && db_get_b(NULL, "Tipper", "TrayTip", 1);
+}
+
+// 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 initcheck if (!fTrayInited) return
+
+#define SIZEOFNID ((cli.shellVersion >= 5) ? NOTIFYICONDATA_V2_SIZE : NOTIFYICONDATA_V1_SIZE)
+
+static BOOL fTrayInited = FALSE;
+
+static TCHAR* sttGetXStatus(const char *szProto)
+{
+ if (CallProtoServiceInt(NULL, szProto, PS_GETSTATUS, 0, 0) > ID_STATUS_OFFLINE) {
+ TCHAR tszStatus[512];
+ CUSTOM_STATUS cs = { sizeof(cs) };
+ cs.flags = CSSF_MASK_MESSAGE | CSSF_TCHAR;
+ cs.ptszMessage = tszStatus;
+ if (CallProtoServiceInt(NULL, szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&cs) == 0)
+ return mir_tstrdup(tszStatus);
+ }
+
+ return NULL;
+}
+
+static HICON lastTaskBarIcon;
+static void SetTaskBarIcon(const HICON hIcon, const TCHAR *szNewTip)
+{
+ if (pTaskbarInterface) {
+ pTaskbarInterface->SetOverlayIcon(cli.hwndContactList, hIcon, szNewTip);
+ lastTaskBarIcon = hIcon;
+ }
+}
+
+TCHAR* fnTrayIconMakeTooltip(const TCHAR *szPrefix, const char *szProto)
+{
+ initcheck NULL;
+
+ mir_cslock lck(trayLockCS);
+ TCHAR *szSeparator = _T("\n");
+
+ if (szProto == NULL) {
+ if (accounts.getCount() == 0)
+ return NULL;
+
+ if (accounts.getCount() == 1)
+ return cli.pfnTrayIconMakeTooltip(szPrefix, accounts[0]->szModuleName);
+
+ CMString tszTip;
+
+ if (szPrefix && szPrefix[0]) {
+ if (!db_get_b(NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT)) {
+ _tcsncpy_s(cli.szTip, MAX_TIP_SIZE, szPrefix, _TRUNCATE);
+ return cli.szTip;
+ }
+ tszTip.Append(szPrefix);
+ }
+
+ for (int t = 0; t < accounts.getCount(); t++) {
+ int i = cli.pfnGetAccountIndexByPos(t);
+ if (i == -1)
+ continue;
+
+ PROTOACCOUNT *pa = accounts[i];
+ if (!cli.pfnGetProtocolVisibility(pa->szModuleName))
+ continue;
+
+ TCHAR *szStatus = cli.pfnGetStatusModeDescription(CallProtoServiceInt(NULL, pa->szModuleName, PS_GETSTATUS, 0, 0), 0);
+ if (!szStatus)
+ continue;
+
+ if (!tszTip.IsEmpty())
+ tszTip.AppendChar('\n');
+ if (hasTips()) {
+ tszTip.AppendFormat(_T("<b>%-12.12s</b>\t%s"), pa->tszAccountName, szStatus);
+
+ ptrT ProtoXStatus(sttGetXStatus(pa->szModuleName));
+ if (ProtoXStatus != NULL) {
+ if (!tszTip.IsEmpty())
+ tszTip.AppendChar('\n');
+ tszTip.AppendFormat(_T("%-24.24s\n"), ProtoXStatus);
+ }
+ }
+ else tszTip.AppendFormat(_T("%s %s"), pa->tszAccountName, szStatus);
+ }
+
+ _tcsncpy_s(cli.szTip, MAX_TIP_SIZE, tszTip, _TRUNCATE);
+ }
+ else {
+ PROTOACCOUNT *pa = Proto_GetAccount(szProto);
+ if (pa != NULL) {
+ ptrT ProtoXStatus(sttGetXStatus(szProto));
+ TCHAR *szStatus = cli.pfnGetStatusModeDescription(CallProtoServiceInt(NULL, szProto, PS_GETSTATUS, 0, 0), 0);
+ if (szPrefix && szPrefix[0]) {
+ if (db_get_b(NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT)) {
+ if (hasTips()) {
+ if (ProtoXStatus != NULL)
+ mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s<b>%-12.12s</b>\t%s%s%-24.24s"), szPrefix, szSeparator, pa->tszAccountName, szStatus, szSeparator, ProtoXStatus);
+ else
+ mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s<b>%-12.12s</b>\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 mir_tstrncpy(cli.szTip, szPrefix, MAX_TIP_SIZE);
+ }
+ else {
+ if (hasTips()) {
+ if (ProtoXStatus != NULL)
+ mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("<b>%-12.12s</b>\t%s\n%-24.24s"), pa->tszAccountName, szStatus, ProtoXStatus);
+ else
+ mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("<b>%-12.12s</b>\t%s"), pa->tszAccountName, szStatus);
+ }
+ else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s %s"), pa->tszAccountName, szStatus);
+ }
+ }
+ }
+
+ return cli.szTip;
+}
+
+int fnTrayIconAdd(HWND hwnd, const char *szProto, const char *szIconProto, int status)
+{
+ initcheck 0;
+
+ mir_cslock lck(trayLockCS);
+ int i;
+ for (i = 0; i < cli.trayIconCount; i++)
+ if (cli.trayIcon[i].id == 0)
+ break;
+
+ trayIconInfo_t &p = cli.trayIcon[i];
+ p.id = TRAYICON_ID_BASE + i;
+ p.szProto = (char*)szProto;
+ p.hBaseIcon = cli.pfnGetIconFromStatusMode(NULL, szIconProto ? szIconProto : p.szProto, status);
+
+ NOTIFYICONDATA nid = { SIZEOFNID };
+ nid.hWnd = hwnd;
+ nid.uID = p.id;
+ nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
+ nid.uCallbackMessage = TIM_CALLBACK;
+ nid.hIcon = p.hBaseIcon;
+
+ if (cli.shellVersion >= 5)
+ nid.uFlags |= NIF_INFO;
+
+ cli.pfnTrayIconMakeTooltip(NULL, p.szProto);
+ if (!hasTips())
+ mir_tstrncpy(nid.szTip, cli.szTip, SIZEOF(nid.szTip));
+ replaceStrT(p.ptszToolTip, cli.szTip);
+
+ Shell_NotifyIcon(NIM_ADD, &nid);
+ p.isBase = 1;
+
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(cli.trayIcon[0].hBaseIcon, cli.szTip);
+ return i;
+}
+
+void fnTrayIconRemove(HWND hwnd, const char *szProto)
+{
+ initcheck;
+
+ mir_cslock lck(trayLockCS);
+ for (int i = 0; i < cli.trayIconCount; i++) {
+ trayIconInfo_t *pii = &cli.trayIcon[i];
+ if (pii->id != 0 && !mir_strcmp(szProto, pii->szProto)) {
+ NOTIFYICONDATA nid = { SIZEOFNID };
+ 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);
+}
+
+int fnTrayIconInit(HWND hwnd)
+{
+ initcheck 0;
+
+ mir_cslock lck(trayLockCS);
+
+ int netProtoCount = 0;
+ int averageMode = cli.pfnGetAverageMode(&netProtoCount);
+
+ 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 = db_get_b(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT);
+ if (trayIconSetting == SETTING_TRAYICON_SINGLE) {
+ DBVARIANT dbv = { DBVT_DELETED };
+ char *szProto;
+ if (!db_get_s(NULL, "CList", "PrimaryStatus", &dbv) && (averageMode < 0 || db_get_b(NULL, "CList", "AlwaysPrimary", 0)))
+ szProto = dbv.pszVal;
+ else
+ szProto = NULL;
+
+ cli.pfnTrayIconAdd(hwnd, NULL, szProto, szProto ? CallProtoServiceInt(NULL, szProto, PS_GETSTATUS, 0, 0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0));
+ db_free(&dbv);
+ }
+ else if (trayIconSetting == SETTING_TRAYICON_MULTI && (averageMode < 0 || db_get_b(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, CallProtoServiceInt(NULL, 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, db_get_w(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));
+ }
+
+ return 0;
+}
+
+int fnTrayIconDestroy(HWND hwnd)
+{
+ initcheck 0;
+
+ mir_cslock lck(trayLockCS);
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(NULL, NULL);
+
+ NOTIFYICONDATA nid = { SIZEOFNID };
+ nid.hWnd = hwnd;
+ for (int 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;
+ 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)
+{
+ if (RefreshTimerId) {
+ KillTimer(NULL, RefreshTimerId);
+ RefreshTimerId = 0;
+ }
+ for (int i = 0; i < accounts.getCount(); i++)
+ cli.pfnTrayIconUpdateBase(accounts[i]->szModuleName);
+}
+
+int fnTrayIconUpdate(HICON hNewIcon, const TCHAR *szNewTip, const char *szPreferredProto, int isBase)
+{
+ initcheck - 1;
+ mir_cslock lck(trayLockCS);
+
+ NOTIFYICONDATA nid = { SIZEOFNID };
+ nid.hWnd = cli.hwndContactList;
+ nid.uFlags = NIF_ICON | NIF_TIP;
+ nid.hIcon = hNewIcon;
+ if (!hNewIcon)
+ return -1;
+
+ for (int i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+ if (mir_strcmp(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 (!hasTips())
+ mir_tstrncpy(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;
+ return i;
+ }
+
+ // if there wasn't a suitable icon, change all the icons
+ for (int 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 (!hasTips())
+ mir_tstrncpy(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 (db_get_b(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT) == SETTING_TRAYICON_MULTI) {
+ DWORD time1 = db_get_w(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 200;
+ DWORD time2 = db_get_w(NULL, "CList", "IconFlashTime", 550) + 1000;
+ DWORD time = max(max(2000, time1), time2);
+ if (RefreshTimerId)
+ KillTimer(NULL, RefreshTimerId);
+
+ // if unknown base was changed - than show preffered proto icon for 2 sec
+ // and reset it to original one after timeout
+ RefreshTimerId = SetTimer(NULL, 0, time, RefreshTimerProc);
+ }
+ return i;
+ }
+
+ return -1;
+}
+
+int fnTrayIconSetBaseInfo(HICON hIcon, const char *szPreferredProto)
+{
+ if (!fTrayInited) {
+ LBL_Error:
+ DestroyIcon(hIcon);
+ return -1;
+ }
+
+ mir_cslock lck(trayLockCS);
+
+ if (szPreferredProto) {
+ for (int i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+ if (mir_strcmp(cli.trayIcon[i].szProto, szPreferredProto))
+ continue;
+
+ DestroyIcon(cli.trayIcon[i].hBaseIcon);
+ cli.trayIcon[i].hBaseIcon = hIcon;
+ return i;
+ }
+ if ((cli.pfnGetProtocolVisibility(szPreferredProto)) &&
+ (cli.pfnGetAverageMode(NULL) == -1) &&
+ (db_get_b(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT) == SETTING_TRAYICON_MULTI) &&
+ !(db_get_b(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT)))
+ goto LBL_Error;
+ }
+
+ // if there wasn't a specific icon, there will only be one suitable
+ for (int i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+
+ DestroyIcon(cli.trayIcon[i].hBaseIcon);
+ cli.trayIcon[i].hBaseIcon = hIcon;
+ return i;
+ }
+
+ goto LBL_Error;
+}
+
+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;
+ mir_cslock lck(trayLockCS);
+
+ int i;
+ for (i = accounts.getCount() + 1; --i;) {
+ cli.cycleStep = (cli.cycleStep + 1) % accounts.getCount();
+ if (cli.pfnGetProtocolVisibility(accounts[cli.cycleStep]->szModuleName))
+ break;
+ }
+
+ if (i) {
+ DestroyIcon(cli.trayIcon[0].hBaseIcon);
+ cli.trayIcon[0].hBaseIcon = cli.pfnGetIconFromStatusMode(NULL, accounts[cli.cycleStep]->szModuleName,
+ CallProtoServiceInt(NULL, accounts[cli.cycleStep]->szModuleName, PS_GETSTATUS, 0, 0));
+ if (cli.trayIcon[0].isBase)
+ cli.pfnTrayIconUpdate(cli.trayIcon[0].hBaseIcon, NULL, NULL, 1);
+ }
+}
+
+void fnTrayIconUpdateBase(const char *szChangedProto)
+{
+ initcheck;
+ if (szChangedProto == NULL) return;
+ if (!cli.pfnGetProtocolVisibility(szChangedProto)) return;
+
+ int netProtoCount;
+ mir_cslock lck(trayLockCS);
+ int averageMode = cli.pfnGetAverageMode(&netProtoCount);
+
+ if (cli.cycleTimerId) {
+ KillTimer(NULL, cli.cycleTimerId);
+ cli.cycleTimerId = 0;
+ }
+
+ for (int i = 0; i < accounts.getCount(); i++)
+ if (!mir_strcmp(szChangedProto, accounts[i]->szModuleName))
+ cli.cycleStep = i;
+
+ int changed = cli.pfnTrayCalcChanged(szChangedProto, averageMode, netProtoCount);
+ if (changed != -1 && cli.trayIcon[changed].isBase)
+ cli.pfnTrayIconUpdate(cli.trayIcon[changed].hBaseIcon, NULL, cli.trayIcon[changed].szProto, 1);
+}
+
+int fnTrayCalcChanged(const char *szChangedProto, int averageMode, int netProtoCount)
+{
+ if (netProtoCount == 0)
+ return cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon(hCListImages, cli.pfnIconFromStatusMode(NULL, averageMode, NULL), ILD_NORMAL), NULL);
+
+ int trayIconSetting = db_get_b(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT);
+
+ if (averageMode > 0) {
+ if (trayIconSetting != SETTING_TRAYICON_MULTI)
+ return cli.pfnTrayIconSetBaseInfo(cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL);
+
+ if (db_get_b(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT))
+ return cli.pfnTrayIconSetBaseInfo(cli.pfnGetIconFromStatusMode(NULL, szChangedProto, CallProtoServiceInt(NULL, szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto);
+
+ if (cli.trayIcon == NULL || cli.trayIcon[0].szProto == NULL)
+ return cli.pfnTrayIconSetBaseInfo(cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL);
+
+ cli.pfnTrayIconDestroy(cli.hwndContactList);
+ cli.pfnTrayIconInit(cli.hwndContactList);
+ }
+ else {
+ switch (trayIconSetting) {
+ case SETTING_TRAYICON_CYCLE:
+ cli.cycleTimerId = SetTimer(NULL, 0, db_get_w(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc);
+ return cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon
+ (hCListImages, cli.pfnIconFromStatusMode(szChangedProto, CallProtoServiceInt(NULL, szChangedProto, PS_GETSTATUS, 0, 0), NULL),
+ ILD_NORMAL), NULL);
+
+ case SETTING_TRAYICON_MULTI:
+ if (!cli.trayIcon)
+ cli.pfnTrayIconRemove(NULL, NULL);
+ else if ((cli.trayIconCount > 1 || netProtoCount == 1) || db_get_b(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT))
+ return cli.pfnTrayIconSetBaseInfo(cli.pfnGetIconFromStatusMode(NULL, szChangedProto, CallProtoServiceInt(NULL, szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto);
+ else {
+ cli.pfnTrayIconDestroy(cli.hwndContactList);
+ cli.pfnTrayIconInit(cli.hwndContactList);
+ }
+ break;
+
+ case SETTING_TRAYICON_SINGLE:
+ ptrA szProto(db_get_sa(NULL, "CList", "PrimaryStatus"));
+ return cli.pfnTrayIconSetBaseInfo(cli.pfnGetIconFromStatusMode(NULL, szProto, szProto ?
+ CallProtoServiceInt(NULL, szProto, PS_GETSTATUS, 0, 0) :
+ CallService(MS_CLIST_GETSTATUSMODE, 0, 0)), szProto);
+ }
+ }
+
+ return -1;
+}
+
+void fnTrayIconSetToBase(char *szPreferredProto)
+{
+ int i;
+ initcheck;
+ mir_cslock lck(trayLockCS);
+
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+ if (mir_strcmp(cli.trayIcon[i].szProto, szPreferredProto))
+ continue;
+ cli.pfnTrayIconUpdate(cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1);
+ 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);
+ return;
+ }
+}
+
+void fnTrayIconIconsChanged(void)
+{
+ initcheck;
+ mir_cslock lck(trayLockCS);
+
+ cli.pfnTrayIconDestroy(cli.hwndContactList);
+ cli.pfnTrayIconInit(cli.hwndContactList);
+}
+
+static UINT_PTR autoHideTimerId;
+static VOID CALLBACK TrayIconAutoHideTimer(HWND hwnd, UINT, UINT_PTR idEvent, DWORD)
+{
+ initcheck;
+ mir_cslock lck(trayLockCS);
+
+ KillTimer(hwnd, idEvent);
+ HWND hwndClui = cli.hwndContactList;
+ if (GetActiveWindow() != hwndClui) {
+ ShowWindow(hwndClui, SW_HIDE);
+ SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+ }
+}
+
+int fnTrayIconPauseAutoHide(WPARAM, LPARAM)
+{
+ initcheck 0;
+ mir_cslock lck(trayLockCS);
+
+ if (db_get_b(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) {
+ if (GetActiveWindow() != cli.hwndContactList) {
+ KillTimer(NULL, autoHideTimerId);
+ autoHideTimerId = SetTimer(NULL, 0, 1000 * db_get_w(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer);
+ }
+ }
+ 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) {
+ 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;
+ }
+ CLCINFOTIP ti = { sizeof(ti) };
+ 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.isTreeFocused = GetFocus() == cli.hwndContactList ? 1 : 0;
+ if (CallService("mToolTip/ShowTipW", (WPARAM)szTipCur, (LPARAM)&ti) == CALLSERVICE_NOTFOUND)
+ CallService("mToolTip/ShowTip", (WPARAM)(char*)_T2A(szTipCur), (LPARAM)&ti);
+
+ 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 (db_get_b(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) {
+ if (LOWORD(msg->wParam) == WA_INACTIVE)
+ autoHideTimerId = SetTimer(NULL, 0, 1000 * db_get_w(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 == (db_get_b(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 (!mir_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) {
+ HMENU hMainMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT));
+ HMENU hMenu = GetSubMenu(hMainMenu, 0);
+ TranslateMenu(hMenu);
+
+ MENUITEMINFO mi = { sizeof(mi) };
+ 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);
+
+ POINT pt;
+ 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)
+{
+ if (msn == NULL)
+ return 1;
+
+ if (msn->cbSize != sizeof(MIRANDASYSTRAYNOTIFY) || msn->szInfo == NULL || msn->szInfoTitle == NULL)
+ return 1;
+
+ if (cli.trayIcon == NULL)
+ return 2;
+
+ UINT iconId = 0;
+ if (msn->szProto) {
+ for (int j = 0; j < cli.trayIconCount; j++) {
+ if (cli.trayIcon[j].szProto != NULL) {
+ if (!mir_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 (msn->dwInfoFlags & NIIF_INTERN_UNICODE) {
+ NOTIFYICONDATAW nid = { 0 };
+ nid.cbSize = (cli.shellVersion >= 5) ? NOTIFYICONDATAW_V2_SIZE : NOTIFYICONDATAW_V1_SIZE;
+ nid.hWnd = cli.hwndContactList;
+ nid.uID = iconId;
+ nid.uFlags = NIF_INFO;
+ mir_wstrncpy(nid.szInfo, msn->tszInfo, SIZEOF(nid.szInfo));
+ mir_wstrncpy(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 {
+ NOTIFYICONDATAA nid = { 0 };
+ nid.cbSize = (cli.shellVersion >= 5) ? NOTIFYICONDATAA_V2_SIZE : NOTIFYICONDATAA_V1_SIZE;
+ nid.hWnd = cli.hwndContactList;
+ nid.uID = iconId;
+ nid.uFlags = NIF_INFO;
+ strncpy_s(nid.szInfo, msn->szInfo, _TRUNCATE);
+ strncpy_s(nid.szInfoTitle, msn->szInfoTitle, _TRUNCATE);
+ nid.uTimeout = msn->uTimeout;
+ nid.dwInfoFlags = msn->dwInfoFlags;
+ return Shell_NotifyIconA(NIM_MODIFY, &nid) == 0;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+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);
+ }
+
+ if (cli.shellVersion >= 5)
+ CreateServiceFunction(MS_CLIST_SYSTRAY_NOTIFY, pfnCListTrayNotifyStub);
+ fTrayInited = TRUE;
+}
+
+void fnUninitTray(void)
+{
+ fTrayInited = FALSE;
+}
+
+#undef initcheck
diff --git a/src/mir_app/src/clui.cpp b/src/mir_app/src/clui.cpp new file mode 100644 index 0000000000..0d4443782b --- /dev/null +++ b/src/mir_app/src/clui.cpp @@ -0,0 +1,1074 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "profilemanager.h"
+#include "clc.h"
+
+#define TM_AUTOALPHA 1
+#define MENU_MIRANDAMENU 0xFFFF1234
+
+extern HANDLE hEventExtraClick;
+
+static HMODULE hUserDll;
+static HANDLE hContactDraggingEvent, hContactDroppedEvent, hContactDragStopEvent;
+static int transparentFocus = 1;
+UINT uMsgProcessProfile;
+
+#define M_RESTORESTATUS (WM_USER+7)
+
+typedef struct {
+ int showsbar;
+ int showgrip;
+ int transparent;
+ int alpha;
+}
+ CluiOpts;
+
+static CluiOpts cluiopt = {0};
+
+void fnLoadCluiGlobalOpts()
+{
+ cluiopt.showsbar = db_get_b(NULL, "CLUI", "ShowSBar", 1);
+ cluiopt.showgrip = db_get_b(NULL, "CLUI", "ShowGrip", 1);
+ cluiopt.transparent = db_get_b(NULL, "CList", "Transparent", SETTING_TRANSPARENT_DEFAULT);
+ cluiopt.alpha = db_get_b(NULL, "CList", "Alpha", SETTING_ALPHA_DEFAULT);
+}
+
+static int CluiModulesLoaded(WPARAM, LPARAM)
+{
+ if (cli.hMenuMain) {
+ MENUITEMINFO mii = { sizeof(mii) };
+ 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()
+{
+ for (int i = 0; i < accounts.getCount(); i++)
+ CallProtoServiceInt(NULL,accounts[i]->szModuleName, PS_SETSTATUS, ID_STATUS_OFFLINE, 0);
+}
+
+static int CluiIconsChanged(WPARAM, LPARAM)
+{
+ DrawMenuBar(cli.hwndContactList);
+ return 0;
+}
+
+static HGENMENU hRenameMenuItem;
+
+static int MenuItem_PreBuild(WPARAM, LPARAM)
+{
+ TCHAR cls[128];
+ HWND hwndClist = GetFocus();
+ GetClassName(hwndClist, cls, SIZEOF(cls));
+ hwndClist = (!mir_tstrcmp( _T(CLISTCONTROL_CLASS), cls)) ? hwndClist : cli.hwndContactList;
+ HANDLE hItem = (HANDLE)SendMessage(hwndClist, CLM_GETSELECTION, 0, 0);
+ Menu_ShowItem(hRenameMenuItem, hItem != 0);
+ return 0;
+}
+
+static INT_PTR MenuItem_RenameContact(WPARAM, LPARAM)
+{
+ TCHAR cls[128];
+ HWND hwndClist = GetFocus();
+ GetClassName(hwndClist, cls, SIZEOF(cls));
+ // worst case scenario, the rename is sent to the main contact list
+ hwndClist = (!mir_tstrcmp( _T(CLISTCONTROL_CLASS), cls)) ? hwndClist : cli.hwndContactList;
+ HANDLE 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)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], szFinal[256];
+ GetDlgItemText(hWnd, IDC_TOPLINE, szFormat, SIZEOF(szFormat));
+ mir_sntprintf(szFinal, SIZEOF(szFinal), szFormat, cli.pfnGetContactDisplayName(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 (db_get_b(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(g_hInst, MAKEINTRESOURCE(IDD_DELETECONTACT), (HWND)lParam, AskForConfirmationDlgProc, wParam);
+ else
+ action = IDYES;
+
+ switch (action) {
+ case IDC_HIDE: // Archive contact
+ db_set_b(wParam, "CList", "Hidden", 1);
+ break;
+
+ case IDYES: // Delete contact
+ char *szProto = GetContactProto(wParam);
+ if (szProto != NULL) {
+ // Check if protocol uses server side lists
+ DWORD caps = CallProtoServiceInt(NULL, szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (caps & PF1_SERVERCLIST) {
+ int status = CallProtoServiceInt(NULL, szProto, PS_GETSTATUS, 0, 0);
+ if (status == ID_STATUS_OFFLINE || IsStatusConnecting(status)) {
+ // Set a flag so we remember to delete the contact when the protocol goes online the next time
+ db_set_b(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_ICONINFORMATION | MB_OK);
+ return 0;
+ }
+ }
+ }
+
+ CallService(MS_DB_CONTACT_DELETE, wParam, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static INT_PTR MenuItem_AddContactToList(WPARAM hContact, LPARAM)
+{
+ ADDCONTACTSTRUCT acs = { 0 };
+ acs.hContact = hContact;
+ acs.handleType = HANDLE_CONTACT;
+ acs.szProto = "";
+ CallService(MS_ADDCONTACT_SHOW, 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);
+
+ 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 = _T(CLISTCONTROL_CLASS);
+ wndclass.hIconSm = NULL;
+ RegisterClassEx(&wndclass);
+
+ wndclass.style = CS_HREDRAW | CS_VREDRAW | ((db_get_b(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 (db_get_ts(NULL, "CList", "TitleText", &dbv))
+ mir_tstrncpy(titleText, _T(MIRANDANAME), SIZEOF(titleText));
+ else {
+ mir_tstrncpy(titleText, dbv.ptszVal, SIZEOF(titleText));
+ db_free(&dbv);
+ }
+
+ RECT pos;
+ pos.left = (int)db_get_dw(NULL, "CList", "x", 700);
+ pos.top = (int)db_get_dw(NULL, "CList", "y", 221);
+ pos.right = pos.left + (int)db_get_dw(NULL, "CList", "Width", 108);
+ pos.bottom = pos.top + (int)db_get_dw(NULL, "CList", "Height", 310);
+
+ AssertInsideScreen(pos);
+
+ cli.hwndContactList = CreateWindowEx(
+ (db_get_b(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ? WS_EX_TOOLWINDOW : WS_EX_APPWINDOW),
+ _T(MIRANDACLASS),
+ titleText,
+ WS_POPUPWINDOW | WS_THICKFRAME | WS_CLIPCHILDREN |
+ (db_get_b(NULL, "CLUI", "ShowCaption", SETTING_SHOWCAPTION_DEFAULT) ? WS_CAPTION | WS_SYSMENU |
+ (db_get_b(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 (db_get_b(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 = db_get_b(NULL, "CList", "State", SETTING_STATE_NORMAL);
+ cli.hMenuMain = GetMenu(cli.hwndContactList);
+ if (!db_get_b(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,
+ db_get_b(NULL, "CList", "OnTop", SETTING_ONTOP_DEFAULT) ? HWND_TOPMOST : HWND_NOTOPMOST,
+ 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+
+ CreateServiceFunction("CList/DeleteContactCommand", MenuItem_DeleteContact);
+ mi.position = 2000070000;
+ mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_DELETE);
+ mi.pszName = LPGEN("De&lete");
+ mi.pszService = "CList/DeleteContactCommand";
+ Menu_AddContactMenuItem(&mi);
+
+ CreateServiceFunction("CList/RenameContactCommand", MenuItem_RenameContact);
+ mi.position = 2000050000;
+ mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_RENAME);
+ mi.pszName = LPGEN("&Rename");
+ mi.pszService = "CList/RenameContactCommand";
+ hRenameMenuItem = Menu_AddContactMenuItem(&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";
+ Menu_AddContactMenuItem(&mi);
+
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, MenuItem_PreBuild);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default contact list window procedure
+
+void fnDrawMenuItem(DRAWITEMSTRUCT *dis, HICON hIcon, HICON eventIcon)
+{
+ 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;
+}
+
+LRESULT CALLBACK fnContactListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static int noRecurse = 0;
+
+ if (msg == uMsgProcessProfile) {
+ TCHAR profile[MAX_PATH];
+ int rc;
+ // wParam = (ATOM)hProfileAtom, lParam = 0
+ if (GlobalGetAtomName((ATOM) wParam, profile, SIZEOF(profile))) {
+ rc = mir_tstrcmpi(profile, VARST(_T("%miranda_userdata%\\%miranda_profilename%.dat"))) == 0;
+ 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 = { sizeof(mii) };
+ 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:
+ TranslateMenu(GetMenu(hwnd));
+ 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);
+ SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA);
+ }
+ transparentFocus = 1;
+ return FALSE;
+
+ case M_CREATECLC:
+ cli.hwndContactTree = CreateWindow( _T(CLISTCONTROL_CLASS), _T(""),
+ WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN
+ | CLS_CONTACTLIST
+ | (db_get_b(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT) ? CLS_USEGROUPS : 0)
+ | (db_get_b(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) ? CLS_HIDEOFFLINE : 0)
+ | (db_get_b(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 = db_get_w(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) || db_get_b(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT)) {
+ ShowWindow(hwnd, SW_HIDE);
+ db_set_b(NULL, "CList", "State", SETTING_STATE_HIDDEN);
+ }
+ else db_set_b(NULL, "CList", "State", SETTING_STATE_MINIMIZED);
+
+ if (db_get_b(NULL, "CList", "DisableWorkingSet", 1))
+ SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+ }
+ // drop thru
+ case WM_MOVE:
+ if (!IsIconic(hwnd)) {
+ RECT rc;
+ GetWindowRect(hwnd, &rc);
+
+ //if docked, dont remember pos (except for width)
+ if (!CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) {
+ db_set_dw(NULL, "CList", "Height", (DWORD) (rc.bottom - rc.top));
+ db_set_dw(NULL, "CList", "x", (DWORD) rc.left);
+ db_set_dw(NULL, "CList", "y", (DWORD) rc.top);
+ }
+ db_set_dw(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);
+ 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(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 (db_get_b(NULL, "CLUI", "AutoSize", 0))
+ return HTCLIENT;
+ return result;
+ }
+
+ case WM_TIMER:
+ if (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) { //change
+ transparentFocus = inwnd;
+ if (transparentFocus)
+ SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA);
+ else
+ SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) db_get_b(NULL, "CList", "AutoAlpha", SETTING_AUTOALPHA_DEFAULT), LWA_ALPHA);
+ }
+ if (!transparentFocus)
+ KillTimer(hwnd, TM_AUTOALPHA);
+ }
+ return TRUE;
+
+ case WM_SHOWWINDOW:
+ if (lParam)
+ break;
+ if (noRecurse)
+ break;
+ if (!db_get_b(NULL, "CLUI", "FadeInOut", 0))
+ 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) ||
+ db_get_b(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT))
+ {
+ ShowWindow(hwnd, SW_HIDE);
+ db_set_b(NULL, "CList", "State", SETTING_STATE_HIDDEN);
+
+ if (db_get_b(NULL, "CList", "DisableWorkingSet", 1))
+ SetProcessWorkingSetSize(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);
+ db_set_b(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);
+ db_set_b(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 && (GetWindowLongPtr(hwnd, GWL_STYLE) & (WS_VISIBLE | WS_MINIMIZE)) == WS_VISIBLE &&
+ !CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0))
+ {
+ RECT rc;
+ GetWindowRect(hwnd, &rc);
+ if (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) {
+ NMCLISTCONTROL *nmc = (NMCLISTCONTROL*)lParam;
+ switch (((LPNMHDR) lParam)->code) {
+ case CLN_EXPANDED:
+ CallService(MS_CLIST_GROUPSETEXPANDED, (WPARAM) nmc->hItem, nmc->action);
+ return FALSE;
+
+ case CLN_DRAGGING:
+ 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:
+ if (!(nmc->flags & CLNF_ISGROUP))
+ NotifyEventHooks(hContactDragStopEvent, (WPARAM) nmc->hItem, 0);
+ break;
+
+ case CLN_DROPPED:
+ 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 CLN_NEWCONTACT:
+ if (nmc != NULL)
+ cli.pfnSetAllExtraIcons((MCONTACT)nmc->hItem);
+ return TRUE;
+
+ case CLN_LISTREBUILT:
+ cli.pfnSetAllExtraIcons(NULL);
+ return(FALSE);
+
+ case NM_KEYDOWN:
+ return CallService(MS_CLIST_MENUPROCESSHOTKEY, ((NMKEY*)lParam)->nVKey, MPCF_MAINMENU | MPCF_CONTACTMENU);
+
+ case CLN_LISTSIZECHANGE:
+ {
+ RECT rcWindow, rcTree, rcWorkArea;
+ int maxHeight, newHeight;
+
+ if (!db_get_b(NULL, "CLUI", "AutoSize", 0))
+ break;
+ if (CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0))
+ break;
+ maxHeight = db_get_b(NULL, "CLUI", "MaxSizeHeight", 75);
+ GetWindowRect(hwnd, &rcWindow);
+ GetWindowRect(cli.hwndContactTree, &rcTree);
+
+ SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, FALSE);
+ HMONITOR hMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO mi;
+ mi.cbSize = sizeof(mi);
+ if (GetMonitorInfo(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 (db_get_b(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:
+ {
+ DWORD hitFlags;
+ HANDLE hItem = (HANDLE)SendMessage(cli.hwndContactTree, CLM_HITTEST, (WPARAM)&hitFlags, MAKELPARAM(nmc->pt.x, nmc->pt.y));
+ if (hItem) {
+ if (hitFlags & CLCHT_ONITEMEXTRA) {
+ if (!IsHContactGroup(hItem) && !IsHContactInfo(hItem))
+ if (cli.pfnGetCacheEntry((MCONTACT)nmc->hItem))
+ NotifyEventHooks(hEventExtraClick, (WPARAM)nmc->hItem, nmc->iColumn+1);
+ }
+ break;
+ }
+
+ if ((hitFlags & (CLCHT_NOWHERE | CLCHT_INLEFTMARGIN | CLCHT_BELOWITEMS)) == 0)
+ break;
+
+ if (db_get_b(NULL, "CLUI", "ClientAreaDrag", SETTING_CLIENTDRAG_DEFAULT)) {
+ POINT pt = nmc->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; i<mcnt; i++) {
+ HMENU hMenus = GetSubMenu(hMenu, i);
+ if (hMenus && cpnl++ == nPanel) {
+ hMenu = hMenus;
+ break;
+ }
+ }
+ }
+ SendMessage(cli.hwndStatus, SB_GETRECT, nPanel, (LPARAM) & rc);
+ pt.x = rc.left;
+ pt.y = rc.top;
+ ClientToScreen(cli.hwndStatus, &pt);
+ TrackPopupMenu(hMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, NULL);
+ }
+ }
+ return FALSE;
+
+ case WM_MENUSELECT:
+ if (lParam && (HMENU)lParam == cli.hMenuMain) {
+ int pos = LOWORD(wParam);
+ POINT pt;
+ GetCursorPos(&pt);
+ if ((pos == 0 || pos == 1) && (HIWORD(wParam) & MF_POPUP) && (!(HIWORD(wParam) & MF_MOUSESELECT) || MenuItemFromPoint(hwnd, cli.hMenuMain, pt) != -1)) {
+ MENUITEMINFO mii = { sizeof(mii) };
+ mii.fMask = MIIM_SUBMENU;
+ mii.hSubMenu = (HMENU)CallService((pos == 0) ? MS_CLIST_MENUGETMAIN : MS_CLIST_MENUGETSTATUS, 0, 0);
+ SetMenuItemInfo(cli.hMenuMain, pos, TRUE, &mii);
+ }
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ {
+ // x/y might be -1 if it was generated by a kb click
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+
+ RECT rc;
+ GetWindowRect(cli.hwndContactTree, &rc);
+ if (pt.x == -1 && pt.y == -1) {
+ // all this is done in screen-coords!
+ GetCursorPos(&pt);
+ // the mouse isnt near the window, so put it in the middle of the window
+ if (!PtInRect(&rc, pt)) {
+ pt.x = rc.left + (rc.right - rc.left) / 2;
+ pt.y = rc.top + (rc.bottom - rc.top) / 2;
+ }
+ }
+ if (PtInRect(&rc, pt)) {
+ HMENU hMenu;
+ hMenu = GetSubMenu(LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT)), 1);
+ TranslateMenu(hMenu);
+ CheckMenuItem(hMenu, POPUP_HIDEOFFLINE,
+ db_get_b(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(hMenu, POPUP_HIDEOFFLINEROOT, SendMessage(cli.hwndContactTree, CLM_GETHIDEOFFLINEROOT, 0, 0) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(hMenu, POPUP_HIDEEMPTYGROUPS,
+ GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(hMenu, POPUP_DISABLEGROUPS, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS ? MF_UNCHECKED : MF_CHECKED);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
+ DestroyMenu(hMenu);
+ return 0;
+ }
+ GetWindowRect(cli.hwndStatus, &rc);
+ if (PtInRect(&rc, pt)) {
+ HMENU hMenu;
+ if (db_get_b(NULL, "CLUI", "SBarRightClk", 0))
+ hMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0);
+ else
+ hMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
+ return 0;
+ }
+ }
+ break;
+
+ case WM_MEASUREITEM:
+ if (((LPMEASUREITEMSTRUCT) lParam)->itemData == 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 = db_get_b(NULL, "CLUI", "SBarShow", 1);
+ status = CallProtoServiceInt(NULL,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);
+ IcoLib_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);
+ IcoLib_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, (int)mir_tstrlen(tszName), &textSize);
+ TextOut(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - textSize.cy) >> 1, tszName, (int)mir_tstrlen(tszName));
+ x += textSize.cx;
+ }
+ if (showOpts & 4) {
+ TCHAR* szStatus = cli.pfnGetStatusModeDescription(status, 0);
+ if (!szStatus)
+ szStatus = _T("");
+ GetTextExtentPoint32(dis->hDC, szStatus, (int)mir_tstrlen(szStatus), &textSize);
+ TextOut(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - textSize.cy) >> 1, szStatus, (int)mir_tstrlen(szStatus));
+ }
+ }
+ else if (dis->CtlType == ODT_MENU) {
+ if (dis->itemData == MENU_MIRANDAMENU) {
+ HICON hIcon = LoadSkinnedIcon(SKINICON_OTHER_MAINMENU);
+ fnDrawMenuItem(dis, CopyIcon(hIcon), NULL);
+ IcoLib_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 docked, dont remember pos (except for width)
+ if (!CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) {
+ db_set_dw(NULL, "CList", "Height", (DWORD) (rc.bottom - rc.top));
+ db_set_dw(NULL, "CList", "x", (DWORD) rc.left);
+ db_set_dw(NULL, "CList", "y", (DWORD) rc.top);
+ }
+ db_set_dw(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/mir_app/src/cluiservices.cpp b/src/mir_app/src/cluiservices.cpp new file mode 100644 index 0000000000..e5f08af088 --- /dev/null +++ b/src/mir_app/src/cluiservices.cpp @@ -0,0 +1,213 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 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 (!mir_tstrcmp(szFocusClass, _T(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 ((db_get_b(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEOFFLINE) == 0)) {
+ if (db_get_b(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 ((db_get_b(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) == 0)) {
+ if (db_get_b(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 ((db_get_b(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS) == 0)) {
+ if (db_get_b(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)
+ cli.pfnInitAutoRebuild(cli.hwndContactTree);
+ 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;
+ case CLUICAPS_FLAGS2:
+ return MAKELONG(EXTRA_ICON_COUNT,1);
+ }
+ return 0;
+}
+
+void LoadCluiServices(void)
+{
+ CreateServiceFunction(MS_CLUI_GETHWND, GetHwnd);
+ CreateServiceFunction(MS_CLUI_GETHWNDTREE, GetHwndTree);
+ 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_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 (db_get_b(NULL, "CLUI", "EqualSections", 0)) {
+ RECT rc;
+ GetClientRect(cli.hwndStatus, &rc);
+ rc.right -= borders[0] * 2 + (db_get_b(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;
+ HFONT hFont;
+ SIZE textSize;
+ BYTE showOpts = db_get_b(NULL, "CLUI", "SBarShow", 1);
+
+ hdc = GetDC(NULL);
+ hFont = (HFONT)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 && mir_tstrlen(tszName) < SIZEOF(tszName)-1)
+ mir_tstrcat(tszName, _T(" "));
+ GetTextExtentPoint32(hdc, tszName, (int)mir_tstrlen(tszName), &textSize);
+ x += textSize.cx;
+ x += GetSystemMetrics(SM_CXBORDER) * 4; // The SB panel doesnt allocate enough room
+ }
+ if (showOpts & 4) {
+ TCHAR* modeDescr = cli.pfnGetStatusModeDescription(CallProtoServiceInt(NULL,cli.menuProtos[i].szProto, PS_GETSTATUS, 0, 0), 0);
+ GetTextExtentPoint32(hdc, modeDescr, (int)mir_tstrlen(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;
+ }
+ SelectObject(hdc, hFont);
+ 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 (db_get_b(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/mir_app/src/colorchooser.cpp b/src/mir_app/src/colorchooser.cpp new file mode 100644 index 0000000000..56dd57c3a9 --- /dev/null +++ b/src/mir_app/src/colorchooser.cpp @@ -0,0 +1,284 @@ +/*
+Chat module plugin for Miranda IM
+
+Copyright 2000-12 Miranda IM, 2012-15 Miranda NG 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 "stdafx.h"
+
+#include "chat.h"
+
+struct COLORCHOOSER
+{
+ MODULEINFO *pModule;
+ int xPosition, yPosition;
+ HWND hWndTarget, hWndChooser;
+ BOOL bForeground;
+ GCSessionInfoBase *si;
+};
+
+static int CalculateCoordinatesToButton(COLORCHOOSER * pCC, POINT pt)
+{
+ int iSquareRoot = (int)sqrt(static_cast<float>(pCC->pModule->nColorCount));
+ int nCols = iSquareRoot * iSquareRoot < pCC->pModule->nColorCount ? iSquareRoot + 1 : iSquareRoot;
+
+ int col = pt.x / 25;
+ int row = (pt.y - 20) / 20;
+ int pos = nCols * row + col;
+
+ if (pt.y < 20 && pos >= pCC->pModule->nColorCount)
+ pos = -1;
+
+ return pos;
+}
+
+static RECT CalculateButtonToCoordinates(COLORCHOOSER * pCC, int buttonPosition)
+{
+ int iSquareRoot = (int)sqrt(static_cast<float>(pCC->pModule->nColorCount));
+ int nCols = iSquareRoot * iSquareRoot < pCC->pModule->nColorCount ? iSquareRoot + 1 : iSquareRoot;
+
+ int row = buttonPosition / nCols;
+ int col = buttonPosition % nCols;
+
+ RECT pt;
+ pt.left = col * 25 + 1;
+ pt.top = row * 20 + 20;
+ pt.right = pt.left + 25 - 1;
+ pt.bottom = pt.top + 20;
+
+ return pt;
+}
+
+static INT_PTR CALLBACK DlgProcColorToolWindow(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static COLORCHOOSER* pCC = NULL;
+ static int iCurrentHotTrack;
+ static BOOL bChoosing;
+ static int iRows;
+ static int iColumns;
+ static HWND hPreviousActiveWindow;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ pCC = (COLORCHOOSER*) lParam;
+
+ iCurrentHotTrack = -2;
+ bChoosing = FALSE;
+
+ int iSquareRoot = (int)sqrt(static_cast<float>(pCC->pModule->nColorCount));
+
+ iColumns = iSquareRoot * iSquareRoot == pCC->pModule->nColorCount ? iSquareRoot : iSquareRoot + 1;
+ iRows = iSquareRoot;
+
+ RECT rc;
+ rc.top = rc.left = 100;
+ rc.right = 100 + iColumns * 25 + 1;
+ rc.bottom = iRows * 20 + 100 + 20;
+
+ AdjustWindowRectEx(&rc, GetWindowLongPtr(hwndDlg, GWL_STYLE), FALSE, GetWindowLongPtr(hwndDlg, GWL_EXSTYLE));
+
+ int width = rc.right - rc.left;
+ int height = rc.bottom - rc.top;
+
+ pCC->yPosition -= height;
+
+ SetDlgItemText(hwndDlg, IDC_COLORTEXT, pCC->bForeground ? TranslateT("Text color") : TranslateT("Background color"));
+ SetWindowPos(GetDlgItem(hwndDlg, IDC_COLORTEXT), NULL, 0, 0, width, 20, 0);
+ SetWindowPos(hwndDlg, NULL, pCC->xPosition, pCC->yPosition, width, height, SWP_SHOWWINDOW);
+ }
+ break;
+
+ case WM_CTLCOLOREDIT:
+ case WM_CTLCOLORSTATIC:
+ if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_COLORTEXT)) {
+ SetTextColor((HDC)wParam, RGB(60, 60, 150));
+ SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW));
+ return (INT_PTR)GetSysColorBrush(COLOR_WINDOW);
+ }
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ if (iCurrentHotTrack >= 0)
+ PostMessage(hwndDlg, WM_LBUTTONUP, 0, 0);
+ break;
+ case IDCANCEL:
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (iCurrentHotTrack >= 0 && iCurrentHotTrack < pCC->pModule->nColorCount && pCC->hWndTarget != NULL) {
+ CHARFORMAT2 cf;
+ cf.cbSize = sizeof(CHARFORMAT2);
+ cf.dwMask = 0;
+ cf.dwEffects = 0;
+
+ HWND hWindow = GetParent(pCC->hWndTarget);
+ int ctrlId = GetDlgCtrlID(pCC->hWndChooser);
+
+ if (pCC->bForeground) {
+ pCC->si->bFGSet = TRUE;
+ pCC->si->iFG = iCurrentHotTrack;
+ if (IsDlgButtonChecked(hWindow, ctrlId)) {
+ cf.dwMask = CFM_COLOR;
+ cf.crTextColor = pCC->pModule->crColors[iCurrentHotTrack];
+ SendMessage(pCC->hWndTarget, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
+ }
+ }
+ else {
+ pCC->si->bBGSet = TRUE;
+ pCC->si->iBG = iCurrentHotTrack;
+ if (IsDlgButtonChecked(hWindow, ctrlId)) {
+ cf.dwMask = CFM_BACKCOLOR;
+ cf.crBackColor = pCC->pModule->crColors[iCurrentHotTrack];
+ SendMessage(pCC->hWndTarget, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
+ }
+ }
+ }
+ PostMessage(hwndDlg, WM_CLOSE, 0, 0);
+ break;
+
+ case WM_ACTIVATE:
+ if (wParam == WA_INACTIVE)
+ PostMessage(hwndDlg, WM_CLOSE, 0, 0);
+ else if ((wParam == WA_ACTIVE) || (wParam == WA_CLICKACTIVE))
+ hPreviousActiveWindow = (HWND)lParam;
+ break;
+
+ case WM_MOUSEMOVE:
+ {
+ HDC hdc = GetDC(hwndDlg);
+ POINT pt;
+ RECT rect;
+ int but;
+
+ pt.x = LOWORD(lParam);
+ pt.y = HIWORD(lParam);
+
+ if (iCurrentHotTrack == -2)
+ return 0; // prevent focussing when not drawn yet!
+
+ but = CalculateCoordinatesToButton(pCC, pt);
+
+ // weird stuff
+ if (but != iCurrentHotTrack) {
+ if (iCurrentHotTrack >= 0) {
+ rect = CalculateButtonToCoordinates(pCC, iCurrentHotTrack);
+ DrawFocusRect(hdc, &rect);
+ iCurrentHotTrack = -1;
+ }
+ iCurrentHotTrack = but;
+
+ if (iCurrentHotTrack >= 0) {
+ rect = CalculateButtonToCoordinates(pCC, iCurrentHotTrack);
+ DrawFocusRect(hdc, &rect);
+ }
+ }
+ ReleaseDC(hwndDlg, hdc);
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ PAINTSTRUCT ps;
+ int iThisRow = 1;
+ int iThisColumn = 0;
+
+ RECT rc;
+ GetClientRect(hwndDlg, &rc);
+ rc.top += 20;
+
+ HDC hdc = BeginPaint(hwndDlg, &ps);
+
+ // fill background
+ FillRect(hdc, &rc, GetSysColorBrush(COLOR_WINDOW));
+
+ for (int i=0; i < pCC->pModule->nColorCount; i++) {
+ // decide place to draw the color block in the window
+ iThisColumn ++;
+ if (iThisColumn > iColumns) {
+ iThisColumn = 1;
+ iThisRow++;
+ }
+
+ if (pCC->bForeground && pCC->si->bFGSet && pCC->si->iFG == i || !pCC->bForeground && pCC->si->bBGSet && pCC->si->iBG == i) {
+ rc.top = (iThisRow - 1) * 20 + 1 + 20 ;
+ rc.left = (iThisColumn - 1) * 25 + 1 + 1 ;
+ rc.bottom = iThisRow * 20 - 1 + 20 ;
+ rc.right = iThisColumn * 25 - 1 ;
+
+ DrawEdge(hdc, &rc, EDGE_RAISED, BF_TOP | BF_LEFT | BF_RIGHT | BF_BOTTOM);
+ }
+
+ rc.top = (iThisRow - 1) * 20 + 3 + 20 ;
+ rc.left = (iThisColumn - 1) * 25 + 3 + 1 ;
+ rc.bottom = iThisRow * 20 - 3 + 20 ;
+ rc.right = iThisColumn * 25 - 3 ;
+
+ FillRect(hdc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
+
+ HBRUSH hbr = CreateSolidBrush(pCC->pModule->crColors[i]);
+
+ rc.top = (iThisRow - 1) * 20 + 4 + 20;
+ rc.left = (iThisColumn - 1) * 25 + 4 + 1;
+ rc.bottom = iThisRow * 20 - 4 + 20;
+ rc.right = iThisColumn * 25 - 4;
+
+ FillRect(hdc, &rc, hbr);
+ DeleteObject(hbr);
+ }
+
+ EndPaint(hwndDlg, &ps);
+ iCurrentHotTrack = -1;
+ }
+ break;
+
+ case WM_CLOSE:
+ SetFocus(pCC->hWndTarget);
+ DestroyWindow(hwndDlg);
+ break;
+
+ case WM_DESTROY:
+ mir_free(pCC);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void ColorChooser(SESSION_INFO *si, BOOL bFG, HWND hwndDlg, HWND hwndTarget, HWND hwndChooser)
+{
+ RECT rc;
+ GetWindowRect(hwndChooser, &rc);
+
+ COLORCHOOSER *pCC = (COLORCHOOSER *)mir_alloc(sizeof(COLORCHOOSER));
+ pCC->hWndTarget = hwndTarget;
+ pCC->pModule = ci.MM_FindModule(si->pszModule);
+ pCC->xPosition = rc.left + 3;
+ pCC->yPosition = IsWindowVisible(hwndChooser) ? rc.top - 1 : rc.top + 20;
+ pCC->bForeground = bFG;
+ pCC->hWndChooser = hwndChooser;
+ pCC->si = si;
+ CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_COLORCHOOSER), hwndDlg, DlgProcColorToolWindow, (LPARAM)pCC);
+}
diff --git a/src/mir_app/src/colourpicker.cpp b/src/mir_app/src/colourpicker.cpp new file mode 100644 index 0000000000..3c30393f11 --- /dev/null +++ b/src/mir_app/src/colourpicker.cpp @@ -0,0 +1,108 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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)g_hInst;
+ 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;
+ }
+ }
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+int InitColourPicker(void)
+{
+ WNDCLASS wcl;
+
+ wcl.lpfnWndProc = ColourPickerWndProc;
+ wcl.cbClsExtra = 0;
+ wcl.cbWndExtra = sizeof(COLORREF)*2;
+ wcl.hInstance = g_hInst;
+ wcl.hCursor = NULL;
+ wcl.lpszClassName = _T(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/mir_app/src/contact.cpp b/src/mir_app/src/contact.cpp new file mode 100644 index 0000000000..f4f9296c63 --- /dev/null +++ b/src/mir_app/src/contact.cpp @@ -0,0 +1,176 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.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(MCONTACT hContact) +{ + char *szProto = GetContactProto(hContact); + if (szProto == NULL) + return ID_STATUS_OFFLINE; + return db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE); +} + +void fnChangeContactIcon(MCONTACT hContact, int iIcon, int add) +{ + CallService(add ? MS_CLUI_CONTACTADDED : MS_CLUI_CONTACTSETICON, hContact, iIcon); + NotifyEventHooks(hContactIconChangedEvent, 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) +{ + CallService(MS_CLUI_LISTBEGINREBUILD, 0, 0); + for (int i = 1;; i++) { + if (cli.pfnGetGroupName(i, NULL) == NULL) + break; + CallService(MS_CLUI_GROUPADDED, i, 0); + } + + int hideOffline = db_get_b(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT); + for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) { + int status = GetContactStatus(hContact); + if ((!hideOffline || status != ID_STATUS_OFFLINE) && !db_get_b(hContact, "CList", "Hidden", 0)) + cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(GetContactProto(hContact), status, hContact), 1); + } + sortByStatus = db_get_b(NULL, "CList", "SortByStatus", SETTING_SORTBYSTATUS_DEFAULT); + sortByProto = db_get_b(NULL, "CList", "SortByProto", SETTING_SORTBYPROTO_DEFAULT); + CallService(MS_CLUI_LISTENDREBUILD, 0, 0); +} + +int fnCompareContacts(const ClcContact* c1, const ClcContact* c2) +{ + MCONTACT a = c1->hContact, b = c2->hContact; + TCHAR namea[128], *nameb; + int statusa, statusb; + int rc; + + statusa = db_get_w(a, c1->proto, "Status", ID_STATUS_OFFLINE); + statusb = db_get_w(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 */ + if (c1->proto != NULL && c2->proto != NULL) { + rc = mir_strcmp(c1->proto, c2->proto); + if (rc != 0) + return rc; + } + /* protocols are the same, order by display name */ + } + + if (sortByStatus) { + int ordera = GetStatusModeOrdering(statusa); + int 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_s(namea, nameb, _TRUNCATE); + namea[ SIZEOF(namea)-1 ] = 0; + nameb = cli.pfnGetContactDisplayName(b, 0); + + //otherwise just compare names + return mir_tstrcmpi(namea, nameb); +} + +void fnSortContacts(void) +{ + //avoid doing lots of resorts in quick succession + sortByStatus = db_get_b(NULL, "CList", "SortByStatus", SETTING_SORTBYSTATUS_DEFAULT); + sortByProto = db_get_b(NULL, "CList", "SortByProto", SETTING_SORTBYPROTO_DEFAULT); +} + +INT_PTR ContactChangeGroup(WPARAM wParam, LPARAM lParam) +{ + CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, NULL }; + + CallService(MS_CLUI_CONTACTDELETED, wParam, 0); + if ((HANDLE) lParam == NULL) + db_unset(wParam, "CList", "Group"); + else { + grpChg.pszNewName = cli.pfnGetGroupName(lParam, NULL); + db_set_ts(wParam, "CList", "Group", grpChg.pszNewName); + } + CallService(MS_CLUI_CONTACTADDED, wParam, + cli.pfnIconFromStatusMode(GetContactProto(wParam), GetContactStatus(wParam), wParam)); + + NotifyEventHooks(hGroupChangeEvent, wParam, (LPARAM)&grpChg); + return 0; +} + +int fnSetHideOffline(WPARAM wParam, LPARAM) +{ + switch((int)wParam) { + case 0: + db_set_b(NULL, "CList", "HideOffline", 0); + break; + case 1: + db_set_b(NULL, "CList", "HideOffline", 1); + break; + case -1: + db_set_b(NULL, "CList", "HideOffline", !db_get_b(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT)); + break; + } + cli.pfnLoadContactTree(); + return 0; +} diff --git a/src/mir_app/src/contacts.cpp b/src/mir_app/src/contacts.cpp new file mode 100644 index 0000000000..2d1729ad73 --- /dev/null +++ b/src/mir_app/src/contacts.cpp @@ -0,0 +1,452 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#define NAMEORDERCOUNT 9
+static TCHAR* nameOrderDescr[ NAMEORDERCOUNT ] =
+{
+ LPGENT("My custom name (not movable)"),
+ LPGENT("Nick"),
+ LPGENT("FirstName"),
+ LPGENT("E-mail"),
+ LPGENT("LastName"),
+ LPGENT("Username"),
+ LPGENT("FirstName LastName"),
+ LPGENT("LastName FirstName"),
+ LPGENT("'(Unknown contact)' (not movable)")
+};
+
+BYTE nameOrder[NAMEORDERCOUNT];
+
+static int GetDatabaseString(CONTACTINFO *ci, const char* setting, DBVARIANT* dbv)
+{
+ if (mir_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 db_get_ws(ci->hContact, ci->szProto, setting, dbv);
+
+ return db_get_s(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;
+ }
+ db_free(&dbv);
+ }
+
+ if (db_get(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;
+ }
+
+ db_free(&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 (!db_get(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; i < countryCount; i++) {
+ if (countries[i].id != dbv.wVal) continue;
+
+ if (ci->dwFlag & 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;
+ db_free(&dbv);
+ return 0;
+ }
+ }
+ else return ProcessDatabaseValueDefault(ci, (ci->dwFlag & 0x7F) == CNF_COUNTRY ? "Country" : "CompanyCountry");
+ db_free(&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 = mir_wstrlen(dbv.pwszVal) + mir_wstrlen(dbv2.pwszVal) + 2;
+ WCHAR* buf = (WCHAR*)mir_alloc(sizeof(WCHAR)*len);
+ if (buf != NULL)
+ mir_wstrcat(mir_wstrcat(mir_wstrcpy(buf, dbv.pwszVal), L" "), dbv2.pwszVal);
+ ci->pszVal = (TCHAR*)buf;
+ }
+ else {
+ size_t len = mir_strlen(dbv.pszVal) + mir_strlen(dbv2.pszVal) + 2;
+ char* buf = (char*)mir_alloc(len);
+ if (buf != NULL)
+ mir_strcat(mir_strcat(mir_strcpy(buf, dbv.pszVal), " "), dbv2.pszVal);
+ ci->pszVal = (TCHAR*)buf;
+ }
+ db_free(&dbv);
+ db_free(&dbv2);
+ return 0;
+ }
+ db_free(&dbv);
+ }
+ break;
+
+ case CNF_UNIQUEID:
+ {
+ if (db_mc_isMeta(ci->hContact)) {
+ TCHAR buf[40];
+ _itot(ci->hContact, buf, 10);
+ ci->pszVal = mir_tstrdup(buf);
+ ci->type = CNFT_ASCIIZ;
+ 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_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:
+ for (int 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
+ case 7: // last + first name
+ if (!GetDatabaseString(ci, nameOrder[i] == 6 ? "FirstName" : "LastName", &dbv)) {
+ DBVARIANT dbv2;
+ if (!GetDatabaseString(ci, nameOrder[i] == 6 ? "LastName" : "FirstName", &dbv2)) {
+ ci->type = CNFT_ASCIIZ;
+
+ if (ci->dwFlag & CNF_UNICODE) {
+ size_t len = mir_wstrlen(dbv.pwszVal) + mir_wstrlen(dbv2.pwszVal) + 2;
+ WCHAR* buf = (WCHAR*)mir_alloc(sizeof(WCHAR)*len);
+ if (buf != NULL)
+ mir_wstrcat(mir_wstrcat(mir_wstrcpy(buf, dbv.pwszVal), L" "), dbv2.pwszVal);
+ ci->pszVal = (TCHAR*)buf;
+ }
+ else {
+ size_t len = mir_strlen(dbv.pszVal) + mir_strlen(dbv2.pszVal) + 2;
+ char* buf = (char*)mir_alloc(len);
+ if (buf != NULL)
+ mir_strcat(mir_strcat(mir_strcpy(buf, dbv.pszVal), " "), dbv2.pszVal);
+ ci->pszVal = (TCHAR*)buf;
+ }
+
+ db_free(&dbv);
+ db_free(&dbv2);
+ return 0;
+ }
+ db_free(&dbv);
+ }
+ break;
+
+ case 8:
+ 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, 0, TZF_KNOWNONLY);
+ if (hTz) {
+ LPTIME_ZONE_INFORMATION tzi = tmi.getTzi(hTz);
+ int offset = tzi->Bias + tzi->StandardBias;
+
+ char str[80];
+ mir_snprintf(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;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Options dialog
+
+class CContactOptsDlg : public CDlgBase
+{
+ CCtrlTreeView m_nameOrder;
+
+public:
+ CContactOptsDlg() :
+ CDlgBase(g_hInst, IDD_OPT_CONTACT),
+ m_nameOrder(this, IDC_NAMEORDER)
+ {
+ m_nameOrder.SetFlags(MTREE_DND);
+ m_nameOrder.OnBeginDrag = Callback(this, &CContactOptsDlg::OnBeginDrag);
+ }
+
+ virtual void OnInitDialog()
+ {
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = NULL;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_TEXT | TVIF_PARAM;
+ for (int i = 0; i < SIZEOF(nameOrderDescr); i++) {
+ tvis.item.lParam = nameOrder[i];
+ tvis.item.pszText = TranslateTS(nameOrderDescr[nameOrder[i]]);
+ m_nameOrder.InsertItem(&tvis);
+ }
+ }
+
+ virtual void OnApply()
+ {
+ TVITEMEX tvi;
+ tvi.hItem = m_nameOrder.GetRoot();
+ int i = 0;
+ while (tvi.hItem != NULL) {
+ tvi.mask = TVIF_PARAM | TVIF_HANDLE;
+ m_nameOrder.GetItem(&tvi);
+ nameOrder[i++] = (BYTE)tvi.lParam;
+ tvi.hItem = m_nameOrder.GetNextSibling(tvi.hItem);
+ }
+ db_set_blob(NULL, "Contact", "NameOrder", nameOrder, SIZEOF(nameOrderDescr));
+ CallService(MS_CLIST_INVALIDATEDISPLAYNAME, (WPARAM)INVALID_HANDLE_VALUE, 0);
+ }
+
+ void OnBeginDrag(CCtrlTreeView::TEventInfo *evt)
+ {
+ LPNMTREEVIEW pNotify = evt->nmtv;
+ if (pNotify->itemNew.lParam == 0 || pNotify->itemNew.lParam == SIZEOF(nameOrderDescr) - 1)
+ pNotify->hdr.code = 0; // deny dragging
+ }
+};
+
+static int ContactOptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.position = -1000000000;
+ odp.pszGroup = LPGEN("Contact list");
+ odp.pszTitle = LPGEN("Contact names");
+ odp.pDialog = new CContactOptsDlg();
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+int LoadContactsModule(void)
+{
+ for (BYTE i = 0; i < NAMEORDERCOUNT; i++)
+ nameOrder[i] = i;
+
+ DBVARIANT dbv;
+ if (!db_get(NULL, "Contact", "NameOrder", &dbv)) {
+ memcpy(nameOrder, dbv.pbVal, dbv.cpbVal);
+ db_free(&dbv);
+ }
+
+ CreateServiceFunction(MS_CONTACT_GETCONTACTINFO, GetContactInfo);
+ HookEvent(ME_OPT_INITIALISE, ContactOptInit);
+ return 0;
+}
diff --git a/src/mir_app/src/database.cpp b/src/mir_app/src/database.cpp new file mode 100644 index 0000000000..802840cb5e --- /dev/null +++ b/src/mir_app/src/database.cpp @@ -0,0 +1,538 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "profilemanager.h"
+
+// contains the location of mirandaboot.ini
+bool g_bDbCreated;
+TCHAR g_profileDir[MAX_PATH], g_profileName[MAX_PATH], g_shortProfileName[MAX_PATH];
+TCHAR* g_defaultProfile;
+void EnsureCheckerLoaded(bool);
+
+void LoadDatabaseServices();
+
+bool fileExist(const TCHAR *fname)
+{
+ if (*fname == 0)
+ return false;
+
+ FILE *fp = _tfopen(fname, _T("r+"));
+ bool res = (fp != NULL);
+ if (fp) fclose(fp);
+ return res;
+}
+
+static void fillProfileName(const TCHAR* ptszFileName)
+{
+ const TCHAR* p = _tcsrchr(ptszFileName, '\\');
+ if (p == NULL)
+ p = ptszFileName;
+ else
+ p++;
+
+ _tcsncpy_s(g_profileName, p, _TRUNCATE);
+
+ _tcsncpy_s(g_shortProfileName, p, _TRUNCATE);
+ TCHAR *pos = _tcsrchr(g_shortProfileName, '.');
+ if (mir_tstrcmpi(pos, _T(".dat")) == 0)
+ *pos = 0;
+}
+
+bool IsInsideRootDir(TCHAR* profiledir, bool exact)
+{
+ VARST pfd( _T("%miranda_path%"));
+ if (exact)
+ return mir_tstrcmpi(profiledir, pfd) == 0;
+
+ return _tcsnicmp(profiledir, pfd, mir_tstrlen(pfd)) == 0;
+}
+
+// returns 1 if the profile path was returned, without trailing slash
+int getProfilePath(TCHAR *buf, size_t)
+{
+ TCHAR profiledir[MAX_PATH];
+ GetPrivateProfileString(_T("Database"), _T("ProfileDir"), _T(""), profiledir, SIZEOF(profiledir), mirandabootini);
+
+ if (profiledir[0] == 0)
+ mir_tstrcpy(profiledir, _T("%miranda_path%\\Profiles"));
+
+ size_t len = PathToAbsoluteT( VARST(profiledir), buf);
+
+ if (buf[len-1] == '/' || buf[len-1] == '\\')
+ buf[len-1] = 0;
+
+ return 0;
+}
+
+// returns 1 if *.dat spec is matched
+int isValidProfileName(const TCHAR *name)
+{
+ size_t len = mir_tstrlen(name) - 4;
+ return len > 0 && mir_tstrcmpi(&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 (mir_tstrcmpi(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 mir_tstrcmpi(ac, _T("yes")) == 0;
+}
+
+static void getDefaultProfile(TCHAR *szProfile, size_t cch)
+{
+ TCHAR defaultProfile[MAX_PATH];
+ GetPrivateProfileString(_T("Database"), _T("DefaultProfile"), _T(""), defaultProfile, SIZEOF(defaultProfile), mirandabootini);
+
+ if (defaultProfile[0] == 0)
+ return;
+
+ VARST res(defaultProfile);
+ if (res)
+ mir_sntprintf(szProfile, cch, _T("%s\\%s\\%s%s"), g_profileDir, (TCHAR*)res, (TCHAR*)res, isValidProfileName(res) ? _T("") : _T(".dat"));
+ else
+ szProfile[0] = 0;
+}
+
+// returns 1 if something that looks like a profile is there
+static void loadProfileByShortName(const TCHAR* src, TCHAR *szProfile, size_t cch)
+{
+ TCHAR buf[MAX_PATH];
+ _tcsncpy_s(buf, src, _TRUNCATE);
+
+ TCHAR *p = _tcsrchr(buf, '\\'); if (p) ++p; else p = buf;
+ if (!isValidProfileName(buf) && *p)
+ mir_tstrcat(buf, _T(".dat"));
+
+ TCHAR profileName[MAX_PATH], newProfileDir[MAX_PATH];
+ _tcsncpy_s(profileName, p, _TRUNCATE);
+ if (!isValidProfileName(profileName) && *p)
+ mir_tstrcat(profileName, _T(".dat"));
+
+ _tcsncpy_s(profileName, p, _TRUNCATE);
+ p = _tcsrchr(profileName, '.'); if (p) *p = 0;
+
+ mir_sntprintf(newProfileDir, cch, _T("%s\\%s\\"), g_profileDir, profileName);
+ PathToAbsoluteT(buf, szProfile, newProfileDir);
+
+ if ( _tcschr(buf, '\\')) {
+ _tcsncpy_s(g_profileDir, szProfile, _TRUNCATE);
+ if (profileName[0]) {
+ p = _tcsrchr(g_profileDir, '\\'); *p = 0;
+ p = _tcsrchr(g_profileDir, '\\');
+ if (p && mir_tstrcmpi(p + 1, profileName) == 0)
+ *p = 0;
+ }
+ else szProfile[0] = 0;
+ }
+}
+
+void getProfileCmdLine(TCHAR *szProfile, size_t cch)
+{
+ LPCTSTR ptszProfileName = CmdLine_GetOption( _T("profile"));
+ if (ptszProfileName != NULL)
+ loadProfileByShortName(ptszProfileName, szProfile, cch);
+}
+
+void getProfileDefault(TCHAR *szProfile, size_t cch)
+{
+ if (g_defaultProfile != NULL) {
+ loadProfileByShortName(g_defaultProfile, szProfile, cch);
+ mir_free(g_defaultProfile);
+ }
+}
+
+// move profile from profile subdir
+static void moveProfileDirProfiles(TCHAR *profiledir, BOOL isRootDir = TRUE)
+{
+ TCHAR pfd[MAX_PATH];
+ if (isRootDir)
+ _tcsncpy_s(pfd, VARST(_T("%miranda_path%\\*.dat")), _TRUNCATE);
+ 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) {
+ TCHAR buf[512];
+ mir_sntprintf(buf,
+ TranslateT("Miranda is trying to upgrade your profile structure.\nIt cannot move profile %s to the new location %s\nBecause profile with this name already exists. Please resolve the issue manually."),
+ path, path2);
+ MessageBox(NULL, buf, _T("Miranda NG"), MB_ICONERROR | MB_OK);
+ }
+ else if (MoveFile(path, path2) == 0) {
+ TCHAR buf[512];
+ mir_sntprintf(buf,
+ TranslateT("Miranda is trying to upgrade your profile structure.\nIt cannot move profile %s to the new location %s automatically\nMost likely this is due to insufficient privileges. Please move profile manually."),
+ path, path2);
+ MessageBox(NULL, buf, _T("Miranda NG"), MB_ICONERROR | MB_OK);
+ mir_free(profile);
+ 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)
+{
+ int found = 0;
+
+ if (IsInsideRootDir(profiledir, false))
+ moveProfileDirProfiles(profiledir);
+ moveProfileDirProfiles(profiledir, FALSE);
+
+ bool bNoDefaultProfile = (*szProfile == 0);
+ bool reqfd = !bNoDefaultProfile && (_taccess(szProfile, 0) == 0 || shouldAutoCreate(szProfile));
+ bool bShowProfileManager = showProfileManager();
+
+ if (reqfd)
+ found++;
+
+ if (bShowProfileManager || !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) || !mir_tstrcmp(ffd.cFileName, _T(".")) || !mir_tstrcmp(ffd.cFileName, _T("..")))
+ continue;
+
+ TCHAR newProfile[MAX_PATH];
+ mir_sntprintf(newProfile, SIZEOF(newProfile), _T("%s\\%s\\%s.dat"), profiledir, ffd.cFileName, ffd.cFileName);
+ if (_taccess(newProfile, 0) != 0)
+ continue;
+
+ switch (touchDatabase(newProfile, NULL)) {
+ case 0:
+ case EGROKPRF_OBSOLETE:
+ if (++found == 1 && bNoDefaultProfile)
+ _tcsncpy_s(szProfile, cch, newProfile, _TRUNCATE);
+ break;
+ }
+ }
+ while (FindNextFile(hFind, &ffd));
+
+ FindClose(hFind);
+ }
+ reqfd = (!bShowProfileManager && found == 1 && bNoDefaultProfile);
+ }
+
+ if (noProfiles)
+ *noProfiles = (found == 0);
+
+ if (bNoDefaultProfile && !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)
+{
+ if (*szProfile == 0)
+ return false;
+
+ TCHAR Mgr[32];
+ GetPrivateProfileString(_T("Database"), _T("ShowProfileMgr"), _T(""), Mgr, SIZEOF(Mgr), mirandabootini);
+ if (mir_tstrcmpi(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);
+ getProfileCmdLine(szProfile, cch);
+ getProfileDefault(szProfile, cch);
+
+ if (IsInsideRootDir(g_profileDir, true)) {
+ MessageBox(NULL,
+ TranslateT("Profile cannot be placed into Miranda root folder.\nPlease move Miranda profile to some other location."),
+ LPGENT("Miranda NG"), MB_ICONERROR | MB_OK);
+ return 0;
+ }
+
+ PROFILEMANAGERDATA pd = { 0 };
+ if (CmdLine_GetOption(_T("ForceShowPM"))) {
+LBL_Show:
+ pd.ptszProfile = szProfile;
+ pd.ptszProfileDir = g_profileDir;
+ if (!getProfileManager(&pd))
+ return 0;
+
+ if (!pd.bRun)
+ return CallService(MS_DB_CHECKPROFILE, WPARAM(szProfile), TRUE);
+
+ return 1;
+ }
+
+ if (getProfileAutoRun(szProfile))
+ return 1;
+
+ if (getProfile1(szProfile, cch, g_profileDir, &pd.noProfiles))
+ return 1;
+
+ goto LBL_Show;
+}
+
+// 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 (mir_tstrcmp(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;
+}
+
+int touchDatabase(const TCHAR *tszProfile, DATABASELINK **dblink)
+{
+ for (int i = arDbPlugins.getCount() - 1; i >= 0; i--) {
+ DATABASELINK *p = arDbPlugins[i];
+ int iErrorCode = p->grokHeader(tszProfile);
+ if (iErrorCode == 0) {
+ if (dblink)
+ *dblink = p;
+ return 0;
+ }
+ if (iErrorCode == EGROKPRF_OBSOLETE) {
+ if (dblink)
+ *dblink = p;
+ return EGROKPRF_OBSOLETE;
+ }
+ }
+
+ if (dblink)
+ *dblink = NULL;
+ return EGROKPRF_CANTREAD;
+}
+
+// enumerate all plugins that had valid DatabasePluginInfo()
+int tryOpenDatabase(const TCHAR *tszProfile)
+{
+ bool bWasOpened = false;
+
+ for (int i = arDbPlugins.getCount() - 1; i >= 0; i--) {
+ DATABASELINK *p = arDbPlugins[i];
+
+ // liked the profile?
+ int err = p->grokHeader(tszProfile);
+ if (err != ERROR_SUCCESS) { // smth went wrong
+ switch (err) {
+ case EGROKPRF_CANTREAD:
+ case EGROKPRF_UNKHEADER:
+ // just not supported.
+ continue;
+
+ case EGROKPRF_OBSOLETE:
+ EnsureCheckerLoaded(true);
+ CallService(MS_DB_CHECKPROFILE, (WPARAM)tszProfile, 2);
+ break;
+
+ default:
+ return err;
+ }
+ }
+
+ bWasOpened = true;
+
+ // try to load database
+ MIDatabase *pDb = p->Load(tszProfile, FALSE);
+ if (pDb) {
+ fillProfileName(tszProfile);
+ currDblink = p;
+ db_setCurrent(currDb = pDb);
+ return 0;
+ }
+ }
+
+ return (bWasOpened) ? -1 : EGROKPRF_CANTREAD;
+}
+
+// enumerate all plugins that had valid DatabasePluginInfo()
+static int tryCreateDatabase(const TCHAR *ptszProfile)
+{
+ TCHAR *tszProfile = NEWTSTR_ALLOCA(ptszProfile);
+ CreatePathToFileT(tszProfile);
+
+ for (int i = 0; i < arDbPlugins.getCount(); i++) {
+ DATABASELINK* p = arDbPlugins[i];
+
+ int err = p->makeDatabase(tszProfile);
+ if (err == ERROR_SUCCESS) {
+ g_bDbCreated = true;
+ MIDatabase *pDb = p->Load(tszProfile, FALSE);
+ if (pDb != NULL) {
+ fillProfileName(tszProfile);
+ currDblink = p;
+ db_setCurrent(currDb = pDb);
+ return 0;
+ }
+ return 1;
+ }
+ }
+ return 1;
+}
+
+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)) && mir_tstrcmp(_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);
+ _tchdir(szProfile);
+ szProfile[0] = 0;
+
+ LoadDatabaseServices();
+
+ // find out which profile to load
+ if (!getProfile(szProfile, SIZEOF(szProfile)))
+ return 1;
+
+ if (arDbPlugins.getCount() == 0) {
+ TCHAR buf[256];
+ TCHAR *p = _tcsrchr(szProfile, '\\');
+ mir_sntprintf(buf, TranslateT("Miranda is unable to open '%s' because you do not have any profile plugins installed.\nYou need to install dbx_mmap.dll"), p ? ++p : szProfile);
+ MessageBox(0, buf, TranslateT("No profile support installed!"), MB_OK | MB_ICONERROR);
+ }
+
+ // find a driver to support the given profile
+ bool retry;
+ int rc;
+ do {
+ retry = false;
+ if (_taccess(szProfile, 0) && shouldAutoCreate(szProfile))
+ rc = tryCreateDatabase(szProfile);
+ else
+ rc = tryOpenDatabase(szProfile);
+
+ if (rc > 0) {
+ // 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, TranslateT("Miranda was unable to open '%s', it's in an unknown format.\nThis profile might also be damaged, please run DbChecker 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, TranslateT("Miranda was unable to open '%s'\nIt's inaccessible or used by other application or Miranda instance"), p ? ++p : szProfile);
+ retry = MessageBox(0, buf, TranslateT("Miranda can't open that profile"), MB_RETRYCANCEL | MB_ICONERROR) == IDRETRY;
+ }
+ }
+ }
+ while (retry);
+
+ EnsureCheckerLoaded(false); // unload dbchecker
+
+ if (rc == ERROR_SUCCESS) {
+ InitIni();
+ return 0;
+ }
+
+ return rc;
+}
diff --git a/src/mir_app/src/database.h b/src/mir_app/src/database.h new file mode 100644 index 0000000000..c0acab5c48 --- /dev/null +++ b/src/mir_app/src/database.h @@ -0,0 +1,53 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright 2012-15 Miranda NG 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.
+*/
+
+class MDatabaseCache : public MIDatabaseCache
+{
+ HANDLE m_hCacheHeap;
+ char* m_lastSetting;
+ size_t m_contactSize;
+ DBCachedContact *m_lastVL;
+ mir_cs m_cs;
+
+ LIST<DBCachedContact> m_lContacts;
+ LIST<DBCachedGlobalValue> m_lGlobalSettings;
+ LIST<char> m_lSettings;
+
+ void FreeCachedVariant(DBVARIANT* V);
+
+public:
+ MDatabaseCache(size_t);
+ ~MDatabaseCache();
+
+protected:
+ STDMETHODIMP_(DBCachedContact*) AddContactToCache(MCONTACT contactID);
+ STDMETHODIMP_(DBCachedContact*) GetCachedContact(MCONTACT contactID);
+ STDMETHODIMP_(DBCachedContact*) GetFirstContact(void);
+ STDMETHODIMP_(DBCachedContact*) GetNextContact(MCONTACT contactID);
+ STDMETHODIMP_(void) FreeCachedContact(MCONTACT contactID);
+
+ STDMETHODIMP_(char*) InsertCachedSetting(const char *szName, int);
+ STDMETHODIMP_(char*) GetCachedSetting(const char *szModuleName, const char *szSettingName, int, int);
+ STDMETHODIMP_(void) SetCachedVariant(DBVARIANT *s, DBVARIANT *d);
+ STDMETHODIMP_(DBVARIANT*) GetCachedValuePtr(MCONTACT contactID, char *szSetting, int bAllocate);
+};
diff --git a/src/mir_app/src/dbini.cpp b/src/mir_app/src/dbini.cpp new file mode 100644 index 0000000000..50f10dfee6 --- /dev/null +++ b/src/mir_app/src/dbini.cpp @@ -0,0 +1,521 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "profilemanager.h"
+
+static bool bModuleInitialized = false;
+static HANDLE hIniChangeNotification;
+
+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 (!mir_tstrcmpi(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 (!mir_tstrcmpi(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 (!mir_tstrcmpi(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;
+ size_t wordLen = mir_strlen(szWord);
+
+ for (szItem = szList;;) {
+ szEnd = strchr(szItem, ' ');
+ if (szEnd == NULL)
+ return !mir_strcmp(szItem, szWord);
+
+ if (size_t(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 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);
+ mir_strcpy(szSettingName, warnInfo->szSection);
+ mir_strcat(szSettingName, " / ");
+ mir_strcat(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;
+ // fall through
+ 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)
+{
+ TCHAR szIniPath[MAX_PATH];
+
+ switch (message) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SetDlgItemText(hwndDlg, IDC_ININAME, (TCHAR*)lParam);
+ SetDlgItemText(hwndDlg, IDC_NEWNAME, (TCHAR*)lParam);
+ return TRUE;
+
+ case WM_COMMAND:
+ 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[mir_tstrlen(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)
+{
+ SettingsList *newItem = (SettingsList *)mir_alloc(sizeof(SettingsList));
+ newItem->name = mir_strdup(szSetting);
+ newItem->next = setting_items;
+ setting_items = newItem;
+ return 0;
+}
+
+static 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, mir_strlen(pstr + 2) + 1);
+ }
+ }
+}
+
+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;
+LBL_NewLine:
+ size_t lineLength = mir_strlen(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 {
+ mir_strncpy(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;
+ mir_strncpy(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;
+
+ db_unset(NULL, szSection, setting_items->name);
+
+ 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];
+ mir_strncpy(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(g_hInst, MAKEINTRESOURCE(IDD_WARNINICHANGE), NULL, WarnIniChangeDlgProc, (LPARAM)&warnInfo))
+ continue;
+ if (warnInfo.cancel)
+ break;
+ if (warnInfo.warnNoMore)
+ warnThisSection = 0;
+ }
+
+ switch (szValue[0]) {
+ case 'b':
+ case 'B':
+ db_set_b(NULL, szSection, szName, (BYTE)strtol(szValue+1, NULL, 0));
+ break;
+ case 'w':
+ case 'W':
+ db_set_w(NULL, szSection, szName, (WORD)strtol(szValue+1, NULL, 0));
+ break;
+ case 'd':
+ case 'D':
+ db_set_dw(NULL, szSection, szName, (DWORD)strtoul(szValue+1, NULL, 0));
+ break;
+ case 'l':
+ case 'L':
+ case '-':
+ db_unset(NULL, szSection, szName);
+ break;
+ case 'e':
+ case 'E':
+ ConvertBackslashes(szValue+1, Langpack_GetDefaultCodePage());
+ case 's':
+ case 'S':
+ db_set_s(NULL, szSection, szName, szValue+1);
+ break;
+ case 'g':
+ case 'G':
+ for (char *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;
+ }
+ memmove(pstr + 1, pstr + 2, mir_strlen(pstr + 2) + 1);
+ }
+ }
+ case 'u':
+ case 'U':
+ db_set_utf(NULL, szSection, szName, szValue + 1);
+ break;
+ case 'm':
+ case 'M':
+ {
+ CMStringA memo(szValue + 1);
+ memo.Append("\r\n");
+ while (fgets(szLine, sizeof(szLine), fp) != NULL) {
+ switch (szLine[0]) {
+ case 0: case '\r': case '\n': case ' ': case '\t':
+ break;
+ default:
+ db_set_utf(NULL, szSection, szName, memo);
+ goto LBL_NewLine;
+ }
+
+ memo.Append(rtrim(szLine + 1));
+ memo.Append("\r\n");
+ }
+ db_set_utf(NULL, szSection, szName, memo);
+ }
+ break;
+ case 'n':
+ case 'h':
+ case 'N':
+ case 'H':
+ {
+ int len;
+ char *pszValue, *pszEnd;
+
+ PBYTE buf = (PBYTE)mir_alloc(mir_strlen(szValue + 1));
+ for (len = 0, pszValue = szValue + 1;; len++) {
+ buf[len] = (BYTE)strtol(pszValue, &pszEnd, 0x10);
+ if (pszValue == pszEnd)
+ break;
+ pszValue = pszEnd;
+ }
+ db_set_blob(NULL, szSection, szName, buf, len);
+ mir_free(buf);
+ }
+ break;
+ default:
+ TCHAR buf[250];
+ mir_sntprintf(buf, TranslateT("Invalid setting type for '%s'. The first character of every value must be b, w, d, l, s, e, u, g, h or n."), _A2T(szName));
+ MessageBox(NULL, buf, TranslateT("Install database settings"), MB_ICONWARNING | MB_OK);
+ break;
+ }
+ }
+ fclose(fp);
+}
+
+static void DoAutoExec(void)
+{
+ TCHAR szUse[7], szIniPath[MAX_PATH], szFindPath[MAX_PATH];
+ TCHAR buf[2048], szSecurity[11], szOverrideSecurityFilename[MAX_PATH], szOnCreateFilename[MAX_PATH];
+
+ GetPrivateProfileString(_T("AutoExec"), _T("Use"), _T("prompt"), szUse, SIZEOF(szUse), mirandabootini);
+ if (!mir_tstrcmpi(szUse, _T("no"))) return;
+ GetPrivateProfileString(_T("AutoExec"), _T("Safe"), _T("CLC Icons CLUI CList SkinSounds"), buf, SIZEOF(buf), mirandabootini);
+ ptrA szSafeSections(mir_t2a(buf));
+ GetPrivateProfileString(_T("AutoExec"), _T("Unsafe"), _T("AIM Facebook GG ICQ IRC JABBER MRA MSN SKYPE Tlen TWITTER VKontakte XFire"), buf, SIZEOF(buf), mirandabootini);
+ ptrA szUnsafeSections(mir_t2a(buf));
+ GetPrivateProfileString(_T("AutoExec"), _T("Warn"), _T("notsafe"), szSecurity, SIZEOF(szSecurity), mirandabootini);
+
+ int secur = 0;
+ if (!mir_tstrcmpi(szSecurity, _T("none"))) secur = 0;
+ else if (!mir_tstrcmpi(szSecurity, _T("notsafe"))) secur = 1;
+ else if (!mir_tstrcmpi(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 (g_bDbCreated && szOnCreateFilename[0]) {
+ PathToAbsoluteT(VARST(szOnCreateFilename), szIniPath);
+ ProcessIniFile(szIniPath, szSafeSections, szUnsafeSections, 0, 1);
+ }
+
+ PathToAbsoluteT(VARST(szFindPath), szFindPath);
+
+ WIN32_FIND_DATA fd;
+ HANDLE hFind = FindFirstFile(szFindPath, &fd);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return;
+
+ TCHAR *str2 = _tcsrchr(szFindPath, '\\');
+ if (str2 == NULL)
+ szFindPath[0] = 0;
+ else
+ str2[1] = 0;
+
+ do {
+ bool secFN = mir_tstrcmpi(fd.cFileName, szOverrideSecurityFilename) == 0;
+
+ mir_sntprintf(szIniPath, SIZEOF(szIniPath), _T("%s%s"), szFindPath, fd.cFileName);
+ if (!mir_tstrcmpi(szUse, _T("prompt")) && !secFN) {
+ int result = DialogBoxParam(g_hInst, 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 (!mir_tstrcmpi(szOnCompletion, _T("delete")))
+ DeleteFile(szIniPath);
+ else if (!mir_tstrcmpi(szOnCompletion, _T("recycle"))) {
+ SHFILEOPSTRUCT shfo = { 0 };
+ shfo.wFunc = FO_DELETE;
+ shfo.pFrom = szIniPath;
+ szIniPath[mir_tstrlen(szIniPath) + 1] = 0;
+ shfo.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO;
+ SHFileOperation(&shfo);
+ }
+ else if (!mir_tstrcmpi(szOnCompletion, _T("rename"))) {
+ TCHAR szRenamePrefix[MAX_PATH], szNewPath[MAX_PATH];
+ GetPrivateProfileString(_T("AutoExec"), _T("RenamePrefix"), _T("done_"), szRenamePrefix, SIZEOF(szRenamePrefix), mirandabootini);
+ mir_tstrcpy(szNewPath, szFindPath);
+ mir_tstrcat(szNewPath, szRenamePrefix);
+ mir_tstrcat(szNewPath, fd.cFileName);
+ MoveFile(szIniPath, szNewPath);
+ }
+ else if (!mir_tstrcmpi(szOnCompletion, _T("ask")))
+ DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_INIIMPORTDONE), NULL, IniImportDoneDlgProc, (LPARAM)szIniPath);
+ }
+ }
+ while (FindNextFile(hFind, &fd));
+
+ FindClose(hFind);
+}
+
+static INT_PTR CheckIniImportNow(WPARAM, LPARAM)
+{
+ DoAutoExec();
+ FindNextChangeNotification(hIniChangeNotification);
+ return 0;
+}
+
+int InitIni(void)
+{
+ bModuleInitialized = true;
+
+ DoAutoExec();
+
+ TCHAR szMirandaDir[MAX_PATH];
+ PathToAbsoluteT(_T("."), szMirandaDir);
+ 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/mir_app/src/dbintf.cpp b/src/mir_app/src/dbintf.cpp new file mode 100644 index 0000000000..1beb0df2a5 --- /dev/null +++ b/src/mir_app/src/dbintf.cpp @@ -0,0 +1,167 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-15 Miranda NG 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 "stdafx.h"
+#include "database.h"
+
+MIDatabase *currDb = NULL;
+DATABASELINK *currDblink = NULL;
+
+MIR_CORE_DLL(void) db_setCurrent(MIDatabase*);
+
+static INT_PTR srvSetSafetyMode(WPARAM wParam, LPARAM)
+{
+ if (!currDb) return 1;
+
+ currDb->SetCacheSafetyMode(wParam != 0);
+ return 0;
+}
+
+static INT_PTR srvGetContactCount(WPARAM, LPARAM)
+{
+ return (currDb) ? currDb->GetContactCount() : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Contacts
+
+static INT_PTR srvDeleteContact(WPARAM wParam, LPARAM)
+{
+ DBVARIANT dbv = { 0 };
+ if (!db_get_ts(wParam, "ContactPhoto", "File", &dbv)) {
+ DeleteFile(dbv.ptszVal);
+ db_free(&dbv);
+ }
+ return (currDb) ? currDb->DeleteContact(wParam) : 0;
+}
+
+static INT_PTR srvAddContact(WPARAM, LPARAM)
+{
+ MCONTACT hNew = (currDb) ? currDb->AddContact() : 0;
+ Netlib_Logf(NULL, "New contact created: %d", hNew);
+ return hNew;
+}
+
+static INT_PTR srvIsDbContact(WPARAM wParam, LPARAM)
+{
+ return (currDb) ? currDb->IsDbContact(wParam) : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Module chain
+
+static INT_PTR srvEnumModuleNames(WPARAM wParam, LPARAM lParam)
+{
+ return (currDb) ? (INT_PTR)currDb->EnumModuleNames((DBMODULEENUMPROC)lParam, (void*)wParam) : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Settings
+
+static INT_PTR srvEnumContactSettings(WPARAM wParam, LPARAM lParam)
+{
+ return (currDb) ? (INT_PTR)currDb->EnumContactSettings(wParam, (DBCONTACTENUMSETTINGS*)lParam) : 0;
+}
+
+static INT_PTR srvEnumResidentSettings(WPARAM wParam, LPARAM lParam)
+{
+ return (currDb) ? (INT_PTR)currDb->EnumResidentSettings((DBMODULEENUMPROC)wParam, (void*)lParam) : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Database list
+
+LIST<DATABASELINK> arDbPlugins(5);
+
+static INT_PTR srvRegisterPlugin(WPARAM, LPARAM lParam)
+{
+ DATABASELINK* pPlug = (DATABASELINK*)lParam;
+ if (pPlug == NULL)
+ return 1;
+
+ arDbPlugins.insert(pPlug);
+ return 0;
+}
+
+static INT_PTR srvFindPlugin(WPARAM, LPARAM lParam)
+{
+ for (int i = arDbPlugins.getCount() - 1; i >= 0; i--) {
+ int error = arDbPlugins[i]->grokHeader((TCHAR*)lParam);
+ if (error == ERROR_SUCCESS || error == EGROKPRF_OBSOLETE)
+ return (INT_PTR)arDbPlugins[i];
+ }
+
+ return NULL;
+}
+
+static INT_PTR srvGetCurrentDb(WPARAM, LPARAM)
+{
+ return (INT_PTR)currDb;
+}
+
+static INT_PTR srvInitInstance(WPARAM, LPARAM lParam)
+{
+ MIDatabase* pDb = (MIDatabase*)lParam;
+ if (pDb != NULL)
+ pDb->m_cache = new MDatabaseCache(pDb->GetContactSize());
+ return 0;
+}
+
+static INT_PTR srvDestroyInstance(WPARAM, LPARAM lParam)
+{
+ MIDatabase* pDb = (MIDatabase*)lParam;
+ if (pDb != NULL) {
+ MDatabaseCache *pCache = (MDatabaseCache*)pDb->m_cache;
+ pDb->m_cache = NULL;
+ delete pCache;
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int LoadDbintfModule()
+{
+ CreateServiceFunction(MS_DB_CONTACT_GETCOUNT, srvGetContactCount);
+ CreateServiceFunction(MS_DB_CONTACT_DELETE, srvDeleteContact);
+ CreateServiceFunction(MS_DB_CONTACT_ADD, srvAddContact);
+ CreateServiceFunction(MS_DB_CONTACT_IS, srvIsDbContact);
+
+ CreateServiceFunction(MS_DB_MODULES_ENUM, srvEnumModuleNames);
+
+ CreateServiceFunction(MS_DB_CONTACT_ENUMSETTINGS, srvEnumContactSettings);
+ CreateServiceFunction("DB/ResidentSettings/Enum", srvEnumResidentSettings);
+
+ CreateServiceFunction(MS_DB_REGISTER_PLUGIN, srvRegisterPlugin);
+ CreateServiceFunction(MS_DB_FIND_PLUGIN, srvFindPlugin);
+ CreateServiceFunction(MS_DB_GET_CURRENT, srvGetCurrentDb);
+
+ CreateServiceFunction(MS_DB_INIT_INSTANCE, srvInitInstance);
+ CreateServiceFunction(MS_DB_DESTROY_INSTANCE, srvDestroyInstance);
+ return 0;
+}
+
+void LoadDatabaseServices()
+{
+ CreateServiceFunction(MS_DB_SETSAFETYMODE, srvSetSafetyMode);
+}
diff --git a/src/mir_app/src/dbutils.cpp b/src/mir_app/src/dbutils.cpp new file mode 100644 index 0000000000..3de2bfb8a2 --- /dev/null +++ b/src/mir_app/src/dbutils.cpp @@ -0,0 +1,367 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "profilemanager.h"
+
+static int CompareEventTypes(const DBEVENTTYPEDESCR *p1, const DBEVENTTYPEDESCR *p2)
+{
+ int result = mir_strcmp(p1->module, p2->module);
+ if (result)
+ return result;
+
+ return p1->eventType - p2->eventType;
+}
+
+static LIST<DBEVENTTYPEDESCR> eventTypes(10, CompareEventTypes);
+
+static BOOL bModuleInitialized = FALSE;
+
+static INT_PTR DbEventTypeRegister(WPARAM, LPARAM lParam)
+{
+ DBEVENTTYPEDESCR *et = (DBEVENTTYPEDESCR*)lParam;
+ if (et == NULL || et->cbSize != sizeof(DBEVENTTYPEDESCR))
+ return -1;
+
+ if (eventTypes.getIndex(et) != -1)
+ return -1;
+
+ DBEVENTTYPEDESCR *p = (DBEVENTTYPEDESCR*)mir_calloc(sizeof(DBEVENTTYPEDESCR));
+ p->cbSize = sizeof(DBEVENTTYPEDESCR);
+ p->module = mir_strdup(et->module);
+ p->eventType = et->eventType;
+ p->descr = mir_strdup(et->descr);
+ 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;
+ tmp.module = (char*)wParam;
+ tmp.eventType = lParam;
+ return (INT_PTR)eventTypes.find(&tmp);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static TCHAR* getEventString(DBEVENTINFO *dbei, LPSTR &buf)
+{
+ LPSTR in = buf;
+ buf += mir_strlen(buf) + 1;
+ return (dbei->flags & DBEF_UTF) ? Utf8DecodeT(in) : mir_a2t(in);
+}
+
+static INT_PTR DbEventGetText(WPARAM wParam, LPARAM lParam)
+{
+ DBEVENTGETTEXT* egt = (DBEVENTGETTEXT*)lParam;
+ if (egt == NULL)
+ return 0;
+
+ DBEVENTINFO *dbei = egt->dbei;
+ if (dbei == NULL || dbei->szModule == NULL || dbei->cbSize != sizeof(DBEVENTINFO))
+ return 0;
+
+ 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_AUTHREQUEST || dbei->eventType == EVENTTYPE_ADDED) {
+ // EVENTTYPE_AUTHREQUEST: uin(DWORD), hContact(DWORD), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ)
+ // EVENTTYPE_ADDED: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ)
+ DWORD uin = *(DWORD*)dbei->pBlob;
+ MCONTACT hContact = (MCONTACT)*(DWORD*)(dbei->pBlob + sizeof(DWORD));
+ char *buf = LPSTR(dbei->pBlob) + sizeof(DWORD)*2;
+ ptrT tszNick(getEventString(dbei, buf));
+ ptrT tszFirst(getEventString(dbei, buf));
+ ptrT tszLast(getEventString(dbei, buf));
+ ptrT tszEmail(getEventString(dbei, buf));
+
+ CMString nick, text;
+ if (tszFirst || tszLast) {
+ nick.AppendFormat(_T("%s %s"), tszFirst, tszLast);
+ nick.Trim();
+ }
+ if (tszEmail) {
+ if (!nick.IsEmpty())
+ nick.Append(_T(", "));
+ nick.Append(tszEmail);
+ }
+ if (uin != 0) {
+ if (!nick.IsEmpty())
+ nick.Append(_T(", "));
+ nick.AppendFormat(_T("%d"), uin);
+ }
+ if (!nick.IsEmpty())
+ nick = _T("(") + nick + _T(")");
+
+ if (dbei->eventType == EVENTTYPE_AUTHREQUEST) {
+ ptrT tszReason(getEventString(dbei, buf));
+ text.Format(TranslateT("Authorization request from %s%s: %s"),
+ (tszNick == NULL) ? cli.pfnGetContactDisplayName(hContact, 0) : tszNick, nick, tszReason);
+ }
+ else text.Format(TranslateT("You were added by %s%s"),
+ (tszNick == NULL) ? cli.pfnGetContactDisplayName(hContact, 0) : tszNick, nick);
+ return (egt->datatype == DBVT_WCHAR) ? (INT_PTR)mir_tstrdup(text) : (INT_PTR)mir_t2a(text);
+ }
+
+ if (dbei->eventType == EVENTTYPE_CONTACTS) {
+ CMString text(TranslateT("Contacts: "));
+ // blob is: [uin(ASCIIZ), nick(ASCIIZ)]*
+ char *buf = LPSTR(dbei->pBlob), *limit = LPSTR(dbei->pBlob) + dbei->cbBlob;
+ while (buf < limit) {
+ ptrT tszUin(getEventString(dbei, buf));
+ ptrT tszNick(getEventString(dbei, buf));
+ if (tszNick && *tszNick)
+ text.AppendFormat(_T("\"%s\" "), tszNick);
+ if (tszUin && *tszUin)
+ text.AppendFormat(_T("<%s>; "), tszUin);
+ }
+ return (egt->datatype == DBVT_WCHAR) ? (INT_PTR)mir_tstrdup(text) : (INT_PTR)mir_t2a(text);
+ }
+
+ if (dbei->eventType == EVENTTYPE_FILE) {
+ char *buf = LPSTR(dbei->pBlob) + sizeof(DWORD);
+ ptrT tszFileName(getEventString(dbei, buf));
+ ptrT tszDescription(getEventString(dbei, buf));
+ ptrT &ptszText = (mir_tstrlen(tszDescription) == 0) ? tszFileName : tszDescription;
+ switch (egt->datatype) {
+ case DBVT_WCHAR:
+ return (INT_PTR)ptszText.detach();
+ case DBVT_ASCIIZ:
+ return (INT_PTR)mir_t2a(ptszText);
+ }
+ return 0;
+ }
+
+ // by default treat an event's blob as a string
+ if (egt->datatype == DBVT_WCHAR) {
+ char *str = (char*)alloca(dbei->cbBlob + 1);
+ memcpy(str, dbei->pBlob, dbei->cbBlob);
+ str[dbei->cbBlob] = 0;
+
+ if (dbei->flags & DBEF_UTF) {
+ WCHAR *msg = NULL;
+ Utf8DecodeCP(str, egt->codepage, &msg);
+ if (msg)
+ return (INT_PTR)msg;
+ }
+
+ return (INT_PTR)mir_a2t_cp(str, egt->codepage);
+ }
+
+ 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 = Skin_GetIconByHandle(et->eventIcon);
+ if (!icon) {
+ char szName[100];
+ mir_snprintf(szName, SIZEOF(szName), "eventicon_%s%d", dbei->szModule, dbei->eventType);
+ icon = Skin_GetIcon(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;
+ }
+ }
+
+ return (INT_PTR)((wParam & LR_SHARED) ? icon : CopyIcon(icon));
+}
+
+static INT_PTR DbEventGetStringT(WPARAM wParam, LPARAM lParam)
+{
+ DBEVENTINFO* dbei = (DBEVENTINFO*)wParam;
+ char *string = (char*)lParam;
+
+ if (dbei->flags & DBEF_UTF)
+ return (INT_PTR)Utf8DecodeW(string);
+
+ return (INT_PTR)mir_a2t(string);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int sttEnumVars(const char *szVarName, LPARAM lParam)
+{
+ LIST<char>* vars = (LIST<char>*)lParam;
+ vars->insert(mir_strdup(szVarName));
+ return 0;
+}
+
+static INT_PTR DbDeleteModule(WPARAM hContact, LPARAM lParam)
+{
+ LIST<char> vars(20);
+
+ DBCONTACTENUMSETTINGS dbces = { 0 };
+ dbces.pfnEnumProc = sttEnumVars;
+ dbces.lParam = (LPARAM)&vars;
+ dbces.szModule = (char*)lParam;
+ CallService(MS_DB_CONTACT_ENUMSETTINGS, hContact, (LPARAM)&dbces);
+
+ for (int i = vars.getCount()-1; i >= 0; i--) {
+ db_unset(hContact, (char*)lParam, vars[i]);
+ mir_free(vars[i]);
+ }
+ return 0;
+}
+
+static INT_PTR GetProfilePath(WPARAM wParam, LPARAM lParam)
+{
+ if (!wParam || !lParam)
+ return 1;
+
+ char *dst = (char*)lParam;
+ strncpy(dst, _T2A(g_profileDir), wParam);
+ dst[wParam-1] = 0;
+ return 0;
+}
+
+static INT_PTR GetProfileName(WPARAM wParam, LPARAM lParam)
+{
+ if (!wParam || !lParam)
+ return 1;
+
+ char *dst = (char*)lParam;
+
+ char *tmp = makeFileName(g_profileName);
+ strncpy(dst, tmp, wParam);
+ mir_free(tmp);
+
+ dst[wParam-1] = 0;
+ return 0;
+}
+
+static INT_PTR GetProfilePathW(WPARAM wParam, LPARAM lParam)
+{
+ if (!wParam || !lParam)
+ return 1;
+
+ wchar_t *dst = (wchar_t*)lParam;
+ wcsncpy(dst, g_profileDir, wParam);
+ dst[wParam-1] = 0;
+ return 0;
+}
+
+static INT_PTR GetProfileNameW(WPARAM wParam, LPARAM lParam)
+{
+ wchar_t *dst = (wchar_t*)lParam;
+ wcsncpy(dst, g_profileName, wParam);
+ dst[wParam-1] = 0;
+ return 0;
+}
+
+static INT_PTR SetDefaultProfile(WPARAM wParam, LPARAM)
+{
+ extern TCHAR* g_defaultProfile;
+ replaceStrT(g_defaultProfile, (TCHAR*)wParam);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int LoadEventsModule()
+{
+ 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);
+ CreateServiceFunction(MS_DB_GETPROFILEPATHW, GetProfilePathW);
+ CreateServiceFunction(MS_DB_GETPROFILENAMEW, GetProfileNameW);
+
+ CreateServiceFunction(MS_DB_SETDEFAULTPROFILE, SetDefaultProfile);
+ return 0;
+}
+
+void UnloadEventsModule()
+{
+ if (!bModuleInitialized)
+ return;
+
+ for (int 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);
+ }
+}
diff --git a/src/mir_app/src/descbutton.cpp b/src/mir_app/src/descbutton.cpp new file mode 100644 index 0000000000..57110d779d --- /dev/null +++ b/src/mir_app/src/descbutton.cpp @@ -0,0 +1,326 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+Copyright (c) 2007 Artem Shpynov
+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 "stdafx.h"
+#include "m_descbutton.h"
+
+extern HINSTANCE hInst;
+
+////////////////////////////////////////////////////////////////////////////////////
+// Internals
+
+#define DBC_BORDER_SIZE 7
+#define DBC_VSPACING 15
+#define DBC_HSPACING 10
+
+static LRESULT CALLBACK MDescButtonWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+// structure is used for storing list of tab info
+struct MDescButtonCtrl
+{
+ HWND hwnd;
+ BOOL bSharedIcon;
+ HICON hIcon;
+ TCHAR *lpzTitle;
+ TCHAR *lpzDescription;
+
+ // UI info
+ BOOL bMouseInside;
+ RECT rc;
+ int width, height;
+ HFONT hfntTitle;
+
+ // control colors
+ RGBQUAD rgbBkgTop, rgbBkgBottom;
+ RGBQUAD rgbSelTop, rgbSelBottom;
+ RGBQUAD rgbHotTop, rgbHotBottom;
+ COLORREF clText, clBackground;
+ COLORREF clSelText, clSelBorder;
+ COLORREF clHotText, clHotBorder;
+
+ // fonts
+ HFONT hFont;
+};
+
+int LoadDescButtonModule()
+{
+ WNDCLASSEX wc;
+
+ memset(&wc, 0, sizeof(wc));
+ wc.cbSize = sizeof(wc);
+ wc.lpszClassName = MIRANDADESCBUTTONCLASS;
+ wc.lpfnWndProc = MDescButtonWndProc;
+ wc.hCursor = LoadCursor(NULL, IDC_HAND);
+ wc.cbWndExtra = sizeof(MDescButtonCtrl *);
+ wc.hbrBackground = 0; //GetStockObject(WHITE_BRUSH);
+ wc.style = CS_GLOBALCLASS | CS_SAVEBITS;
+ RegisterClassEx(&wc);
+ return 0;
+}
+
+static void MDescButton_SetupColors(MDescButtonCtrl *dat)
+{
+ COLORREF 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;
+
+ 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)
+{
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(hwndDlg, &ps);
+ HDC tempDC = CreateCompatibleDC(hdc);
+
+ SIZE titleSize = { 0 };
+
+ HBITMAP hBmp = CreateCompatibleBitmap(hdc, dat->width, dat->height);
+ HBITMAP hOldBmp = (HBITMAP)SelectObject(tempDC, hBmp);
+
+ RECT temprc;
+ 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);
+
+ HFONT hfntSave = (HFONT)SelectObject(tempDC, dat->hFont);
+ SetBkMode(tempDC, TRANSPARENT);
+
+ if (dat->lpzTitle) {
+ LOGFONT lf;
+ GetObject(dat->hFont, sizeof(lf), &lf);
+ lf.lfWeight = FW_BOLD;
+ lf.lfHeight *= 1.5;
+ HFONT hfntSave = (HFONT)SelectObject(tempDC, CreateFontIndirect(&lf));
+
+ 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;
+ textRect.bottom = dat->height - DBC_BORDER_SIZE;
+ DrawText(tempDC, dat->lpzTitle, -1, &textRect, DT_TOP | DT_LEFT | DT_END_ELLIPSIS);
+ GetTextExtentPoint32(tempDC, dat->lpzTitle, (int)mir_tstrlen(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, (int)mir_tstrlen(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);
+ 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/mir_app/src/dll_sniffer.cpp b/src/mir_app/src/dll_sniffer.cpp new file mode 100644 index 0000000000..b19451eb55 --- /dev/null +++ b/src/mir_app/src/dll_sniffer.cpp @@ -0,0 +1,159 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "plugins.h"
+
+static IMAGE_SECTION_HEADER *getSectionByRVA(IMAGE_SECTION_HEADER *pISH, int nSections, IMAGE_DATA_DIRECTORY *pIDD)
+{
+ for (int i=0; i < nSections; i++, pISH++)
+ if (pIDD->VirtualAddress >= pISH->VirtualAddress && pIDD->VirtualAddress + pIDD->Size <= pISH->VirtualAddress + pISH->SizeOfRawData )
+ return pISH;
+
+ return NULL;
+}
+
+MUUID* GetPluginInterfaces(const TCHAR* ptszFileName, bool& bIsPlugin)
+{
+ bIsPlugin = false;
+
+ HANDLE hFile = CreateFile( ptszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
+ if (hFile == INVALID_HANDLE_VALUE)
+ return NULL;
+
+ MUUID* pResult = NULL;
+ BYTE* ptr = NULL;
+ HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL );
+
+ __try {
+ __try {
+ if (!hMap )
+ __leave;
+
+ DWORD dwHSize = 0, filesize = GetFileSize( hFile, &dwHSize );
+ if (!filesize || filesize == INVALID_FILE_SIZE || dwHSize)
+ __leave;
+
+ if ( filesize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) )
+ __leave;
+
+ ptr = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+ if (ptr == NULL)
+ __leave;
+
+ PIMAGE_NT_HEADERS pINTH = { 0 };
+ PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)ptr;
+ if ( pIDH->e_magic == IMAGE_DOS_SIGNATURE )
+ pINTH = (PIMAGE_NT_HEADERS)(ptr + pIDH->e_lfanew);
+ else
+ __leave;
+
+ if ((PBYTE)pINTH + sizeof(IMAGE_NT_HEADERS) >= ptr + filesize )
+ __leave;
+ if ( pINTH->Signature != IMAGE_NT_SIGNATURE )
+ __leave;
+
+ int nSections = pINTH->FileHeader.NumberOfSections;
+ if (!nSections )
+ __leave;
+
+ INT_PTR base;
+ PIMAGE_DATA_DIRECTORY pIDD;
+ if ( pINTH->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 &&
+ pINTH->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER32) &&
+ pINTH->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+ {
+ pIDD = (PIMAGE_DATA_DIRECTORY)( (PBYTE)pINTH + offsetof( IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory ));
+ base = *(DWORD*)((PBYTE)pINTH + offsetof(IMAGE_NT_HEADERS32, OptionalHeader.ImageBase));
+ }
+ else if ( pINTH->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 &&
+ pINTH->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER64) &&
+ pINTH->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
+ {
+ pIDD = (PIMAGE_DATA_DIRECTORY)( (PBYTE)pINTH + offsetof( IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory ));
+ base = *(ULONGLONG*)((PBYTE)pINTH + offsetof(IMAGE_NT_HEADERS64, OptionalHeader.ImageBase ));
+ }
+ else __leave;
+
+ // Export information entry
+ DWORD expvaddr = pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
+ DWORD expsize = pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
+ if (expsize < sizeof(IMAGE_EXPORT_DIRECTORY)) __leave;
+
+ BYTE* pImage = ptr + pIDH->e_lfanew + pINTH->FileHeader.SizeOfOptionalHeader + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER);
+ IMAGE_SECTION_HEADER *pExp = getSectionByRVA((IMAGE_SECTION_HEADER *)pImage, nSections, pIDD);
+ if (!pExp) __leave;
+
+ BYTE *pSecStart = ptr + pExp->PointerToRawData - pExp->VirtualAddress;
+ IMAGE_EXPORT_DIRECTORY *pED = (PIMAGE_EXPORT_DIRECTORY)&pSecStart[expvaddr];
+ DWORD *ptrRVA = (DWORD*)&pSecStart[pED->AddressOfNames];
+ WORD *ptrOrdRVA = (WORD*)&pSecStart[pED->AddressOfNameOrdinals];
+ DWORD *ptrFuncList = (DWORD*)&pSecStart[pED->AddressOfFunctions];
+
+ MUUID* pIds = NULL;
+ bool bHasLoad = false, bHasUnload = false, bHasInfo = false, bHasMuuids = false;
+ for (size_t i=0; i < pED->NumberOfNames; i++, ptrRVA++, ptrOrdRVA++) {
+ char *szName = (char*)&pSecStart[*ptrRVA];
+ if (!mir_strcmp(szName, "Load"))
+ bHasLoad = true;
+ if (!mir_strcmp(szName, "MirandaPluginInfoEx"))
+ bHasInfo = true;
+ else if (!mir_strcmp(szName, "Unload"))
+ bHasUnload = true;
+ else if (!mir_strcmp(szName, "MirandaInterfaces")) {
+ bHasMuuids = true;
+ pIds = (MUUID*)&pSecStart[ ptrFuncList[*ptrOrdRVA]];
+ }
+ // old plugin, skip it
+ else if (!mir_strcmp(szName, "MirandaPluginInterfaces"))
+ __leave;
+ }
+
+ // a plugin might have no interfaces
+ if (bHasLoad && bHasUnload && bHasInfo)
+ bIsPlugin = true;
+
+ if (!bHasLoad || !bHasMuuids || !bHasUnload)
+ __leave;
+
+ int nLength = 1; // one for MIID_LAST
+ for (MUUID* p = pIds; !equalUUID(*p, miid_last); p++)
+ nLength++;
+
+ pResult = (MUUID*)mir_alloc( sizeof(MUUID)*nLength);
+ if (pResult)
+ memcpy(pResult, pIds, sizeof(MUUID)*nLength);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {};
+ }
+ __finally
+ {
+ if (ptr) UnmapViewOfFile(ptr);
+ if (hMap) CloseHandle(hMap);
+ CloseHandle(hFile);
+ };
+
+ return pResult;
+}
diff --git a/src/mir_app/src/encrypt.cpp b/src/mir_app/src/encrypt.cpp new file mode 100644 index 0000000000..91cfaf2614 --- /dev/null +++ b/src/mir_app/src/encrypt.cpp @@ -0,0 +1,88 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+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 "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int CompareFunc(const CRYPTO_PROVIDER *p1, const CRYPTO_PROVIDER *p2)
+{
+ return mir_strcmp(p1->pszName, p2->pszName);
+}
+
+static LIST<CRYPTO_PROVIDER> arProviders(5, CompareFunc);
+
+static INT_PTR srvRegister(WPARAM wParam, LPARAM lParam)
+{
+ CRYPTO_PROVIDER *p = (CRYPTO_PROVIDER*)lParam;
+ if (p == NULL || p->dwSize != sizeof(CRYPTO_PROVIDER))
+ return 1;
+
+ CRYPTO_PROVIDER *pNew = new CRYPTO_PROVIDER(*p);
+ pNew->pszName = mir_strdup(p->pszName);
+ if (pNew->dwFlags & CPF_UNICODE)
+ pNew->ptszDescr = mir_u2t(TranslateW_LP(p->pwszDescr, wParam));
+ else
+ pNew->ptszDescr = mir_a2t(TranslateA_LP(p->pszDescr, wParam));
+ arProviders.insert(pNew);
+ return 0;
+}
+
+static INT_PTR srvEnumProviders(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam && lParam) {
+ *(int*)wParam = arProviders.getCount();
+ *(CRYPTO_PROVIDER***)lParam = arProviders.getArray();
+ }
+ return 0;
+}
+
+static INT_PTR srvGetProvider(WPARAM, LPARAM lParam)
+{
+ if (lParam == 0)
+ return 0;
+
+ CRYPTO_PROVIDER tmp;
+ tmp.pszName = (LPSTR)lParam;
+ return (INT_PTR)arProviders.find(&tmp);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int InitCrypt(void)
+{
+ CreateServiceFunction(MS_CRYPTO_REGISTER_ENGINE, srvRegister);
+ CreateServiceFunction(MS_CRYPTO_ENUM_PROVIDERS, srvEnumProviders);
+ CreateServiceFunction(MS_CRYPTO_GET_PROVIDER, srvGetProvider);
+ return 0;
+}
+
+void UninitCrypt(void)
+{
+ for (int i = 0; i < arProviders.getCount(); i++) {
+ CRYPTO_PROVIDER *p = arProviders[i];
+ mir_free(p->pszName);
+ mir_free(p->pszDescr);
+ delete p;
+ }
+}
diff --git a/src/mir_app/src/enterstring.cpp b/src/mir_app/src/enterstring.cpp new file mode 100644 index 0000000000..4e76e94a6e --- /dev/null +++ b/src/mir_app/src/enterstring.cpp @@ -0,0 +1,274 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+struct EnterStringFormParam : public ENTER_STRING
+{
+ int idcControl;
+ int height;
+};
+
+static int UIEmulateBtnClick(HWND hwndDlg, UINT idcButton)
+{
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, idcButton)))
+ PostMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(idcButton, BN_CLICKED), (LPARAM)GetDlgItem(hwndDlg, idcButton));
+ return 0;
+}
+
+static int sttEnterStringResizer(HWND, LPARAM, UTILRESIZECONTROL *urc)
+{
+ switch (urc->wId) {
+ case IDC_TXT_MULTILINE:
+ case IDC_TXT_COMBO:
+ case IDC_TXT_RICHEDIT:
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT;
+
+ case IDOK:
+ case IDCANCEL:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;
+ }
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP;
+}
+
+static void ComboLoadRecentStrings(HWND hwndDlg, EnterStringFormParam *pForm)
+{
+ for (int i = 0; i < pForm->recentCount; i++) {
+ char setting[MAXMODULELABELLENGTH];
+ mir_snprintf(setting, "%s%d", pForm->szDataPrefix, i);
+ ptrT tszRecent(db_get_tsa(NULL, pForm->szModuleName, setting));
+ if (tszRecent != NULL)
+ SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_ADDSTRING, 0, tszRecent);
+ }
+
+ if (!SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_GETCOUNT, 0, 0))
+ SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_ADDSTRING, 0, (LPARAM)_T(""));
+}
+
+static void ComboAddRecentString(HWND hwndDlg, EnterStringFormParam *pForm)
+{
+ TCHAR *string = pForm->ptszResult;
+ if (!string || !*string)
+ return;
+
+ if (SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_FINDSTRING, (WPARAM)-1, (LPARAM)string) != CB_ERR)
+ return;
+
+ int id;
+ SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_ADDSTRING, 0, (LPARAM)string);
+ if ((id = SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_FINDSTRING, (WPARAM)-1, (LPARAM)_T(""))) != CB_ERR)
+ SendDlgItemMessage(hwndDlg, pForm->idcControl, CB_DELETESTRING, id, 0);
+
+ id = db_get_b(NULL, pForm->szModuleName, pForm->szDataPrefix, 0);
+ char setting[MAXMODULELABELLENGTH];
+ mir_snprintf(setting, "%s%d", pForm->szDataPrefix, id);
+ db_set_ts(NULL, pForm->szModuleName, setting, string);
+ db_set_b(NULL, pForm->szModuleName, pForm->szDataPrefix, (id + 1) % pForm->idcControl);
+}
+
+static INT_PTR CALLBACK sttEnterStringDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ EnterStringFormParam *params = (EnterStringFormParam *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadSkinnedIconBig(SKINICON_OTHER_RENAME));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadSkinnedIcon(SKINICON_OTHER_RENAME));
+ params = (EnterStringFormParam *)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)params);
+ SetWindowText(hwndDlg, params->caption);
+ {
+ RECT rc; GetWindowRect(hwndDlg, &rc);
+ switch (params->type) {
+ case ESF_PASSWORD:
+ params->idcControl = IDC_TXT_PASSWORD;
+ params->height = rc.bottom - rc.top;
+ break;
+
+ case ESF_MULTILINE:
+ params->idcControl = IDC_TXT_MULTILINE;
+ params->height = 0;
+ rc.bottom += (rc.bottom - rc.top) * 2;
+ SetWindowPos(hwndDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOREPOSITION);
+ break;
+
+ case ESF_COMBO:
+ params->idcControl = IDC_TXT_COMBO;
+ params->height = rc.bottom - rc.top;
+ if (params->szDataPrefix && params->recentCount)
+ ComboLoadRecentStrings(hwndDlg, params);
+ break;
+
+ case ESF_RICHEDIT:
+ params->idcControl = IDC_TXT_RICHEDIT;
+ SendDlgItemMessage(hwndDlg, IDC_TXT_RICHEDIT, EM_AUTOURLDETECT, TRUE, 0);
+ SendDlgItemMessage(hwndDlg, IDC_TXT_RICHEDIT, EM_SETEVENTMASK, 0, ENM_LINK);
+ params->height = 0;
+ rc.bottom += (rc.bottom - rc.top) * 2;
+ SetWindowPos(hwndDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOREPOSITION);
+ break;
+ }
+ }
+ ShowWindow(GetDlgItem(hwndDlg, params->idcControl), SW_SHOW);
+ if (params->ptszInitVal)
+ SetDlgItemText(hwndDlg, params->idcControl, params->ptszInitVal);
+
+ if (params->szDataPrefix)
+ Utils_RestoreWindowPosition(hwndDlg, NULL, params->szModuleName, params->szDataPrefix);
+
+ SetTimer(hwndDlg, 1000, 50, NULL);
+
+ if (params->timeout > 0) {
+ SetTimer(hwndDlg, 1001, 1000, NULL);
+ TCHAR buf[128];
+ mir_sntprintf(buf, TranslateT("OK (%d)"), params->timeout);
+ SetDlgItemText(hwndDlg, IDOK, buf);
+ }
+
+ return TRUE;
+
+ case WM_DESTROY:
+ Window_FreeIcon_IcoLib(hwndDlg);
+ break;
+
+ case WM_TIMER:
+ switch (wParam) {
+ case 1000:
+ KillTimer(hwndDlg, 1000);
+ EnableWindow(GetParent(hwndDlg), TRUE);
+ break;
+
+ case 1001:
+ TCHAR buf[128];
+ mir_sntprintf(buf, TranslateT("OK (%d)"), --params->timeout);
+ SetDlgItemText(hwndDlg, IDOK, buf);
+
+ if (params->timeout < 0) {
+ KillTimer(hwndDlg, 1001);
+ UIEmulateBtnClick(hwndDlg, IDOK);
+ }
+ }
+ return TRUE;
+
+ case WM_SIZE:
+ {
+ UTILRESIZEDIALOG urd = { 0 };
+ urd.cbSize = sizeof(urd);
+ urd.hInstance = g_hInst;
+ urd.hwndDlg = hwndDlg;
+ urd.lpTemplate = MAKEINTRESOURCEA(IDD_ENTER_STRING);
+ urd.pfnResizer = sttEnterStringResizer;
+ CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd);
+ }
+ break;
+
+ case WM_GETMINMAXINFO:
+ {
+ LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam;
+ if (params && params->height)
+ lpmmi->ptMaxSize.y = lpmmi->ptMaxTrackSize.y = params->height;
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ ENLINK *param = (ENLINK *)lParam;
+ if (param->nmhdr.idFrom != IDC_TXT_RICHEDIT) break;
+ if (param->nmhdr.code != EN_LINK) break;
+ if (param->msg != WM_LBUTTONUP) break;
+
+ CHARRANGE sel;
+ SendMessage(param->nmhdr.hwndFrom, EM_EXGETSEL, 0, (LPARAM)& sel);
+ if (sel.cpMin != sel.cpMax) break; // allow link selection
+
+ TEXTRANGE tr;
+ tr.chrg = param->chrg;
+ tr.lpstrText = (TCHAR *)mir_alloc(sizeof(TCHAR)*(tr.chrg.cpMax - tr.chrg.cpMin + 2));
+ SendMessage(param->nmhdr.hwndFrom, EM_GETTEXTRANGE, 0, (LPARAM)& tr);
+
+ char *tmp = mir_t2a(tr.lpstrText);
+ CallService(MS_UTILS_OPENURL, OUF_NEWWINDOW, (LPARAM)tmp);
+ mir_free(tmp);
+ mir_free(tr.lpstrText);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_TXT_MULTILINE:
+ case IDC_TXT_RICHEDIT:
+ if ((HIWORD(wParam) != EN_SETFOCUS) && (HIWORD(wParam) != EN_KILLFOCUS)) {
+ SetDlgItemText(hwndDlg, IDOK, TranslateT("OK"));
+ KillTimer(hwndDlg, 1001);
+ }
+ break;
+
+ case IDC_TXT_COMBO:
+ if ((HIWORD(wParam) != CBN_SETFOCUS) && (HIWORD(wParam) != CBN_KILLFOCUS)) {
+ SetDlgItemText(hwndDlg, IDOK, TranslateT("OK"));
+ KillTimer(hwndDlg, 1001);
+ }
+ break;
+
+ case IDCANCEL:
+ if (params->szDataPrefix)
+ Utils_SaveWindowPosition(hwndDlg, NULL, params->szModuleName, params->szDataPrefix);
+
+ EndDialog(hwndDlg, 0);
+ break;
+
+ case IDOK:
+ HWND hWnd = GetDlgItem(hwndDlg, params->idcControl);
+ int len = GetWindowTextLength(hWnd)+1;
+ params->ptszResult = (LPTSTR)mir_alloc(sizeof(TCHAR)*len);
+ GetWindowText(hWnd, params->ptszResult, len);
+
+ if ((params->type == ESF_COMBO) && params->szDataPrefix && params->recentCount)
+ ComboAddRecentString(hwndDlg, params);
+ if (params->szDataPrefix)
+ Utils_SaveWindowPosition(hwndDlg, NULL, params->szModuleName, params->szDataPrefix);
+
+ EndDialog(hwndDlg, 1);
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+INT_PTR __cdecl svcEnterString(WPARAM, LPARAM lParam)
+{
+ ENTER_STRING *pForm = (ENTER_STRING*)lParam;
+ if (pForm == NULL || pForm->cbSize != sizeof(ENTER_STRING))
+ return FALSE;
+
+ EnterStringFormParam param;
+ memcpy(¶m, pForm, sizeof(ENTER_STRING));
+ if (!DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_ENTER_STRING), GetForegroundWindow(), sttEnterStringDlgProc, LPARAM(¶m)))
+ return FALSE;
+
+ pForm->ptszResult = param.ptszResult;
+ return TRUE;
+}
diff --git a/src/mir_app/src/extracticon.cpp b/src/mir_app/src/extracticon.cpp new file mode 100644 index 0000000000..cfd2ed22db --- /dev/null +++ b/src/mir_app/src/extracticon.cpp @@ -0,0 +1,282 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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/mir_app/src/extraicons.cpp b/src/mir_app/src/extraicons.cpp new file mode 100644 index 0000000000..fc4d267043 --- /dev/null +++ b/src/mir_app/src/extraicons.cpp @@ -0,0 +1,545 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "m_cluiframes.h"
+
+#include "extraicons.h"
+#include "usedIcons.h"
+#include "clc.h"
+
+// Prototypes ///////////////////////////////////////////////////////////////////////////
+
+int SortFunc(const ExtraIcon *p1, const ExtraIcon *p2)
+{
+ int ret = p1->getPosition() - p2->getPosition();
+ if (ret != 0)
+ return ret;
+
+ int id1 = (p1->getType() != EXTRAICON_TYPE_GROUP) ? ((BaseExtraIcon*) p1)->getID() : 0;
+ int id2 = (p2->getType() != EXTRAICON_TYPE_GROUP) ? ((BaseExtraIcon*) p2)->getID() : 0;
+ return id1 - id2;
+}
+
+LIST<ExtraIcon> extraIconsByHandle(10), extraIconsBySlot(10, SortFunc);
+LIST<BaseExtraIcon> registeredExtraIcons(10);
+
+BOOL clistRebuildAlreadyCalled = FALSE;
+BOOL clistApplyAlreadyCalled = FALSE;
+
+int clistFirstSlot = 0;
+int clistSlotCount = 0;
+
+// Functions ////////////////////////////////////////////////////////////////////////////
+
+int InitOptionsCallback(WPARAM wParam, LPARAM lParam);
+
+// Called when all the modules are loaded
+int ModulesLoaded(WPARAM, LPARAM)
+{
+ // add our modules to the KnownModules list
+ CallService("DBEditorpp/RegisterSingleModule", (WPARAM) MODULE_NAME, 0);
+ CallService("DBEditorpp/RegisterSingleModule", (WPARAM) MODULE_NAME "Groups", 0);
+
+ HookEvent(ME_OPT_INITIALISE, InitOptionsCallback);
+ return 0;
+}
+
+int GetNumberOfSlots()
+{
+ return clistSlotCount;
+}
+
+int ConvertToClistSlot(int slot)
+{
+ if (slot < 0)
+ return slot;
+
+ return clistFirstSlot + slot;
+}
+
+int ExtraImage_ExtraIDToColumnNum(int extra)
+{
+ return (extra < 1 || extra > EXTRA_ICON_COUNT) ? -1 : extra-1;
+}
+
+int Clist_SetExtraIcon(MCONTACT hContact, int slot, HANDLE hImage)
+{
+ if (cli.hwndContactTree == 0)
+ return -1;
+
+ int icol = ExtraImage_ExtraIDToColumnNum( ConvertToClistSlot(slot));
+ if (icol == -1)
+ return -1;
+
+ HANDLE hItem = (HANDLE)SendMessage(cli.hwndContactTree, CLM_FINDCONTACT, hContact, 0);
+ if (hItem == 0)
+ return -1;
+
+ SendMessage(cli.hwndContactTree, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(icol,hImage));
+ return 0;
+}
+
+ExtraIcon* GetExtraIcon(HANDLE id)
+{
+ int i = (int)id;
+ if (i < 1 || i > extraIconsByHandle.getCount())
+ return NULL;
+
+ return extraIconsByHandle[i-1];
+}
+
+ExtraIcon* GetExtraIconBySlot(int slot)
+{
+ for (int i = 0; i < extraIconsBySlot.getCount(); i++) {
+ ExtraIcon *extra = extraIconsBySlot[i];
+ if (extra->getSlot() == slot)
+ return extra;
+ }
+ return NULL;
+}
+
+BaseExtraIcon* GetExtraIconByName(const char *name)
+{
+ for (int i=0; i < registeredExtraIcons.getCount(); i++) {
+ BaseExtraIcon *extra = registeredExtraIcons[i];
+ if (mir_strcmp(name, extra->getName()) == 0)
+ return extra;
+ }
+ return NULL;
+}
+
+static void LoadGroups(LIST<ExtraIconGroup> &groups)
+{
+ int count = db_get_w(NULL, MODULE_NAME "Groups", "Count", 0);
+ for (int i=0; i < count; i++) {
+ char setting[512];
+ mir_snprintf(setting, "%d_count", i);
+ unsigned int items = db_get_w(NULL, MODULE_NAME "Groups", setting, 0);
+ if (items < 1)
+ continue;
+
+ mir_snprintf(setting, "__group_%d", i);
+ ExtraIconGroup *group = new ExtraIconGroup(setting);
+
+ for (unsigned int j = 0; j < items; j++) {
+ mir_snprintf(setting, "%d_%d", i, j);
+ ptrA szIconName(db_get_sa(NULL, MODULE_NAME "Groups", setting));
+ if (IsEmpty(szIconName))
+ continue;
+
+ BaseExtraIcon *extra = GetExtraIconByName(szIconName);
+ if (extra == NULL)
+ continue;
+
+ group->items.insert(extra);
+ if (extra->getSlot() >= 0)
+ group->setSlot(extra->getSlot());
+ }
+
+ if (group->items.getCount() < 2) {
+ delete group;
+ continue;
+ }
+
+ groups.insert(group);
+ }
+}
+
+static ExtraIconGroup* IsInGroup(LIST<ExtraIconGroup> &groups, BaseExtraIcon *extra)
+{
+ for (int i = 0; i < groups.getCount(); i++) {
+ ExtraIconGroup *group = groups[i];
+ for (int j = 0; j < group->items.getCount(); j++) {
+ if (extra == group->items[j])
+ return group;
+ }
+ }
+ return NULL;
+}
+
+void RebuildListsBasedOnGroups(LIST<ExtraIconGroup> &groups)
+{
+ extraIconsByHandle.destroy();
+
+ for (int i=0; i < registeredExtraIcons.getCount(); i++)
+ extraIconsByHandle.insert(registeredExtraIcons[i]);
+
+ for (int k=0; k < extraIconsBySlot.getCount(); k++) {
+ ExtraIcon *extra = extraIconsBySlot[k];
+ if (extra->getType() == EXTRAICON_TYPE_GROUP)
+ delete extra;
+ }
+ extraIconsBySlot.destroy();
+
+ for (int i=0; i < groups.getCount(); i++) {
+ ExtraIconGroup *group = groups[i];
+
+ for (int j = 0; j < group->items.getCount(); j++)
+ extraIconsByHandle.put(group->items[j]->getID()-1, group);
+
+ extraIconsBySlot.insert(group);
+ }
+
+ for (int k=0; k < extraIconsByHandle.getCount(); k++) {
+ ExtraIcon *extra = extraIconsByHandle[k];
+ if (extra->getType() != EXTRAICON_TYPE_GROUP)
+ extraIconsBySlot.insert(extra);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void KillModuleExtraIcons(int hLangpack)
+{
+ LIST<ExtraIcon> arDeleted(1);
+
+ for (int i=registeredExtraIcons.getCount()-1; i >= 0; i--) {
+ BaseExtraIcon *p = registeredExtraIcons[i];
+ if (p->hLangpack == hLangpack) {
+ registeredExtraIcons.remove(i);
+ arDeleted.insert(p);
+ }
+ }
+
+ if (arDeleted.getCount() == 0)
+ return;
+
+ LIST<ExtraIconGroup> groups(1);
+ LoadGroups(groups);
+ RebuildListsBasedOnGroups(groups);
+
+ for (int k=0; k < arDeleted.getCount(); k++)
+ delete arDeleted[k];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ClistExtraListRebuild(WPARAM, LPARAM)
+{
+ clistRebuildAlreadyCalled = TRUE;
+
+ ResetIcons();
+
+ for (int i=0; i < extraIconsBySlot.getCount(); i++)
+ extraIconsBySlot[i]->rebuildIcons();
+
+ return 0;
+}
+
+int ClistExtraImageApply(WPARAM hContact, LPARAM)
+{
+ if (hContact == NULL)
+ return 0;
+
+ clistApplyAlreadyCalled = TRUE;
+
+ for (int i=0; i < extraIconsBySlot.getCount(); i++)
+ extraIconsBySlot[i]->applyIcon(hContact);
+
+ return 0;
+}
+
+int ClistExtraClick(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact == NULL)
+ return 0;
+
+ int clistSlot = (int)lParam;
+
+ for (int i=0; i < extraIconsBySlot.getCount(); i++) {
+ ExtraIcon *extra = extraIconsBySlot[i];
+ if (ConvertToClistSlot(extra->getSlot()) == clistSlot) {
+ extra->onClick(hContact);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Extra image list functions
+
+HANDLE hEventExtraImageListRebuilding, hEventExtraImageApplying, hEventExtraClick;
+
+static bool bImageCreated = false;
+static int g_mutex_bSetAllExtraIconsCycle = 0;
+static HIMAGELIST hExtraImageList;
+
+HANDLE ExtraIcon_Add(HICON hIcon)
+{
+ if (hExtraImageList == 0 || hIcon == 0)
+ return INVALID_HANDLE_VALUE;
+
+ int res = ImageList_AddIcon(hExtraImageList, hIcon);
+ return (res > 0xFFFE) ? INVALID_HANDLE_VALUE : (HANDLE)res;
+}
+
+void fnReloadExtraIcons()
+{
+ SendMessage(cli.hwndContactTree, CLM_SETEXTRASPACE, db_get_b(NULL,"CLUI","ExtraColumnSpace",18), 0);
+ SendMessage(cli.hwndContactTree, CLM_SETEXTRAIMAGELIST, 0, 0);
+
+ if (hExtraImageList)
+ ImageList_Destroy(hExtraImageList);
+
+ hExtraImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),ILC_COLOR32|ILC_MASK,1,256);
+
+ SendMessage(cli.hwndContactTree, CLM_SETEXTRAIMAGELIST, 0, (LPARAM)hExtraImageList);
+ SendMessage(cli.hwndContactTree, CLM_SETEXTRACOLUMNS, EXTRA_ICON_COUNT, 0);
+ NotifyEventHooks(hEventExtraImageListRebuilding,0,0);
+ bImageCreated = true;
+}
+
+void fnSetAllExtraIcons(MCONTACT hContact)
+{
+ if (cli.hwndContactTree == 0)
+ return;
+
+ g_mutex_bSetAllExtraIconsCycle = 1;
+ bool hcontgiven = (hContact != 0);
+
+ if (!bImageCreated)
+ cli.pfnReloadExtraIcons();
+
+ SendMessage(cli.hwndContactTree, CLM_SETEXTRACOLUMNS, EXTRA_ICON_COUNT, 0);
+
+ if (hContact == NULL)
+ hContact = db_find_first();
+
+ for (; hContact; hContact = db_find_next(hContact)) {
+ ClcCacheEntry* pdnce = (ClcCacheEntry*)cli.pfnGetCacheEntry(hContact);
+ if (pdnce == NULL)
+ continue;
+
+ NotifyEventHooks(hEventExtraImageApplying, hContact, 0);
+ if (hcontgiven) break;
+ Sleep(0);
+ }
+
+ g_mutex_bSetAllExtraIconsCycle = 0;
+ cli.pfnInvalidateRect(cli.hwndContactTree, NULL, FALSE);
+ Sleep(0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Services
+
+INT_PTR ExtraIcon_Register(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam == 0)
+ return 0;
+
+ EXTRAICON_INFO *ei = (EXTRAICON_INFO *)wParam;
+ if (ei->cbSize < sizeof(EXTRAICON_INFO))
+ return 0;
+ if (ei->type != EXTRAICON_TYPE_CALLBACK && ei->type != EXTRAICON_TYPE_ICOLIB)
+ return 0;
+ if (IsEmpty(ei->name) || IsEmpty(ei->description))
+ return 0;
+ if (ei->type == EXTRAICON_TYPE_CALLBACK && (ei->ApplyIcon == NULL || ei->RebuildIcons == NULL))
+ return 0;
+
+ ptrT tszDesc(mir_a2t(ei->description));
+ TCHAR *desc = TranslateTH(lParam, tszDesc);
+
+ BaseExtraIcon *extra = GetExtraIconByName(ei->name);
+ if (extra != NULL) {
+ if (ei->type != extra->getType() || ei->type != EXTRAICON_TYPE_ICOLIB)
+ return 0;
+
+ // Found one, now merge it
+ if (mir_tstrcmpi(extra->getDescription(), desc)) {
+ CMString newDesc = extra->getDescription();
+ newDesc += _T(" / ");
+ newDesc += desc;
+ extra->setDescription(newDesc.c_str());
+ }
+
+ if (!IsEmpty(ei->descIcon))
+ extra->setDescIcon(ei->descIcon);
+
+ if (ei->OnClick != NULL)
+ extra->setOnClick(ei->OnClick, ei->onClickParam);
+
+ if (extra->getSlot() > 0) {
+ if (clistRebuildAlreadyCalled)
+ extra->rebuildIcons();
+ if (clistApplyAlreadyCalled)
+ extraIconsByHandle[extra->getID() - 1]->applyIcons();
+ }
+
+ return extra->getID();
+ }
+
+ int id = registeredExtraIcons.getCount() + 1;
+
+ switch (ei->type) {
+ case EXTRAICON_TYPE_CALLBACK:
+ extra = new CallbackExtraIcon(id, ei->name, desc, ei->descIcon == NULL ? "" : ei->descIcon,
+ ei->RebuildIcons, ei->ApplyIcon, ei->OnClick, ei->onClickParam);
+ break;
+ case EXTRAICON_TYPE_ICOLIB:
+ extra = new IcolibExtraIcon(id, ei->name, desc, ei->descIcon == NULL ? "" : ei->descIcon, ei->OnClick,
+ ei->onClickParam);
+ break;
+ default:
+ return 0;
+ }
+
+ char setting[512];
+ mir_snprintf(setting, "Position_%s", ei->name);
+ extra->setPosition(db_get_w(NULL, MODULE_NAME, setting, 1000));
+
+ mir_snprintf(setting, "Slot_%s", ei->name);
+ int slot = db_get_w(NULL, MODULE_NAME, setting, 1);
+ if (slot == (WORD)-1)
+ slot = -1;
+ extra->setSlot(slot);
+
+ extra->hLangpack = (int)lParam;
+
+ registeredExtraIcons.insert(extra);
+ extraIconsByHandle.insert(extra);
+
+ LIST<ExtraIconGroup> groups(1);
+ LoadGroups(groups);
+
+ ExtraIconGroup *group = IsInGroup(groups, extra);
+ if (group != NULL)
+ RebuildListsBasedOnGroups(groups);
+ else {
+ for (int i = 0; i < groups.getCount(); i++)
+ delete groups[i];
+
+ extraIconsBySlot.insert(extra);
+ }
+
+ if (slot >= 0 || group != NULL) {
+ if (clistRebuildAlreadyCalled)
+ extra->rebuildIcons();
+
+ slot = 0;
+ for (int i = 0; i < extraIconsBySlot.getCount(); i++) {
+ ExtraIcon *ex = extraIconsBySlot[i];
+ if (ex->getSlot() < 0)
+ continue;
+
+ int oldSlot = ex->getSlot();
+ ex->setSlot(slot++);
+
+ if (clistApplyAlreadyCalled && (ex == group || ex == extra || oldSlot != slot))
+ extra->applyIcons();
+ }
+ }
+
+ return id;
+}
+
+INT_PTR ExtraIcon_SetIcon(WPARAM wParam, LPARAM)
+{
+ if (wParam == 0)
+ return -1;
+
+ EXTRAICON *ei = (EXTRAICON*)wParam;
+ if (ei->cbSize < sizeof(EXTRAICON) || ei->hExtraIcon == NULL || ei->hContact == NULL)
+ return -1;
+
+ ExtraIcon *extra = GetExtraIcon(ei->hExtraIcon);
+ if (extra == NULL)
+ return -1;
+
+ return extra->setIcon((int)ei->hExtraIcon, ei->hContact, ei->hImage);
+}
+
+INT_PTR ExtraIcon_SetIconByName(WPARAM wParam, LPARAM)
+{
+ if (wParam == 0)
+ return -1;
+
+ EXTRAICON *ei = (EXTRAICON*)wParam;
+ if (ei->cbSize < sizeof(EXTRAICON) || ei->hExtraIcon == NULL || ei->hContact == NULL)
+ return -1;
+
+ ExtraIcon *extra = GetExtraIcon(ei->hExtraIcon);
+ if (extra == NULL)
+ return -1;
+
+ return extra->setIconByName((int)ei->hExtraIcon, ei->hContact, ei->icoName);
+}
+
+static INT_PTR svcExtraIcon_Add(WPARAM wParam, LPARAM)
+{
+ return (INT_PTR)ExtraIcon_Add((HICON)wParam);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static IconItem iconList[] =
+{
+ { LPGEN("Chat activity"), "ChatActivity", IDI_CHAT },
+ { LPGEN("Male"), "gender_male", IDI_MALE },
+ { LPGEN("Female"), "gender_female", IDI_FEMALE }
+};
+
+void LoadExtraIconsModule()
+{
+ DWORD ret = CallService(MS_CLUI_GETCAPS, CLUICAPS_FLAGS2, 0);
+ clistFirstSlot = HIWORD(ret);
+ clistSlotCount = LOWORD(ret);
+
+ // Services
+ CreateServiceFunction(MS_EXTRAICON_REGISTER, ExtraIcon_Register);
+ CreateServiceFunction(MS_EXTRAICON_SET_ICON, ExtraIcon_SetIcon);
+ CreateServiceFunction(MS_EXTRAICON_SET_ICON_BY_NAME, &ExtraIcon_SetIconByName);
+
+ CreateServiceFunction(MS_CLIST_EXTRA_ADD_ICON, svcExtraIcon_Add);
+
+ hEventExtraClick = CreateHookableEvent(ME_CLIST_EXTRA_CLICK);
+ hEventExtraImageApplying = CreateHookableEvent(ME_CLIST_EXTRA_IMAGE_APPLY);
+ hEventExtraImageListRebuilding = CreateHookableEvent(ME_CLIST_EXTRA_LIST_REBUILD);
+
+ // Icons
+ Icon_Register(NULL, LPGEN("Contact list"), iconList, SIZEOF(iconList));
+
+ // Hooks
+ HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded);
+
+ HookEvent(ME_CLIST_EXTRA_LIST_REBUILD, ClistExtraListRebuild);
+ HookEvent(ME_CLIST_EXTRA_IMAGE_APPLY, ClistExtraImageApply);
+ HookEvent(ME_CLIST_EXTRA_CLICK, ClistExtraClick);
+
+ DefaultExtraIcons_Load();
+}
+
+void UnloadExtraIconsModule(void)
+{
+ for (int k = 0; k < extraIconsBySlot.getCount(); k++) {
+ ExtraIcon *extra = extraIconsBySlot[k];
+ if (extra->getType() == EXTRAICON_TYPE_GROUP)
+ delete extra;
+ }
+
+ for (int i = 0; i < registeredExtraIcons.getCount(); i++)
+ delete registeredExtraIcons[i];
+}
diff --git a/src/mir_app/src/extraicons.h b/src/mir_app/src/extraicons.h new file mode 100644 index 0000000000..795556cbd8 --- /dev/null +++ b/src/mir_app/src/extraicons.h @@ -0,0 +1,71 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#ifndef __COMMONS_H__
+# define __COMMONS_H__
+
+#define MODULE_NAME "ExtraIcons"
+
+// Global Variables
+
+#define FREE(_m_) if (_m_ != NULL) { free(_m_); _m_ = NULL; }
+
+#define ICON_SIZE 16
+
+#include "Extraicon.h"
+
+extern LIST<BaseExtraIcon> registeredExtraIcons;
+extern LIST<ExtraIcon> extraIconsByHandle, extraIconsBySlot;
+void RebuildListsBasedOnGroups(LIST<ExtraIconGroup> &groups);
+ExtraIcon * GetExtraIconBySlot(int slot);
+
+int GetNumberOfSlots();
+int ConvertToClistSlot(int slot);
+
+int Clist_SetExtraIcon(MCONTACT hContact, int slot, HANDLE hImage);
+
+void DefaultExtraIcons_Load();
+
+HANDLE ExtraIcon_Add(HICON hIcon);
+
+void fnReloadExtraIcons();
+void fnSetAllExtraIcons(MCONTACT hContact);
+
+static inline BOOL IsEmpty(const char *str)
+{
+ return str == NULL || str[0] == 0;
+}
+
+static inline int MIN(int a, int b)
+{
+ if (a <= b)
+ return a;
+ return b;
+}
+
+static inline int MAX(int a, int b)
+{
+ if (a >= b)
+ return a;
+ return b;
+}
+
+#endif // __COMMONS_H__
diff --git a/src/mir_app/src/filter.cpp b/src/mir_app/src/filter.cpp new file mode 100644 index 0000000000..83f91c407c --- /dev/null +++ b/src/mir_app/src/filter.cpp @@ -0,0 +1,159 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "filter.h"
+
+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 (mir_tstrlen(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 (mir_tstrlen(title) > 0)
+ AddFilterString(key, title);
+
+ TCHAR szClass[64];
+ GetClassName(hWndDlg, szClass, SIZEOF(szClass));
+
+ if (mir_tstrcmpi(szClass, _T("SysTreeView32")) == 0) {
+ HTREEITEM hItem = TreeView_GetRoot(hWndDlg);
+ AddTreeViewNodes(hWndDlg, key, hItem);
+ return;
+ }
+
+ if (mir_tstrcmpi(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 (mir_tstrlen(title) > 0)
+ AddFilterString(key, title);
+ }
+ }
+ }
+ return;
+ }
+
+ if (mir_tstrcmpi(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 (mir_tstrlen(title) > 0)
+ AddFilterString(key, title);
+ }
+ return;
+ }
+
+ if (mir_tstrcmpi(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 (mir_tstrlen(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/mir_app/src/filter.h b/src/mir_app/src/filter.h new file mode 100644 index 0000000000..9317547726 --- /dev/null +++ b/src/mir_app/src/filter.h @@ -0,0 +1,95 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#ifndef M_OPTIONS_FILTERING_H
+#define M_OPTIONS_FILTERING_H
+
+extern HANDLE hOptionsInitialize;
+
+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(Langpack_GetDefaultLocale() , LCMAP_LOWERCASE, buf, (int)mir_tstrlen(buf), buf, (int)mir_tstrlen(buf));
+ return buf;
+}
+
+typedef LIST<TCHAR> KeywordList;
+class CPageKeywords
+{
+ PageHash _pageHashKey;
+ KeywordList _pageKeyWords;
+ static int _KeyWordsSortFunc(const TCHAR* p1, const TCHAR* p2) { return mir_tstrcmp(p1, p2); };
+
+public:
+ CPageKeywords(PageHash pageHashKey) : _pageHashKey(pageHashKey), _pageKeyWords(1, _KeyWordsSortFunc) {};
+ ~CPageKeywords()
+ {
+ for (int j = 0; j < _pageKeyWords.getCount(); j++)
+ mir_free(_pageKeyWords[j]);
+ };
+
+ 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<CPageKeywords>
+{
+ CPageList();
+public:
+ CPageList( int aincr, FTSortFunc afunc = CPageKeywords::PageSortFunc) : OBJLIST<CPageKeywords>(aincr, afunc) {};
+ CPageKeywords * operator[](PageHash key)
+ {
+ CPageKeywords keyToSearch(key);
+ return this->find(&keyToSearch);
+ }
+ ~CPageList() {};
+};
+
+#endif //M_OPTIONS_FILTERING_H
diff --git a/src/mir_app/src/findadd.cpp b/src/mir_app/src/findadd.cpp new file mode 100644 index 0000000000..e1fa64a9b5 --- /dev/null +++ b/src/mir_app/src/findadd.cpp @@ -0,0 +1,1061 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.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 HGENMENU hMainMenuItem = NULL; +static int OnSystemModulesLoaded(WPARAM wParam, LPARAM lParam); + +static int FindAddDlgResizer(HWND, LPARAM lParam, UTILRESIZECONTROL *urc) +{ + static int y, nextY, oldTop; + FindAddDlgData *dat = (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) +{ + InflateRect(rcItem, -1, 0); + int width = rcItem->right - rcItem->left; + int height = rcItem->bottom - rcItem->top; + int height2 = height / 2; + + if (*throbbing) { + /* create memdc */ + HDC hMemDC = CreateCompatibleDC(0); + HBITMAP hBitmap = (HBITMAP)SelectObject(hMemDC, CreateCompatibleBitmap(hdc, width, height)); + /* flush it */ + RECT rc; + rc.left = rc.top = 0; + rc.right = width; + rc.bottom = height; + HBRUSH hBr = GetSysColorBrush(COLOR_BTNFACE); + FillRect(hMemDC, &rc, hBr); + DeleteObject(hBr); + /* set up the pen */ + HPEN hPen = (HPEN)SelectObject(hMemDC, CreatePen(PS_SOLID, 4, GetSysColor(COLOR_BTNSHADOW))); + /* draw everything before the pivot */ + int 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 */ + HBRUSH hBr = GetSysColorBrush(COLOR_BTNFACE); + FillRect(hdc, rcItem, hBr); + DeleteObject(hBr); + } +} + +static void StartThrobber(HWND hwndDlg, FindAddDlgData *dat) +{ + dat->throbbing = 1; + SetTimer(hwndDlg, TIMERID_THROBBER, 25, NULL); +} + +static void StopThrobber(HWND hwndDlg, FindAddDlgData *dat) +{ + KillTimer(hwndDlg, TIMERID_THROBBER); + dat->throbbing = 0; + dat->pivot = 0; + InvalidateRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), NULL, FALSE); +} + +static LRESULT CALLBACK AdvancedSearchDlgSubclassProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_COMMAND) { + HWND parentHwnd = GetParent(hwndDlg); + switch (LOWORD(wParam)) { + case IDOK: + SendMessage(parentHwnd, WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED), (LPARAM)GetDlgItem(parentHwnd, IDOK)); + SetFocus(GetDlgItem(parentHwnd, IDC_ADVANCED)); + break; + + case IDCANCEL: + CheckDlgButton(parentHwnd, IDC_ADVANCED, BST_UNCHECKED); + SendMessage(parentHwnd, WM_COMMAND, MAKEWPARAM(IDC_ADVANCED, BN_CLICKED), (LPARAM)GetDlgItem(parentHwnd, IDC_ADVANCED)); + SetFocus(GetDlgItem(parentHwnd, IDC_ADVANCED)); + break; + } + } + return mir_callNextSubclass(hwndDlg, AdvancedSearchDlgSubclassProc, msg, wParam, lParam); +} + +static void ShowAdvancedSearchDlg(HWND hwndDlg, 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)CallProtoServiceInt(NULL, szProto, PS_CREATEADVSEARCHUI, 0, (LPARAM)hwndDlg); + if (dat->hwndAdvSearch != NULL) + mir_subclassWindow(dat->hwndAdvSearch, AdvancedSearchDlgSubclassProc); + GetWindowRect(GetDlgItem(hwndDlg, IDC_RESULTS), &rc); + SetWindowPos(dat->hwndAdvSearch, 0, rc.left, rc.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + } + + AnimateWindow(dat->hwndAdvSearch, 150, AW_ACTIVATE | AW_SLIDE | AW_HOR_POSITIVE); + RedrawWindow(dat->hwndAdvSearch, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); + + CheckDlgButton(hwndDlg, IDC_ADVANCED, BST_CHECKED); +} + +static void ReposTinySearchDlg(HWND hwndDlg, FindAddDlgData *dat) +{ + if (dat->hwndTinySearch == NULL) + return; + + 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); +} + +static void ShowTinySearchDlg(HWND hwndDlg, 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)CallProtoServiceInt(NULL, 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, FindAddDlgData *dat) +{ + if (dat->hwndAdvSearch == NULL) + return; + + AnimateWindow(dat->hwndAdvSearch, 150, AW_HIDE | AW_BLEND); + CheckDlgButton(hwndDlg, IDC_ADVANCED, BST_UNCHECKED); +} + +void EnableResultButtons(HWND hwndDlg, int enable) +{ + EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), enable); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), enable); +} + +static const int controls[] = { IDC_BYPROTOID, IDC_BYEMAIL, IDC_BYNAME, IDC_BYADVANCED, IDC_BYCUSTOM }; + +static void CheckSearchTypeRadioButton(HWND hwndDlg, int idControl) +{ + for (int 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 == NULL || *szText == 0) + szText = TranslateT("<not specified>"); + + ListView_SetItemText(hwndList, idx, col, szText); +} + +static TCHAR* sttDecodeString(DWORD dwFlags, MAllStrings &src) +{ + if (dwFlags & PSR_UNICODE) + return mir_u2t(src.w); + + if (dwFlags & PSR_UTF8) + return mir_utf8decodeT(src.a); + + return mir_a2t(src.a); +} + +static INT_PTR CALLBACK DlgProcFindAdd(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + FindAddDlgData *dat = (FindAddDlgData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + HWND hwndList = GetDlgItem(hwndDlg, IDC_RESULTS); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + Window_SetIcon_IcoLib(hwndDlg, SKINICON_OTHER_FINDUSER); + dat = (FindAddDlgData*)mir_calloc(sizeof(FindAddDlgData)); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + dat->notSearchedYet = 1; + dat->iLastColumnSortIndex = 1; + dat->bSortAscending = 1; + SendDlgItemMessage(hwndDlg, IDC_MOREOPTIONS, BUTTONSETARROW, 1, 0); + SendDlgItemMessage(hwndDlg, IDOK, BUTTONADDTOOLTIP, (WPARAM)LPGENT("Ctrl+Search add contact"), BATF_TCHAR); + + ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP); + { + RECT rc; + GetClientRect(hwndList, &rc); + + LVCOLUMN lvc; + lvc.mask = LVCF_TEXT | LVCF_WIDTH; + lvc.pszText = TranslateT("Results"); + lvc.cx = rc.right - 1; + ListView_InsertColumn(hwndList, 0, &lvc); + + LVITEM lvi; + 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 + HDC hdc = GetDC(GetDlgItem(hwndDlg, IDC_STATUSBAR)); + SelectObject(hdc, (HFONT)SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, WM_GETFONT, 0, 0)); + + SIZE textSize; + GetTextExtentPoint32(hdc, TranslateT("Searching"), (int)mir_tstrlen(TranslateT("Searching")), &textSize); + + int partWidth[3]; + 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; + DBVARIANT dbv; + if (!db_get_ts(NULL, "FindAdd", "LastSearched", &dbv)) { + szProto = NEWTSTR_ALLOCA(dbv.ptszVal); + db_free(&dbv); /* free string szProto was fetched with */ + } + + int i, index = 0, cbwidth = 0, netProtoCount = 0; + for (i = 0; i < accounts.getCount(); i++) { + if (!Proto_IsAccountEnabled(accounts[i])) + continue; + + DWORD caps = (DWORD)CallProtoServiceInt(NULL, accounts[i]->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); + if (caps & PF1_ANYSEARCH) + netProtoCount++; + } + dat->himlComboIcons = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, netProtoCount + 1, netProtoCount + 1); + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_SETIMAGELIST, 0, (LPARAM)dat->himlComboIcons); + + COMBOBOXEXITEM cbei; + 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, (int)mir_tstrlen(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)CallProtoServiceInt(NULL, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); + if (!(caps & PF1_ANYSEARCH)) + continue; + + cbei.pszText = pa->tszAccountName; + GetTextExtentPoint32(hdc, cbei.pszText, (int)mir_tstrlen(cbei.pszText), &textSize); + if (textSize.cx > cbwidth) + cbwidth = textSize.cx; + + HICON hIcon = (HICON)CallProtoServiceInt(NULL, 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 && !mir_tstrcmp(szProto, pa->tszAccountName)) + index = cbei.iItem; + cbei.iItem++; + } + cbwidth += 32; + + RECT rect; + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&rect); + if ((rect.right - rect.left) < cbwidth) + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_SETDROPPEDWIDTH, cbwidth, 0); + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_SETCURSEL, index, 0); + + SendMessage(hwndDlg, M_SETGROUPVISIBILITIES, 0, 0); + Utils_RestoreWindowPosition(hwndDlg, NULL, "FindAdd", ""); + } + return TRUE; + + case WM_SIZE: + { + UTILRESIZEDIALOG urd = { 0 }; + urd.cbSize = sizeof(urd); + urd.hwndDlg = hwndDlg; + urd.hInstance = g_hInst; + urd.lpTemplate = MAKEINTRESOURCEA(IDD_FINDADD); + urd.lParam = (LPARAM)dat; + urd.pfnResizer = FindAddDlgResizer; + CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd); + ReposTinySearchDlg(hwndDlg, dat); + SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, WM_SIZE, 0, 0); + if (dat->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: + RECT rc, rc2; + GetWindowRect(hwndList, &rc); + GetWindowRect(hwndDlg, &rc2); + { + MINMAXINFO *mmi = (MINMAXINFO*)lParam; + 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: + dat->showAdvanced = dat->showEmail = dat->showName = dat->showProtoId = dat->showTiny = 0; + { + char *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 (int i = 0; i < accounts.getCount(); i++) { + PROTOACCOUNT *pa = accounts[i]; + if (Proto_IsAccountEnabled(pa)) { + DWORD protoCaps = (DWORD)CallProtoServiceInt(NULL, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); + if (protoCaps & PF1_SEARCHBYEMAIL) dat->showEmail = 1; + if (protoCaps & PF1_SEARCHBYNAME) dat->showName = 1; + } + } + } + else { + DWORD protoCaps = (DWORD)CallProtoServiceInt(NULL, 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_EXTSEARCHUI) dat->showAdvanced = 1; + else if (protoCaps & PF1_EXTSEARCH) dat->showTiny = 1; + + if (protoCaps & PF1_USERIDISEMAIL && dat->showProtoId) { dat->showProtoId = 0; dat->showEmail = 1; } + if (dat->showProtoId) { + char *szUniqueId = (char*)CallProtoServiceInt(NULL, szProto, PS_GETCAPS, PFLAG_UNIQUEIDTEXT, 0); + if (szUniqueId) + SetDlgItemTextA(hwndDlg, IDC_BYPROTOID, szUniqueId); + 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 + int 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); + + MINMAXINFO mmi; + SendMessage(hwndDlg, WM_GETMINMAXINFO, 0, (LPARAM)&mmi); + + RECT rc; + GetWindowRect(hwndDlg, &rc); + if (rc.bottom - rc.top < mmi.ptMinTrackSize.y) + SetWindowPos(hwndDlg, 0, 0, 0, rc.right - rc.left, mmi.ptMinTrackSize.y, SWP_NOZORDER | SWP_NOMOVE); + } + break; + + case WM_TIMER: + if (wParam == TIMERID_THROBBER) { + int borders[3]; + SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_GETBORDERS, 0, (LPARAM)borders); + + RECT rc; + SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_GETRECT, 1, (LPARAM)&rc); + InflateRect(&rc, -borders[2] / 2, -borders[1] / 2); + HDC hdc = GetDC(GetDlgItem(hwndDlg, IDC_STATUSBAR)); + RenderThrobber(hdc, &rc, &dat->throbbing, &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: + HDITEM hdi; + hdi.mask = HDI_FORMAT; + hdi.fmt = HDF_LEFT | HDF_STRING; + Header_SetItem(ListView_GetHeader(hwndList), dat->iLastColumnSortIndex, &hdi); + + LPNMLISTVIEW nmlv = (LPNMLISTVIEW)lParam; + if (nmlv->iSubItem != dat->iLastColumnSortIndex) { + dat->bSortAscending = TRUE; + dat->iLastColumnSortIndex = nmlv->iSubItem; + } + else dat->bSortAscending = !dat->bSortAscending; + + hdi.fmt = HDF_LEFT | HDF_STRING | (dat->bSortAscending ? HDF_SORTDOWN : HDF_SORTUP); + Header_SetItem(ListView_GetHeader(hwndList), dat->iLastColumnSortIndex, &hdi); + + ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); + } + } + 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: + case IDC_BYEMAIL: + case IDC_BYNAME: + { + int count = ListView_GetSelectedCount(hwndList); + if (dat->notSearchedYet) + count = 0; + EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), count); + HideAdvancedSearchDlg(hwndDlg, dat); + } + break; + + case IDC_PROTOID: + if (HIWORD(wParam) == EN_CHANGE) { + HideAdvancedSearchDlg(hwndDlg, dat); + CheckSearchTypeRadioButton(hwndDlg, IDC_BYPROTOID); + } + 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); + } + else { + 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)); + rtrimt(str); + if (str[0] == 0) + MessageBox(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK); + else + BeginSearch(hwndDlg, dat, szProto, PS_BASICSEARCH, PF1_BASICSEARCH, str); + } + else if (IsDlgButtonChecked(hwndDlg, IDC_BYEMAIL)) { + TCHAR str[256]; + GetDlgItemText(hwndDlg, IDC_EMAIL, str, SIZEOF(str)); + rtrimt(str); + if (str[0] == 0) + MessageBox(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK); + else + BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYEMAIL, 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_ICONERROR | MB_OK); + else + BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYNAME, PF1_SEARCHBYNAME, &psbn); + } + else if (IsDlgButtonChecked(hwndDlg, IDC_BYADVANCED)) { + if (dat->hwndAdvSearch == NULL) + MessageBox(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | 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: + { + ADDCONTACTSTRUCT acs = { 0 }; + + if (ListView_GetSelectedCount(hwndList) == 1) { + LVITEM lvi; + lvi.mask = LVIF_PARAM; + lvi.iItem = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED); + ListView_GetItem(hwndList, &lvi); + ListSearchResult *lsr = (ListSearchResult*)lvi.lParam; + acs.szProto = lsr->szProto; + acs.psr = &lsr->psr; + } + else { + TCHAR str[256]; + GetDlgItemText(hwndDlg, IDC_PROTOID, str, SIZEOF(str)); + if (*rtrimt(str) == 0) + break; + + PROTOSEARCHRESULT psr = { 0 }; + psr.cbSize = sizeof(psr); + psr.flags = PSR_TCHAR; + psr.id.t = 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 && + BST_UNCHECKED == IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) { + CheckSearchTypeRadioButton(hwndDlg, IDC_BYCUSTOM); + } + break; + + case WM_CONTEXTMENU: + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + + LVHITTESTINFO lvhti; + 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; + if (ack->type != ACKTYPE_SEARCH) + break; + + int i; + for (i = 0; i < dat->searchCount; i++) + if (dat->search[i].hProcess == ack->hProcess && dat->search[i].hProcess != NULL && !mir_strcmp(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) { + CUSTOMSEARCHRESULTS *csr = (CUSTOMSEARCHRESULTS*)ack->lParam; + dat->bFlexSearchResult = TRUE; + PROTOSEARCHRESULT *psr = &csr->psr; + // check if this is column names data (psr->cbSize == 0) + if (psr->cbSize == 0) { // blob contain info about columns + // first remove all exist items + FreeSearchResults(hwndList); + ListView_DeleteAllItems(hwndList); //not sure if previous delete list items too + + //second remove all columns + while (ListView_DeleteColumn(hwndList, 1)); //will delete fist column till it possible + + // now will add columns and captions; + LVCOLUMN lvc = { 0 }; + lvc.mask = LVCF_TEXT; + for (int iColumn = 0; iColumn < csr->nFieldCount; iColumn++) { + lvc.pszText = TranslateTS(csr->pszFields[iColumn]); + ListView_InsertColumn(hwndList, iColumn + 1, &lvc); + } + } + else { // blob contain info about found contacts + ListSearchResult *lsr = (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 */ + lsr->psr.id.t = sttDecodeString(psr->flags, psr->id); + lsr->psr.nick.t = sttDecodeString(psr->flags, psr->nick); + lsr->psr.firstName.t = sttDecodeString(psr->flags, psr->firstName); + lsr->psr.lastName.t = sttDecodeString(psr->flags, psr->lastName); + lsr->psr.email.t = sttDecodeString(psr->flags, psr->email); + lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_TCHAR; + + LVITEM lvi = { 0 }; + lvi.mask = LVIF_PARAM | LVIF_IMAGE; + lvi.lParam = (LPARAM)lsr; + for (int i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--;) { + char *szComboProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0); + if (szComboProto == NULL) continue; + if (!mir_strcmp(szComboProto, ack->szModule)) { + COMBOBOXEXITEM cbei = { 0 }; + cbei.mask = CBEIF_IMAGE; + cbei.iItem = i; + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_GETITEM, 0, (LPARAM)&cbei); + lvi.iImage = cbei.iImage; + } + } + int iItem = ListView_InsertItem(hwndList, &lvi); + for (int col = 0; col < csr->nFieldCount; col++) + SetListItemText(hwndList, iItem, col + 1, csr->pszFields[col]); + + ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); + iItem = 0; + while (ListView_SetColumnWidth(hwndList, iItem++, LVSCW_AUTOSIZE_USEHEADER)); + SetStatusBarResultInfo(hwndDlg); + } + break; + } + else if (ack->result == ACKRESULT_DATA) { + PROTOSEARCHRESULT *psr = (PROTOSEARCHRESULT*)ack->lParam; + ListSearchResult *lsr = (ListSearchResult*)mir_alloc(offsetof(struct ListSearchResult, psr) + psr->cbSize); + lsr->szProto = ack->szModule; + + dat->bFlexSearchResult = FALSE; + + memcpy(&lsr->psr, psr, psr->cbSize); + lsr->psr.nick.t = sttDecodeString(psr->flags, psr->nick); + lsr->psr.firstName.t = sttDecodeString(psr->flags, psr->firstName); + lsr->psr.lastName.t = sttDecodeString(psr->flags, psr->lastName); + lsr->psr.email.t = sttDecodeString(psr->flags, psr->email); + lsr->psr.id.t = sttDecodeString(psr->flags, psr->id); + lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_TCHAR; + + LVITEM lvi = { 0 }; + lvi.mask = LVIF_PARAM | LVIF_IMAGE; + lvi.lParam = (LPARAM)lsr; + for (int i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--;) { + char *szComboProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0); + if (szComboProto == NULL) continue; + if (!mir_strcmp(szComboProto, ack->szModule)) { + COMBOBOXEXITEM cbei = { 0 }; + cbei.mask = CBEIF_IMAGE; + cbei.iItem = i; + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_GETITEM, 0, (LPARAM)&cbei); + lvi.iImage = cbei.iImage; + break; + } + } + + int iItem = ListView_InsertItem(hwndList, &lvi); + SetListItemText(hwndList, iItem, 1, lsr->psr.id.t); + SetListItemText(hwndList, iItem, 2, lsr->psr.nick.t); + SetListItemText(hwndList, iItem, 3, lsr->psr.firstName.t); + SetListItemText(hwndList, iItem, 4, lsr->psr.lastName.t); + SetListItemText(hwndList, iItem, 5, lsr->psr.email.t); + SetStatusBarResultInfo(hwndDlg); + } + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + int len = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETLBTEXTLEN, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); + TCHAR *szProto = (TCHAR*)alloca(sizeof(TCHAR)*(len + 1)); + if (szProto != NULL) { + *szProto = '\0'; + SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETLBTEXT, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), (LPARAM)szProto); + db_set_ts(NULL, "FindAdd", "LastSearched", szProto); + } + + 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; + } + 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 = 0; + + // 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 (int i = 0; i < accounts.getCount(); i++) { + PROTOACCOUNT *pa = accounts[i]; + if (!Proto_IsAccountEnabled(pa)) + continue; + + int protoCaps = CallProtoServiceInt(NULL, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); + if (protoCaps & PF1_ANYSEARCH) + netProtoCount++; + } + if (netProtoCount > 0) + hwndFindAdd = CreateDialog(g_hInst, 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 = { sizeof(mi) }; + mi.position = 500020000; + mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_FINDUSER); + mi.pszName = LPGEN("&Find/add contacts..."); + mi.pszService = MS_FINDADD_FINDADD; + hMainMenuItem = Menu_AddMainMenuItem(&mi); + return 0; +} + +static int OnSystemModulesLoaded(WPARAM, LPARAM) +{ + int netProtoCount = 0; + + // Make sure we have some networks to search on. + for (int i = 0; i < accounts.getCount(); i++) { + PROTOACCOUNT *pa = accounts[i]; + int protoCaps = CallProtoServiceInt(NULL, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); + if (protoCaps & PF1_ANYSEARCH) + netProtoCount++; + } + + Menu_ShowItem(hMainMenuItem, netProtoCount != 0); + return 0; +} diff --git a/src/mir_app/src/findadd.h b/src/mir_app/src/findadd.h new file mode 100644 index 0000000000..4d7ecd27c2 --- /dev/null +++ b/src/mir_app/src/findadd.h @@ -0,0 +1,65 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#define PF1_ANYSEARCH (PF1_BASICSEARCH | PF1_EXTSEARCHUI | PF1_SEARCHBYEMAIL | PF1_SEARCHBYNAME | PF1_EXTSEARCH)
+
+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;
+ 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/mir_app/src/genmenu.cpp b/src/mir_app/src/genmenu.cpp new file mode 100644 index 0000000000..eac64d523e --- /dev/null +++ b/src/mir_app/src/genmenu.cpp @@ -0,0 +1,1236 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "genmenu.h"
+
+static bool bIsGenMenuInited;
+bool bIconsDisabled;
+static mir_cs 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 mir_strcmp(p1->pszName, p2->pszName);
+}
+
+LIST<TIntMenuObject> 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;
+}
+
+LPTSTR GetMenuItemText(PMO_IntMenuItem pimi)
+{
+ if (pimi->mi.flags & CMIF_KEEPUNTRANSLATED)
+ return pimi->mi.ptszName;
+
+ return TranslateTH(pimi->mi.hLangpack, pimi->mi.ptszName);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ if (mis == NULL)
+ return FALSE;
+
+ // prevent win9x from ugly menus displaying when there is no icon
+ mis->itemWidth = 0;
+ mis->itemHeight = 0;
+
+ 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;
+
+ mir_cslock lck(csMenuHook);
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)dis->itemData);
+ if (pimi == NULL || pimi->iconId == -1)
+ 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);
+ }
+ return TRUE;
+}
+
+int MO_RemoveAllObjects()
+{
+ for (int 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)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ mir_cslock lck(csMenuHook);
+ int objidx = GetMenuObjbyId((int)wParam);
+ if (objidx == -1)
+ return -1;
+
+ delete g_menus[objidx];
+ g_menus.remove(objidx);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// wparam = MenuObjectHandle
+// lparam = vKey
+
+INT_PTR MO_ProcessHotKeys(HANDLE menuHandle, INT_PTR vKey)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ mir_cslock lck(csMenuHook);
+ int objidx = GetMenuObjbyId((int)menuHandle);
+ if (objidx == -1)
+ 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);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+INT_PTR MO_GetProtoRootMenu(WPARAM wParam, LPARAM)
+{
+ char *szProto = (char*)wParam;
+ if (szProto == NULL)
+ return 0;
+
+ if (db_get_b(NULL, "CList", "MoveProtoMenus", TRUE))
+ return (INT_PTR)cli.pfnGetProtocolMenu(szProto);
+
+ int objidx = GetMenuObjbyId((int)hMainMenuObject);
+ if (objidx == -1)
+ return NULL;
+
+ mir_cslock lck(csMenuHook);
+
+ TIntMenuObject* pmo = g_menus[objidx];
+ for (PMO_IntMenuItem p = pmo->m_items.first; p != NULL; p = p->next)
+ if (!mir_strcmp(p->UniqName, szProto))
+ return (INT_PTR)p;
+
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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);
+ mir_cslock lck(csMenuHook);
+ if (pimi == NULL)
+ return -1;
+
+ *mi = pimi->mi;
+ 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);
+ mir_cslock lck(csMenuHook);
+ return (pimi) ? (INT_PTR)MO_RecursiveWalkMenu(pimi, FindDefaultItem, NULL) : NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+ mir_cslock lck(csMenuHook);
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)menuHandle);
+ if (pimi == NULL)
+ return -1;
+
+ if (pmi->flags & CMIM_NAME) {
+ FreeAndNil((void**)&pimi->mi.pszName);
+
+ if (pmi->flags & CMIF_UNICODE)
+ pimi->mi.ptszName = mir_tstrdup(pmi->ptszName);
+ else
+ pimi->mi.ptszName = mir_a2t(pmi->pszName);
+ }
+
+ if (pmi->flags & CMIM_FLAGS) {
+ oldflags = (pimi->mi.flags & CMIF_ROOTHANDLE);
+ pimi->mi.flags = (pmi->flags & ~CMIM_ALL) | oldflags;
+ }
+
+ if ((pmi->flags & CMIM_ICON) && !bIconsDisabled) {
+ HANDLE hIcolibItem = IcoLib_IsManaged(pmi->hIcon);
+ if (hIcolibItem) {
+ HICON hIcon = IcoLib_GetIconByHandle(hIcolibItem, false);
+ if (hIcon != NULL) {
+ pimi->hIcolibItem = hIcolibItem;
+ pimi->iconId = ImageList_ReplaceIcon(pimi->parent->m_hMenuIcons, pimi->iconId, hIcon);
+ IcoLib_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;
+
+ 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;
+
+ mir_cslock lck(csMenuHook);
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)wParam);
+ return (pimi) ? (INT_PTR)pimi->mi.ownerdata : -1;
+}
+
+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;
+
+ PMO_IntMenuItem pimi;
+ {
+ mir_cslock lck(csMenuHook);
+ int objidx = GetMenuObjbyId(menuID);
+ if (objidx == -1)
+ return -1;
+
+ pimi = MO_RecursiveWalkMenu(g_menus[objidx]->m_items.first, FindMenuByCommand, (void*)command);
+ }
+
+ return (pimi) ? MO_ProcessCommand(pimi, lParam) : -1;
+}
+
+INT_PTR MO_ProcessCommandByMenuIdent(WPARAM wParam, LPARAM lParam)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ PMO_IntMenuItem pimi = NULL;
+ {
+ mir_cslock lck(csMenuHook);
+ for (int i = 0; i < g_menus.getCount(); i++)
+ if ((pimi = MO_RecursiveWalkMenu(g_menus[i]->m_items.first, FindMenuByCommand, (void*)wParam)) != NULL)
+ break;
+ }
+
+ return (pimi) ? MO_ProcessCommand(pimi, lParam) : FALSE;
+}
+
+int MO_ProcessCommand(PMO_IntMenuItem aHandle, LPARAM lParam)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ PMO_IntMenuItem pimi;
+ {
+ mir_cslock lck(csMenuHook);
+ if ((pimi = MO_GetIntMenuItem(aHandle)) == NULL)
+ return -1;
+ }
+
+ LPCSTR srvname = pimi->parent->ExecService;
+ void *ownerdata = pimi->mi.ownerdata;
+ CallService(srvname, (WPARAM)ownerdata, lParam);
+ return 1;
+}
+
+int MO_SetOptionsMenuItem(PMO_IntMenuItem aHandle, int setting, INT_PTR value)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ mir_cslock lck(csMenuHook);
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(aHandle);
+ if (pimi == NULL)
+ return -1;
+
+ if (setting == OPT_MENUITEMSETUNIQNAME) {
+ mir_free(pimi->UniqName);
+ pimi->UniqName = mir_strdup((char*)value);
+ }
+
+ return 1;
+}
+
+int MO_SetOptionsMenuObject(HANDLE handle, int setting, INT_PTR value)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ mir_cslock lck(csMenuHook);
+
+ int pimoidx = GetMenuObjbyId((int)handle);
+ int 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;
+ }
+ }
+
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// wparam = LPCSTR szDisplayName;
+// lparam = PMenuParam;
+// result = MenuObjectHandle
+
+INT_PTR MO_CreateNewMenuObject(WPARAM wParam, LPARAM lParam)
+{
+ TMenuParam *pmp = (TMenuParam *)lParam;
+ if (!bIsGenMenuInited || pmp == NULL)
+ return -1;
+
+ mir_cslock lck(csMenuHook);
+
+ TIntMenuObject* p = new TIntMenuObject();
+ p->id = NextObjectId++;
+ p->pszName = mir_strdup(pmp->name);
+ p->ptszDisplayName = mir_a2t(LPCSTR(wParam));
+ p->CheckService = mir_strdup(pmp->CheckService);
+ p->ExecService = mir_strdup(pmp->ExecService);
+ p->m_hMenuIcons = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 15, 100);
+ g_menus.insert(p);
+ 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)
+{
+ mir_cslock lck(csMenuHook);
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)wParam);
+ if (pimi == NULL)
+ 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);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct KillMenuItemsParam
+{
+ KillMenuItemsParam(int _hLangpack) :
+ hLangpack(_hLangpack),
+ arItems(10)
+ {}
+
+ int hLangpack;
+ LIST<TMO_IntMenuItem> arItems;
+};
+
+int KillMenuItems(PMO_IntMenuItem pimi, KillMenuItemsParam* param)
+{
+ if (pimi->hLangpack == param->hLangpack)
+ param->arItems.insert(pimi);
+ return FALSE;
+}
+
+void KillModuleMenus(int hLangpack)
+{
+ if (!bIsGenMenuInited)
+ return;
+
+ KillMenuItemsParam param(hLangpack);
+
+ mir_cslock lck(csMenuHook);
+ for (int i = 0; i < g_menus.getCount(); i++)
+ MO_RecursiveWalkMenu(g_menus[i]->m_items.first, (pfnWalkFunc)KillMenuItems, ¶m);
+
+ for (int k = 0; k < param.arItems.getCount(); k++)
+ MO_RemoveMenuItem((WPARAM)param.arItems[k], 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);
+
+ mir_cslock lck(csMenuHook);
+ int objidx = GetMenuObjbyId((int)menuobjecthandle);
+ if (objidx == -1)
+ 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;
+ p->hLangpack = pmi->hLangpack;
+
+ if (pmi->flags & CMIF_UNICODE)
+ p->mi.ptszName = mir_tstrdup(pmi->ptszName);
+ else
+ p->mi.ptszName = mir_a2u(pmi->pszName);
+
+ if (pmi->hIcon != NULL && !bIconsDisabled) {
+ HANDLE hIcolibItem = IcoLib_IsManaged(pmi->hIcon);
+ if (hIcolibItem != NULL) {
+ HICON hIcon = IcoLib_GetIconByHandle(hIcolibItem, false);
+ p->iconId = ImageList_AddIcon(pmo->m_hMenuIcons, hIcon);
+ p->hIcolibItem = hIcolibItem;
+ IcoLib_ReleaseIcon(hIcon, 0);
+ }
+ 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;
+ return p;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// wparam = MenuObjectHandle
+// lparam = PMO_MenuItem
+
+int FindRoot(PMO_IntMenuItem pimi, void* param)
+{
+ if (pimi->mi.pszName != NULL)
+ if (pimi->submenu.first && !mir_tstrcmp(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 (pmi->flags & CMIF_UNICODE)
+ tszRoot = mir_tstrdup((TCHAR*)pmi->root);
+ else
+ tszRoot = mir_a2t((char*)pmi->root);
+
+ 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 = *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 = { sizeof(mii) };
+ 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 DWORD GetMenuItemType(HMENU hMenu, int uItem)
+{
+ MENUITEMINFO mii = { sizeof(mii) };
+ mii.fMask = MIIM_TYPE;
+ GetMenuItemInfo(hMenu, uItem, TRUE, &mii);
+ return mii.fType;
+}
+
+static UINT GetMenuItemTypeData(HMENU hMenu, int uItem, PMO_IntMenuItem& p)
+{
+ MENUITEMINFO mii = { sizeof(mii) };
+ mii.fMask = MIIM_DATA | MIIM_TYPE;
+ GetMenuItemInfo(hMenu, uItem, TRUE, &mii);
+ p = MO_GetIntMenuItem((HGENMENU)mii.dwItemData);
+ return mii.fType;
+}
+
+static void InsertSeparator(HMENU hMenu, int uItem)
+{
+ MENUITEMINFO mii = { sizeof(mii) };
+ mii.fMask = MIIM_TYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(hMenu, uItem, TRUE, &mii);
+}
+
+static void InsertMenuItemWithSeparators(HMENU hMenu, int uItem, MENUITEMINFO *lpmii)
+{
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem((HGENMENU)lpmii->dwItemData), p;
+ if (pimi == NULL)
+ return;
+
+ // check for separator before
+ if (uItem) {
+ UINT fType = GetMenuItemTypeData(hMenu, uItem - 1, p);
+ if (p != NULL && fType != MFT_SEPARATOR) {
+ if ((p->mi.position / SEPARATORPOSITIONINTERVAL) != (pimi->mi.position / SEPARATORPOSITIONINTERVAL)) {
+ // but might be supposed to be after the next one instead
+ if (!(uItem < GetMenuItemCount(hMenu) && GetMenuItemType(hMenu, uItem) == MFT_SEPARATOR))
+ InsertSeparator(hMenu, uItem);
+ uItem++;
+ }
+ }
+ }
+
+ // check for separator after
+ if (uItem < GetMenuItemCount(hMenu)) {
+ UINT fType = GetMenuItemTypeData(hMenu, uItem, p);
+ if (p != NULL && fType != MFT_SEPARATOR)
+ if ((p->mi.position / SEPARATORPOSITIONINTERVAL) != (pimi->mi.position / SEPARATORPOSITIONINTERVAL))
+ InsertSeparator(hMenu, uItem);
+ }
+
+ // create local copy *lpmii so we can change some flags
+ MENUITEMINFO mii = *lpmii;
+
+ int count = GetMenuItemCount(hMenu);
+ if (count != 0 && (count % 33) == 0 && pimi->mi.root != NULL) {
+ if (!(mii.fMask & MIIM_FTYPE))
+ mii.fType = 0;
+ mii.fMask |= MIIM_FTYPE;
+ mii.fType |= MFT_MENUBARBREAK;
+ }
+
+ if (!pimi->CustomName)
+ mii.dwTypeData = GetMenuItemText(pimi);
+
+ InsertMenuItem(hMenu, uItem, TRUE, &mii);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// wparam started hMenu
+// lparam ListParam*
+// result hMenu
+
+INT_PTR MO_BuildMenu(WPARAM wParam, LPARAM lParam)
+{
+ if (!bIsGenMenuInited)
+ return -1;
+
+ mir_cslock lck(csMenuHook);
+
+ ListParam *lp = (ListParam*)lParam;
+ int pimoidx = GetMenuObjbyId((int)lp->MenuObjectHandle);
+ if (pimoidx == -1)
+ return 0;
+
+ #if defined(_DEBUG)
+ // DumpMenuItem(g_menus[pimoidx]->m_items.first);
+ #endif
+
+ return (INT_PTR)BuildRecursiveMenu((HMENU)wParam, g_menus[pimoidx]->m_items.first, (ListParam*)lParam);
+}
+
+#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)
+ mir_snprintf(pszDest, cbDestSize, "{%s}", (char*)_T2A(pMenuItem->mi.ptszName));
+ 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->pszName);
+
+ char menuItemName[256];
+ GetMenuItemName(pmi, menuItemName, sizeof(menuItemName));
+
+ // check if it visible
+ mir_snprintf(DBString, SIZEOF(DBString), "%s_visible", menuItemName);
+ if (db_get_b(NULL, MenuNameItems, DBString, -1) == -1)
+ db_set_b(NULL, MenuNameItems, DBString, 1);
+
+ pmi->OverrideShow = TRUE;
+ if (!db_get_b(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 (!db_get_ts(NULL, MenuNameItems, DBString, &dbv)) {
+ if (mir_tstrlen(dbv.ptszVal) > 0)
+ replaceStrT(pmi->CustomName, dbv.ptszVal);
+ db_free(&dbv);
+ }
+
+ mir_snprintf(DBString, SIZEOF(DBString), "%s_pos", menuItemName);
+ if ((pos = db_get_dw(NULL, MenuNameItems, DBString, -1)) == -1) {
+ db_set_dw(NULL, MenuNameItems, DBString, mi->position);
+ if (pmi->submenu.first)
+ mi->position = 0;
+ }
+ else mi->position = pos;
+ }
+
+ /**************************************/
+
+ if (rootlevel != (int)pmi->mi.root)
+ continue;
+
+ int i = WhereToPlace(hMenu, mi);
+
+ MENUITEMINFO mii = { sizeof(mii) };
+ mii.dwItemData = (LPARAM)pmi;
+ 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);
+
+ IcoLib_ReleaseIcon(newIcon, 0);
+ }
+
+ return FALSE;
+}
+
+int OnIconLibChanges(WPARAM, LPARAM)
+{
+ {
+ mir_cslock lck(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);
+ }
+
+ cli.pfnReloadProtoMenus();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int MO_RegisterIcon(PMO_IntMenuItem pmi, void*)
+{
+ TCHAR *uname = (pmi->UniqName) ? mir_a2t(pmi->UniqName) : mir_tstrdup(pmi->CustomName),
+ *descr = GetMenuItemText(pmi);
+
+ if (!uname && !descr)
+ return FALSE;
+
+ if (!pmi->hIcolibItem) {
+ HICON hIcon = ImageList_GetIcon(pmi->parent->m_hMenuIcons, pmi->iconId, 0);
+
+ TCHAR sectionName[256];
+ mir_sntprintf(sectionName, SIZEOF(sectionName), LPGENT("Menu icons") _T("/%s"), TranslateTS(pmi->parent->ptszDisplayName));
+
+ char iconame[256];
+ mir_snprintf(iconame, SIZEOF(iconame), "genmenu_%s_%s", pmi->parent->pszName, uname && *uname ? uname : descr);
+
+ // remove '&'
+ if (descr) {
+ descr = NEWTSTR_ALLOCA(descr);
+
+ for (TCHAR *p = descr; *p; p++) {
+ if ((p = _tcschr(p, '&')) == NULL)
+ break;
+
+ memmove(p, p + 1, sizeof(TCHAR)*(mir_tstrlen(p + 1) + 1));
+ if (*p == '\0')
+ p++;
+ }
+ }
+
+ SKINICONDESC sid = { 0 };
+ sid.flags = SIDF_TCHAR;
+ sid.section.t = sectionName;
+ sid.pszName = iconame;
+ sid.description.t = descr;
+ sid.hDefaultIcon = hIcon;
+ pmi->hIcolibItem = IcoLib_AddNewIcon(0, &sid);
+
+ Safe_DestroyIcon(hIcon);
+ if (hIcon = Skin_GetIcon(iconame)) {
+ ImageList_ReplaceIcon(pmi->parent->m_hMenuIcons, pmi->iconId, hIcon);
+ IcoLib_ReleaseIcon(hIcon, 0);
+ }
+ }
+
+ mir_free(uname);
+ 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(MCONTACT hContact)
+{
+ int iMenuID = GetMenuObjbyId((int)hContactMenuObject);
+ if (iMenuID != -1) {
+ NotifyEventHooks(hPreBuildContactMenuEvent, hContact, 0);
+
+ PMO_IntMenuItem pimi = (PMO_IntMenuItem)MO_GetDefaultMenuItem((WPARAM)g_menus[iMenuID]->m_items.first, 0);
+ if (pimi != NULL) {
+ MO_ProcessCommand(pimi, 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()
+{
+ 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_SRV_SETOPTIONSMENUOBJECT, SRVMO_SetOptionsMenuObject);
+ CreateServiceFunction(MO_SETOPTIONSMENUITEM, SRVMO_SetOptionsMenuItem);
+
+ bIconsDisabled = db_get_b(NULL, "CList", "DisableMenuIcons", 0) != 0;
+
+ bIsGenMenuInited = true;
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+ HookEvent(ME_OPT_INITIALISE, GenMenuOptInit);
+ return 0;
+}
+
+int UnitGenMenu()
+{
+ if (bIsGenMenuInited) {
+ mir_cslock lck(csMenuHook);
+ MO_RemoveAllObjects();
+ bIsGenMenuInited = false;
+ }
+ 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**)&ptszDisplayName);
+ FreeAndNil((void**)&pszName);
+
+ ImageList_Destroy(m_hMenuIcons);
+}
+
+void TIntMenuObject::freeItem(TMO_IntMenuItem *p)
+{
+ if (FreeService)
+ CallService(FreeService, (WPARAM)p, (LPARAM)p->mi.ownerdata);
+
+ p->signature = 0;
+ 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/mir_app/src/genmenu.h b/src/mir_app/src/genmenu.h new file mode 100644 index 0000000000..198d5417bc --- /dev/null +++ b/src/mir_app/src/genmenu.h @@ -0,0 +1,146 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#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;
+ int hLangpack;
+
+ struct _tagIntMenuItem *next; // next item in list
+ struct TIntMenuObject *parent;
+ TMO_LinkedList *owner;
+ TMO_LinkedList submenu;
+}
+ TMO_IntMenuItem, *PMO_IntMenuItem;
+
+struct TIntMenuObject : public MZeroedObject
+{
+ TIntMenuObject();
+ ~TIntMenuObject();
+
+ char *pszName;
+ TCHAR *ptszDisplayName;
+ int id;
+
+ //ExecService
+ //LPARAM lParam;//owner data
+ //WPARAM wParam;//allways lparam from winproc
+ LPCSTR ExecService;
+
+ //CheckService called when building menu
+ //return false to skip item.
+ //LPARAM lParam;//0
+ //WPARAM wParam;//CheckParam
+ LPCSTR CheckService;//analog to check_proc
+
+ //LPARAM lParam;//ownerdata
+ //WPARAM wParam;//menuitemhandle
+ LPCSTR FreeService;//callback service used to free ownerdata for menuitems
+
+ //LPARAM lParam;//MENUITEMINFO filled with all needed data
+ //WPARAM wParam;//menuitemhandle
+ LPCSTR onAddService;//called just before add MENUITEMINFO to hMenu
+
+ TMO_LinkedList m_items;
+ HIMAGELIST m_hMenuIcons;
+ BOOL m_bUseUserDefinedItems;
+
+ void freeItem(TMO_IntMenuItem*);
+};
+
+extern LIST<TIntMenuObject> 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
+
+LPTSTR GetMenuItemText(PMO_IntMenuItem);
+
+int GenMenuOptInit(WPARAM wParam, LPARAM);
+int GetMenuObjbyId(const int id);
+int GetMenuItembyId(const int objpos, const int id);
+
+int ProtocolOrderOptInit(WPARAM wParam, LPARAM);
+
+INT_PTR MO_GetMenuItem(WPARAM wParam, LPARAM lParam);
+void FreeAndNil(void **p);
+#endif
diff --git a/src/mir_app/src/genmenuopt.cpp b/src/mir_app/src/genmenuopt.cpp new file mode 100644 index 0000000000..8a1cbf497c --- /dev/null +++ b/src/mir_app/src/genmenuopt.cpp @@ -0,0 +1,481 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.h" +#include "genmenu.h" + +#define STR_SEPARATOR _T("-----------------------------------") + +extern bool bIconsDisabled; +extern int DefaultImageListColorDepth; +void RebuildProtoMenus(); + +///////////////////////////////////////////////////////////////////////////////////////// + +struct MenuItemOptData : public MZeroedObject +{ + ~MenuItemOptData() {} + + int pos; + + ptrT name; + ptrT defname; + ptrA uniqname; + + bool bShow; + int id; + + PMO_IntMenuItem pimi; +}; + +static int SortMenuItems(const MenuItemOptData *p1, const MenuItemOptData *p2) +{ + if (p1->pos < p2->pos) return -1; + if (p1->pos > p2->pos) return 1; + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +class CGenMenuOptionsPage : public CDlgBase +{ + int iInitMenuValue; + bool bRebuild; + + void SaveTree() + { + int MenuObjectId; + if (!GetCurrentMenuObjectID(MenuObjectId)) + return; + + TCHAR idstr[100]; + + TVITEMEX tvi; + tvi.hItem = m_menuItems.GetRoot(); + tvi.cchTextMax = SIZEOF(idstr); + tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE | TVIF_IMAGE; + tvi.pszText = idstr; + + int count = 0; + int menupos = GetMenuObjbyId(MenuObjectId); + if (menupos == -1) + return; + + TIntMenuObject *pimo = g_menus[menupos]; + + char MenuNameItems[256]; + mir_snprintf(MenuNameItems, SIZEOF(MenuNameItems), "%s_Items", pimo->pszName); + int runtimepos = 100; + + while (tvi.hItem != NULL) { + m_menuItems.GetItem(&tvi); + MenuItemOptData *iod = (MenuItemOptData*)tvi.lParam; + if (iod->pimi) { + char menuItemName[256], DBString[300]; + GetMenuItemName(iod->pimi, menuItemName, sizeof(menuItemName)); + + mir_snprintf(DBString, SIZEOF(DBString), "%s_visible", menuItemName); + db_set_b(NULL, MenuNameItems, DBString, tvi.iImage != 0); + + mir_snprintf(DBString, SIZEOF(DBString), "%s_pos", menuItemName); + db_set_dw(NULL, MenuNameItems, DBString, runtimepos); + + mir_snprintf(DBString, SIZEOF(DBString), "%s_name", menuItemName); + if (iod->name != NULL && iod->defname != NULL && + mir_tstrcmp(iod->name, iod->defname) != 0) + db_set_ts(NULL, MenuNameItems, DBString, iod->name); + else + db_unset(NULL, MenuNameItems, DBString); + + runtimepos += 100; + } + + if (iod->name && !mir_tstrcmp(iod->name, STR_SEPARATOR) && tvi.iImage) + runtimepos += SEPARATORPOSITIONINTERVAL; + + tvi.hItem = m_menuItems.GetNextSibling(tvi.hItem); + count++; + } + } + + void FreeTreeData() + { + HTREEITEM hItem = m_menuItems.GetRoot(); + while (hItem != NULL) { + TVITEMEX tvi; + tvi.mask = TVIF_HANDLE | TVIF_PARAM; + tvi.hItem = hItem; + m_menuItems.GetItem(&tvi); + delete (MenuItemOptData *)tvi.lParam; + + tvi.lParam = 0; + m_menuItems.SetItem(&tvi); + + hItem = m_menuItems.GetNextSibling(hItem); + } + } + + void RebuildCurrent() + { + int MenuObjectID; + if (GetCurrentMenuObjectID(MenuObjectID)) + BuildTree(MenuObjectID, true); + } + + bool BuildTree(int MenuObjectId, bool bReread) + { + FreeTreeData(); + + int menupos = GetMenuObjbyId(MenuObjectId); + if (menupos == -1) + return false; + + TIntMenuObject* pimo = g_menus[menupos]; + if (pimo->m_items.first == NULL) + return false; + + char menuItemName[256], MenuNameItems[256]; + mir_snprintf(MenuNameItems, SIZEOF(MenuNameItems), "%s_Items", pimo->pszName); + + LIST<MenuItemOptData> arItems(10, SortMenuItems); + + 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 = new MenuItemOptData(); + GetMenuItemName(p, menuItemName, sizeof(menuItemName)); + + char buf[256]; + mir_snprintf(buf, "%s_name", menuItemName); + ptrT tszName(db_get_tsa(NULL, MenuNameItems, buf)); + if (tszName != 0) + PD->name = tszName.detach(); + else + PD->name = mir_tstrdup(GetMenuItemText(p)); + + PD->pimi = p; + PD->defname = mir_tstrdup(GetMenuItemText(p)); + + mir_snprintf(buf, "%s_visible", menuItemName); + PD->bShow = db_get_b(NULL, MenuNameItems, buf, 1) != 0; + + if (bReread) { + mir_snprintf(buf, "%s_pos", menuItemName); + PD->pos = db_get_dw(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); + + arItems.insert(PD); + } + + bRebuild = true; + m_menuItems.SendMsg(WM_SETREDRAW, FALSE, 0); + m_menuItems.DeleteAllItems(); + + int lastpos = 0; + bool bIsFirst = 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 < arItems.getCount(); i++) { + MenuItemOptData *PD = arItems[i]; + if (PD->pos - lastpos >= SEPARATORPOSITIONINTERVAL) { + MenuItemOptData *sep = new MenuItemOptData(); + sep->id = -1; + sep->name = mir_tstrdup(STR_SEPARATOR); + sep->pos = PD->pos - 1; + + tvis.item.lParam = (LPARAM)sep; + tvis.item.pszText = sep->name; + tvis.item.iImage = tvis.item.iSelectedImage = 1; + m_menuItems.InsertItem(&tvis); + } + + tvis.item.lParam = (LPARAM)PD; + tvis.item.pszText = PD->name; + tvis.item.iImage = tvis.item.iSelectedImage = PD->bShow; + + HTREEITEM hti = m_menuItems.InsertItem(&tvis); + if (bIsFirst) { + m_menuItems.SelectItem(hti); + bIsFirst = false; + } + + lastpos = PD->pos; + } + + m_menuItems.SendMsg(WM_SETREDRAW, TRUE, 0); + bRebuild = false; + + ShowWindow(m_warning.GetHwnd(), (pimo->m_bUseUserDefinedItems) ? SW_HIDE : SW_SHOW); + m_menuItems.Enable(pimo->m_bUseUserDefinedItems); + m_btnInsert.Enable(pimo->m_bUseUserDefinedItems); + return 1; + } + + bool GetCurrentMenuObjectID(int &result) + { + int iItem = m_menuObjects.GetCurSel(); + if (iItem == -1) + return false; + + result = (int)m_menuObjects.GetItemData(iItem); + return true; + } + + CCtrlListBox m_menuObjects; + CCtrlTreeView m_menuItems; + CCtrlCheck m_radio1, m_radio2, m_enableIcons; + CCtrlEdit m_customName, m_service; + CCtrlButton m_btnInsert, m_btnReset, m_btnSet, m_btnDefault; + CCtrlBase m_warning; + +public: + CGenMenuOptionsPage() : + CDlgBase(g_hInst, IDD_OPT_GENMENU), + m_menuItems(this, IDC_MENUITEMS), + m_menuObjects(this, IDC_MENUOBJECTS), + m_radio1(this, IDC_RADIO1), + m_radio2(this, IDC_RADIO2), + m_enableIcons(this, IDC_DISABLEMENUICONS), + m_btnInsert(this, IDC_INSERTSEPARATOR), + m_btnReset(this, IDC_RESETMENU), + m_btnSet(this, IDC_GENMENU_SET), + m_btnDefault(this, IDC_GENMENU_DEFAULT), + m_customName(this, IDC_GENMENU_CUSTOMNAME), + m_service(this, IDC_GENMENU_SERVICE), + m_warning(this, IDC_NOTSUPPORTWARNING), + bRebuild(false) + { + m_btnSet.OnClick = Callback(this, &CGenMenuOptionsPage::btnSet_Clicked); + m_btnReset.OnClick = Callback(this, &CGenMenuOptionsPage::btnReset_Clicked); + m_btnInsert.OnClick = Callback(this, &CGenMenuOptionsPage::btnInsert_Clicked); + m_btnDefault.OnClick = Callback(this, &CGenMenuOptionsPage::btnDefault_Clicked); + + m_menuObjects.OnSelChange = Callback(this, &CGenMenuOptionsPage::onMenuObjectChanged); + + m_menuItems.SetFlags(MTREE_CHECKBOX | MTREE_DND | MTREE_MULTISELECT); + m_menuItems.OnSelChanged = Callback(this, &CGenMenuOptionsPage::onMenuItemChanged); + + m_customName.SetSilent(); + m_service.SetSilent(); + } + + //---- init dialog ------------------------------------------- + virtual void OnInitDialog() + { + iInitMenuValue = db_get_b(NULL, "CList", "MoveProtoMenus", TRUE); + + 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); + m_menuItems.SetImageList(himlCheckBoxes, TVSIL_NORMAL); + + if (iInitMenuValue) + m_radio2.SetState(true); + else + m_radio1.SetState(true); + + m_enableIcons.SetState(!bIconsDisabled); + + //---- init menu object list -------------------------------------- + for (int i = 0; i < g_menus.getCount(); i++) { + TIntMenuObject *p = g_menus[i]; + if (p->id != (int)hStatusMenuObject && p->m_bUseUserDefinedItems) + m_menuObjects.AddString(TranslateTS(p->ptszDisplayName), p->id); + } + + m_menuObjects.SetCurSel(0); + RebuildCurrent(); + } + + virtual void OnApply() + { + bIconsDisabled = m_enableIcons.GetState() == 0; + db_set_b(NULL, "CList", "DisableMenuIcons", bIconsDisabled); + SaveTree(); + + int iNewMenuValue = !m_radio1.GetState(); + if (iNewMenuValue != iInitMenuValue) { + db_set_b(NULL, "CList", "MoveProtoMenus", iNewMenuValue); + + RebuildProtoMenus(); + iInitMenuValue = iNewMenuValue; + } + RebuildCurrent(); + } + + virtual void OnDestroy() + { + ImageList_Destroy(m_menuItems.GetImageList(TVSIL_NORMAL)); + FreeTreeData(); + } + + void btnInsert_Clicked(CCtrlButton*) + { + HTREEITEM hti = m_menuItems.GetSelection(); + if (hti == NULL) + return; + + TVITEMEX tvi = { 0 }; + tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT; + tvi.hItem = hti; + if (!m_menuItems.GetItem(&tvi)) + return; + + MenuItemOptData *PD = new MenuItemOptData(); + PD->id = -1; + PD->name = mir_tstrdup(STR_SEPARATOR); + PD->pos = ((MenuItemOptData *)tvi.lParam)->pos - 1; + + TVINSERTSTRUCT tvis = { 0 }; + tvis.item.lParam = (LPARAM)PD; + tvis.item.pszText = PD->name; + tvis.item.iImage = tvis.item.iSelectedImage = 1; + tvis.hInsertAfter = hti; + tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; + m_menuItems.InsertItem(&tvis); + + NotifyChange(); + } + + void btnReset_Clicked(CCtrlButton*) + { + int MenuObjectID; + if (GetCurrentMenuObjectID(MenuObjectID)) { + BuildTree(MenuObjectID, false); + NotifyChange(); + } + } + + void btnDefault_Clicked(CCtrlButton*) + { + HTREEITEM hti = m_menuItems.GetSelection(); + if (hti == NULL) + return; + + TVITEMEX tvi; + tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; + tvi.hItem = hti; + m_menuItems.GetItem(&tvi); + + MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; + if (iod->name && _tcsstr(iod->name, STR_SEPARATOR)) + return; + + iod->name = mir_tstrdup(iod->defname); + + SaveTree(); + RebuildCurrent(); + NotifyChange(); + } + + void btnSet_Clicked(CCtrlButton*) + { + HTREEITEM hti = m_menuItems.GetSelection(); + if (hti == NULL) + return; + + TVITEMEX tvi; + tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; + tvi.hItem = hti; + m_menuItems.GetItem(&tvi); + + MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; + if (iod->name && _tcsstr(iod->name, STR_SEPARATOR)) + return; + + iod->name = m_customName.GetText(); + + SaveTree(); + RebuildCurrent(); + NotifyChange(); + } + + void onMenuObjectChanged(void*) + { + m_initialized = false; + RebuildCurrent(); + m_initialized = true; + } + + void onMenuItemChanged(void*) + { + if (bRebuild) + return; + + m_customName.SetTextA(""); + m_service.SetTextA(""); + + m_btnDefault.Enable(false); + m_btnSet.Enable(false); + m_customName.Enable(false); + + HTREEITEM hti = m_menuItems.GetSelection(); + if (hti == NULL) + return; + + TVITEMEX tvi; + tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; + tvi.hItem = hti; + m_menuItems.GetItem(&tvi); + if (tvi.lParam == 0) + return; + + MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; + if (iod->name && _tcsstr(iod->name, STR_SEPARATOR)) + return; + + m_customName.SetText(iod->name); + + if (iod->pimi->submenu.first == NULL && iod->uniqname) + m_service.SetTextA(iod->uniqname); + + m_btnDefault.Enable(mir_tstrcmp(iod->name, iod->defname) != 0); + m_btnSet.Enable(true); + m_customName.Enable(true); + } +}; + +int GenMenuOptInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.position = -1000000000; + odp.pszTitle = LPGEN("Menus"); + odp.pszGroup = LPGEN("Customize"); + odp.flags = ODPF_BOLDGROUPS; + odp.pDialog = new CGenMenuOptionsPage(); + Options_AddPage(wParam, &odp); + + return ProtocolOrderOptInit(wParam, 0); +} diff --git a/src/mir_app/src/groups.cpp b/src/mir_app/src/groups.cpp new file mode 100644 index 0000000000..8ad218df03 --- /dev/null +++ b/src/mir_app/src/groups.cpp @@ -0,0 +1,587 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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)
+{
+ for (int i=0;; i++) {
+ char str[33];
+ _itoa(i, str, 10);
+ ptrT grpName( db_get_tsa(NULL, "CListGroups", str));
+ if (grpName == NULL)
+ return i;
+ }
+}
+
+static int GroupNameExists(const TCHAR *name, int skipGroup)
+{
+ for (int i=0;; i++) {
+ if (i == skipGroup)
+ continue;
+
+ char idstr[33];
+ _itoa(i, idstr, 10);
+ ptrT grpName( db_get_tsa(NULL, "CListGroups", idstr));
+ if (grpName == NULL)
+ break;
+
+ if (!mir_tstrcmp((TCHAR*)grpName+1, name))
+ return i+1;
+ }
+ return 0;
+}
+
+static INT_PTR GroupExists(WPARAM, LPARAM lParam)
+{
+ if (lParam == 0)
+ return FALSE;
+
+ return GroupNameExists((LPCTSTR)lParam, -1);
+}
+
+static INT_PTR CreateGroupInternal(INT_PTR iParent, const TCHAR *ptszName)
+{
+ int newId = CountGroups();
+ TCHAR newBaseName[127], newName[128];
+ char str[33];
+ int i;
+
+ const TCHAR* grpName = ptszName ? ptszName : TranslateT("New group");
+ if (iParent) {
+ _itoa(iParent - 1, str, 10);
+ DBVARIANT dbv;
+ if (db_get_ts(NULL, "CListGroups", str, &dbv))
+ return 0;
+
+ mir_sntprintf(newBaseName, SIZEOF(newBaseName), _T("%s\\%s"), dbv.ptszVal + 1, grpName);
+ mir_free(dbv.pszVal);
+ }
+ else mir_tstrncpy(newBaseName, grpName, SIZEOF(newBaseName));
+
+ _itoa(newId, str, 10);
+ mir_tstrncpy(newName + 1, newBaseName, SIZEOF(newName) - 1);
+ if (ptszName) {
+ 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'
+ db_set_ts(NULL, "CListGroups", str, newName);
+ CallService(MS_CLUI_GROUPADDED, newId + 1, 1);
+
+ NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg);
+ }
+
+ return newId + 1;
+}
+
+static INT_PTR CreateGroup(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam == 0)
+ return CreateGroupInternal(wParam, NULL);
+
+ LPCTSTR ptszName = (LPCTSTR)lParam;
+ if (ptszName == NULL || ptszName[0] == '\0' || ptszName[0] == '\\')
+ return 0;
+
+ TCHAR *tszName = NEWTSTR_ALLOCA(ptszName);
+ for (TCHAR *p = tszName; *p; p++) {
+ if (*p == '\\') {
+ *p = '\0';
+ CreateGroupInternal(wParam, tszName);
+ *p = '\\';
+ }
+ }
+ return CreateGroupInternal(wParam, tszName);
+}
+
+static INT_PTR GetGroupName2(WPARAM wParam, LPARAM lParam)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ static char name[128];
+
+ _itoa(wParam - 1, idstr, 10);
+ if (db_get_s(NULL, "CListGroups", idstr, &dbv))
+ return (INT_PTR) (char *) NULL;
+ mir_strncpy(name, dbv.pszVal + 1, SIZEOF(name));
+ if ((DWORD *) lParam != NULL)
+ *(DWORD *) lParam = dbv.pszVal[0];
+ db_free(&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 (db_get_ts(NULL, "CListGroups", idstr, &dbv))
+ return NULL;
+
+ mir_tstrncpy(name, dbv.ptszVal + 1, SIZEOF(name));
+ if (pdwFlags != NULL)
+ *pdwFlags = dbv.ptszVal[0];
+ db_free(&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;
+ MCONTACT hContact;
+ TCHAR name[256], szNewParent[256], *pszLastBackslash;
+
+ //get the name
+ _itoa(wParam - 1, str, 10);
+ if (db_get_ts(NULL, "CListGroups", str, &dbv))
+ return 1;
+ mir_tstrncpy(name, dbv.ptszVal + 1, SIZEOF(name));
+ db_free(&dbv);
+ if (db_get_b(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 cannot 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.
+ mir_tstrcpy(szNewParent, name);
+ pszLastBackslash = _tcsrchr(szNewParent, '\\');
+ if (pszLastBackslash)
+ pszLastBackslash[0] = '\0';
+ else
+ szNewParent[0] = '\0';
+
+ CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, NULL };
+
+ for (hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ if (db_get_ts(hContact, "CList", "Group", &dbv))
+ continue;
+
+ if (mir_tstrcmp(dbv.ptszVal, name))
+ {
+ db_free(&dbv);
+ continue;
+ }
+ db_free(&dbv);
+
+ if (szNewParent[0])
+ {
+ db_set_ts(hContact, "CList", "Group", szNewParent);
+ grpChg.pszNewName = szNewParent;
+ }
+ else
+ {
+ db_unset(hContact, "CList", "Group");
+ grpChg.pszNewName = NULL;
+ }
+ NotifyEventHooks(hGroupChangeEvent, hContact, (LPARAM)&grpChg);
+ }
+ //shuffle list of groups up to fill gap
+ for (i = wParam - 1;; i++) {
+ _itoa(i + 1, str, 10);
+ if (db_get_utf(NULL, "CListGroups", str, &dbv))
+ break;
+ _itoa(i, str, 10);
+ db_set_utf(NULL, "CListGroups", str, dbv.pszVal);
+ db_free(&dbv);
+ }
+ _itoa(i, str, 10);
+ db_unset(NULL, "CListGroups", str);
+ //rename subgroups
+ {
+ TCHAR szNewName[256];
+ size_t len = mir_tstrlen(name);
+ for (i=0;; i++) {
+ _itoa(i, str, 10);
+ if (db_get_ts(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
+ mir_tstrncpy(szNewName, dbv.ptszVal + len + 2, SIZEOF(szNewName));
+ cli.pfnRenameGroup(i + 1, szNewName);
+ }
+ db_free(&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;
+
+ 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_ICONERROR | MB_OK);
+ return 1;
+ }
+
+ //do the change
+ _itoa(groupId, idstr, 10);
+ if (db_get_ts(NULL, "CListGroups", idstr, &dbv))
+ return 1;
+ str[0] = dbv.pszVal[0] & 0x7F;
+ mir_tstrncpy(oldName, dbv.ptszVal + 1, SIZEOF(oldName));
+ db_free(&dbv);
+ mir_tstrncpy(str + 1, szName, SIZEOF(str) - 1);
+ db_set_ts(NULL, "CListGroups", idstr, str);
+
+ //must rename setting in all child contacts too
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ ClcCacheEntry *cache = cli.pfnGetCacheEntry(hContact);
+ if (!mir_tstrcmp(cache->tszGroup, oldName)) {
+ db_set_ts(hContact, "CList", "Group", szName);
+ mir_free(cache->tszGroup);
+ cache->tszGroup = 0;
+ cli.pfnCheckCacheItem(cache);
+ }
+ }
+
+ //rename subgroups
+ {
+ TCHAR szNewName[256];
+ size_t len = mir_tstrlen(oldName);
+ for (int i=0;; i++) {
+ if (i == groupId)
+ continue;
+ _itoa(i, idstr, 10);
+ if (db_get_ts(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
+ }
+ db_free(&dbv);
+ }
+ }
+
+ //finally must make sure it's after any parent items
+ if (move) {
+ TCHAR *pszLastBackslash;
+ int i;
+
+ mir_tstrncpy(str, szName, SIZEOF(str));
+ pszLastBackslash = _tcsrchr(str, '\\');
+ if (pszLastBackslash != NULL) {
+ *pszLastBackslash = '\0';
+ for (i=0;; i++) {
+ _itoa(i, idstr, 10);
+ if (db_get_ts(NULL, "CListGroups", idstr, &dbv))
+ break;
+ if (!mir_tstrcmp(dbv.ptszVal + 1, str)) {
+ if (i < groupId)
+ break; //is OK
+ MoveGroupBefore(groupId + 1, i + 2);
+ break;
+ }
+ db_free(&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)
+{
+ WCHAR* temp = mir_a2u((char*)lParam);
+ int result = (-1 != RenameGroupWithMove(wParam - 1, temp, 1));
+ mir_free(temp);
+ return result;
+}
+
+static INT_PTR SetGroupExpandedState(WPARAM wParam, LPARAM lParam)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+
+ _itoa(wParam - 1, idstr, 10);
+ if (db_get_utf(NULL, "CListGroups", idstr, &dbv))
+ return 1;
+ if (lParam)
+ dbv.pszVal[0] |= GROUPF_EXPANDED;
+ else
+ dbv.pszVal[0] = dbv.pszVal[0] & ~GROUPF_EXPANDED;
+ db_set_utf(NULL, "CListGroups", idstr, dbv.pszVal);
+ db_free(&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 (db_get_utf(NULL, "CListGroups", idstr, &dbv))
+ return 1;
+ flags = LOWORD(lParam) & HIWORD(lParam);
+ oldval = dbv.pszVal[0];
+ newval = dbv.pszVal[0] = ((oldval & ~HIWORD(lParam)) | flags) & 0x7f;
+ db_set_utf(NULL, "CListGroups", idstr, dbv.pszVal);
+ db_free(&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 (db_get_ts(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 (db_get_utf(NULL, "CListGroups", str, &dbv)) {
+ shuffleTo = i;
+ break;
+ }
+ _itoa(i, str, 10);
+ db_set_utf(NULL, "CListGroups", str, dbv.pszVal);
+ db_free(&dbv);
+ }
+ }
+ else {
+ for (i = shuffleFrom; i != shuffleTo; i--) {
+ _itoa(i - 1, str, 10);
+ if (db_get_utf(NULL, "CListGroups", str, &dbv)) {
+ mir_free(szMoveName);
+ return 1;
+ } //never happens
+ _itoa(i, str, 10);
+ db_set_utf(NULL, "CListGroups", str, dbv.pszVal);
+ db_free(&dbv);
+ }
+ }
+ _itoa(shuffleTo, str, 10);
+ db_set_ts(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;
+
+ if (db_get_utf(NULL, "CListGroups", "0", &dbv))
+ return (INT_PTR) (HMENU) NULL;
+ db_free(&dbv);
+ hRootMenu = CreateMenu();
+ for (groupId = 0;; groupId++) {
+ _itoa(groupId, idstr, 10);
+ if (db_get_ts(NULL, "CListGroups", idstr, &dbv))
+ break;
+
+ pNextField = dbv.ptszVal + 1;
+ hThisMenu = hRootMenu;
+
+ MENUITEMINFO mii = { sizeof(mii) };
+ do {
+ pBackslash = _tcschr(pNextField, '\\');
+ if (pBackslash == NULL) {
+ mir_tstrncpy(szThisField, pNextField, SIZEOF(szThisField));
+ pNextField = NULL;
+ }
+ else {
+ mir_tstrncpy(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 = mir_tstrcmp(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);
+
+ db_free(&dbv);
+ }
+ return (INT_PTR) hRootMenu;
+}
+
+int InitGroupServices(void)
+{
+ for (int i=0;; i++)
+ {
+ char str[32];
+ _itoa(i, str, 10);
+
+ DBVARIANT dbv;
+ if (db_get_utf(NULL, "CListGroups", str, &dbv))
+ break;
+ if (dbv.pszVal[0] & 0x80)
+ {
+ dbv.pszVal[0] &= 0x7f;
+ db_set_utf(NULL, "CListGroups", str, dbv.pszVal);
+ }
+ db_free(&dbv);
+ }
+
+ CreateServiceFunction(MS_CLIST_GROUPEXISTS, GroupExists);
+ 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/mir_app/src/headerbar.cpp b/src/mir_app/src/headerbar.cpp new file mode 100644 index 0000000000..b321a7853b --- /dev/null +++ b/src/mir_app/src/headerbar.cpp @@ -0,0 +1,349 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+Copyright (c) 2007 Artem Shpynov
+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 "stdafx.h"
+#include "m_iconheader.h"
+
+extern HINSTANCE hInst;
+
+static BOOL IsAeroMode()
+{
+ BOOL result;
+ return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result;
+}
+
+static BOOL IsVSMode()
+{
+ return 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 : public MZeroedObject
+{
+ 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 = { 0 };
+ wc.cbSize = sizeof(wc);
+ wc.lpszClassName = _T("MHeaderbarCtrl");
+ wc.lpfnWndProc = MHeaderbarWndProc;
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.cbWndExtra = sizeof(MHeaderbarCtrl*);
+ wc.style = CS_GLOBALCLASS|CS_SAVEBITS;
+ RegisterClassEx(&wc);
+ return 0;
+}
+
+static void MHeaderbar_SetupColors(MHeaderbarCtrl *dat)
+{
+ COLORREF cl = GetSysColor(COLOR_WINDOW);
+ dat->rgbBkgBottom.rgbRed = (dat->rgbBkgTop.rgbRed = GetRValue(cl)) * .95;
+ dat->rgbBkgBottom.rgbBlue = (dat->rgbBkgTop.rgbBlue = GetBValue(cl)) * .95;
+ dat->rgbBkgBottom.rgbGreen = (dat->rgbBkgTop.rgbGreen = GetGValue(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 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 MHeaderbar_OnPaint(HWND hwndDlg, MHeaderbarCtrl *mit)
+{
+ 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 = { 0, 0, mit->width, 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_NOSYSMENU | 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), hOldFont;
+
+ 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;
+ hOldFont = (HFONT)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;
+ hOldFont = (HFONT)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);
+ SelectObject(tempDC, hOldFont);
+ 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));
+
+ 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);
+ break;
+
+ case WM_DESTROY:
+ delete itc;
+ break;
+ }
+ return DefWindowProc(hwndDlg, msg, wParam, lParam);
+}
diff --git a/src/mir_app/src/hotkey_opts.cpp b/src/mir_app/src/hotkey_opts.cpp new file mode 100644 index 0000000000..8224fd7ee0 --- /dev/null +++ b/src/mir_app/src/hotkey_opts.cpp @@ -0,0 +1,1048 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.h" + +#include <m_hotkeys.h> +#include "skin.h" + +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_BROWSER_BACK: + return TranslateT("Browser: Back"); + case VK_BROWSER_FORWARD: + return TranslateT("Browser: Forward"); + case VK_BROWSER_REFRESH: + return TranslateT("Browser: Refresh"); + case VK_BROWSER_STOP: + return TranslateT("Browser: Stop"); + case VK_BROWSER_SEARCH: + return TranslateT("Browser: Search"); + case VK_BROWSER_FAVORITES: + return TranslateT("Browser: Fav"); + case VK_BROWSER_HOME: + return TranslateT("Browser: Home"); + case VK_VOLUME_MUTE: + return TranslateT("Mute"); + case VK_VOLUME_DOWN: + return TranslateT("Vol-"); + case VK_VOLUME_UP: + return TranslateT("Vol+"); + case VK_MEDIA_NEXT_TRACK: + return TranslateT("Media: Next Track"); + case VK_MEDIA_PREV_TRACK: + return TranslateT("Media: Prev. Track"); + case VK_MEDIA_STOP: + return TranslateT("Media: Stop"); + case VK_MEDIA_PLAY_PAUSE: + return TranslateT("Media: Play/Pause"); + case VK_LAUNCH_MAIL: + return TranslateT("Mail"); + case VK_LAUNCH_MEDIA_SELECT: + return TranslateT("Media: Select"); + case VK_LAUNCH_APP1: + return TranslateT("App 1"); + case VK_LAUNCH_APP2: + return TranslateT("App 2"); + + 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) ? TranslateT("Ctrl + ") : _T(""), + (shift & HOTKEYF_ALT) ? TranslateT("Alt + ") : _T(""), + (shift & HOTKEYF_SHIFT) ? TranslateT("Shift + ") : _T(""), + (shift & HOTKEYF_EXT) ? TranslateT("Win + ") : _T(""), + sttHokeyVkToName(key)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Hotkey control + +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: + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + mir_free(data); + } + + return mir_callNextSubclass(hwnd, sttHotkeyEditProc, msg, wParam, lParam); +} + +void HotkeyEditCreate(HWND hwnd) +{ + THotkeyBoxData *data = (THotkeyBoxData *)mir_alloc(sizeof(THotkeyBoxData)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (ULONG_PTR)data); + mir_subclassWindow(hwnd, sttHotkeyEditProc); +} + +void HotkeyEditDestroy(HWND hwnd) +{ + THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + 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->getDescr(); + 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 mir_tstrcmp(title1, title2); + + if (!item1 && item2) { + if (res = mir_tstrcmp(title1, item2->getSection())) + return res; + return -1; + } + + if (!item2 && item1) { + if (res = mir_tstrcmp(item1->getSection(), title2)) + return res; + return 1; + } + /* item1 != NULL && item2 != NULL */ + + if (res = mir_tstrcmp(item1->getSection(), item2->getSection())) return res; + if (res = mir_tstrcmp(item1->getDescr(), item2->getDescr())) return res; + if (!item1->rootHotkey && item2->rootHotkey) return -1; + if (item1->rootHotkey && !item2->rootHotkey) return 1; + return 0; +} + +static void sttOptionsAddHotkey(HWND hwndList, THotkeyItem *item) +{ + char buf[256]; + mir_snprintf(buf, "mir_hotkey_%d_%d", g_pid, g_hkid++); + + 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->lParam = item->lParam; + 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); + + LVITEM lvi = { 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; + + db_set_w(NULL, DBMODULENAME, item->pszName, item->Hotkey); + db_set_b(NULL, DBMODULENAME "Off", item->pszName, (BYTE)!item->Enabled); + if (item->type != HKT_MANUAL) + db_set_b(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, "%s$%d", item->pszName, item->nSubHotkeys); + db_set_w(NULL, DBMODULENAME, buf, subItem->Hotkey); + if (subItem->type != HKT_MANUAL) + db_set_b(NULL, DBMODULENAME "Types", buf, (BYTE)subItem->type); + + ++item->nSubHotkeys; + } + } + + mir_snprintf(buf, "%s$count", item->pszName); + db_set_dw(NULL, DBMODULENAME, buf, item->nSubHotkeys); +} + +static void sttBuildHotkeyList(HWND hwndList) +{ + 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 (!i || mir_tstrcmp(item->ptszSection, hotkeys[i - 1]->ptszSection)) { + lvi.mask = LVIF_TEXT | LVIF_PARAM; + lvi.iItem = nItems++; + lvi.iSubItem = 0; + lvi.lParam = 0; + lvi.pszText = item->getSection(); + 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 | LVIF_INDENT; + lvi.iIndent = 1; + lvi.iItem = nItems++; + lvi.lParam = (LPARAM)item; + ListView_InsertItem(hwndList, &lvi); + sttOptionsSetupItem(hwndList, nItems - 1, item); + } + + ListView_SortItemsEx(hwndList, sttOptionsSortList, (LPARAM)hwndList); +} + +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) +{ + DrawText(hdc, text, -1, rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS); + + SIZE sz; + GetTextExtentPoint32(hdc, text, (int)mir_tstrlen(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: + initialized = FALSE; + + TranslateDialogDefault(hwndDlg); + + HotkeyEditCreate(GetDlgItem(hwndDlg, IDC_HOTKEY)); + { + HIMAGELIST hIml = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 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); + 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); + { + RECT rc; + GetClientRect(hwndHotkey, &rc); + colWidth = rc.right - GetSystemMetrics(SM_CXHTHUMB) - 3 * GetSystemMetrics(SM_CXSMICON) - 5; + + LVCOLUMN lvc; + 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 (int 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); + } + 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, + db_get_b(NULL, DBMODULENAME "UI", szSetting, TRUE)); + + mir_free(szSetting); + } + } + + g_hwndHkOptions = hwndDlg; + break; + + case WM_TIMER: + if (initialized) { + WORD newLanguage = LOWORD(GetKeyboardLayout(0)); + if (newLanguage == currentLanguage) + break; + + int count = ListView_GetItemCount(hwndHotkey); + + LVITEM lvi = { 0 }; + lvi.mask = LVIF_PARAM; + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + ListView_GetItem(hwndHotkey, &lvi); + if (lvi.lParam) + sttOptionsSetupItem(hwndHotkey, lvi.iItem, (THotkeyItem *)lvi.lParam); + } + currentLanguage = newLanguage; + } + break; + + case WM_HOTKEYUNREGISTERED: + { + int count = ListView_GetItemCount(hwndHotkey); + + LVITEM lvi = { 0 }; + 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; + 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; + 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 = prefix; + 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 = { GET_X_LPARAM(lParam), GET_Y_LPARAM(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; + + UnregisterHotkeys(); + + 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) { + FreeHotkey(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); + } + + RegisterHotkeys(); + + 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 { + 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 (!mir_tstrcmp(item->getSection(), 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 (mir_tstrcmp(buf, item->getSection())) + 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_DESTROY: + { + int count = ListView_GetItemCount(hwndHotkey); + + g_hwndHkOptions = NULL; + + KillTimer(hwndDlg, 1024); + + TCHAR buf[128]; + LVITEM lvi = { 0 }; + lvi.pszText = buf; + lvi.cchTextMax = SIZEOF(buf); + for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { + 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); + + db_set_b(NULL, DBMODULENAME "UI", _T2A(lvi.pszText), ListView_GetCheckState(hwndHotkey, lvi.iItem)); + } + } + } + + return FALSE; +} + +int HotkeyOptionsInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.hInstance = g_hInst; + odp.flags = ODPF_BOLDGROUPS; + odp.position = -180000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_HOTKEYS); + odp.pszTitle = LPGEN("Hotkeys"); + odp.pszGroup = LPGEN("Customize"); + odp.pfnDlgProc = sttOptionsDlgProc; + Options_AddPage(wParam, &odp); + return 0; +} diff --git a/src/mir_app/src/hotkeys.cpp b/src/mir_app/src/hotkeys.cpp new file mode 100644 index 0000000000..edc92b10dd --- /dev/null +++ b/src/mir_app/src/hotkeys.cpp @@ -0,0 +1,412 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include <m_hotkeys.h>
+#include "skin.h"
+
+static int sttCompareHotkeys(const THotkeyItem *p1, const THotkeyItem *p2)
+{
+ int res;
+ if (res = mir_tstrcmp(p1->ptszSection, p2->ptszSection))
+ return res;
+ if (res = mir_tstrcmp(p1->ptszDescription, p2->ptszDescription))
+ return res;
+ if (!p1->rootHotkey && p2->rootHotkey)
+ return -1;
+ if (p1->rootHotkey && !p2->rootHotkey)
+ return 1;
+ return 0;
+}
+
+LIST<THotkeyItem> hotkeys(10, sttCompareHotkeys);
+DWORD g_pid = 0, g_hkid = 1;
+HWND g_hwndHotkeyHost = NULL, g_hwndHkOptions = NULL;
+HANDLE hEvChanged = 0;
+
+static BOOL bModuleInitialized = FALSE;
+static HHOOK hhkKeyboard = NULL;
+
+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 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) {
+ for (int i = 0; i < hotkeys.getCount(); i++) {
+ THotkeyItem *item = hotkeys[i];
+ if (item->type != HKT_GLOBAL || !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)) {
+ 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 (int i = 0; i < hotkeys.getCount(); i++) {
+ THotkeyItem *item = hotkeys[i];
+ if (item->type != HKT_LOCAL || !item->Enabled)
+ continue;
+
+ BYTE hkMod, hkVk;
+ sttWordToModAndVk(item->Hotkey, &hkMod, &hkVk);
+ if (!hkVk) 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)
+{
+ HotkeyEditCreate((HWND)wParam);
+ return 0;
+}
+
+static INT_PTR svcHotkeyUnsubclass(WPARAM wParam, LPARAM)
+{
+ HotkeyEditDestroy((HWND)wParam);
+ return 0;
+}
+
+static INT_PTR svcHotkeyRegister(WPARAM wParam, LPARAM lParam)
+{
+ HOTKEYDESC *desc = (HOTKEYDESC *)lParam;
+ if (desc->cbSize != sizeof(HOTKEYDESC))
+ return 0;
+
+ THotkeyItem *item = (THotkeyItem*)mir_alloc(sizeof(THotkeyItem));
+ 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);
+ }
+
+ item->hLangpack = (int)wParam;
+ 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 = !db_get_b(NULL, DBMODULENAME "Off", item->pszName, 0);
+ }
+
+ item->pszService = desc->pszService ? mir_strdup(desc->pszService) : 0;
+ item->DefHotkey = desc->DefHotKey & ~HKF_MIRANDA_LOCAL;
+ item->Hotkey = db_get_w(NULL, DBMODULENAME, item->pszName, item->DefHotkey);
+ item->type = item->pszService ?
+ (THotkeyType)db_get_b(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, "mir_hotkey_%d_%d", g_pid, g_hkid++);
+ 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, "%s$count", item->pszName);
+ count = (int)db_get_dw(NULL, DBMODULENAME, buf, -1);
+ for (i = 0; i < count; i++) {
+ mir_snprintf(buf, "%s$%d", item->pszName, i);
+ if (!db_get_w(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 = mir_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 =
+ !mir_strcmp(pszCurrentName, pszName) ||
+ !strncmp(pszCurrentName, pszNamePrefix, cbNamePrefix);
+ }
+
+ if (g_hwndHkOptions)
+ SendMessage(g_hwndHkOptions, WM_HOTKEYUNREGISTERED, 0, 0);
+
+ for (i = 0; i < hotkeys.getCount(); i++)
+ if (hotkeys[i]->UnregisterHotkey) {
+ FreeHotkey(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) || mir_tstrcmp(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;
+}
+
+void FreeHotkey(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);
+}
+
+void RegisterHotkeys()
+{
+ for (int 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);
+ }
+ }
+}
+
+void KillModuleHotkeys(int hLangpack)
+{
+ for (int i = hotkeys.getCount() - 1; i >= 0; i--) {
+ THotkeyItem *item = hotkeys[i];
+ if (item->hLangpack == hLangpack) {
+ FreeHotkey(item);
+ hotkeys.remove(i);
+ }
+ }
+}
+
+void UnregisterHotkeys()
+{
+ for (int i = 0; i < hotkeys.getCount(); i++) {
+ THotkeyItem *item = hotkeys[i];
+ if (item->type == HKT_GLOBAL && item->Enabled)
+ UnregisterHotKey(g_hwndHotkeyHost, item->idHotkey);
+ }
+}
+
+static int sttModulesLoaded(WPARAM, LPARAM)
+{
+ HookEvent(ME_OPT_INITIALISE, HotkeyOptionsInit);
+ 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 = g_hInst;
+ 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, g_hInst, 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, hMainThreadId);
+
+ hEvChanged = CreateHookableEvent(ME_HOTKEYS_CHANGED);
+
+ CreateServiceFunction("CoreHotkeys/Register", svcHotkeyRegister);
+ CreateServiceFunction(MS_HOTKEY_UNREGISTER, svcHotkeyUnregister);
+ CreateServiceFunction(MS_HOTKEY_SUBCLASS, svcHotkeySubclass);
+ CreateServiceFunction(MS_HOTKEY_UNSUBCLASS, svcHotkeyUnsubclass);
+ CreateServiceFunction(MS_HOTKEY_CHECK, svcHotkeyCheck);
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, sttModulesLoaded);
+
+ for (int i = 0; i < SIZEOF(oldSettings); i++) {
+ char szSetting[100];
+ mir_snprintf(szSetting, "HK%s", oldSettings[i]);
+
+ WORD key;
+ if ((key = db_get_w(NULL, "Clist", szSetting, 0))) {
+ db_unset(NULL, "Clist", szSetting);
+ db_set_w(NULL, DBMODULENAME, newSettings[i], key);
+ }
+
+ mir_snprintf(szSetting, "HKEn%s", oldSettings[i]);
+ if ((key = db_get_b(NULL, "Clist", szSetting, 0))) {
+ db_unset(NULL, "Clist", szSetting);
+ db_set_b(NULL, DBMODULENAME "Off", newSettings[i], (BYTE)(key == 0));
+ }
+ }
+
+ return 0;
+}
+
+void UnloadSkinHotkeys(void)
+{
+ if (!bModuleInitialized)
+ return;
+
+ DestroyHookableEvent(hEvChanged);
+ UnhookWindowsHookEx(hhkKeyboard);
+
+ for (int i = 0; i < hotkeys.getCount(); i++)
+ FreeHotkey(hotkeys[i]);
+
+ DestroyWindow(g_hwndHotkeyHost);
+}
diff --git a/src/mir_app/src/hyperlink.cpp b/src/mir_app/src/hyperlink.cpp new file mode 100644 index 0000000000..bb62808f44 --- /dev/null +++ b/src/mir_app/src/hyperlink.cpp @@ -0,0 +1,272 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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, (int)mir_tstrlen((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))) {
+ 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 = g_hInst;
+ wcl.hCursor = NULL;
+ 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/mir_app/src/icolib.cpp b/src/mir_app/src/icolib.cpp new file mode 100644 index 0000000000..06d8b132b5 --- /dev/null +++ b/src/mir_app/src/icolib.cpp @@ -0,0 +1,902 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include "IcoLib.h"
+
+static BOOL bModuleInitialized = FALSE;
+HANDLE hIcons2ChangedEvent, hIconsChangedEvent;
+
+HICON hIconBlank = NULL;
+
+static HANDLE
+ hIcoLib_AddNewIcon, hIcoLib_RemoveIcon, hIcoLib_GetIcon, hIcoLib_GetIcon2,
+ hIcoLib_GetIconHandle, hIcoLib_IsManaged, hIcoLib_AddRef, hIcoLib_ReleaseIcon;
+
+int iconEventActive = 0;
+
+BOOL bNeedRebuild = FALSE;
+
+mir_cs csIconList;
+
+static int sttCompareSections(const SectionItem* p1, const SectionItem* p2)
+{
+ return mir_tstrcmp(p1->name, p2->name);
+}
+
+LIST<SectionItem> sectionList(20, sttCompareSections);
+
+static int sttCompareIconSourceFiles(const IconSourceFile* p1, const IconSourceFile* p2)
+{
+ return mir_tstrcmpi(p1->file, p2->file);
+}
+
+static LIST<IconSourceFile> 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<IconSourceItem> iconSourceList(20, sttCompareIconSourceItems);
+
+static int sttCompareIcons(const IcolibItem* p1, const IcolibItem* p2)
+{
+ return mir_strcmp(p1->name, p2->name);
+}
+
+LIST<IcolibItem> iconList(20, sttCompareIcons);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Utility functions
+
+void __fastcall SAFE_FREE(void** p)
+{
+ if (*p) {
+ mir_free(*p);
+ *p = NULL;
+ }
+}
+
+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); /// TODO: convert path to long - eliminate duplicate items
+ else
+ _tcsncpy_s(fileFull, file, _TRUNCATE);
+
+ 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
+
+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;
+}
+
+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)
+{
+ if (!path)
+ return NULL;
+
+ TCHAR file[ MAX_PATH ];
+ mir_tstrncpy(file, path, SIZEOF(file));
+ TCHAR *comma = _tcsrchr(file, ',');
+
+ int n;
+ 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;
+}
+
+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 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;
+ }
+}
+
+IcolibItem* IcoLib_FindIcon(const char* pszIconName)
+{
+ int indx = iconList.getIndex((IcolibItem*)&pszIconName);
+ return (indx != -1) ? iconList[ indx ] : 0;
+}
+
+IcolibItem* IcoLib_FindHIcon(HICON hIcon, bool &big)
+{
+ if (hIcon == NULL)
+ return NULL;
+
+ for (int i = 0; i < iconList.getCount(); i++) {
+ IcolibItem *p = iconList[i];
+ if ((void*)p == hIcon) {
+ big = (p->source_small == NULL);
+ return p;
+ }
+ if (p->source_small && p->source_small->icon == hIcon) {
+ big = false;
+ return p;
+ }
+ if (p->source_big && p->source_big->icon == hIcon) {
+ big = true;
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+static void IcoLib_FreeIcon(IcolibItem* 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
+
+MIR_APP_DLL(HANDLE) IcoLib_AddNewIcon(int hLangpack, SKINICONDESC *sid)
+{
+ bool utf = (sid->flags & SIDF_UNICODE) != 0;
+ bool utf_path = (sid->flags & SIDF_PATH_UNICODE) != 0;
+
+ mir_cslock lck(csIconList);
+
+ IcolibItem *item = IcoLib_FindIcon(sid->pszName);
+ if (!item) {
+ item = (IcolibItem*)mir_calloc(sizeof(IcolibItem));
+ item->name = sid->pszName;
+ iconList.insert(item);
+ }
+ else IcoLib_FreeIcon(item);
+
+ item->name = mir_strdup(sid->pszName);
+ if (utf) {
+ item->description = mir_u2t(sid->description.w);
+ item->section = IcoLib_AddSection(sid->section.w, TRUE);
+ }
+ else {
+ item->description = mir_a2t(sid->description.a);
+ WCHAR *pwszSection = sid->section.a ? mir_a2u(sid->section.a) : NULL;
+ item->section = IcoLib_AddSection(pwszSection, TRUE);
+ SAFE_FREE((void**)&pwszSection);
+ }
+
+ if (item->section) {
+ item->section->ref_count++;
+ item->orderID = ++item->section->maxOrder;
+ }
+ else item->orderID = 0;
+
+ if (sid->defaultFile.a) {
+ WCHAR fileFull[ MAX_PATH ];
+ if (utf_path)
+ PathToAbsoluteT(sid->defaultFile.w, fileFull);
+ else
+ PathToAbsoluteT(_A2T(sid->defaultFile.a), fileFull);
+ item->default_file = mir_wstrdup(fileFull);
+ }
+ item->default_indx = sid->iDefaultIndex;
+
+ item->cx = sid->cx;
+ item->cy = sid->cy;
+ item->hLangpack = hLangpack;
+
+ if (sid->hDefaultIcon) {
+ bool big;
+ IcolibItem* 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 (item->section)
+ item->section->flags = sid->flags & SIDF_SORTED;
+
+ return item;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib_RemoveIcon
+
+static int IcoLib_RemoveIcon_Internal(int i)
+{
+ IcolibItem *item = iconList[ i ];
+ IcoLib_FreeIcon(item);
+ iconList.remove(i);
+ SAFE_FREE((void**)&item);
+ return 0;
+}
+
+static INT_PTR IcoLib_RemoveIcon(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam) {
+ mir_cslock lck(csIconList);
+
+ int i = iconList.indexOf((IcolibItem*)wParam);
+ if (i != -1)
+ return IcoLib_RemoveIcon_Internal(i);
+ }
+
+ if (lParam) {
+ mir_cslock lck(csIconList);
+
+ int i = iconList.getIndex((IcolibItem*)&lParam);
+ if (i != -1)
+ return IcoLib_RemoveIcon_Internal(i);
+ }
+ return 1; // Failed
+}
+
+void KillModuleIcons(int hLangpack)
+{
+ if (!bModuleInitialized)
+ return;
+
+ mir_cslock lck(csIconList);
+ for (int i = iconList.getCount()-1; i >= 0; i--) {
+ IcolibItem *item = iconList[i];
+ if ( item->hLangpack == hLangpack) {
+ IcoLib_FreeIcon(item);
+ iconList.remove(i);
+ SAFE_FREE((void**)&item);
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IconItem_GetDefaultIcon
+
+HICON IconItem_GetDefaultIcon(IcolibItem* 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(IcolibItem* item, bool big)
+{
+ DBVARIANT dbv = {0};
+ HICON hIcon = NULL;
+
+ big = big && !item->cx;
+ IconSourceItem* &source = big ? item->source_big : item->source_small;
+
+ if (!source && !db_get_ts(NULL, "SkinIcons", item->name, &dbv)) {
+ TCHAR tszFullPath[MAX_PATH];
+ PathToAbsoluteT(dbv.ptszVal, tszFullPath);
+ 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(tszFullPath, cx, cy);
+ db_free(&dbv);
+ }
+
+ if (source)
+ hIcon = IconSourceItem_GetIcon(source);
+
+ if (!hIcon)
+ hIcon = IconItem_GetDefaultIcon(item, big);
+
+ if (!hIcon)
+ return 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
+
+MIR_APP_DLL(HICON) IcoLib_GetIcon(const char* pszIconName, bool big)
+{
+ if (!pszIconName)
+ return hIconBlank;
+
+ mir_cslock lck(csIconList);
+ IcolibItem* item = IcoLib_FindIcon(pszIconName);
+ return (item) ? IconItem_GetIcon(item, big) : NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib_GetIconHandle
+// lParam: pszIconName
+
+MIR_APP_DLL(HANDLE) IcoLib_GetIconHandle(const char* pszIconName)
+{
+ if (!pszIconName)
+ return NULL;
+
+ mir_cslock lck(csIconList);
+ return IcoLib_FindIcon(pszIconName);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib_GetIconByHandle
+// lParam: icolib item handle
+// wParam: 0
+
+MIR_APP_DLL(HICON) IcoLib_GetIconByHandle(HANDLE hItem, bool big)
+{
+ if (hItem == NULL)
+ return NULL;
+
+ mir_cslock lck(csIconList);
+ IcolibItem* pi = (IcolibItem*)hItem;
+ if ( iconList.getIndex(pi) != -1)
+ return IconItem_GetIcon(pi, big);
+
+ return hIconBlank;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib_IsManaged
+// lParam: NULL
+// wParam: HICON
+
+MIR_APP_DLL(HANDLE) IcoLib_IsManaged(HICON hIcon)
+{
+ mir_cslock lck(csIconList);
+
+ bool big;
+ return IcoLib_FindHIcon(hIcon, big);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib_AddRef
+// lParam: NULL
+// wParam: HICON
+
+static INT_PTR IcoLib_AddRef(WPARAM wParam, LPARAM)
+{
+ mir_cslock lck(csIconList);
+
+ bool big;
+ IcolibItem *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;
+ }
+ }
+
+ return res;
+}
+
+static int SkinSystemModulesLoaded(WPARAM, LPARAM)
+{
+ HookEvent(ME_OPT_INITIALISE, SkinOptionsInit);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Module initialization and finalization procedure
+
+static INT_PTR sttIcoLib_AddNewIcon(WPARAM wParam, LPARAM lParam)
+{
+ return (INT_PTR)IcoLib_AddNewIcon((int)wParam, (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(g_hInst, MAKEINTRESOURCE(IDI_BLANK), 0);
+
+ hIcoLib_AddNewIcon = CreateServiceFunction("Skin2/Icons/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 i;
+
+ 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);
+
+ for (i = iconList.getCount()-1; i >= 0; i--) {
+ IcolibItem* p = iconList[i];
+ iconList.remove(i);
+ IcoLib_FreeIcon(p);
+ mir_free(p);
+ }
+
+ for (i = iconSourceList.getCount()-1; i >= 0; i--) {
+ IconSourceItem* p = iconSourceList[i];
+ iconSourceList.remove(i);
+ IconSourceFile_Release(&p->file);
+ SafeDestroyIcon(&p->icon);
+ SAFE_FREE((void**)&p->icon_data);
+ SAFE_FREE((void**)&p);
+ }
+
+ for (i = iconSourceFileList.getCount()-1; i >= 0; i--) {
+ IconSourceFile* p = iconSourceFileList[i];
+ iconSourceFileList.remove(i);
+ SAFE_FREE((void**)&p->file);
+ SAFE_FREE((void**)&p);
+ }
+
+ for (i = 0; i < sectionList.getCount(); i++) {
+ SAFE_FREE((void**)§ionList[i]->name);
+ mir_free(sectionList[i]);
+ }
+
+ SafeDestroyIcon(&hIconBlank);
+ bModuleInitialized = false;
+}
diff --git a/src/mir_app/src/iconheader.cpp b/src/mir_app/src/iconheader.cpp new file mode 100644 index 0000000000..103948ef32 --- /dev/null +++ b/src/mir_app/src/iconheader.cpp @@ -0,0 +1,534 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+Copyright (c) 2007 Artem Shpynov
+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 "stdafx.h"
+#include "m_iconheader.h"
+
+extern HINSTANCE hInst;
+
+static BOOL IsAeroMode()
+{
+ BOOL result;
+ return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result;
+}
+
+static BOOL IsVSMode()
+{
+ return 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 : public MZeroedObject
+{
+ MIcoTabCtrl(): pList(1) {}
+
+ HWND hwnd;
+ int nSelectedIdx, nHotIdx;
+ LIST<MIcoTab> 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<MIcoTab> &pList, ItemDestuctor pItemDestructor)
+{
+ for (int i=0; i<pList.getCount(); i++) pItemDestructor(pList[i]);
+ pList.destroy();
+}
+
+int LoadIcoTabsModule()
+{
+ WNDCLASSEX wc;
+
+ memset(&wc, 0, sizeof(wc));
+ wc.cbSize = sizeof(wc);
+ wc.lpszClassName = MIRANDAICOTABCLASS;
+ wc.lpfnWndProc = MIcoTabWndProc;
+// wc.hCursor = LoadCursor(NULL, IDC_HAND);
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.cbWndExtra = sizeof(MIcoTabCtrl*);
+ wc.hbrBackground = 0; //GetStockObject(WHITE_BRUSH);
+ wc.style = CS_GLOBALCLASS/*|CS_SAVEBITS*/;
+ RegisterClassEx(&wc);
+ return 0;
+}
+
+static void MIcoTab_SetupColors(MIcoTabCtrl *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;
+
+ 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)
+{
+ PAINTSTRUCT ps;
+
+ 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;
+ HBITMAP hBmp = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, NULL, NULL, 0);
+
+ HBITMAP 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));
+ }
+ 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 hFont = mit->hFont;
+ HFONT hOldFont = (HFONT)SelectObject(tempDC, hFont);
+ SetBkMode(tempDC, TRANSPARENT);
+
+ for (int i=0; i<mit->pList.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);
+ SelectObject(tempDC,hOldFont);
+ 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));
+ 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);
+ 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 ((int)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 ((int)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/mir_app/src/ignore.cpp b/src/mir_app/src/ignore.cpp new file mode 100644 index 0000000000..fc5fa575b5 --- /dev/null +++ b/src/mir_app/src/ignore.cpp @@ -0,0 +1,441 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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(MCONTACT hContact)
+{
+ DWORD mask = db_get_dw(hContact, "Ignore", "Mask1", (DWORD)(-1));
+ if (mask == (DWORD)(-1)) {
+ if (hContact == NULL) mask = 0;
+ else {
+ if (db_get_b(hContact, "CList", "Hidden", 0) || db_get_b(hContact, "CList", "NotOnList", 0))
+ mask = db_get_dw(NULL, "Ignore", "Mask1", 0);
+ else
+ mask = db_get_dw(NULL, "Ignore", "Default1", 0);
+ }
+ }
+ return mask;
+}
+
+static void SetListGroupIcons(HWND hwndList, HANDLE hFirstItem, HANDLE hParentItem, int *groupChildCount)
+{
+ 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;
+
+ int 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 != EMPTY_EXTRA_ICON)
+ 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) : EMPTY_EXTRA_ICON));
+ 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)
+{
+ HANDLE hItem;
+
+ int 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) {
+ HANDLE 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) {
+ int iOldIcon = SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, iColumn);
+ if (iOldIcon != EMPTY_EXTRA_ICON && 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)
+{
+ SendMessage(hwndList, CLM_SETHIDEEMPTYGROUPS, 1, 0);
+}
+
+static void SetIconsForColumn(HWND hwndList, HANDLE hItem, HANDLE hItemAll, int iColumn, int iImage)
+{
+ switch ( SendMessage(hwndList, CLM_GETITEMTYPE, (WPARAM)hItem, 0)) {
+ case CLCIT_CONTACT:
+ {
+ int oldiImage = SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, iColumn);
+ if (oldiImage != EMPTY_EXTRA_ICON && oldiImage != iImage)
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(iColumn, iImage));
+ }
+ break;
+ case CLCIT_INFO:
+ if (hItem == hItemAll)
+ SetAllChildIcons(hwndList, hItem, iColumn, iImage);
+ else
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(iColumn, iImage)); //hItemUnknown
+ break;
+
+ case CLCIT_GROUP:
+ hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
+ if (hItem)
+ SetAllChildIcons(hwndList, hItem, iColumn, iImage);
+ }
+}
+
+static void InitialiseItem(HWND hwndList, MCONTACT hContact, HANDLE hItem, DWORD proto1Caps, DWORD proto4Caps)
+{
+ DWORD mask = GetMask(hContact);
+ for (int i=0; i < IGNOREEVENT_MAX; i++)
+ if ((ignoreIdToPf1[i] == 0xFFFFFFFF && ignoreIdToPf4[i] == 0xFFFFFFFF) || (proto1Caps&ignoreIdToPf1[i] || proto4Caps&ignoreIdToPf4[i]))
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(i, mask&(1<<i)?i+3:0));
+
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(IGNOREEVENT_MAX, 1));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(IGNOREEVENT_MAX+1, 2));
+}
+
+static void SaveItemMask(HWND hwndList, MCONTACT hContact, HANDLE hItem, const char *pszSetting)
+{
+ DWORD mask = 0;
+ for (int i=0; i < IGNOREEVENT_MAX; i++) {
+ int iImage = SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(i, 0));
+ if (iImage && iImage != EMPTY_EXTRA_ICON)
+ mask |= 1 << i;
+ }
+ db_set_dw(hContact, "Ignore", pszSetting, mask);
+}
+
+static void SetAllContactIcons(HWND hwndList)
+{
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, hContact, 0);
+ if (hItem && SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(IGNOREEVENT_MAX, 0)) == EMPTY_EXTRA_ICON) {
+ DWORD proto1Caps, proto4Caps;
+ char *szProto = GetContactProto(hContact);
+ if (szProto) {
+ proto1Caps = CallProtoServiceInt(NULL,szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ proto4Caps = CallProtoServiceInt(NULL,szProto, PS_GETCAPS, PFLAGNUM_4, 0);
+ }
+ else proto1Caps = proto4Caps = 0;
+ InitialiseItem(hwndList, hContact, hItem, proto1Caps, proto4Caps);
+ if (!db_get_b(hContact, "CList", "Hidden", 0))
+ SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)hItem, 1);
+ }
+ }
+}
+
+static INT_PTR CALLBACK DlgProcIgnoreOpts(HWND hwndDlg, UINT msg, WPARAM, LPARAM lParam)
+{
+ static HICON hIcons[IGNOREEVENT_MAX+2];
+ static HANDLE hItemAll, hItemUnknown;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ HIMAGELIST hIml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 3 + IGNOREEVENT_MAX, 3 + IGNOREEVENT_MAX);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_SMALLDOT);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_FILLEDBLOB);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_EMPTYBLOB);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_EVENT_MESSAGE);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_EVENT_URL);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_EVENT_FILE);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_USERONLINE);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_AUTH_REQUEST);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_AUTH_ADD);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_TYPING);
+
+ SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_SETEXTRAIMAGELIST, 0, (LPARAM)hIml);
+ for (int i=0; i < SIZEOF(hIcons); i++)
+ hIcons[i] = ImageList_GetIcon(hIml, 1+i, ILD_NORMAL);
+ }
+
+ SendDlgItemMessage(hwndDlg, IDC_ALLICON, STM_SETICON, (WPARAM)hIcons[0], 0);
+ SendDlgItemMessage(hwndDlg, IDC_NONEICON, STM_SETICON, (WPARAM)hIcons[1], 0);
+ SendDlgItemMessage(hwndDlg, IDC_MSGICON, STM_SETICON, (WPARAM)hIcons[2], 0);
+ SendDlgItemMessage(hwndDlg, IDC_URLICON, STM_SETICON, (WPARAM)hIcons[3], 0);
+ SendDlgItemMessage(hwndDlg, IDC_FILEICON, STM_SETICON, (WPARAM)hIcons[4], 0);
+ SendDlgItemMessage(hwndDlg, IDC_ONLINEICON, STM_SETICON, (WPARAM)hIcons[5], 0);
+ SendDlgItemMessage(hwndDlg, IDC_AUTHICON, STM_SETICON, (WPARAM)hIcons[6], 0);
+ SendDlgItemMessage(hwndDlg, IDC_ADDED, STM_SETICON, (WPARAM)hIcons[7], 0);
+ SendDlgItemMessage(hwndDlg, IDC_TYPINGICON, STM_SETICON, (WPARAM)hIcons[8], 0);
+
+ ResetListOptions(GetDlgItem(hwndDlg, IDC_LIST));
+ SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_SETEXTRACOLUMNS, IGNOREEVENT_MAX+2, 0);
+ {
+ CLCINFOITEM cii = { sizeof(cii) };
+ cii.flags = CLCIIF_GROUPFONT;
+ cii.pszText = TranslateT("** All contacts **");
+ hItemAll = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_ADDINFOITEM, 0, (LPARAM)&cii);
+
+ cii.pszText = TranslateT("** Unknown contacts **");
+ hItemUnknown = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_ADDINFOITEM, 0, (LPARAM)&cii);
+ InitialiseItem( GetDlgItem(hwndDlg, IDC_LIST), NULL, hItemUnknown, 0xFFFFFFFF, 0xFFFFFFFF);
+ }
+
+ 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 CLN_CHECKCHANGED:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ case NM_CLICK:
+ {
+ NMCLISTCONTROL *nm = (NMCLISTCONTROL*)lParam;
+ if (nm->iColumn == -1)
+ break;
+
+ DWORD hitFlags;
+ HANDLE hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_HITTEST, (WPARAM)&hitFlags, MAKELPARAM(nm->pt.x, nm->pt.y));
+ if (hItem == NULL || !(hitFlags & CLCHT_ONITEMEXTRA))
+ break;
+
+ if (nm->iColumn == IGNOREEVENT_MAX) { // ignore all
+ for (int iImage = 0;iImage<IGNOREEVENT_MAX;iImage++)
+ SetIconsForColumn( GetDlgItem(hwndDlg, IDC_LIST), hItem, hItemAll, iImage, iImage+3);
+ }
+ else if (nm->iColumn == IGNOREEVENT_MAX+1) { // ignore none
+ for (int iImage = 0;iImage<IGNOREEVENT_MAX;iImage++)
+ SetIconsForColumn( GetDlgItem(hwndDlg, IDC_LIST), hItem, hItemAll, iImage, 0);
+ }
+ else {
+ int iImage = SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nm->iColumn, 0));
+ if (iImage == 0)
+ iImage = nm->iColumn+3;
+ else if (iImage != EMPTY_EXTRA_ICON)
+ 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;
+
+ case 0:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_APPLY:
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ HANDLE hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_FINDCONTACT, hContact, 0);
+ if (hItem) SaveItemMask( GetDlgItem(hwndDlg, IDC_LIST), hContact, hItem, "Mask1");
+ if (SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETCHECKMARK, (WPARAM)hItem, 0))
+ db_unset(hContact, "CList", "Hidden");
+ else
+ db_set_b(hContact, "CList", "Hidden", 1);
+ }
+
+ SaveItemMask( GetDlgItem(hwndDlg, IDC_LIST), NULL, hItemAll, "Default1");
+ SaveItemMask( GetDlgItem(hwndDlg, IDC_LIST), NULL, hItemUnknown, "Mask1");
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i=0; i < SIZEOF(hIcons); i++)
+ DestroyIcon(hIcons[i]);
+ HIMAGELIST hIml = (HIMAGELIST)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_GETEXTRAIMAGELIST, 0, 0);
+ ImageList_Destroy(hIml);
+ break;
+ }
+ return FALSE;
+}
+
+static int IgnoreOptInitialise(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.position = 900000000;
+ odp.hInstance = g_hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_IGNORE);
+ odp.pszTitle = LPGEN("Ignore");
+ odp.pszGroup = LPGEN("Contacts");
+ odp.pfnDlgProc = DlgProcIgnoreOpts;
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+static INT_PTR IsIgnored(WPARAM wParam, LPARAM lParam)
+{
+ DWORD mask = GetMask(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(wParam);
+ if ((lParam < 1 || lParam > IGNOREEVENT_MAX) && lParam != IGNOREEVENT_ALL)
+ return 1;
+ if (lParam == IGNOREEVENT_ALL)
+ mask = (1 << IGNOREEVENT_MAX)-1;
+ else
+ mask |= 1 << (lParam-1);
+ db_set_dw(wParam, "Ignore", "Mask1", mask);
+ return 0;
+}
+
+static INT_PTR Unignore(WPARAM wParam, LPARAM lParam)
+{
+ DWORD mask = GetMask(wParam);
+ if ((lParam < 1 || lParam > IGNOREEVENT_MAX) && lParam != IGNOREEVENT_ALL)
+ return 1;
+
+ if (lParam == IGNOREEVENT_ALL)
+ mask = 0;
+ else
+ mask &= ~(1 << (lParam-1));
+ db_set_dw(wParam, "Ignore", "Mask1", mask);
+ 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) {
+ MCONTACT hContact = DbGetAuthEventContact(dbei);
+ if (CallService(MS_DB_CONTACT_IS, hContact, 0) && IsIgnored(hContact, IGNOREEVENT_YOUWEREADDED))
+ return 1;
+ }
+ return 0;
+}
+
+static int iBoldControls[] = { IDC_TXT_TITLE1, IDC_TXT_TITLE2, IDC_TXT_TITLE3, MODERNOPT_CTRL_LAST };
+
+static int IgnoreModernOptInit(WPARAM wParam, LPARAM)
+{
+ MODERNOPTOBJECT obj = { sizeof(obj) };
+ obj.hInstance = g_hInst;
+ 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;
+ CallService(MS_MODERNOPT_ADDOBJECT, wParam, (LPARAM)&obj);
+ return 0;
+}
+
+int LoadIgnoreModule(void)
+{
+ PROTOCOLDESCRIPTOR pd = { sizeof(pd) };
+ pd.szName = "Ignore";
+ pd.type = PROTOTYPE_IGNORE;
+ CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd);
+
+ CreateProtoServiceFunction("Ignore", PSR_MESSAGE, IgnoreRecvMessage);
+ CreateProtoServiceFunction("Ignore", PSR_URL, IgnoreRecvUrl);
+ CreateProtoServiceFunction("Ignore", PSR_FILE, IgnoreRecvFile);
+ CreateProtoServiceFunction("Ignore", PSR_AUTH, IgnoreRecvAuth);
+
+ CreateServiceFunction(MS_IGNORE_ISIGNORED, IsIgnored);
+ CreateServiceFunction(MS_IGNORE_IGNORE, Ignore);
+ CreateServiceFunction(MS_IGNORE_UNIGNORE, Unignore);
+
+ HookEvent(ME_DB_EVENT_FILTER_ADD, IgnoreAddedNotify);
+ HookEvent(ME_MODERNOPT_INITIALIZE, IgnoreModernOptInit);
+ HookEvent(ME_OPT_INITIALISE, IgnoreOptInitialise);
+ return 0;
+}
diff --git a/src/mir_app/src/imgconv.cpp b/src/mir_app/src/imgconv.cpp new file mode 100644 index 0000000000..75974fc541 --- /dev/null +++ b/src/mir_app/src/imgconv.cpp @@ -0,0 +1,144 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+typedef DWORD ARGB;
+
+void InitBitmapInfo(BITMAPINFO &bmi, const SIZE &size)
+{
+ memset(&bmi, 0, 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/mir_app/src/keyboard.cpp b/src/mir_app/src/keyboard.cpp new file mode 100644 index 0000000000..508cc8ba6a --- /dev/null +++ b/src/mir_app/src/keyboard.cpp @@ -0,0 +1,112 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+#include <m_hotkeys.h>
+
+static INT_PTR hkHideShow(WPARAM, LPARAM)
+{
+ cli.pfnShowHide(0, 0);
+ 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;
+}
+
+int InitClistHotKeys(void)
+{
+ CreateServiceFunction("CLIST/HK/SHOWHIDE", hkHideShow);
+ CreateServiceFunction("CLIST/HK/Opts", hkOpts);
+ CreateServiceFunction("CLIST/HK/Read", hkRead);
+
+ HOTKEYDESC shk = { sizeof(shk) };
+ shk.dwFlags = HKD_TCHAR;
+ shk.ptszDescription = LPGENT("Show/Hide contact list");
+ shk.pszName = "ShowHide";
+ shk.ptszSection = _T("Main");
+ shk.pszService = "CLIST/HK/SHOWHIDE";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'A');
+ Hotkey_Register(&shk);
+
+ shk.ptszDescription = LPGENT("Read message");
+ shk.pszName = "ReadMessage";
+ shk.ptszSection = _T("Main");
+ shk.pszService = "CLIST/HK/Read";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'I');
+ Hotkey_Register(&shk);
+
+ shk.ptszDescription = LPGENT("Open Options page");
+ shk.pszName = "ShowOptions";
+ shk.ptszSection = _T("Main");
+ shk.pszService = "CLIST/HK/Opts";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'O') | HKF_MIRANDA_LOCAL;
+ Hotkey_Register(&shk);
+
+ shk.ptszDescription = LPGENT("Open logging options");
+ shk.pszName = "ShowLogOptions";
+ shk.ptszSection = _T("Main");
+ shk.pszService = "Netlib/Log/Win";
+ shk.DefHotKey = 0;
+ Hotkey_Register(&shk);
+
+ shk.ptszDescription = LPGENT("Open 'Find user' dialog");
+ shk.pszName = "FindUsers";
+ shk.ptszSection = _T("Main");
+ shk.pszService = "FindAdd/FindAddCommand";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'F') | HKF_MIRANDA_LOCAL;
+ Hotkey_Register(&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/mir_app/src/langpack.cpp b/src/mir_app/src/langpack.cpp new file mode 100644 index 0000000000..9e5e547c50 --- /dev/null +++ b/src/mir_app/src/langpack.cpp @@ -0,0 +1,94 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "langpack.h"
+
+MIR_CORE_DLL(int) LoadLangPackDescr(const TCHAR *szLangPack, LANGPACK_INFO *lpInfo);
+
+BOOL EnumLangpacks(ENUM_PACKS_CALLBACK callback, WPARAM wParam, LPARAM lParam)
+{
+ if (callback == NULL) return FALSE;
+
+ BOOL res = FALSE;
+
+ /* language folder */
+ ptrT langpack(db_get_tsa(NULL, "Langpack", "Current"));
+
+ TCHAR tszFullPath[MAX_PATH];
+ PathToAbsoluteT(_T("\\Languages\\langpack_*.txt"), tszFullPath);
+
+ BOOL fPackFound = FALSE;
+ WIN32_FIND_DATA wfd;
+ HANDLE hFind = FindFirstFile(tszFullPath, &wfd);
+ if (hFind != INVALID_HANDLE_VALUE) {
+ do {
+ if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
+ /* get data */
+ PathToAbsoluteT(_T("\\Languages\\"), tszFullPath);
+ mir_tstrcat(tszFullPath, wfd.cFileName);
+
+ LANGPACK_INFO pack;
+ if (!LoadLangPackDescr(tszFullPath, &pack)) {
+ pack.ftFileDate = wfd.ftLastWriteTime;
+ /* enabled? */
+ if (langpack && !mir_tstrcmpi(langpack, wfd.cFileName)) {
+ if (!fPackFound) pack.flags |= LPF_ENABLED;
+ fPackFound = TRUE;
+ }
+ /* callback */
+ res = callback(&pack, wParam, lParam);
+ if (!res) { FindClose(hFind); return FALSE; }
+ }
+ } while (FindNextFile(hFind, &wfd));
+ FindClose(hFind);
+ }
+
+ /* default langpack: English */
+ if (callback != NULL) {
+ LANGPACK_INFO pack;
+ pack.Locale = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
+ mir_tstrcpy(pack.tszLanguage, _T("English"));
+ pack.szAuthors = "Miranda NG Development Team";
+ pack.szAuthorEmail = "project-info@miranda-ng.org";
+ DWORD v = CallService(MS_SYSTEM_GETVERSION, 0, 0);
+ pack.szLastModifiedUsing.Format("%d.%d.%d", ((v >> 24) & 0xFF), ((v >> 16) & 0xFF), ((v >> 8) & 0xFF));
+ /* file date */
+ if (GetModuleFileName(g_hInst, pack.tszFullPath, SIZEOF(pack.tszFullPath))) {
+ mir_tstrcpy(pack.tszFileName, _T("default"));
+ HANDLE hFile = CreateFile(pack.tszFileName, 0, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+ if (hFile != INVALID_HANDLE_VALUE) {
+ GetFileTime(hFile, NULL, NULL, &pack.ftFileDate);
+ CloseHandle(hFile);
+ }
+ }
+ pack.flags = LPF_DEFAULT;
+
+ if (!fPackFound) pack.flags |= LPF_ENABLED;
+ /* callback */
+ if (!callback(&pack, wParam, lParam)) return FALSE;
+ }
+
+ return fPackFound;
+}
diff --git a/src/mir_app/src/langpack.h b/src/mir_app/src/langpack.h new file mode 100644 index 0000000000..900f853f69 --- /dev/null +++ b/src/mir_app/src/langpack.h @@ -0,0 +1,58 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvTranslateString(WPARAM wParam, LPARAM lParam);
+static INT_PTR srvTranslateMenu(WPARAM wParam, LPARAM lParam);
+static INT_PTR srvRegisterLP(WPARAM wParam, LPARAM lParam);
+static INT_PTR srvGetDefaultCodePage(WPARAM, LPARAM);
+static INT_PTR srvGetDefaultLocale(WPARAM, LPARAM);
+static INT_PTR srvPcharToTchar(WPARAM wParam, LPARAM lParam);
+static INT_PTR srvReloadLangpack(WPARAM wParam, LPARAM lParam);
+static INT_PTR srvGetPluginLangpack(WPARAM wParam, LPARAM lParam);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define LPF_ENABLED (1<<0) // pack is enabled
+#define LPF_NOLOCALE (1<<1) // pack has no valid locale
+#define LPF_DEFAULT (1<<2) // pack is the english default (no langpack)
+
+/* Langpack Info */
+struct LANGPACK_INFO
+{
+ TCHAR tszLanguage[64];
+ LCID Locale;
+ WORD codepage;
+ CMStringA szAuthors, szAuthorEmail, szLastModifiedUsing;
+ FILETIME ftFileDate;
+ TCHAR tszFileName[MAX_PATH]; /* just the file name itself */
+ TCHAR tszFullPath[MAX_PATH]; /* full path to the langpack */
+ BYTE flags; /* see LPIF_* flags */
+};
+
+typedef BOOL(*ENUM_PACKS_CALLBACK) (LANGPACK_INFO *pack, WPARAM wParam, LPARAM lParam);
+BOOL EnumLangpacks(ENUM_PACKS_CALLBACK callback, WPARAM wParam, LPARAM lParam);
+
+int LangpackOptionsInit(WPARAM wParam, LPARAM);
diff --git a/src/mir_app/src/lpopts.cpp b/src/mir_app/src/lpopts.cpp new file mode 100644 index 0000000000..fac4b383e2 --- /dev/null +++ b/src/mir_app/src/lpopts.cpp @@ -0,0 +1,212 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "langpack.h"
+
+static void SetDlgItemText_CP(HWND hwndDlg, int ctrlID, LPCSTR str)
+{
+ SetDlgItemText(hwndDlg, ctrlID, ptrT(mir_utf8decodeT(str)));
+}
+
+static void DisplayPackInfo(HWND hwndDlg, const LANGPACK_INFO *pack)
+{
+ /* locale string */
+ if (!(pack->flags & LPF_NOLOCALE)) {
+ TCHAR szLocaleName[256], szLanguageName[128], szContryName[128];
+
+ if (!GetLocaleInfo(pack->Locale, WINVER >= _WIN32_WINNT_WIN7 ? LOCALE_SENGLISHLANGUAGENAME : LOCALE_SENGLANGUAGE, szLanguageName, SIZEOF(szLanguageName)))
+ szLanguageName[0] = _T('\0');
+ if (!GetLocaleInfo(pack->Locale, WINVER >= _WIN32_WINNT_WIN7 ? LOCALE_SENGLISHCOUNTRYNAME : LOCALE_SENGCOUNTRY, szContryName, SIZEOF(szContryName)))
+ szContryName[0] = _T('\0');
+
+ /* add some note if its incompatible */
+ if (szLanguageName[0] && szContryName[0]) {
+ mir_sntprintf(szLocaleName, SIZEOF(szLocaleName), _T("%s (%s)"), TranslateTS(szLanguageName), TranslateTS(szContryName));
+ if (!IsValidLocale(pack->Locale, LCID_INSTALLED)) {
+ TCHAR *pszIncompat;
+ pszIncompat = TranslateT("(incompatible)");
+ szLocaleName[SIZEOF(szLocaleName) - mir_tstrlen(pszIncompat) - 1] = 0;
+ mir_tstrcat(mir_tstrcat(szLocaleName, _T(" ")), pszIncompat);
+ }
+ SetDlgItemText(hwndDlg, IDC_LANGLOCALE, szLocaleName);
+ }
+ else SetDlgItemText(hwndDlg, IDC_LANGLOCALE, TranslateT("Unknown"));
+ }
+ else SetDlgItemText(hwndDlg, IDC_LANGLOCALE, TranslateT("Unknown"));
+
+ /* file date */
+ SYSTEMTIME stFileDate;
+ TCHAR szDate[128]; szDate[0] = 0;
+ if (FileTimeToSystemTime(&pack->ftFileDate, &stFileDate))
+ GetDateFormat((LCID)CallService(MS_LANGPACK_GETLOCALE, 0, 0), DATE_SHORTDATE, &stFileDate, NULL, szDate, SIZEOF(szDate));
+ SetDlgItemText(hwndDlg, IDC_LANGDATE, szDate);
+
+ /* general */
+ SetDlgItemText_CP(hwndDlg, IDC_LANGMODUSING, pack->szLastModifiedUsing);
+ SetDlgItemText_CP(hwndDlg, IDC_LANGAUTHORS, pack->szAuthors);
+ SetDlgItemText_CP(hwndDlg, IDC_LANGEMAIL, pack->szAuthorEmail);
+ SetDlgItemText(hwndDlg, IDC_LANGINFOFRAME, TranslateTS(pack->tszLanguage));
+}
+
+static BOOL InsertPackItemEnumProc(LANGPACK_INFO *pack, WPARAM wParam, LPARAM)
+{
+ LANGPACK_INFO *pack2 = new LANGPACK_INFO();
+ *pack2 = *pack;
+
+ /* insert */
+ TCHAR tszName[512];
+ mir_sntprintf(tszName, SIZEOF(tszName), _T("%s [%s]"),
+ TranslateTS(pack->tszLanguage),
+ pack->flags & LPF_DEFAULT ? TranslateT("built-in") : pack->tszFileName);
+ UINT message = pack->flags & LPF_DEFAULT ? CB_INSERTSTRING : CB_ADDSTRING;
+ int idx = SendMessage((HWND)wParam, message, 0, (LPARAM)tszName);
+ SendMessage((HWND)wParam, CB_SETITEMDATA, idx, (LPARAM)pack2);
+ if (pack->flags & LPF_ENABLED) {
+ SendMessage((HWND)wParam, CB_SETCURSEL, idx, 0);
+ DisplayPackInfo(GetParent((HWND)wParam), pack);
+ EnableWindow(GetDlgItem(GetParent((HWND)wParam), IDC_RELOAD), !(pack->flags & LPF_DEFAULT));
+ }
+
+ return TRUE;
+}
+
+static void CALLBACK OpenOptions(void*)
+{
+ OPENOPTIONSDIALOG ood = { sizeof(ood) };
+ ood.pszGroup = "Customize";
+ ood.pszPage = "Languages";
+ Options_Open(&ood);
+}
+
+static void ReloadOptions(void *hWnd)
+{
+ while (IsWindow((HWND)hWnd))
+ Sleep(50);
+
+ CallFunctionAsync(OpenOptions, 0);
+}
+
+INT_PTR CALLBACK DlgLangpackOpt(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LANGUAGES);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ ComboBox_ResetContent(hwndList);
+ EnumLangpacks(InsertPackItemEnumProc, (WPARAM)hwndList, (LPARAM)0);
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_LANGEMAIL:
+ {
+ char buf[512];
+ mir_strcpy(buf, "mailto:");
+ if (GetDlgItemTextA(hwndDlg, LOWORD(wParam), &buf[7], SIZEOF(buf) - 7))
+ CallService(MS_UTILS_OPENURL, 0, (LPARAM)buf);
+ }
+ break;
+
+ case IDC_MORELANG:
+ CallService(MS_UTILS_OPENURL, OUF_NEWWINDOW, (LPARAM)"http://wiki.miranda-ng.org/index.php?title=Langpacks/en#Download");
+ break;
+
+ case IDC_LANGUAGES:
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ int idx = ComboBox_GetCurSel(hwndList);
+ LANGPACK_INFO *pack = (LANGPACK_INFO*)ComboBox_GetItemData(hwndList, idx);
+ DisplayPackInfo(hwndDlg, pack);
+ if (!(pack->flags & LPF_ENABLED))
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_RELOAD), (pack->flags & LPF_ENABLED) && !(pack->flags & LPF_DEFAULT));
+ }
+ break;
+
+ case IDC_RELOAD:
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_RELOAD), FALSE);
+ int idx = ComboBox_GetCurSel(hwndList);
+ LANGPACK_INFO *pack = (LANGPACK_INFO*)ComboBox_GetItemData(hwndList, idx);
+ ReloadLangpack(pack->tszFullPath);
+ DisplayPackInfo(hwndDlg, pack);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_RELOAD), TRUE);
+ }
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (LPNMHDR(lParam)->code == PSN_APPLY) {
+ TCHAR tszPath[MAX_PATH]; tszPath[0] = 0;
+ int idx = ComboBox_GetCurSel(hwndList);
+ int count = ComboBox_GetCount(hwndList);
+ for (int i = 0; i < count; i++) {
+ LANGPACK_INFO *pack = (LANGPACK_INFO*)ComboBox_GetItemData(hwndList, i);
+ if (i == idx) {
+ db_set_ts(NULL, "Langpack", "Current", pack->tszFileName);
+ mir_tstrcpy(tszPath, pack->tszFullPath);
+ pack->flags |= LPF_ENABLED;
+ }
+ else pack->flags &= ~LPF_ENABLED;
+ }
+
+ if (tszPath[0]) {
+ ReloadLangpack(tszPath);
+
+ if (LPPSHNOTIFY(lParam)->lParam == IDC_APPLY) {
+ HWND hwndParent = GetParent(hwndDlg);
+ PostMessage(hwndParent, WM_CLOSE, 1, 0);
+ mir_forkthread(ReloadOptions, hwndParent);
+ }
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ int count = ListBox_GetCount(hwndList);
+ for (int i = 0; i < count; i++)
+ delete (LANGPACK_INFO*)ListBox_GetItemData(hwndList, i);
+ ComboBox_ResetContent(hwndList);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int LangpackOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.hInstance = g_hInst;
+ odp.pfnDlgProc = DlgLangpackOpt;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_LANGUAGES);
+ odp.position = -1300000000;
+ odp.pszTitle = LPGEN("Languages");
+ odp.pszGroup = LPGEN("Customize");
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/lpservices.cpp b/src/mir_app/src/lpservices.cpp new file mode 100644 index 0000000000..ce01393140 --- /dev/null +++ b/src/mir_app/src/lpservices.cpp @@ -0,0 +1,113 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvTranslateString(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam & LANG_UNICODE)
+ return (INT_PTR)TranslateW_LP((const WCHAR*)lParam, wParam);
+ return (INT_PTR)TranslateA_LP((const char *)lParam, wParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvTranslateMenu(WPARAM wParam, LPARAM lParam)
+{
+ TranslateMenu_LP((HMENU)wParam, lParam);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvRegisterLP(WPARAM wParam, LPARAM lParam)
+{
+ PLUGININFOEX* ppi = (PLUGININFOEX*)lParam;
+ if (wParam && ppi)
+ *(int*)wParam = GetPluginFakeId(ppi->uuid, Langpack_MarkPluginLoaded(ppi));
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvGetDefaultCodePage(WPARAM, LPARAM)
+{
+ return Langpack_GetDefaultCodePage();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvGetDefaultLocale(WPARAM, LPARAM)
+{
+ return Langpack_GetDefaultLocale();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvPcharToTchar(WPARAM wParam, LPARAM lParam)
+{
+ char *pszStr = (char*)lParam;
+ if (pszStr == NULL)
+ return NULL;
+
+ int len = (int)mir_strlen(pszStr);
+ TCHAR *result = (TCHAR*)alloca((len+1)*sizeof(TCHAR));
+ MultiByteToWideChar(Langpack_GetDefaultCodePage(), 0, pszStr, -1, result, len);
+ result[len] = 0;
+ return (INT_PTR)mir_wstrdup(TranslateW_LP(result, wParam));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvReloadLangpack(WPARAM, LPARAM lParam)
+{
+ ReloadLangpack((TCHAR*)lParam);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR srvGetPluginLangpack(WPARAM, LPARAM lParam)
+{
+ return GetPluginLangByInstance((HINSTANCE)lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int LoadLangpackModule(void)
+{
+ LoadLangPackModule();
+
+ CreateServiceFunction(MS_LANGPACK_TRANSLATESTRING, srvTranslateString);
+ CreateServiceFunction(MS_LANGPACK_TRANSLATEMENU, srvTranslateMenu);
+ CreateServiceFunction(MS_LANGPACK_GETCODEPAGE, srvGetDefaultCodePage);
+ CreateServiceFunction(MS_LANGPACK_GETLOCALE, srvGetDefaultLocale);
+ CreateServiceFunction(MS_LANGPACK_PCHARTOTCHAR, srvPcharToTchar);
+ CreateServiceFunction(MS_LANGPACK_REGISTER, srvRegisterLP);
+ CreateServiceFunction(MS_LANGPACK_RELOAD, srvReloadLangpack);
+ CreateServiceFunction(MS_LANGPACK_LOOKUPHANDLE, srvGetPluginLangpack);
+ return 0;
+}
diff --git a/src/mir_app/src/mdatabasecache.cpp b/src/mir_app/src/mdatabasecache.cpp new file mode 100644 index 0000000000..82c5a54ef8 --- /dev/null +++ b/src/mir_app/src/mdatabasecache.cpp @@ -0,0 +1,252 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-15 Miranda NG 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 "stdafx.h"
+#include "database.h"
+
+static int stringCompare(const char *p1, const char *p2)
+{
+ return mir_strcmp(p1, p2);
+}
+
+static int compareGlobals(const DBCachedGlobalValue *p1, const DBCachedGlobalValue *p2)
+{
+ return mir_strcmp(p1->name, p2->name);
+}
+
+MDatabaseCache::MDatabaseCache(size_t _size) :
+ m_contactSize(_size),
+ m_lSettings(100, stringCompare),
+ m_lContacts(50, NumericKeySortT),
+ m_lGlobalSettings(50, compareGlobals),
+ m_lastSetting(NULL),
+ m_lastVL(NULL)
+{
+ m_hCacheHeap = HeapCreate(0, 0, 0);
+}
+
+MDatabaseCache::~MDatabaseCache()
+{
+ for (int i = 0; i < m_lContacts.getCount(); i++)
+ mir_free(m_lContacts[i]->pSubs);
+
+ HeapDestroy(m_hCacheHeap);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+DBCachedContact* MDatabaseCache::AddContactToCache(MCONTACT contactID)
+{
+ mir_cslock lck(m_cs);
+
+ int index = m_lContacts.getIndex((DBCachedContact*)&contactID);
+ if (index != -1)
+ return m_lContacts[index];
+
+ DBCachedContact *cc = (DBCachedContact*)HeapAlloc(m_hCacheHeap, HEAP_ZERO_MEMORY, m_contactSize);
+ cc->contactID = contactID;
+ cc->nSubs = -1;
+ m_lContacts.insert(cc);
+ return cc;
+}
+
+DBCachedContact* MDatabaseCache::GetCachedContact(MCONTACT contactID)
+{
+ mir_cslock lck(m_cs);
+
+ int index = m_lContacts.getIndex((DBCachedContact*)&contactID);
+ return (index == -1) ? NULL : m_lContacts[index];
+}
+
+DBCachedContact* MDatabaseCache::GetFirstContact()
+{
+ mir_cslock lck(m_cs);
+ return m_lContacts[0];
+}
+
+DBCachedContact* MDatabaseCache::GetNextContact(MCONTACT contactID)
+{
+ mir_cslock lck(m_cs);
+
+ int index = m_lContacts.getIndex((DBCachedContact*)&contactID);
+ return (index == -1) ? NULL : m_lContacts[index+1];
+}
+
+void MDatabaseCache::FreeCachedContact(MCONTACT contactID)
+{
+ mir_cslock lck(m_cs);
+
+ int index = m_lContacts.getIndex((DBCachedContact*)&contactID);
+ if (index == -1)
+ return;
+
+ DBCachedContact *cc = m_lContacts[index];
+ DBCachedContactValue* V = cc->first;
+ while (V != NULL) {
+ DBCachedContactValue* V1 = V->next;
+ FreeCachedVariant(&V->value);
+ HeapFree(m_hCacheHeap, 0, V);
+ V = V1;
+ }
+
+ mir_free(cc->pSubs);
+ HeapFree(m_hCacheHeap, 0, cc);
+
+ m_lContacts.remove(index);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+char* MDatabaseCache::InsertCachedSetting(const char* szName, int cbLen)
+{
+ char* newValue = (char*)HeapAlloc(m_hCacheHeap, 0, cbLen);
+ *newValue++ = 0;
+ mir_strcpy(newValue, szName);
+ m_lSettings.insert(newValue);
+ return newValue;
+}
+
+char* MDatabaseCache::GetCachedSetting(const char *szModuleName, const char *szSettingName, int moduleNameLen, int settingNameLen)
+{
+ char szFullName[512];
+ const char *szKey;
+ if (szModuleName != NULL) {
+ mir_strcpy(szFullName, szModuleName);
+ szFullName[moduleNameLen] = '/';
+ mir_strcpy(szFullName + moduleNameLen + 1, szSettingName);
+ szKey = szFullName;
+ }
+ else szKey = szSettingName;
+
+ if (m_lastSetting && !mir_strcmp(szKey, m_lastSetting))
+ return m_lastSetting;
+
+ int index = m_lSettings.getIndex((char*)szKey);
+ if (index != -1)
+ m_lastSetting = m_lSettings[index];
+ else
+ m_lastSetting = InsertCachedSetting(szKey, settingNameLen + moduleNameLen + 3);
+
+ return m_lastSetting;
+}
+
+void MDatabaseCache::SetCachedVariant(DBVARIANT* s /* new */, DBVARIANT* d /* cached */)
+{
+ char* szSave = (d->type == DBVT_UTF8 || d->type == DBVT_ASCIIZ) ? d->pszVal : NULL;
+
+ memcpy(d, s, sizeof(DBVARIANT));
+ if ((s->type == DBVT_UTF8 || s->type == DBVT_ASCIIZ) && s->pszVal != NULL) {
+ if (szSave != NULL)
+ d->pszVal = (char*)HeapReAlloc(m_hCacheHeap, 0, szSave, mir_strlen(s->pszVal) + 1);
+ else
+ d->pszVal = (char*)HeapAlloc(m_hCacheHeap, 0, mir_strlen(s->pszVal) + 1);
+ mir_strcpy(d->pszVal, s->pszVal);
+ }
+ else if (szSave != NULL)
+ HeapFree(m_hCacheHeap, 0, szSave);
+}
+
+void MDatabaseCache::FreeCachedVariant(DBVARIANT* V)
+{
+ if ((V->type == DBVT_ASCIIZ || V->type == DBVT_UTF8) && V->pszVal != NULL)
+ HeapFree(m_hCacheHeap, 0, V->pszVal);
+}
+
+STDMETHODIMP_(DBVARIANT*) MDatabaseCache::GetCachedValuePtr(MCONTACT contactID, char *szSetting, int bAllocate)
+{
+ // a global setting
+ if (contactID == 0) {
+ DBCachedGlobalValue Vtemp, *V;
+ Vtemp.name = szSetting;
+ int index = m_lGlobalSettings.getIndex(&Vtemp);
+ if (index != -1) {
+ V = m_lGlobalSettings[index];
+ if (bAllocate == -1) {
+ FreeCachedVariant(&V->value);
+ m_lGlobalSettings.remove(index);
+ HeapFree(m_hCacheHeap, 0, V);
+ return NULL;
+ }
+ }
+ else {
+ if (bAllocate != 1)
+ return NULL;
+
+ V = (DBCachedGlobalValue*)HeapAlloc(m_hCacheHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedGlobalValue));
+ V->name = szSetting;
+ m_lGlobalSettings.insert(V);
+ }
+
+ return &V->value;
+ }
+
+ // a contact setting
+ DBCachedContactValue *V, *V1;
+ DBCachedContact ccTemp, *cc;
+
+ ccTemp.contactID = contactID;
+
+ int index = m_lContacts.getIndex(&ccTemp);
+ if (index == -1)
+ return NULL;
+
+ m_lastVL = cc = m_lContacts[index];
+
+ for (V = cc->first; V != NULL; V = V->next)
+ if (V->name == szSetting)
+ break;
+
+ if (V == NULL) {
+ if (bAllocate != 1)
+ return NULL;
+
+ V = (DBCachedContactValue *)HeapAlloc(m_hCacheHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedContactValue));
+ if (cc->last)
+ cc->last->next = V;
+ else
+ cc->first = V;
+ cc->last = V;
+ V->name = szSetting;
+ }
+ else if (bAllocate == -1) {
+ m_lastVL = NULL;
+ FreeCachedVariant(&V->value);
+ if (cc->first == V) {
+ cc->first = V->next;
+ if (cc->last == V)
+ cc->last = V->next; // NULL
+ }
+ else
+ for (V1 = cc->first; V1 != NULL; V1 = V1->next)
+ if (V1->next == V) {
+ V1->next = V->next;
+ if (cc->last == V)
+ cc->last = V1;
+ break;
+ }
+ HeapFree(m_hCacheHeap, 0, V);
+ return NULL;
+ }
+
+ return &V->value;
+}
diff --git a/src/mir_app/src/meta_addto.cpp b/src/mir_app/src/meta_addto.cpp new file mode 100644 index 0000000000..d66c173d7f --- /dev/null +++ b/src/mir_app/src/meta_addto.cpp @@ -0,0 +1,224 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "metacontacts.h"
+
+/** Adds all the metacontacts desired in the listview.
+*
+* Adds all the metacontacts present in the database in the list,
+*
+* @param list : HANDLE to the list which will contain the columns.
+* @param nb_contacts : Number of loaded contacts.
+* @param contacts : A list of the contacts' identifiers
+*
+* @param id : Reference to a list of the MetaContacts IDs loaded in the listview.
+* Since this list is resized, its address must be passed.
+*
+* @return An integer specifying the number of rows added in the list.
+*/
+
+static int FillList(HWND list, BOOL sort)
+{
+ int i = 0;
+
+ // The DB is searched through, to get all the metacontacts
+ for (MCONTACT hMetaUser = db_find_first(); hMetaUser; hMetaUser = db_find_next(hMetaUser)) {
+ // if it's not a MetaContact, go to the next
+ DBCachedContact *cc = CheckMeta(hMetaUser);
+ if (cc == NULL)
+ continue;
+
+ // get contact display name from clist
+ TCHAR *swzContactDisplayName = cli.pfnGetContactDisplayName(hMetaUser, 0);
+ // don't insert huge strings that we have to compare with later
+ if (mir_tstrlen(swzContactDisplayName) > 1023)
+ swzContactDisplayName[1024] = 0;
+
+ int pos = -1;
+ if (sort) {
+ for (pos = 0; pos < i; pos++) {
+ TCHAR buff[1024];
+ SendMessage(list, LB_GETTEXT, pos, (LPARAM)buff);
+ if (mir_tstrcmp(buff, swzContactDisplayName) > 0)
+ break;
+ }
+ }
+
+ int index = SendMessage(list, LB_INSERTSTRING, pos, (LPARAM)swzContactDisplayName);
+ SendMessage(list, LB_SETITEMDATA, index, hMetaUser);
+ i++;
+ }
+ return i;
+}
+
+/** Build or update the list.
+*
+* @param list : HANDLE to the list which will contain the columns
+* @param id : Reference to a list that will contain all the MetaContacts IDs loaded in the listview
+* otherwise the list is only refilled \n (Warning : this value must be
+* set to TRUE only once per Dialog display, otherwise all columns will be doubled)
+*
+* @returns An integer specifying the number of rows inserted or -1 if there was a problem
+*/
+
+static int BuildList(HWND list, BOOL sort)
+{
+ SendMessage(list, LB_RESETCONTENT, 0, 0);
+ return FillList(list, sort);
+}
+
+/** Callback function for the <b>'Add To'</b> Dialog.
+*
+* All the UI is controlled here, from display to functionnalities.
+*
+* @param hwndDlg : HANDLE to the <b>'Add To'</b> Dialog.
+* @param uMsg : Specifies the message received by this dialog.
+* @param wParam : Specifies additional message-specific information.
+* @param lParam : Specifies additional message-specific information.
+*
+* @return TRUE if the dialog processed the message, FALSE if it did not.
+*/
+
+#define szConvMsg LPGEN("Either there is no metacontact in the database (in this case you should first convert a contact into one)\n\
+or there is none that can host this contact.\n\
+Another solution could be to convert this contact into a new metacontact.\n\nConvert this contact into a new metacontact?")
+
+static INT_PTR CALLBACK Meta_SelectDialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(lParam);
+ if (cc == NULL) {
+ DestroyWindow(hwndDlg);
+ return TRUE;
+ }
+
+ if (cc->IsMeta()) {
+ MessageBox(hwndDlg,
+ TranslateT("This contact is a metacontact.\nYou can't add a metacontact to another metacontact.\n\nPlease choose another."),
+ TranslateT("Metacontact conflict"), MB_ICONERROR);
+ DestroyWindow(hwndDlg);
+ return TRUE;
+ }
+
+ if (cc->IsSub()) {
+ MessageBox(hwndDlg,
+ TranslateT("This contact is already associated to a metacontact.\nYou cannot add a contact to multiple metacontacts."),
+ TranslateT("Multiple metacontacts"), MB_ICONERROR);
+ DestroyWindow(hwndDlg);
+ return TRUE;
+ }
+ }
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); // user data is contact handle
+
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIconEx(I_ADD));
+
+ // Initialize the graphical part
+ CheckDlgButton(hwndDlg, IDC_ONLYAVAIL, BST_CHECKED); // Initially checked; display all metacontacts is only an option
+ // Besides, we can check if there is at least one metacontact to add the contact to.
+ if (BuildList(GetDlgItem(hwndDlg, IDC_METALIST), FALSE) <= 0) {
+ if (MessageBox(hwndDlg, TranslateT(szConvMsg), TranslateT("No suitable metacontact found"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON1) == IDYES)
+ Meta_Convert(lParam, 0);
+ DestroyWindow(hwndDlg);
+ return TRUE;
+ }
+ else {
+ // get contact display name from clist
+ TCHAR *ptszCDN = cli.pfnGetContactDisplayName(lParam, 0);
+ if (!ptszCDN)
+ ptszCDN = TranslateT("a contact");
+
+ // ... and set it to the Window title.
+ TCHAR buf[256];
+ mir_sntprintf(buf, TranslateT("Adding %s..."), ptszCDN);
+ SetWindowText(hwndDlg, buf);
+ }
+ ShowWindow(hwndDlg, SW_SHOWNORMAL);
+ return TRUE;
+
+ case WM_COMMAND:
+ if (HIWORD(wParam) == LBN_DBLCLK) // emulate click ok Ok
+ wParam = MAKEWPARAM(IDOK, BN_CLICKED);
+
+ if (HIWORD(wParam) != BN_CLICKED)
+ break; // Only clicks of buttons are relevant, let other COMMANDs through
+
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ {
+ int item = SendDlgItemMessage(hwndDlg, IDC_METALIST, LB_GETCURSEL, 0, 0); // Get the index of the selected metacontact
+ if (item == -1)
+ return IDOK == MessageBox(hwndDlg, TranslateT("Please select a metacontact"), TranslateT("No metacontact selected"), MB_ICONHAND);
+
+ MCONTACT hContact = (MCONTACT)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ MCONTACT hMeta = (MCONTACT)SendDlgItemMessage(hwndDlg, IDC_METALIST, LB_GETITEMDATA, item, 0);
+ if (!Meta_Assign(hContact, hMeta, FALSE))
+ MessageBox(hwndDlg, TranslateT("Assignment to the metacontact failed."), TranslateT("Assignment failure"), MB_ICONERROR);
+ }
+ // fall through
+ case IDCANCEL:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case IDC_CHK_SRT:
+ SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_METALIST), GWL_STYLE, GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_METALIST), GWL_STYLE) ^ LBS_SORT);
+ if (BuildList(GetDlgItem(hwndDlg, IDC_METALIST), IsDlgButtonChecked(hwndDlg, IDC_CHK_SRT) ? TRUE : FALSE) <= 0) {
+ if (MessageBox(hwndDlg, TranslateT(szConvMsg), TranslateT("No suitable metacontact found"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON1) == IDYES)
+ Meta_Convert(lParam, 0);
+ DestroyWindow(hwndDlg);
+ return TRUE;
+ }
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ // Free all allocated memory and return the focus to the CList
+ HWND clist = GetParent(hwndDlg);
+ Skin_ReleaseIcon((HICON)SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 0));
+ EndDialog(hwndDlg, TRUE);
+ SetFocus(clist);
+ return TRUE;
+ }
+ return FALSE; // All other Message are not handled
+}
+
+/** Display the <b>'Add to'</b> Dialog
+*
+* Present a dialog in which the user can choose to which MetaContact this
+* contact will be added
+*
+* @param wParam : HANDLE to the contact that has been chosen.
+* @param lParam : Allways set to 0.
+*/
+
+INT_PTR Meta_AddTo(WPARAM hContact, LPARAM)
+{
+ HWND clui = (HWND)CallService(MS_CLUI_GETHWND, 0, 0);
+ DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_METASELECT), clui, &Meta_SelectDialogProc, hContact);
+ return 0;
+}
diff --git a/src/mir_app/src/meta_api.cpp b/src/mir_app/src/meta_api.cpp new file mode 100644 index 0000000000..f5642bdf9f --- /dev/null +++ b/src/mir_app/src/meta_api.cpp @@ -0,0 +1,72 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "metacontacts.h"
+
+// gets the handle for the 'most online' contact
+// wParam=(MCONTACT)hMetaContact
+// lParam=0
+// returns a handle to the 'most online' contact
+
+static INT_PTR MetaAPI_GetMostOnline(WPARAM hMetaContact, LPARAM)
+{
+ DBCachedContact *cc = CheckMeta(hMetaContact);
+ return (cc == NULL) ? NULL : Meta_GetMostOnline(cc);
+}
+
+// wParam=(HANDLE)hContact
+// lParam=0
+// convert a given contact into a metacontact
+
+static INT_PTR MetaAPI_ConvertToMeta(WPARAM wParam, LPARAM lParam)
+{
+ return Meta_Convert(wParam, lParam);
+}
+
+// wParam=(HANDLE)hContact
+// lParam=(HANDLE)hMeta
+// add an existing contact to a metacontact
+
+static INT_PTR MetaAPI_AddToMeta(WPARAM wParam, LPARAM lParam)
+{
+ return Meta_Assign(wParam, lParam, FALSE);
+}
+
+// wParam=0
+// lParam=(HANDLE)hContact
+// remove a contact from a metacontact
+
+static INT_PTR MetaAPI_RemoveFromMeta(WPARAM wParam, LPARAM lParam)
+{
+ // notice we switch args - to keep the API function consistent with the others
+ return Meta_Delete(lParam, wParam);
+}
+
+void CreateApiServices()
+{
+ CreateServiceFunction(MS_MC_GETMOSTONLINECONTACT, MetaAPI_GetMostOnline);
+ CreateServiceFunction(MS_MC_CONVERTTOMETA, MetaAPI_ConvertToMeta);
+ CreateServiceFunction(MS_MC_ADDTOMETA, MetaAPI_AddToMeta);
+ CreateServiceFunction(MS_MC_REMOVEFROMMETA, MetaAPI_RemoveFromMeta);
+}
diff --git a/src/mir_app/src/meta_edit.cpp b/src/mir_app/src/meta_edit.cpp new file mode 100644 index 0000000000..85e8154aaa --- /dev/null +++ b/src/mir_app/src/meta_edit.cpp @@ -0,0 +1,452 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "metacontacts.h"
+
+// Holds the differents changes that have to made
+struct
+{
+ MCONTACT hMeta; // HANDLE of the MetaContact that is edited.
+ DBCachedContact *cc;
+ MCONTACT hDefaultContact; // HANDLE of the new default contact
+ MCONTACT hOfflineContact;
+ int num_deleted, // DWORD number of deleted contacts
+ num_contacts; // DWORD number of contacts
+ MCONTACT hDeletedContacts[MAX_CONTACTS]; // HANDLEs of the subcontacts to be removed from this metacontact
+ MCONTACT hContact[MAX_CONTACTS]; // HANDLEs of the subcontacts, in the order they should be in
+}
+static g_data; // global CHANGES structure
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void FillContactList(HWND hList)
+{
+ TCHAR buff[256];
+
+ SendMessage(hList, LVM_DELETEALLITEMS, 0, 0);
+
+ LVITEM LvItem = { 0 };
+ LvItem.mask = LVIF_TEXT; // Text Style
+
+ for (int i = 0; i < g_data.num_contacts; i++) {
+ LvItem.iItem = i;
+
+ TCHAR *ptszCDN = cli.pfnGetContactDisplayName(g_data.hContact[i], 0);
+ if (ptszCDN == NULL)
+ ptszCDN = TranslateT("(Unknown contact)");
+
+ LvItem.iSubItem = 0; // clist display name
+ LvItem.pszText = ptszCDN;
+ ListView_InsertItem(hList, &LvItem);
+
+ LvItem.iSubItem = 1; // id
+ char *szProto = GetContactProto(g_data.hContact[i]);
+ if (szProto) {
+ PROTOACCOUNT *pa = ProtoGetAccount(szProto);
+
+ char *szField = (char *)CallProtoService(szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
+
+ DBVARIANT dbv;
+ if (!db_get(g_data.hContact[i], szProto, szField, &dbv)) {
+ switch (dbv.type) {
+ case DBVT_ASCIIZ:
+ _tcsncpy_s(buff, _A2T(dbv.pszVal), _TRUNCATE);
+ break;
+ case DBVT_WCHAR:
+ _tcsncpy_s(buff, dbv.ptszVal, _TRUNCATE);
+ break;
+ case DBVT_BYTE:
+ _itot(dbv.bVal, buff, 10);
+ break;
+ case DBVT_WORD:
+ _itot(dbv.wVal, buff, 10);
+ break;
+ case DBVT_DWORD:
+ _itot(dbv.dVal, buff, 10);
+ break;
+ default:
+ buff[0] = 0;
+ }
+ db_free(&dbv);
+ }
+ else buff[0] = 0;
+
+ LvItem.pszText = buff;
+ SendMessage(hList, LVM_SETITEM, 0, (LPARAM)&LvItem); // Enter text to SubItems
+
+ LvItem.iSubItem = 2; // protocol
+ _tcsncpy_s(buff, (pa == NULL) ? _A2T(szProto) : pa->tszAccountName, _TRUNCATE);
+ ListView_SetItem(hList, &LvItem);
+ }
+ else {
+ LvItem.pszText = TranslateT("Unknown");
+ ListView_SetItem(hList, &LvItem);
+
+ LvItem.iSubItem = 2; // protocol
+ ListView_SetItem(hList, &LvItem);
+ }
+ LvItem.iSubItem = 3; // Default (Yes/No)
+ LvItem.pszText = (g_data.hContact[i] == g_data.hDefaultContact ? TranslateT("Yes") : TranslateT("No"));
+ ListView_SetItem(hList, &LvItem);
+
+ LvItem.iSubItem = 4; // Offline (Yes/No)
+ LvItem.pszText = (g_data.hContact[i] == g_data.hOfflineContact ? TranslateT("Yes") : TranslateT("No"));
+ ListView_SetItem(hList, &LvItem);
+ }
+}
+
+static void SetListSelection(HWND hList, int sel)
+{
+ ListView_SetItemState(hList, sel, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
+}
+
+/** Scans the \c CHANGES and call the appropriate function for each change.
+*
+* @param chg : Structure holding all the change info (See CHANGES).
+*/
+
+static void ApplyChanges()
+{
+ // remove removed contacts
+ for (int i = 0; i < g_data.num_deleted; i++) {
+ if (Meta_Delete(g_data.hDeletedContacts[i], 0) != 0) // error, delete anyway
+ Meta_RemoveContactNumber(g_data.cc, Meta_GetContactNumber(g_data.cc, g_data.hDeletedContacts[i]), true);
+ if (g_data.hDeletedContacts[i] == g_data.hDefaultContact)
+ g_data.hDefaultContact = 0;
+ if (g_data.hDeletedContacts[i] == g_data.hOfflineContact)
+ g_data.hOfflineContact = 0;
+ }
+
+ // set contact positions
+ for (int i = 0; i < g_data.num_contacts; i++)
+ if (Meta_GetContactNumber(g_data.cc, g_data.hContact[i]) != i)
+ Meta_SwapContacts(g_data.cc, Meta_GetContactNumber(g_data.cc, g_data.hContact[i]), i);
+
+ NotifyEventHooks(hSubcontactsChanged, g_data.hMeta, g_data.hDefaultContact);
+
+ // set default
+ db_mc_setDefaultNum(g_data.hMeta, (g_data.hDefaultContact) ? Meta_GetContactNumber(g_data.cc, g_data.hDefaultContact) : 0, true);
+
+ // set offline
+ if (g_data.hOfflineContact)
+ db_set_dw(g_data.hMeta, META_PROTO, "OfflineSend", Meta_GetContactNumber(g_data.cc, g_data.hOfflineContact));
+ else
+ db_set_dw(g_data.hMeta, META_PROTO, "OfflineSend", INVALID_CONTACT_ID);
+
+ // fix nick
+ MCONTACT most_online = Meta_GetMostOnline(g_data.cc);
+ Meta_CopyContactNick(g_data.cc, most_online);
+
+ // fix status
+ Meta_FixStatus(g_data.cc);
+
+ // fix avatar
+ most_online = Meta_GetMostOnlineSupporting(g_data.cc, PFLAGNUM_4, PF4_AVATARS);
+ if (most_online) {
+ PROTO_AVATAR_INFORMATION ai = { 0 };
+ ai.hContact = g_data.hMeta;
+ ai.format = PA_FORMAT_UNKNOWN;
+ _tcsncpy_s(ai.filename, _T("X"), _TRUNCATE);
+ if (CallProtoService(META_PROTO, PS_GETAVATARINFO, 0, (LPARAM)&ai) == GAIR_SUCCESS)
+ db_set_ts(g_data.hMeta, "ContactPhoto", "File", ai.filename);
+ }
+}
+
+LRESULT ProcessCustomDraw(LPARAM lParam)
+{
+ LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;
+ switch (lplvcd->nmcd.dwDrawStage) {
+ case CDDS_PREPAINT: //Before the paint cycle begins
+ //request notifications for individual listview items
+ return CDRF_NOTIFYITEMDRAW;
+
+ case CDDS_ITEMPREPAINT: //Before an item is drawn
+ if (g_data.hContact[(int)lplvcd->nmcd.dwItemSpec] == g_data.hDefaultContact)
+ lplvcd->clrText = RGB(255, 0, 0);
+
+ return CDRF_NEWFONT;
+ }
+
+ return 0;
+}
+
+/** Callback function for the <b>'Edit'</b> Dialog.
+*
+* All the UI is controlled here, from display to functionnalities.
+*
+* @param hwndDlg : HANDLE to the <b>'Edit'</b> Dialog.
+* @param uMsg : Specifies the message received by this dialog.
+* @param wParam : Specifies additional message-specific information.
+* @param lParam : Specifies additional message-specific information (handle of MetaContact to edit)
+*
+* @return TRUE if the dialog processed the message, FALSE if it did not.
+*/
+
+#define WMU_SETTITLE (WM_USER + 1)
+
+static INT_PTR CALLBACK Meta_EditDialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LST_CONTACTS);
+ int sel;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIconEx(I_EDIT));
+ {
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(lParam);
+ if (cc == NULL) {
+ DestroyWindow(hwndDlg);
+ return FALSE;
+ }
+
+ // Disable the 'Apply' button.
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), FALSE);
+
+ ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT);
+
+ // Create list columns
+ LVCOLUMN LvCol = { 0 };
+ LvCol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
+
+ LvCol.pszText = TranslateT("Contact");
+ LvCol.cx = 150;
+ ListView_InsertColumn(hwndList, 0, &LvCol);
+
+ LvCol.pszText = TranslateT("ID");
+ LvCol.cx = 130;
+ ListView_InsertColumn(hwndList, 1, &LvCol);
+
+ LvCol.pszText = TranslateT("Protocol");
+ LvCol.cx = 100;
+ ListView_InsertColumn(hwndList, 2, &LvCol);
+
+ LvCol.pszText = TranslateT("Default");
+ LvCol.cx = 60;
+ ListView_InsertColumn(hwndList, 3, &LvCol);
+
+ LvCol.pszText = TranslateT("Send offline");
+ LvCol.cx = 85;
+ ListView_InsertColumn(hwndList, 4, &LvCol);
+
+ int offline_contact_number = db_get_dw(lParam, META_PROTO, "OfflineSend", INVALID_CONTACT_ID);
+
+ memset(&g_data, 0, sizeof(g_data));
+ g_data.cc = cc;
+ g_data.hMeta = lParam;
+ g_data.num_contacts = cc->nSubs;
+ g_data.num_deleted = 0;
+ g_data.hDefaultContact = Meta_GetContactHandle(g_data.cc, cc->nDefault);
+ g_data.hOfflineContact = Meta_GetContactHandle(g_data.cc, offline_contact_number);
+ for (int i = 0; i < cc->nSubs; i++)
+ g_data.hContact[i] = Meta_GetContactHandle(g_data.cc, i);
+
+ SendMessage(hwndDlg, WMU_SETTITLE, 0, lParam);
+ }
+ FillContactList(hwndList);
+ ListView_SetItemState(hwndList, 0, LVIS_FOCUSED | LVIS_SELECTED, 0x000F);
+ return TRUE;
+
+ case WMU_SETTITLE:
+ {
+ TCHAR *ptszCDN = cli.pfnGetContactDisplayName(lParam, 0);
+ if (ptszCDN == NULL)
+ ptszCDN = TranslateT("(Unknown contact)");
+
+ SetDlgItemText(hwndDlg, IDC_ED_NAME, ptszCDN);
+ }
+ return TRUE;
+
+ case WM_NOTIFY:
+ if (LOWORD(wParam) == IDC_LST_CONTACTS) {
+ LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
+ if (pnmv->hdr.code == LVN_ITEMCHANGED) {
+ int sel = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED | LVNI_SELECTED); // return item selected
+
+ // enable buttons
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_REM), sel != -1);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SETDEFAULT), sel != -1 && g_data.hContact[sel] != g_data.hDefaultContact);
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UP), sel > 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_DOWN), (sel != -1 && sel < g_data.num_contacts - 1));
+
+ HWND hwndOffline = GetDlgItem(hwndDlg, IDC_BTN_SETOFFLINE);
+ EnableWindow(hwndOffline, sel != -1);
+ SetWindowText(hwndOffline, (sel != -1 && g_data.hContact[sel] != g_data.hOfflineContact) ? TranslateT("Send &offline") : TranslateT("Send &online"));
+ }
+ }
+ break;
+
+ case WM_COMMAND: // the message that is being sent always
+ switch (HIWORD(wParam)) {
+ case BN_CLICKED: // A button ('Remove', 'OK', 'Cancel' or 'Apply', normally) has been clicked
+ switch (LOWORD(wParam)) {
+ case IDC_VALIDATE: // Apply changes, if there is still one contact attached to the metacontact.
+ if (g_data.num_contacts == 0) { // Otherwise, delete the metacontact.
+ if (IDYES == MessageBox(hwndDlg, TranslateT(szDelMsg), TranslateT("Delete metacontact?"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON1)) {
+ Meta_Delete(g_data.hMeta, 0);
+ DestroyWindow(hwndDlg);
+ }
+ return TRUE;
+ }
+ ApplyChanges();
+
+ // Disable the 'Apply' button.
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), FALSE);
+ break;
+
+ case IDOK:
+ if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_VALIDATE))) { // If there are changes that could be made,
+ if (g_data.num_contacts == 0) { // do the work that would have be done if the 'Apply' button was clicked.
+ if (IDYES == MessageBox(hwndDlg, TranslateT(szDelMsg), TranslateT("Delete metacontact?"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON1)) {
+ Meta_Delete(g_data.hMeta, 0);
+ DestroyWindow(hwndDlg);
+ }
+ return TRUE;
+ }
+ ApplyChanges();
+ }
+ EndDialog(hwndDlg, IDOK);
+ return TRUE;
+
+ case IDCANCEL: // Simply close the dialog
+ EndDialog(hwndDlg, IDCANCEL);
+ return TRUE;
+
+ case IDC_BTN_SETDEFAULT:
+ sel = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED | LVNI_SELECTED);
+ InvalidateRect(hwndList, 0, TRUE);
+ g_data.hDefaultContact = g_data.hContact[sel];
+ SendMessage(hwndDlg, WMU_SETTITLE, 0, (LPARAM)g_data.hContact[sel]);
+
+ FillContactList(hwndList);
+ SetListSelection(hwndList, sel);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SETDEFAULT), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), TRUE);
+ return TRUE;
+
+ case IDC_BTN_SETOFFLINE:
+ sel = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED | LVNI_SELECTED);
+ InvalidateRect(hwndList, 0, TRUE);
+ if (g_data.hContact[sel] != g_data.hOfflineContact)
+ g_data.hOfflineContact = g_data.hContact[sel];
+ else
+ g_data.hOfflineContact = 0;
+
+ FillContactList(hwndList);
+ SetListSelection(hwndList, sel);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), TRUE);
+ return TRUE;
+
+ case IDC_BTN_REM:
+ sel = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED | LVNI_SELECTED);
+ g_data.num_contacts--;
+ g_data.hDeletedContacts[g_data.num_deleted++] = g_data.hContact[sel];
+ if (g_data.hDefaultContact == g_data.hContact[sel]) {
+ if (g_data.num_contacts > 0) {
+ g_data.hDefaultContact = g_data.hContact[0];
+ SetDlgItemText(hwndDlg, IDC_ED_DEFAULT, cli.pfnGetContactDisplayName(g_data.hDefaultContact, 0));
+ }
+ else {
+ g_data.hDefaultContact = 0;
+ SetDlgItemText(hwndDlg, IDC_ED_DEFAULT, _T("None"));
+ }
+ }
+
+ for (int i = sel; i < g_data.num_contacts; i++)
+ g_data.hContact[i] = g_data.hContact[i + 1];
+ FillContactList(hwndList);
+
+ // disable buttons
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_REM), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_SETDEFAULT), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UP), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_DOWN), FALSE);
+
+ // Enable the 'Apply' button.
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), TRUE);
+ return TRUE;
+
+ case IDC_BTN_UP:
+ sel = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED | LVNI_SELECTED);
+ {
+ MCONTACT temp = g_data.hContact[sel];
+ g_data.hContact[sel] = g_data.hContact[sel - 1];
+ g_data.hContact[sel - 1] = temp;
+ }
+ FillContactList(hwndList);
+ sel--;
+ SetListSelection(hwndList, sel);
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UP), (sel > 0));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_DOWN), (sel < g_data.num_contacts - 1));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), TRUE);
+ return TRUE;
+
+ case IDC_BTN_DOWN:
+ sel = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED | LVNI_SELECTED);
+ {
+ MCONTACT temp = g_data.hContact[sel];
+ g_data.hContact[sel] = g_data.hContact[sel + 1];
+ g_data.hContact[sel + 1] = temp;
+ }
+ FillContactList(hwndList);
+ sel++;
+ SetListSelection(hwndList, sel);
+
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_UP), (sel > 0));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_DOWN), (sel < g_data.num_contacts - 1));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VALIDATE), TRUE);
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ return TRUE;
+
+ case WM_DESTROY:
+ Skin_ReleaseIcon((HICON)SendMessage(hwndDlg, WM_SETICON, ICON_BIG, 0));
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+
+ return FALSE;
+}
+
+/** Display the <b>'Edit'</b> Dialog
+*
+* Present a dialog in which the user can edit some properties of the MetaContact.
+*
+* @param wParam : HANDLE to the MetaContact to be edited.
+* @param lParam : Allways set to 0.
+*/
+
+INT_PTR Meta_Edit(WPARAM wParam, LPARAM)
+{
+ HWND clui = (HWND)CallService(MS_CLUI_GETHWND, 0, 0);
+ DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_METAEDIT), clui, Meta_EditDialogProc, (LPARAM)wParam);
+ return 0;
+}
diff --git a/src/mir_app/src/meta_main.cpp b/src/mir_app/src/meta_main.cpp new file mode 100644 index 0000000000..b13c607e64 --- /dev/null +++ b/src/mir_app/src/meta_main.cpp @@ -0,0 +1,85 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "metacontacts.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// icolib support
+
+static IconItem iconList[] = {
+ { LPGEN("Toggle off"), "off", IDI_MCMENUOFF },
+ { LPGEN("Toggle on"), "on", IDI_MCMENU },
+ { LPGEN("Convert to metacontact"), "convert", IDI_MCCONVERT },
+ { LPGEN("Add to existing"), "add", IDI_MCADD },
+ { LPGEN("Edit"), "edit", IDI_MCEDIT },
+ { LPGEN("Set to default"), "default", IDI_MCSETDEFAULT },
+ { LPGEN("Remove"), "remove", IDI_MCREMOVE },
+};
+
+HANDLE GetIconHandle(IconIndex i)
+{
+ return iconList[i].hIcolib;
+}
+
+HICON LoadIconEx(IconIndex i)
+{
+ return Skin_GetIcon(iconList[i].szName);
+}
+
+void UnloadMetacontacts(void)
+{
+ Meta_CloseHandles();
+}
+
+// Initializes the services provided and the link to those needed
+// Called when the plugin is loaded into Miranda
+int LoadMetacontacts(void)
+{
+ Icon_Register(g_hInst, LPGEN("MetaContacts"), iconList, SIZEOF(iconList), "mc");
+
+ db_set_resident(META_PROTO, "Status");
+ db_set_resident(META_PROTO, "IdleTS");
+
+ //set all contacts to 'offline', and initialize subcontact counter for db consistency check
+ for (MCONTACT hContact = db_find_first(META_PROTO); hContact; hContact = db_find_next(hContact, META_PROTO)) {
+ db_set_w(hContact, META_PROTO, "Status", ID_STATUS_OFFLINE);
+ db_set_dw(hContact, META_PROTO, "IdleTS", 0);
+ }
+
+ Meta_ReadOptions();
+
+ PROTOCOLDESCRIPTOR pd = { 0 };
+ pd.cbSize = sizeof(pd);
+ pd.szName = META_FILTER;
+ pd.type = PROTOTYPE_FILTER;
+ CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd);
+
+ pd.szName = META_PROTO;
+ pd.type = PROTOTYPE_VIRTUAL;
+ CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd);
+
+ // further db setup done in modules loaded (nick [protocol string required] & clist display name)
+ Meta_InitServices();
+ return 0;
+}
diff --git a/src/mir_app/src/meta_menu.cpp b/src/mir_app/src/meta_menu.cpp new file mode 100644 index 0000000000..b2c168b169 --- /dev/null +++ b/src/mir_app/src/meta_menu.cpp @@ -0,0 +1,448 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include <m_nudge.h>
+#include "metacontacts.h"
+
+static HGENMENU hMenuContact[MAX_CONTACTS];
+
+static HGENMENU
+ hMenuRoot, // root menu item of all subs
+ hMenuConvert, // HANDLE to the convert menu item.
+ hMenuAdd, // HANDLE to the add to menu item.
+ hMenuEdit, // HANDLE to the edit menu item.
+ hMenuDelete, // HANDLE to the delete menu item.
+ hMenuDefault, // HANDLE to the delete menu item.
+ hMenuForceDefault, // HANDLE to the delete menu item.
+ hMenuOnOff; // HANDLE to the enable/disable menu item.
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Convert the contact chosen into a MetaContact.
+//
+// Create a new MetaContact, remove the selected contact from the \c CList
+// and attach it to the MetaContact.
+//
+// @param wParam : HANDLE to the contact that has been chosen.
+// @param lParam : Allways set to 0.
+
+INT_PTR Meta_Convert(WPARAM wParam, LPARAM)
+{
+ ptrT tszGroup(db_get_tsa(wParam, "CList", "Group"));
+
+ // Create a new metacontact
+ MCONTACT hMetaContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0);
+ if (hMetaContact == NULL)
+ return NULL;
+
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hMetaContact);
+ if (cc == NULL)
+ return 0;
+
+ db_set_dw(hMetaContact, META_PROTO, "NumContacts", 0);
+ cc->nSubs = 0;
+ currDb->MetaSetDefault(cc); // explicitly write default sub to a db
+
+ // Add the MetaContact protocol to the new meta contact
+ CallService(MS_PROTO_ADDTOCONTACT, hMetaContact, (LPARAM)META_PROTO);
+
+ if (tszGroup)
+ db_set_ts(hMetaContact, "CList", "Group", tszGroup);
+
+ // Assign the contact to the MetaContact just created (and make default).
+ if (!Meta_Assign(wParam, hMetaContact, TRUE)) {
+ MessageBox(0, TranslateT("There was a problem in assigning the contact to the metacontact"), TranslateT("Error"), MB_ICONEXCLAMATION);
+ CallService(MS_DB_CONTACT_DELETE, hMetaContact, 0);
+ return 0;
+ }
+
+ // hide the contact if clist groups disabled (shouldn't create one anyway since menus disabled)
+ if (!db_mc_isEnabled())
+ db_set_b(hMetaContact, "CList", "Hidden", 1);
+
+ return hMetaContact;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Removes a sub from a metacontact
+
+void Meta_RemoveContactNumber(DBCachedContact *ccMeta, int number, bool bUpdateInfo)
+{
+ if (ccMeta == NULL)
+ return;
+
+ // make sure this contact thinks it's part of this metacontact
+ DBCachedContact *ccSub = currDb->m_cache->GetCachedContact(Meta_GetContactHandle(ccMeta, number));
+ if (ccSub != NULL) {
+ if (ccSub->parentID == ccMeta->contactID) {
+ db_unset(ccSub->contactID, "CList", "Hidden");
+
+ // stop ignoring, if we were
+ if (options.bSuppressStatus)
+ CallService(MS_IGNORE_UNIGNORE, ccSub->contactID, IGNOREEVENT_USERONLINE);
+ }
+ }
+
+ // each contact from 'number' upwards will be moved down one
+ // and the last one will be deleted
+ for (int i = number+1; i < ccMeta->nSubs; i++)
+ Meta_SwapContacts(ccMeta, i, i - 1);
+
+ // remove the last one
+ int id = ccMeta->nSubs - 1;
+ char buffer[512];
+ mir_snprintf(buffer, "Handle%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ mir_snprintf(buffer, "Protocol%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ mir_snprintf(buffer, "Status%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ mir_snprintf(buffer, "StatusString%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ mir_snprintf(buffer, "Login%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ mir_snprintf(buffer, "Nick%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ mir_snprintf(buffer, "CListName%d", id);
+ db_unset(ccMeta->contactID, META_PROTO, buffer);
+
+ if (ccSub != NULL) {
+ ccSub->parentID = 0;
+ currDb->MetaDetouchSub(ccMeta, ccMeta->nSubs - 1);
+
+ currDb->MetaSplitHistory(ccMeta, ccSub);
+ }
+
+ // if the default contact was equal to or greater than 'number', decrement it (and deal with ends)
+ if (ccMeta->nDefault >= number) {
+ int iNumber = ccMeta->nDefault-1;
+ if (iNumber < 0)
+ iNumber = 0;
+ db_mc_setDefaultNum(ccMeta->contactID, iNumber, true);
+ }
+
+ ccMeta->nSubs--;
+ db_set_dw(ccMeta->contactID, META_PROTO, "NumContacts", ccMeta->nSubs);
+
+ if (bUpdateInfo) {
+ // fix nick
+ Meta_CopyContactNick(ccMeta, Meta_GetMostOnline(ccMeta));
+
+ // fix status
+ Meta_FixStatus(ccMeta);
+
+ // fix avatar
+ MCONTACT hContact = Meta_GetMostOnlineSupporting(ccMeta, PFLAGNUM_4, PF4_AVATARS);
+ if (hContact) {
+ PROTO_AVATAR_INFORMATION ai = { 0 };
+ ai.hContact = ccMeta->contactID;
+ ai.format = PA_FORMAT_UNKNOWN;
+ _tcsncpy_s(ai.filename, _T("X"), _TRUNCATE);
+
+ if (CallProtoService(META_PROTO, PS_GETAVATARINFO, 0, (LPARAM)&ai) == GAIR_SUCCESS)
+ db_set_ts(ccMeta->contactID, "ContactPhoto", "File", ai.filename);
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Delete a MetaContact from the database
+//
+// Delete a MetaContact and remove all the information
+// concerning this MetaContact in the contact linked to it.
+//
+// @param wParam : HANDLE to the MetaContact to be deleted, or to the subcontact to be removed from the MetaContact
+// @param lParam : BOOL flag indicating whether to ask 'are you sure' when deleting a MetaContact
+
+INT_PTR Meta_Delete(WPARAM hContact, LPARAM bSkipQuestion)
+{
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hContact);
+ if (cc == NULL)
+ return 1;
+
+ // The wParam is a metacontact
+ if (cc->IsMeta()) {
+ // check from recursion - see second half of this function
+ if (!bSkipQuestion && IDYES !=
+ MessageBox((HWND)CallService(MS_CLUI_GETHWND, 0, 0),
+ TranslateT("This will remove the metacontact permanently.\n\nProceed anyway?"),
+ TranslateT("Are you sure?"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2))
+ return 0;
+
+ for (int i = cc->nSubs-1; i >= 0; i--)
+ Meta_RemoveContactNumber(cc, i, false);
+
+ NotifyEventHooks(hSubcontactsChanged, hContact, 0);
+ CallService(MS_DB_CONTACT_DELETE, hContact, 0);
+ }
+ else if (cc->IsSub()) {
+ if ((cc = currDb->m_cache->GetCachedContact(cc->parentID)) == NULL)
+ return 2;
+
+ if (cc->nSubs == 1) {
+ if (IDYES == MessageBox(0, TranslateT(szDelMsg), TranslateT("Delete metacontact?"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON1))
+ Meta_Delete(cc->contactID, 1);
+
+ return 0;
+ }
+
+ Meta_RemoveContactNumber(cc, Meta_GetContactNumber(cc, hContact), true);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Set contact as MetaContact default
+//
+// Set the given contact to be the default one for the metacontact to which it is linked.
+//
+// @param wParam : HANDLE to the MetaContact to be set as default
+// @param lParam : HWND to the clist window
+// (This means the function has been called via the contact menu).
+
+INT_PTR Meta_Default(WPARAM hSub, LPARAM)
+{
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(db_mc_getMeta(hSub));
+ if (cc && cc->IsMeta())
+ db_mc_setDefault(cc->contactID, hSub, true);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Called when the context-menu of a contact is about to be displayed
+//
+// This will test which of the 4 menu item should be displayed, depending
+// on which contact triggered the event
+//
+// @param wParam : HANDLE to the contact that triggered the event
+// @param lParam : Always set to 0;
+
+int Meta_ModifyMenu(WPARAM hMeta, LPARAM)
+{
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hMeta);
+ if (cc == NULL)
+ return 0;
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+ Menu_ShowItem(hMenuRoot, false);
+
+ if (cc->IsMeta()) {
+ // save the mouse pos in case they open a subcontact menu
+ GetCursorPos(&menuMousePoint);
+
+ // This is a MetaContact, show the edit, force default, and the delete menu, and hide the others
+ Menu_ShowItem(hMenuEdit, true);
+ Menu_ShowItem(hMenuAdd, false);
+ Menu_ShowItem(hMenuConvert, false);
+ Menu_ShowItem(hMenuDefault, false);
+ Menu_ShowItem(hMenuDelete, false);
+
+ mi.flags = CMIM_NAME;
+ mi.pszName = LPGEN("Remove from metacontact");
+ Menu_ModifyItem(hMenuDelete, &mi);
+
+ // show subcontact menu items
+ CMString tszNick;
+ for (int i = 0; i < MAX_CONTACTS; i++) {
+ if (i >= cc->nSubs) {
+ Menu_ShowItem(hMenuContact[i], false);
+ continue;
+ }
+
+ MCONTACT hContact = Meta_GetContactHandle(cc, i);
+
+ if (options.menu_contact_label == DNT_UID) {
+ Meta_GetSubNick(hMeta, i, tszNick);
+ mi.ptszName = tszNick.GetBuffer();
+ }
+ else mi.ptszName = cli.pfnGetContactDisplayName(hContact, 0);
+
+ mi.flags = CMIF_TCHAR | CMIM_FLAGS | CMIM_NAME | CMIM_ICON;
+
+ int iconIndex = CallService(MS_CLIST_GETCONTACTICON, hContact, 0);
+ mi.hIcon = ImageList_GetIcon((HIMAGELIST)CallService(MS_CLIST_GETICONSIMAGELIST, 0, 0), iconIndex, 0);
+
+ Menu_ModifyItem(hMenuContact[i], &mi);
+ DestroyIcon(mi.hIcon);
+
+ Menu_ShowItem(hMenuRoot, true);
+ }
+
+ // show hide nudge menu item
+ char serviceFunc[256];
+ mir_snprintf(serviceFunc, SIZEOF(serviceFunc), "%s%s", GetContactProto(Meta_GetMostOnline(cc)), PS_SEND_NUDGE);
+ CallService(MS_NUDGE_SHOWMENU, (WPARAM)META_PROTO, ServiceExists(serviceFunc));
+ return 0;
+ }
+
+ PROTOACCOUNT *pa = Proto_GetAccount(cc->szProto);
+ if (!db_mc_isEnabled() || !pa || pa->bIsVirtual) {
+ // groups disabled - all meta menu options hidden
+ Menu_ShowItem(hMenuDefault, false);
+ Menu_ShowItem(hMenuDelete, false);
+ Menu_ShowItem(hMenuAdd, false);
+ Menu_ShowItem(hMenuConvert, false);
+ Menu_ShowItem(hMenuEdit, false);
+ return 0;
+ }
+
+ // the contact is affected to a metacontact
+ if (cc->IsSub()) {
+ Menu_ShowItem(hMenuDefault, true);
+
+ mi.flags = CMIM_NAME;
+ mi.pszName = LPGEN("Remove from metacontact");
+ Menu_ModifyItem(hMenuDelete, &mi);
+ Menu_ShowItem(hMenuDelete, true);
+
+ Menu_ShowItem(hMenuAdd, false);
+ Menu_ShowItem(hMenuConvert, false);
+ Menu_ShowItem(hMenuEdit, false);
+ }
+ else {
+ // The contact is neutral
+ bool bHideChat = db_get_b(hMeta, cc->szProto, "ChatRoom", 0) == 0;
+ Menu_ShowItem(hMenuAdd, bHideChat);
+ Menu_ShowItem(hMenuConvert, bHideChat);
+ Menu_ShowItem(hMenuEdit, false);
+ Menu_ShowItem(hMenuDelete, false);
+ Menu_ShowItem(hMenuDefault, false);
+ }
+
+ for (int i = 0; i < MAX_CONTACTS; i++)
+ Menu_ShowItem(hMenuContact[i], false);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Toggle metacontacts on/off
+
+INT_PTR Meta_OnOff(WPARAM, LPARAM)
+{
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIM_NAME | CMIM_ICON;
+
+ bool bToggled = !db_mc_isEnabled();
+ db_set_b(0, META_PROTO, "Enabled", bToggled);
+ if (bToggled) {
+ mi.icolibItem = GetIconHandle(I_MENUOFF);
+ mi.pszName = LPGEN("Toggle metacontacts off");
+ }
+ else {
+ mi.icolibItem = GetIconHandle(I_MENU);
+ mi.pszName = LPGEN("Toggle metacontacts on");
+ }
+ Menu_ModifyItem(hMenuOnOff, &mi);
+
+ db_mc_enable(bToggled);
+ Meta_HideMetaContacts(!bToggled);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Menu initialization
+
+void InitMenus()
+{
+ CLISTMENUITEM mi = { sizeof(mi) };
+
+ // main menu item
+ mi.icolibItem = GetIconHandle(I_MENUOFF);
+ mi.pszName = LPGEN("Toggle metacontacts off");
+ mi.pszService = "MetaContacts/OnOff";
+ mi.position = 500010000;
+ hMenuOnOff = Menu_AddMainMenuItem(&mi);
+
+ // contact menu items
+ mi.icolibItem = GetIconHandle(I_CONVERT);
+ mi.position = -200010;
+ mi.pszName = LPGEN("Convert to metacontact");
+ mi.pszService = "MetaContacts/Convert";
+ hMenuConvert = Menu_AddContactMenuItem(&mi);
+
+ mi.icolibItem = GetIconHandle(I_ADD);
+ mi.position = -200009;
+ mi.pszName = LPGEN("Add to existing metacontact...");
+ mi.pszService = "MetaContacts/AddTo";
+ hMenuAdd = Menu_AddContactMenuItem(&mi);
+
+ mi.icolibItem = GetIconHandle(I_EDIT);
+ mi.position = -200010;
+ mi.pszName = LPGEN("Edit metacontact...");
+ mi.pszService = "MetaContacts/Edit";
+ hMenuEdit = Menu_AddContactMenuItem(&mi);
+
+ mi.icolibItem = GetIconHandle(I_SETDEFAULT);
+ mi.position = -200009;
+ mi.pszName = LPGEN("Set as metacontact default");
+ mi.pszService = "MetaContacts/Default";
+ hMenuDefault = Menu_AddContactMenuItem(&mi);
+
+ mi.icolibItem = GetIconHandle(I_REMOVE);
+ mi.position = -200008;
+ mi.pszName = LPGEN("Delete metacontact");
+ mi.pszService = "MetaContacts/Delete";
+ hMenuDelete = Menu_AddContactMenuItem(&mi);
+
+ mi.position = -99000;
+ mi.flags = CMIF_HIDDEN | CMIF_ROOTPOPUP;
+ mi.icolibItem = 0;
+ mi.pszName = LPGEN("Subcontacts");
+ hMenuRoot = Menu_AddContactMenuItem(&mi);
+
+ mi.flags = CMIF_HIDDEN | CMIF_CHILDPOPUP;
+ mi.hParentMenu = hMenuRoot;
+ for (int i = 0; i < MAX_CONTACTS; i++) {
+ mi.position--;
+ mi.pszName = "";
+
+ char buffer[512];
+ mir_snprintf(buffer, "MetaContacts/MenuFunc%d", i);
+ mi.pszService = buffer;
+ mi.position++;
+ hMenuContact[i] = Menu_AddContactMenuItem(&mi);
+ }
+
+ Meta_HideLinkedContacts();
+
+ if (!db_mc_isEnabled()) {
+ // modify main menu item
+ mi.flags = CMIM_NAME | CMIM_ICON;
+ mi.icolibItem = GetIconHandle(I_MENU);
+ mi.pszName = LPGEN("Toggle metacontacts on");
+ Menu_ModifyItem(hMenuOnOff, &mi);
+
+ Meta_HideMetaContacts(true);
+ }
+ else {
+ Meta_SuppressStatus(options.bSuppressStatus);
+ Meta_HideMetaContacts(false);
+ }
+}
diff --git a/src/mir_app/src/meta_options.cpp b/src/mir_app/src/meta_options.cpp new file mode 100644 index 0000000000..2588c7d8c9 --- /dev/null +++ b/src/mir_app/src/meta_options.cpp @@ -0,0 +1,129 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "metacontacts.h"
+
+MetaOptions options;
+
+int Meta_WriteOptions()
+{
+ db_set_b(NULL, META_PROTO, "LockHandle", options.bLockHandle);
+ db_set_b(NULL, META_PROTO, "SuppressStatus", options.bSuppressStatus);
+ db_set_w(NULL, META_PROTO, "MenuContactLabel", (WORD)options.menu_contact_label);
+ db_set_w(NULL, META_PROTO, "MenuContactFunction", (WORD)options.menu_function);
+ db_set_w(NULL, META_PROTO, "CListContactName", (WORD)options.clist_contact_name);
+ db_set_dw(NULL, META_PROTO, "SetStatusFromOfflineDelay", (DWORD)(options.set_status_from_offline_delay));
+ return 0;
+}
+
+int Meta_ReadOptions()
+{
+ db_mc_enable(db_get_b(NULL, META_PROTO, "Enabled", true) != 0);
+ options.bSuppressStatus = db_get_b(NULL, META_PROTO, "SuppressStatus", true) != 0;
+ options.menu_contact_label = (int)db_get_w(NULL, META_PROTO, "MenuContactLabel", DNT_UID);
+ options.menu_function = (int)db_get_w(NULL, META_PROTO, "MenuContactFunction", FT_MENU);
+ options.clist_contact_name = (int)db_get_w(NULL, META_PROTO, "CListContactName", CNNT_DISPLAYNAME);
+ options.set_status_from_offline_delay = (int)db_get_dw(NULL, META_PROTO, "SetStatusFromOfflineDelay", DEFAULT_SET_STATUS_SLEEP_TIME);
+ options.bLockHandle = db_get_b(NULL, META_PROTO, "LockHandle", false) != 0;
+ return 0;
+}
+
+class CMetaOptionsDlg : public CDlgBase
+{
+ CCtrlCheck m_btnUid, m_btnDid, m_btnCheck;
+ CCtrlCheck m_btnMsg, m_btnMenu, m_btnInfo;
+ CCtrlCheck m_btnNick, m_btnName, m_btnLock;
+
+public:
+ CMetaOptionsDlg() :
+ CDlgBase(g_hInst, IDD_METAOPTIONS),
+ m_btnUid(this, IDC_RAD_UID),
+ m_btnDid(this, IDC_RAD_DID),
+ m_btnMsg(this, IDC_RAD_MSG),
+ m_btnMenu(this, IDC_RAD_MENU),
+ m_btnInfo(this, IDC_RAD_INFO),
+ m_btnNick(this, IDC_RAD_NICK),
+ m_btnName(this, IDC_RAD_NAME),
+ m_btnLock(this, IDC_CHK_LOCKHANDLE),
+ m_btnCheck(this, IDC_CHK_SUPPRESSSTATUS)
+ {
+ }
+
+ virtual void OnInitDialog()
+ {
+ m_btnLock.SetState(options.bLockHandle);
+ m_btnCheck.SetState(options.bSuppressStatus);
+
+ if (options.menu_contact_label == DNT_UID)
+ m_btnUid.SetState(true);
+ else
+ m_btnDid.SetState(true);
+
+ switch (options.menu_function) {
+ case FT_MSG: m_btnMsg.SetState(true); break;
+ case FT_MENU: m_btnMenu.SetState(true); break;
+ case FT_INFO: m_btnInfo.SetState(true); break;
+ }
+
+ if (options.clist_contact_name == CNNT_NICK)
+ m_btnNick.SetState(true);
+ else
+ m_btnName.SetState(true);
+ }
+
+ virtual void OnApply()
+ {
+ options.bLockHandle = m_btnLock.GetState() != 0;
+ options.bSuppressStatus = m_btnCheck.GetState() != 0;
+
+ if (m_btnUid.GetState()) options.menu_contact_label = DNT_UID;
+ else if (m_btnDid.GetState()) options.menu_contact_label = DNT_DID;
+
+ if (m_btnMsg.GetState()) options.menu_function = FT_MSG;
+ else if (m_btnMenu.GetState()) options.menu_function = FT_MENU;
+ else if (m_btnInfo.GetState()) options.menu_function = FT_INFO;
+
+ if (m_btnNick.GetState()) options.clist_contact_name = CNNT_NICK;
+ else if (m_btnName.GetState()) options.clist_contact_name = CNNT_DISPLAYNAME;
+
+ Meta_WriteOptions();
+
+ Meta_SuppressStatus(options.bSuppressStatus);
+ Meta_SetAllNicks();
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int Meta_OptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.position = -790000000;
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pszTitle = LPGEN("Metacontacts");
+ odp.pszGroup = LPGEN("Contacts");
+ odp.pDialog = new CMetaOptionsDlg();
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/meta_services.cpp b/src/mir_app/src/meta_services.cpp new file mode 100644 index 0000000000..e4e40b2891 --- /dev/null +++ b/src/mir_app/src/meta_services.cpp @@ -0,0 +1,912 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include <m_nudge.h>
+
+#include "metacontacts.h"
+
+extern "C" MIR_CORE_DLL(void) db_mc_notifyDefChange(WPARAM wParam, LPARAM lParam);
+
+char *pendingACK = 0; // Name of the protocol in which an ACK is about to come.
+
+int previousMode, // Previous status of the MetaContacts Protocol
+mcStatus; // Current status of the MetaContacts Protocol
+
+HANDLE
+hSubcontactsChanged, // HANDLE to the 'contacts changed' event
+hEventNudge;
+
+UINT_PTR setStatusTimerId = 0;
+BOOL firstSetOnline = TRUE; // see Meta_SetStatus function
+
+OBJLIST<MetaSrmmData> arMetaWindows(1, NumericKeySortT);
+
+/** Get the capabilities of the "MetaContacts" protocol.
+*
+* @param wParam : equals to one of the following values :\n
+<tt> PFLAGNUM_1 | PFLAGNUM_2 | PFLAGNUM_3 | PFLAGNUM_4 | PFLAG_UNIQUEIDTEXT | PFLAG_MAXLENOFMESSAGE | PFLAG_UNIQUEIDSETTING </tt>.
+* @param lParam : Allways set to 0.
+*
+* @return Depending on the \c WPARAM.
+*/
+INT_PTR Meta_GetCaps(WPARAM wParam, LPARAM)
+{
+ switch (wParam) {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_CHAT | PF1_FILESEND | PF1_MODEMSGRECV | PF1_NUMERICUSERID;
+
+ case PFLAGNUM_2:
+ return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE;
+
+ case PFLAGNUM_3:
+ return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE;
+
+ case PFLAGNUM_4:
+ return PF4_SUPPORTTYPING | PF4_AVATARS;
+
+ case PFLAGNUM_5:
+ return PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE;
+
+ case PFLAG_MAXLENOFMESSAGE:
+ return 2000;
+ }
+ return 0;
+}
+
+/** Copy the name of the protocole into lParam
+* @param wParam : max size of the name
+* @param lParam : reference to a char *, which will hold the name
+*/
+
+INT_PTR Meta_GetName(WPARAM wParam, LPARAM lParam)
+{
+ char *name = (char *)Translate(META_PROTO);
+ size_t size = min(mir_strlen(name), wParam - 1); // copy only the first size bytes.
+ if (strncpy((char *)lParam, name, size) == NULL)
+ return 1;
+ ((char *)lParam)[size] = '\0';
+ return 0;
+}
+
+/** Loads the icon corresponding to the status
+* Called by the CList when the status changes.
+* @param wParam : one of the following values : \n
+<tt>PLI_PROTOCOL | PLI_ONLINE | PLI_OFFLINE</tt>
+* @return an \c HICON in which the icon has been loaded.
+*/
+
+INT_PTR Meta_LoadIcon(WPARAM wParam, LPARAM)
+{
+ UINT id;
+ switch (wParam & 0xFFFF) {
+ case PLI_PROTOCOL:
+ id = IDI_MCMENU;
+ break;
+ case PLI_ONLINE:
+ id = IDI_MCMENU;
+ break;
+ case PLI_OFFLINE:
+ id = IDI_MCMENU;
+ break;
+ default:
+ return 0;
+ }
+
+ return (INT_PTR)LoadImage(g_hInst, MAKEINTRESOURCE(id), IMAGE_ICON,
+ GetSystemMetrics(wParam & PLIF_SMALL ? SM_CXSMICON : SM_CXICON),
+ GetSystemMetrics(wParam & PLIF_SMALL ? SM_CYSMICON : SM_CYICON), 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void Meta_SetSrmmSub(MCONTACT hMeta, MCONTACT hSub)
+{
+ MetaSrmmData tmp = { hMeta };
+ if (MetaSrmmData *p = arMetaWindows.find(&tmp))
+ p->m_hSub = hSub;
+}
+
+static INT_PTR MetaFilter_RecvMessage(WPARAM wParam, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(ccs->hContact);
+ if (cc && cc->IsSub())
+ Meta_SetSrmmSub(cc->parentID, cc->contactID);
+
+ CallService(MS_PROTO_CHAINRECV, wParam, lParam);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CALLBACK SetStatusThread(HWND, UINT, UINT_PTR, DWORD)
+{
+ previousMode = mcStatus;
+
+ mcStatus = ID_STATUS_ONLINE;
+ ProtoBroadcastAck(META_PROTO, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)previousMode, mcStatus);
+
+ KillTimer(0, setStatusTimerId);
+}
+
+/** Changes the status and notifies everybody
+* @param wParam : The new mode
+* @param lParam : Allways set to 0.
+*/
+
+INT_PTR Meta_SetStatus(WPARAM wParam, LPARAM)
+{
+ // firstSetOnline starts out true - used to delay metacontact's 'onlineness' to prevent double status notifications on startup
+ if (mcStatus == ID_STATUS_OFFLINE && firstSetOnline) {
+ // causes crash on exit if miranda is closed in under options.set_status_from_offline milliseconds!
+ setStatusTimerId = SetTimer(0, 0, options.set_status_from_offline_delay, SetStatusThread);
+ firstSetOnline = FALSE;
+ }
+ else {
+ previousMode = mcStatus;
+ mcStatus = (int)wParam;
+ ProtoBroadcastAck(META_PROTO, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)previousMode, mcStatus);
+ }
+ return 0;
+}
+
+/** Returns the current status
+*/
+INT_PTR Meta_GetStatus(WPARAM, LPARAM)
+{
+ return mcStatus;
+}
+
+//////////////////////////////////////////////////////////
+/// Copied from MSN plugin - sent acks need to be from different thread :(
+//////////////////////////////////////////////////////////
+
+struct TFakeAckParams
+{
+ HANDLE hEvent;
+ MCONTACT hContact;
+ LONG id;
+ char msg[512];
+};
+
+static void __cdecl sttFakeAckFail(void *param)
+{
+ TFakeAckParams *tParam = (TFakeAckParams*)param;
+ WaitForSingleObject(tParam->hEvent, INFINITE);
+
+ Sleep(100);
+ ProtoBroadcastAck(META_PROTO, tParam->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)tParam->id, (WPARAM)tParam->msg);
+
+ CloseHandle(tParam->hEvent);
+ mir_free(tParam);
+}
+
+INT_PTR Meta_SendNudge(WPARAM wParam, LPARAM lParam)
+{
+ DBCachedContact *cc = CheckMeta(wParam);
+ if (cc == NULL)
+ return 1;
+
+ MCONTACT hSubContact = Meta_GetMostOnline(cc);
+ return CallProtoService(GetContactProto(hSubContact), PS_SEND_NUDGE, hSubContact, lParam);
+}
+
+/** Send a message to the protocol specific network.
+*
+* Call the function specific to the protocol that belongs
+* to the contact chosen to send the message.
+*
+* @param wParam : index of the protocol in the protocol chain.
+* @param lParam : CCSDATA structure holding all the information abour rhe message.
+*
+* @return 0 on success, 1 otherwise.
+*/
+
+INT_PTR Meta_SendMessage(WPARAM wParam, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+
+ DBCachedContact *cc = CheckMeta(ccs->hContact);
+ if (cc == NULL || cc->nDefault == -1) {
+ // This is a simple contact, let through the stack of protocols
+ // (this should normally not happen, since linked contacts do not appear on the list.)
+ return CallService(MS_PROTO_CHAINSEND, wParam, lParam);
+ }
+
+ MCONTACT hMostOnline = db_mc_getSrmmSub(cc->contactID);
+ if (!hMostOnline) {
+ // send failure to notify user of reason
+ HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ TFakeAckParams *tfap = (TFakeAckParams *)mir_alloc(sizeof(TFakeAckParams));
+ tfap->hContact = ccs->hContact;
+ tfap->hEvent = hEvent;
+ tfap->id = 10;
+ strncpy(tfap->msg, Translate("No online contacts found."), SIZEOF(tfap->msg) - 1);
+
+ CloseHandle(mir_forkthread(sttFakeAckFail, (void*)tfap));
+ SetEvent(hEvent);
+ return 10;
+ }
+
+ Meta_CopyContactNick(cc, hMostOnline);
+
+ ccs->hContact = hMostOnline;
+ char *proto = GetContactProto(hMostOnline);
+ Meta_SetNick(proto); // (no matter what was there before)
+
+ return CallContactService(ccs->hContact, PSS_MESSAGE, ccs->wParam, ccs->lParam);
+}
+
+/** Called when an ACK is received.
+*
+* Retransmit the ACK sent by a simple contact so that it
+* looks like it was the MetaContact that sends the ACK.
+*
+* @param wParam : Allways set to 0.
+* @param lParam : Reference to a ACKDATA that contains information about the ACK.
+* @returns 0 on success, 1 otherwise.
+*/
+
+int Meta_HandleACK(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA*)lParam;
+ if (ack == NULL)
+ return 0;
+ DBCachedContact *cc = CheckMeta(ack->hContact);
+ if (cc == NULL)
+ return 0;
+
+ if (!mir_strcmp(ack->szModule, META_PROTO))
+ return 0; // don't rebroadcast our own acks
+
+ // if it's for something we don't support, ignore
+ if (ack->type != ACKTYPE_MESSAGE && ack->type != ACKTYPE_CHAT && ack->type != ACKTYPE_FILE && ack->type != ACKTYPE_AWAYMSG && ack->type != ACKTYPE_AVATAR && ack->type != ACKTYPE_GETINFO)
+ return 0;
+
+ // change the hContact in the avatar info struct, if it's the avatar we're using - else drop it
+ if (ack->type == ACKTYPE_AVATAR) {
+ if (ack->result == ACKRESULT_SUCCESS || ack->result == ACKRESULT_FAILED || ack->result == ACKRESULT_STATUS) {
+ DBVARIANT dbv;
+
+ // change avatar if the most online supporting avatars changes, or if we don't have one
+ MCONTACT hMostOnline = Meta_GetMostOnlineSupporting(cc, PFLAGNUM_4, PF4_AVATARS);
+ if (ack->hContact == 0 || ack->hContact != hMostOnline)
+ return 0;
+
+ if (!db_get(ack->hContact, "ContactPhoto", "File", &dbv)) {
+ db_set_ts(cc->contactID, "ContactPhoto", "File", dbv.ptszVal);
+ db_free(&dbv);
+ }
+
+ if (ack->hProcess) {
+ PROTO_AVATAR_INFORMATION ai;
+ memcpy(&ai, (PROTO_AVATAR_INFORMATION*)ack->hProcess, sizeof(ai));
+ if (ai.hContact)
+ ai.hContact = cc->contactID;
+
+ return ProtoBroadcastAck(META_PROTO, cc->contactID, ack->type, ack->result, (HANDLE)&ai, ack->lParam);
+ }
+
+ return ProtoBroadcastAck(META_PROTO, cc->contactID, ack->type, ack->result, 0, ack->lParam);
+ }
+ }
+
+ return ProtoBroadcastAck(META_PROTO, cc->contactID, ack->type, ack->result, ack->hProcess, ack->lParam);
+}
+
+/** Call whenever a contact changes one of its settings (for example, the status)
+**
+* @param wParam HANDLE to the contact that has change of its setting.
+* @param lParam Reference to a structure that contains the setting that has changed (not used)
+*/
+
+int Meta_SettingChanged(WPARAM hContact, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *dcws = (DBCONTACTWRITESETTING *)lParam;
+ char buffer[512];
+
+ // the only global options we're interested in
+ if (hContact == 0)
+ return 0;
+
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hContact);
+ if (cc == NULL || !cc->IsSub())
+ return 0;
+
+ DBCachedContact *ccMeta = currDb->m_cache->GetCachedContact(cc->parentID);
+ if (ccMeta == NULL || !ccMeta->IsMeta())
+ return 0;
+
+ // This contact is attached to a MetaContact.
+ int contact_number = Meta_GetContactNumber(ccMeta, hContact);
+ if (contact_number == -1)
+ return 0; // exit - db corruption
+
+ if (!mir_strcmp(dcws->szSetting, "IP")) {
+ if (dcws->value.type == DBVT_DWORD)
+ db_set_dw(ccMeta->contactID, META_PROTO, "IP", dcws->value.dVal);
+ else
+ db_unset(ccMeta->contactID, META_PROTO, "IP");
+ }
+ else if (!mir_strcmp(dcws->szSetting, "RealIP")) {
+ if (dcws->value.type == DBVT_DWORD)
+ db_set_dw(ccMeta->contactID, META_PROTO, "RealIP", dcws->value.dVal);
+ else
+ db_unset(ccMeta->contactID, META_PROTO, "RealIP");
+ }
+ else if (!mir_strcmp(dcws->szSetting, "ListeningTo")) {
+ switch (dcws->value.type) {
+ case DBVT_ASCIIZ:
+ db_set_s(ccMeta->contactID, META_PROTO, "ListeningTo", dcws->value.pszVal);
+ break;
+ case DBVT_UTF8:
+ db_set_utf(ccMeta->contactID, META_PROTO, "ListeningTo", dcws->value.pszVal);
+ break;
+ case DBVT_WCHAR:
+ db_set_ws(ccMeta->contactID, META_PROTO, "ListeningTo", dcws->value.pwszVal);
+ break;
+ case DBVT_DELETED:
+ db_unset(ccMeta->contactID, META_PROTO, "ListeningTo");
+ break;
+ }
+ }
+ else if (!mir_strcmp(dcws->szSetting, "Nick") && dcws->value.type != DBVT_DELETED) {
+ // subcontact nick has changed - update metacontact
+ mir_snprintf(buffer, "Nick%d", contact_number);
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dcws->value);
+
+ ptrT tszMyhandle(db_get_tsa(hContact, "CList", "MyHandle"));
+ if (tszMyhandle == NULL) {
+ mir_snprintf(buffer, "CListName%d", contact_number);
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dcws->value);
+ }
+
+ // copy nick to metacontact, if it's the most online
+ MCONTACT hMostOnline = Meta_GetMostOnline(ccMeta);
+ Meta_CopyContactNick(ccMeta, hMostOnline);
+ }
+ else if (!mir_strcmp(dcws->szSetting, "IdleTS")) {
+ if (dcws->value.type == DBVT_DWORD)
+ db_set_dw(ccMeta->contactID, META_PROTO, "IdleTS", dcws->value.dVal);
+ else if (dcws->value.type == DBVT_DELETED)
+ db_set_dw(ccMeta->contactID, META_PROTO, "IdleTS", 0);
+ }
+ else if (!mir_strcmp(dcws->szSetting, "LogonTS")) {
+ if (dcws->value.type == DBVT_DWORD)
+ db_set_dw(ccMeta->contactID, META_PROTO, "LogonTS", dcws->value.dVal);
+ else if (dcws->value.type == DBVT_DELETED)
+ db_set_dw(ccMeta->contactID, META_PROTO, "LogonTS", 0);
+ }
+ else if (!mir_strcmp(dcws->szModule, "CList") && !mir_strcmp(dcws->szSetting, "MyHandle")) {
+ if (dcws->value.type == DBVT_DELETED) {
+ char *proto = GetContactProto(hContact);
+ mir_snprintf(buffer, "CListName%d", contact_number);
+
+ DBVARIANT dbv;
+ if (proto && !db_get_ts(hContact, proto, "Nick", &dbv)) {
+ db_set_ts(ccMeta->contactID, META_PROTO, buffer, dbv.ptszVal);
+ db_free(&dbv);
+ }
+ else db_unset(ccMeta->contactID, META_PROTO, buffer);
+ }
+ else {
+ // subcontact clist displayname has changed - update metacontact
+ mir_snprintf(buffer, "CListName%d", contact_number);
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dcws->value);
+ }
+
+ // copy nick to metacontact, if it's the most online
+ Meta_CopyContactNick(ccMeta, Meta_GetMostOnline(ccMeta));
+ }
+ // subcontact changing status
+ else if (!mir_strcmp(dcws->szSetting, "Status") && dcws->value.type != DBVT_DELETED) {
+ // update subcontact status setting
+ mir_snprintf(buffer, "Status%d", contact_number);
+ db_set_w(ccMeta->contactID, META_PROTO, buffer, dcws->value.wVal);
+
+ mir_snprintf(buffer, "StatusString%d", contact_number);
+ db_set_ts(ccMeta->contactID, META_PROTO, buffer, cli.pfnGetStatusModeDescription(dcws->value.wVal, 0));
+
+ // set status to that of most online contact
+ MCONTACT hMostOnline = Meta_GetMostOnline(ccMeta);
+ if (hMostOnline != db_mc_getDefault(ccMeta->contactID))
+ db_mc_notifyDefChange(ccMeta->contactID, hMostOnline);
+
+ Meta_CopyContactNick(ccMeta, hMostOnline);
+ Meta_FixStatus(ccMeta);
+
+ // most online contact with avatar support might have changed - update avatar
+ hMostOnline = Meta_GetMostOnlineSupporting(ccMeta, PFLAGNUM_4, PF4_AVATARS);
+ if (hMostOnline) {
+ PROTO_AVATAR_INFORMATION ai = { 0 };
+ ai.hContact = ccMeta->contactID;
+ ai.format = PA_FORMAT_UNKNOWN;
+ _tcsncpy_s(ai.filename, _T("X"), _TRUNCATE);
+ if (CallProtoService(META_PROTO, PS_GETAVATARINFO, 0, (LPARAM)&ai) == GAIR_SUCCESS)
+ db_set_ts(ccMeta->contactID, "ContactPhoto", "File", ai.filename);
+ }
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Contact's deletion hook
+
+int Meta_ContactDeleted(WPARAM hContact, LPARAM)
+{
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hContact);
+ if (cc == NULL)
+ return 0;
+
+ // is a subcontact - update meta contact
+ if (cc->IsSub()) {
+ DBCachedContact *ccMeta = CheckMeta(cc->parentID);
+ if (ccMeta) {
+ Meta_RemoveContactNumber(ccMeta, Meta_GetContactNumber(ccMeta, hContact), true);
+ NotifyEventHooks(hSubcontactsChanged, ccMeta->contactID, 0);
+
+ // no more subs? remove the meta itself
+ if (ccMeta->nSubs == 0)
+ CallService(MS_DB_CONTACT_DELETE, ccMeta->contactID, 0);
+ }
+ return 0;
+ }
+
+ // not a subcontact - is it a metacontact?
+ if (!cc->IsMeta())
+ return 0;
+
+ if (cc->nSubs > 0)
+ NotifyEventHooks(hSubcontactsChanged, hContact, 0);
+
+ // remove & restore all subcontacts
+ for (int i = 0; i < cc->nSubs; i++) {
+ currDb->MetaDetouchSub(cc, i);
+
+ // stop ignoring, if we were
+ if (options.bSuppressStatus)
+ CallService(MS_IGNORE_UNIGNORE, cc->pSubs[i], IGNOREEVENT_USERONLINE);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Call when we want to send a user is typing message
+//
+// @param wParam HANDLE to the contact that we are typing to
+// @param lParam either PROTOTYPE_SELFTYPING_ON or PROTOTYPE_SELFTYPING_OFF
+
+static INT_PTR Meta_UserIsTyping(WPARAM hMeta, LPARAM lParam)
+{
+ DBCachedContact *cc = CheckMeta(hMeta);
+ if (cc == NULL)
+ return 0;
+
+ // forward to sending protocol, if supported
+ MCONTACT hMostOnline = Meta_GetMostOnline(cc);
+ Meta_CopyContactNick(cc, hMostOnline);
+ if (!hMostOnline)
+ return 0;
+
+ char *proto = GetContactProto(hMostOnline);
+ if (proto)
+ if (ProtoServiceExists(proto, PSS_USERISTYPING))
+ CallProtoService(proto, PSS_USERISTYPING, hMostOnline, lParam);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Called when user info is about to be shown
+//
+// Returns 1 to stop event processing and opens page for metacontact default
+// contact (returning 1 to stop it doesn't work!)
+
+static int Meta_UserInfo(WPARAM, LPARAM hMeta)
+{
+ DBCachedContact *cc = CheckMeta(hMeta);
+ if (cc == NULL || cc->nDefault == -1)
+ return 0;
+
+ CallService(MS_USERINFO_SHOWDIALOG, Meta_GetContactHandle(cc, cc->nDefault), 0);
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// record window open/close status for subs & metas
+
+static int Meta_MessageWindowEvent(WPARAM, LPARAM lParam)
+{
+ MessageWindowEventData *mwed = (MessageWindowEventData*)lParam;
+ if (mwed->uType == MSG_WINDOW_EVT_OPEN) {
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(mwed->hContact);
+ if (cc != NULL) {
+ Meta_UpdateSrmmIcon(cc, db_get_w(cc->contactID, META_PROTO, "Status", ID_STATUS_OFFLINE));
+ if (cc->IsMeta()) {
+ MetaSrmmData *p = new MetaSrmmData;
+ p->m_hMeta = cc->contactID;
+ p->m_hSub = db_mc_getMostOnline(cc->contactID);
+ p->m_hWnd = mwed->hwndWindow;
+ arMetaWindows.insert(p);
+
+ if (p->m_hSub != db_mc_getDefault(cc->contactID))
+ db_mc_setDefault(cc->contactID, p->m_hSub, false);
+ }
+ }
+ }
+ else if (mwed->uType == MSG_WINDOW_EVT_CLOSING) {
+ for (int i = 0; i < arMetaWindows.getCount(); i++)
+ if (arMetaWindows[i].m_hWnd == mwed->hwndWindow)
+ arMetaWindows.remove(i);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// returns manually chosen sub in the meta window
+
+static INT_PTR Meta_SrmmCurrentSub(WPARAM hMeta, LPARAM)
+{
+ MetaSrmmData tmp = { hMeta };
+ if (MetaSrmmData *p = arMetaWindows.find(&tmp))
+ return p->m_hSub;
+
+ return db_mc_getMostOnline(hMeta);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// we assume that it could be called only for the metacontacts
+
+static int Meta_SrmmIconClicked(WPARAM hMeta, LPARAM lParam)
+{
+ StatusIconClickData *sicd = (StatusIconClickData*)lParam;
+ if (mir_strcmp(sicd->szModule, META_PROTO))
+ return 0;
+
+ DBCachedContact *cc = CheckMeta(hMeta);
+ if (cc == NULL)
+ return 0;
+
+ HMENU hMenu = CreatePopupMenu();
+ int iDefault = Meta_GetContactNumber(cc, db_mc_getSrmmSub(cc->contactID));
+
+ MENUITEMINFO mii = { sizeof(mii) };
+ mii.fMask = MIIM_ID | MIIM_STATE | MIIM_STRING;
+ for (int i = 0; i < cc->nSubs; i++) {
+ char *szProto = GetContactProto(cc->pSubs[i]);
+ if (szProto == NULL) continue;
+
+ PROTOACCOUNT *pa = ProtoGetAccount(szProto);
+ if (pa == NULL)
+ continue;
+
+ CMString tszNick;
+ if (options.menu_contact_label == DNT_DID)
+ tszNick = cli.pfnGetContactDisplayName(cc->pSubs[i], 0);
+ else
+ Meta_GetSubNick(hMeta, i, tszNick);
+ tszNick.AppendFormat(_T(" [%s]"), pa->tszAccountName);
+
+ mii.wID = i + 1;
+ mii.fState = (i == iDefault) ? MFS_CHECKED : MFS_ENABLED;
+ mii.dwTypeData = tszNick.GetBuffer();
+ mii.cch = tszNick.GetLength();
+ InsertMenuItem(hMenu, i, TRUE, &mii);
+ }
+
+ UINT res = TrackPopupMenu(hMenu, TPM_NONOTIFY | TPM_RETURNCMD | TPM_BOTTOMALIGN | TPM_LEFTALIGN, sicd->clickLocation.x, sicd->clickLocation.y, 0, cli.hwndContactTree, NULL);
+ if (res > 0) {
+ MCONTACT hChosen = Meta_GetContactHandle(cc, res - 1);
+
+ MetaSrmmData tmp = { cc->contactID };
+ if (MetaSrmmData *p = arMetaWindows.find(&tmp))
+ p->m_hSub = hChosen;
+
+ db_mc_setDefault(cc->contactID, hChosen, true);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Called when all the plugin are loaded into Miranda.
+//
+// Initializes the 4 menus present in the context-menu
+
+int Meta_ModulesLoaded(WPARAM, LPARAM)
+{
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, Meta_ModifyMenu);
+
+ // hook srmm window close/open events
+ HookEvent(ME_MSG_WINDOWEVENT, Meta_MessageWindowEvent);
+ HookEvent(ME_MSG_ICONPRESSED, Meta_SrmmIconClicked);
+
+ // create menu items
+ InitMenus();
+
+ // create srmm icon
+ StatusIconData sid = { sizeof(sid) };
+ sid.szModule = META_PROTO;
+ sid.flags = MBF_TCHAR;
+ sid.tszTooltip = LPGENT("Select metacontact");
+ sid.hIcon = LoadSkinnedProtoIcon(META_PROTO, ID_STATUS_ONLINE);
+ Srmm_AddIcon(&sid);
+ return 0;
+}
+
+static VOID CALLBACK sttMenuThread(PVOID param)
+{
+ HMENU hMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)param, 0);
+
+ TPMPARAMS tpmp = { 0 };
+ tpmp.cbSize = sizeof(tpmp);
+ BOOL menuRet = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, menuMousePoint.x, menuMousePoint.y, (HWND)CallService(MS_CLUI_GETHWND, 0, 0), &tpmp);
+
+ CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(menuRet), MPCF_CONTACTMENU), (LPARAM)param);
+
+ DestroyMenu(hMenu);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR Meta_ContactMenuFunc(WPARAM hMeta, LPARAM lParam)
+{
+ DBCachedContact *cc = CheckMeta(hMeta);
+ if (cc == NULL)
+ return 0;
+
+ MCONTACT hContact = Meta_GetContactHandle(cc, (int)lParam);
+
+ if (options.menu_function == FT_MSG) {
+ // open message window if protocol supports message sending or chat, else simulate double click
+ char *proto = GetContactProto(hContact);
+ if (proto) {
+ INT_PTR caps = CallProtoService(proto, PS_GETCAPS, PFLAGNUM_1, 0);
+ if ((caps & PF1_IMSEND) || (caps & PF1_CHAT)) {
+ // set default contact for sending/status and open message window
+ Meta_SetSrmmSub(hMeta, hContact);
+ db_mc_setDefaultNum(hMeta, lParam, false);
+ CallService(MS_MSG_SENDMESSAGET, hMeta, 0);
+ }
+ else // protocol does not support messaging - simulate double click
+ CallService(MS_CLIST_CONTACTDOUBLECLICKED, hContact, 0);
+ }
+ else // protocol does not support messaging - simulate double click
+ CallService(MS_CLIST_CONTACTDOUBLECLICKED, hContact, 0);
+ }
+ else if (options.menu_function == FT_MENU) // show contact's context menu
+ CallFunctionAsync(sttMenuThread, (void*)hContact);
+ else if (options.menu_function == FT_INFO) // show user info for subcontact
+ CallService(MS_USERINFO_SHOWDIALOG, hContact, 0);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// file transfer support - mostly not required, since subcontacts do the receiving
+
+INT_PTR Meta_FileSend(WPARAM, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ DBCachedContact *cc = CheckMeta(ccs->hContact);
+ if (cc == NULL || cc->nDefault == -1)
+ return 0;
+
+ MCONTACT hMostOnline = Meta_GetMostOnlineSupporting(cc, PFLAGNUM_1, PF1_FILESEND);
+ if (!hMostOnline)
+ return 0;
+
+ char *proto = GetContactProto(hMostOnline);
+ if (proto)
+ return CallContactService(hMostOnline, PSS_FILE, ccs->wParam, ccs->lParam);
+
+ return 0; // fail
+}
+
+INT_PTR Meta_GetAwayMsg(WPARAM, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ DBCachedContact *cc = CheckMeta(ccs->hContact);
+ if (cc == NULL || cc->nDefault == -1)
+ return 0;
+
+ MCONTACT hMostOnline = Meta_GetMostOnlineSupporting(cc, PFLAGNUM_1, PF1_MODEMSGRECV);
+ if (!hMostOnline)
+ return 0;
+
+ char *proto = GetContactProto(hMostOnline);
+ if (!proto)
+ return 0;
+
+ ccs->hContact = hMostOnline;
+ return CallContactService(ccs->hContact, PSS_GETAWAYMSG, ccs->wParam, ccs->lParam);
+}
+
+INT_PTR Meta_GetAvatarInfo(WPARAM wParam, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam;
+ DBCachedContact *cc = CheckMeta(pai->hContact);
+ if (cc == NULL)
+ return GAIR_NOAVATAR;
+
+ if (cc->nDefault == -1)
+ return 0;
+
+ MCONTACT hSub = Meta_GetMostOnlineSupporting(cc, PFLAGNUM_4, PF4_AVATARS);
+ if (!hSub)
+ return GAIR_NOAVATAR;
+
+ char *proto = GetContactProto(hSub);
+ if (!proto)
+ return GAIR_NOAVATAR;
+
+ pai->hContact = hSub;
+ INT_PTR result = CallProtoService(proto, PS_GETAVATARINFO, wParam, lParam);
+ pai->hContact = cc->contactID;
+ if (result != CALLSERVICE_NOTFOUND)
+ return result;
+
+ return GAIR_NOAVATAR; // fail
+}
+
+INT_PTR Meta_GetInfo(WPARAM, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+
+ // This is a simple contact
+ // (this should normally not happen, since linked contacts do not appear on the list.)
+ DBCachedContact *cc = CheckMeta(ccs->hContact);
+ if (cc == NULL || cc->nDefault == -1)
+ return 0;
+
+ MCONTACT hMostOnline = Meta_GetMostOnlineSupporting(cc, PFLAGNUM_4, PF4_AVATARS);
+ if (!hMostOnline)
+ return 0;
+
+ char *proto = GetContactProto(hMostOnline);
+ if (!proto)
+ return 0;
+
+ PROTO_AVATAR_INFORMATION ai;
+ ai.hContact = ccs->hContact;
+ ai.format = PA_FORMAT_UNKNOWN;
+ _tcsncpy_s(ai.filename, _T("X"), _TRUNCATE);
+ if (CallProtoService(META_PROTO, PS_GETAVATARINFO, 0, (LPARAM)&ai) == GAIR_SUCCESS)
+ db_set_ts(ccs->hContact, "ContactPhoto", "File", ai.filename);
+
+ hMostOnline = Meta_GetMostOnline(cc);
+ Meta_CopyContactNick(cc, hMostOnline);
+
+ if (!hMostOnline)
+ return 0;
+
+ ccs->hContact = hMostOnline;
+ if (!ProtoServiceExists(proto, PSS_GETINFO))
+ return 0; // fail
+
+ return CallContactService(ccs->hContact, PSS_GETINFO, ccs->wParam, ccs->lParam);
+}
+
+int Meta_CallMostOnline(WPARAM hContact, LPARAM)
+{
+ DBCachedContact *cc = CheckMeta(hContact);
+ if (cc == NULL)
+ return 0;
+
+ Meta_CopyContactNick(cc, db_mc_getSrmmSub(cc->contactID));
+ Meta_FixStatus(cc);
+ return 0;
+}
+
+int Meta_PreShutdown(WPARAM, LPARAM)
+{
+ Meta_SetStatus(ID_STATUS_OFFLINE, 0);
+ Meta_SuppressStatus(FALSE);
+ if (setStatusTimerId)
+ KillTimer(0, setStatusTimerId);
+ return 0;
+}
+
+INT_PTR MenuFunc(WPARAM wParam, LPARAM, LPARAM param)
+{
+ return Meta_ContactMenuFunc(wParam, param);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Initializes all services provided by the plugin
+//
+// Creates every function and hooks the event desired.
+
+void Meta_InitServices()
+{
+ previousMode = mcStatus = ID_STATUS_OFFLINE;
+
+ CreateServiceFunction("MetaContacts/Convert", Meta_Convert);
+ CreateServiceFunction("MetaContacts/AddTo", Meta_AddTo);
+ CreateServiceFunction("MetaContacts/Edit", Meta_Edit);
+ CreateServiceFunction("MetaContacts/Delete", Meta_Delete);
+ CreateServiceFunction("MetaContacts/Default", Meta_Default);
+
+ // hidden contact menu items...ho hum
+ for (int i = 0; i < MAX_CONTACTS; i++) {
+ char szServiceName[100];
+ mir_snprintf(szServiceName, SIZEOF(szServiceName), "MetaContacts/MenuFunc%d", i);
+ CreateServiceFunctionParam(szServiceName, MenuFunc, i);
+ }
+
+ CreateProtoServiceFunction(META_PROTO, PS_GETCAPS, Meta_GetCaps);
+ CreateProtoServiceFunction(META_PROTO, PS_GETNAME, Meta_GetName);
+ CreateProtoServiceFunction(META_PROTO, PS_LOADICON, Meta_LoadIcon);
+
+ CreateProtoServiceFunction(META_PROTO, PS_SETSTATUS, Meta_SetStatus);
+
+ CreateProtoServiceFunction(META_PROTO, PS_GETSTATUS, Meta_GetStatus);
+ CreateProtoServiceFunction(META_PROTO, PSS_MESSAGE, Meta_SendMessage);
+
+ CreateProtoServiceFunction(META_PROTO, PSS_USERISTYPING, Meta_UserIsTyping);
+
+ // file recv is done by subcontacts
+ CreateProtoServiceFunction(META_PROTO, PSS_FILE, Meta_FileSend);
+ CreateProtoServiceFunction(META_PROTO, PSS_GETAWAYMSG, Meta_GetAwayMsg);
+ CreateProtoServiceFunction(META_PROTO, PS_GETAVATARINFO, Meta_GetAvatarInfo);
+ CreateProtoServiceFunction(META_PROTO, PSS_GETINFO, Meta_GetInfo);
+
+ // receive filter
+ CreateProtoServiceFunction(META_FILTER, PSR_MESSAGE, MetaFilter_RecvMessage);
+
+ // API services and events
+ CreateApiServices();
+
+ CreateServiceFunction("MetaContacts/OnOff", Meta_OnOff);
+ CreateServiceFunction(MS_MC_GETSRMMSUB, Meta_SrmmCurrentSub);
+
+ CreateProtoServiceFunction(META_PROTO, PS_SEND_NUDGE, Meta_SendNudge);
+
+ // create our hookable events
+ hSubcontactsChanged = CreateHookableEvent(ME_MC_SUBCONTACTSCHANGED);
+
+ // hook other module events we need
+ HookEvent(ME_PROTO_ACK, Meta_HandleACK);
+ HookEvent(ME_DB_CONTACT_DELETED, Meta_ContactDeleted);
+ HookEvent(ME_DB_CONTACT_SETTINGCHANGED, Meta_SettingChanged);
+ HookEvent(ME_OPT_INITIALISE, Meta_OptInit);
+ HookEvent(ME_SYSTEM_MODULESLOADED, Meta_ModulesLoaded);
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, Meta_PreShutdown);
+
+ // hook our own events, used to call Meta_GetMostOnline which sets nick for metacontact
+ HookEvent(ME_MC_DEFAULTTCHANGED, Meta_CallMostOnline);
+
+ // redirect nudge events
+ hEventNudge = CreateHookableEvent(META_PROTO "/Nudge");
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Destroy created events
+
+void Meta_CloseHandles()
+{
+ DestroyHookableEvent(hSubcontactsChanged);
+ DestroyHookableEvent(hEventNudge);
+}
diff --git a/src/mir_app/src/meta_utils.cpp b/src/mir_app/src/meta_utils.cpp new file mode 100644 index 0000000000..fc32d1a929 --- /dev/null +++ b/src/mir_app/src/meta_utils.cpp @@ -0,0 +1,576 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "metacontacts.h"
+
+HANDLE invisiGroup;
+POINT menuMousePoint;
+
+/** Update the MetaContact login, depending on the protocol desired
+*
+* The login of the "MetaContacts" protocol will be copied from the login
+* of the specified protocol.
+*
+* @param szProto : The name of the protocol used to get the login that will be
+* affected to the "MetaContacts" protocol.
+*
+* @return O on success, 1 otherwise.
+*/
+
+int Meta_SetNick(char *szProto)
+{
+ CONTACTINFO ci = { sizeof(ci) };
+ ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
+ ci.szProto = szProto;
+ if (CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci))
+ return 1;
+
+ switch (ci.type) {
+ case CNFT_BYTE:
+ if (db_set_b(NULL, META_PROTO, "Nick", ci.bVal))
+ return 1;
+ break;
+ case CNFT_WORD:
+ if (db_set_w(NULL, META_PROTO, "Nick", ci.wVal))
+ return 1;
+ break;
+ case CNFT_DWORD:
+ if (db_set_dw(NULL, META_PROTO, "Nick", ci.dVal))
+ return 1;
+ break;
+ case CNFT_ASCIIZ:
+ if (db_set_ts(NULL, META_PROTO, "Nick", ci.pszVal))
+ return 1;
+ mir_free(ci.pszVal);
+ break;
+ default:
+ if (db_set_s(NULL, META_PROTO, "Nick", (char *)TranslateT("Sender")))
+ return 1;
+ break;
+ }
+ return 0;
+}
+
+/** Assign a contact (hSub) to a metacontact (hMeta)
+*
+* @param hSub : HANDLE to a contact that should be assigned
+* @param hMeta : HANDLE to a metacontact that will host the contact
+* @param set_as_default : bool flag to indicate whether the new contact becomes the default
+*
+* @return TRUE on success, FALSE otherwise
+*/
+
+BOOL Meta_Assign(MCONTACT hSub, MCONTACT hMeta, BOOL set_as_default)
+{
+ DBCachedContact *ccDest = CheckMeta(hMeta), *ccSub = currDb->m_cache->GetCachedContact(hSub);
+ if (ccDest == NULL || ccSub == NULL)
+ return FALSE;
+
+ char *szProto = GetContactProto(hSub);
+ if (szProto == NULL) {
+ MessageBox(0, TranslateT("Could not retrieve contact protocol"), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ return FALSE;
+ }
+
+ // Get the login of the subcontact
+ char *field = (char *)CallProtoService(szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
+ DBVARIANT dbv;
+ if (db_get(hSub, szProto, field, &dbv)) {
+ MessageBox(0, TranslateT("Could not get unique ID of contact"), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ return FALSE;
+ }
+
+ // Check that is is 'on the list'
+ if (db_get_b(hSub, "CList", "NotOnList", 0) == 1) {
+ MessageBox(0, TranslateT("Contact is 'not on list' - please add the contact to your contact list before assigning."), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ db_free(&dbv);
+ return FALSE;
+ }
+
+ if (ccDest->nSubs >= MAX_CONTACTS) {
+ MessageBox(0, TranslateT("Metacontact is full"), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ db_free(&dbv);
+ return FALSE;
+ }
+
+ // write the contact's protocol
+ char buffer[512];
+ mir_snprintf(buffer, "Protocol%d", ccDest->nSubs);
+ if (db_set_s(hMeta, META_PROTO, buffer, szProto)) {
+ MessageBox(0, TranslateT("Could not write contact protocol to metacontact"), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ db_free(&dbv);
+ return FALSE;
+ }
+
+ // write the login
+ mir_snprintf(buffer, "Login%d", ccDest->nSubs);
+ if (db_set(hMeta, META_PROTO, buffer, &dbv)) {
+ MessageBox(0, TranslateT("Could not write unique ID of contact to metacontact"), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ db_free(&dbv);
+ return FALSE;
+ }
+
+ db_free(&dbv);
+
+ // If we can get the nickname of the subcontact...
+ if (!db_get(hSub, szProto, "Nick", &dbv)) {
+ // write the nickname
+ mir_snprintf(buffer, "Nick%d", ccDest->nSubs);
+ if (db_set(hMeta, META_PROTO, buffer, &dbv)) {
+ MessageBox(0, TranslateT("Could not write nickname of contact to metacontact"), TranslateT("Assignment error"), MB_OK | MB_ICONWARNING);
+ db_free(&dbv);
+ return FALSE;
+ }
+
+ db_free(&dbv);
+ }
+
+ // write the display name
+ mir_snprintf(buffer, "CListName%d", ccDest->nSubs);
+ db_set_ts(hMeta, META_PROTO, buffer, cli.pfnGetContactDisplayName(hSub, 0));
+
+ // Get the status
+ WORD status = db_get_w(hSub, szProto, "Status", ID_STATUS_OFFLINE);
+
+ // write the status
+ mir_snprintf(buffer, "Status%d", ccDest->nSubs);
+ db_set_w(hMeta, META_PROTO, buffer, status);
+
+ // write the handle
+ mir_snprintf(buffer, "Handle%d", ccDest->nSubs);
+ db_set_dw(hMeta, META_PROTO, buffer, hSub);
+
+ // write status string
+ mir_snprintf(buffer, "StatusString%d", ccDest->nSubs);
+
+ TCHAR *szStatus = cli.pfnGetStatusModeDescription(status, 0);
+ db_set_ts(hMeta, META_PROTO, buffer, szStatus);
+
+ // Write the link in the contact
+ db_set_dw(hSub, META_PROTO, "ParentMeta", hMeta);
+ db_set_b(hSub, META_PROTO, "IsSubcontact", true);
+
+ // update count of contacts
+ db_set_dw(hMeta, META_PROTO, "NumContacts", ccDest->nSubs+1);
+ ccDest->nSubs++;
+ ccDest->pSubs = (MCONTACT*)mir_realloc(ccDest->pSubs, sizeof(MCONTACT)*ccDest->nSubs);
+ ccDest->pSubs[ccDest->nSubs-1] = hSub;
+ ccSub->parentID = hMeta;
+
+ if (set_as_default)
+ db_mc_setDefaultNum(hMeta, ccDest->nSubs-1, true);
+
+ // set nick to most online contact that can message
+ MCONTACT most_online = Meta_GetMostOnline(ccDest);
+ Meta_CopyContactNick(ccDest, most_online);
+
+ // set status to that of most online contact
+ Meta_FixStatus(ccDest);
+
+ // if the new contact is the most online contact with avatar support, get avatar info
+ most_online = Meta_GetMostOnlineSupporting(ccDest, PFLAGNUM_4, PF4_AVATARS);
+ if (most_online == hSub) {
+ PROTO_AVATAR_INFORMATION ai;
+ ai.hContact = hMeta;
+ ai.format = PA_FORMAT_UNKNOWN;
+ _tcsncpy_s(ai.filename, _T("X"), _TRUNCATE);
+
+ if (CallProtoService(META_PROTO, PS_GETAVATARINFO, 0, (LPARAM)&ai) == GAIR_SUCCESS)
+ db_set_ts(hMeta, "ContactPhoto", "File", ai.filename);
+ }
+
+ // merge sub's events to the meta-history
+ currDb->MetaMergeHistory(ccDest, ccSub);
+
+ // Ignore status if the option is on
+ if (options.bSuppressStatus)
+ CallService(MS_IGNORE_IGNORE, hSub, IGNOREEVENT_USERONLINE);
+
+ NotifyEventHooks(hSubcontactsChanged, hMeta, 0);
+ return TRUE;
+}
+
+/**
+* Convenience method - get most online contact supporting messaging
+*
+*/
+
+MCONTACT Meta_GetMostOnline(DBCachedContact *cc)
+{
+ return Meta_GetMostOnlineSupporting(cc, PFLAGNUM_1, PF1_IM);
+}
+
+/** Get the 'most online' contact for a meta contact (according to above order) which supports the specified
+* protocol service, and copies nick from that contact
+*
+* @param hMetaHANDLE to a metacontact
+*
+* @return HANDLE to a contact
+*/
+
+static int GetStatusPriority(int status)
+{
+ switch (status) {
+ case ID_STATUS_OFFLINE: return 8;
+ case ID_STATUS_AWAY: return 4;
+ case ID_STATUS_DND: return 7;
+ case ID_STATUS_NA: return 6;
+ case ID_STATUS_OCCUPIED: return 5;
+ case ID_STATUS_FREECHAT: return 1;
+ case ID_STATUS_ONTHEPHONE: return 2;
+ case ID_STATUS_OUTTOLUNCH: return 3;
+ }
+
+ return 0;
+}
+
+MCONTACT Meta_GetMostOnlineSupporting(DBCachedContact *cc, int pflagnum, unsigned long capability)
+{
+ if (cc == NULL || cc->nDefault == -1)
+ return NULL;
+
+ // if the default is beyond the end of the list (eek!) return null
+ if (cc->nDefault >= cc->nSubs)
+ return NULL;
+
+ int most_online_status = ID_STATUS_OFFLINE;
+ MCONTACT most_online_contact = Meta_GetContactHandle(cc, cc->nDefault);
+ char *szProto = GetContactProto(most_online_contact);
+ if (szProto && CallProtoService(szProto, PS_GETSTATUS, 0, 0) >= ID_STATUS_ONLINE) {
+ DWORD caps = CallProtoService(szProto, PS_GETCAPS, pflagnum, 0);
+ if (capability == -1 || (caps & capability) == capability) {
+ most_online_status = db_get_w(most_online_contact, szProto, "Status", ID_STATUS_OFFLINE);
+
+ // if our default is not offline, and option to use default is set - return default
+ // and also if our default is online, return it
+ if (most_online_status != ID_STATUS_OFFLINE)
+ return most_online_contact;
+ }
+ else most_online_status = ID_STATUS_OFFLINE;
+ }
+ else most_online_status = ID_STATUS_OFFLINE;
+
+ char *most_online_proto = szProto;
+ // otherwise, check all the subcontacts for the one closest to the ONLINE state which supports the required capability
+ for (int i = 0; i < cc->nSubs; i++) {
+ if (i == cc->nDefault) // already checked that (i.e. initial value of most_online_contact and most_online_status are those of the default contact)
+ continue;
+
+ MCONTACT hContact = Meta_GetContactHandle(cc, i);
+ szProto = GetContactProto(hContact);
+ if (szProto == NULL || CallProtoService(szProto, PS_GETSTATUS, 0, 0) < ID_STATUS_ONLINE) // szProto offline or connecting
+ continue;
+
+ DWORD caps = CallProtoService(szProto, PS_GETCAPS, pflagnum, 0);
+ if (capability == -1 || (caps & capability) == capability) {
+ int status = db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+ if (status == ID_STATUS_ONLINE) {
+ most_online_contact = hContact;
+ most_online_proto = szProto;
+ return most_online_contact;
+ }
+ if (status <= ID_STATUS_OFFLINE) // status below ID_STATUS_OFFLINE is a connecting status
+ continue;
+
+ if (GetStatusPriority(status) < GetStatusPriority(most_online_status)) {
+ most_online_status = status;
+ most_online_contact = hContact;
+ most_online_proto = szProto;
+ }
+ }
+ }
+
+ // no online contacts? if we're trying to message, use 'send offline' contact
+ if (most_online_status == ID_STATUS_OFFLINE && capability == PF1_IM) {
+ MCONTACT hOffline = Meta_GetContactHandle(cc, db_get_dw(cc->contactID, META_PROTO, "OfflineSend", INVALID_CONTACT_ID));
+ if (hOffline)
+ most_online_contact = hOffline;
+ }
+
+ return most_online_contact;
+}
+
+DBCachedContact* CheckMeta(MCONTACT hMeta)
+{
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hMeta);
+ return (cc == NULL || cc->nSubs == -1) ? NULL : cc;
+}
+
+int Meta_GetContactNumber(DBCachedContact *cc, MCONTACT hContact)
+{
+ for (int i = 0; i < cc->nSubs; i++)
+ if (cc->pSubs[i] == hContact)
+ return i;
+
+ return -1;
+}
+
+MCONTACT Meta_GetContactHandle(DBCachedContact *cc, int contact_number)
+{
+ if (contact_number >= cc->nSubs || contact_number < 0)
+ return 0;
+
+ return cc->pSubs[contact_number];
+}
+
+/** Hide all contacts linked to any meta contact, and set handle links
+*
+* Additionally, set all sub contacts and metacontacts to offline so that status notifications are always sent
+*
+* and ensure metafilter in place
+*/
+int Meta_HideLinkedContacts(void)
+{
+ DBVARIANT dbv, dbv2;
+ char buffer[512];
+
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hContact);
+ if (cc == NULL || cc->parentID == 0)
+ continue;
+
+ DBCachedContact *ccMeta = CheckMeta(cc->parentID);
+ if (ccMeta == NULL)
+ continue;
+
+ // get contact number
+ int contact_number = Meta_GetContactNumber(cc, hContact);
+
+ // find metacontact
+ if (contact_number < 0 || contact_number >= ccMeta->nSubs)
+ continue;
+
+ // update metacontact's record of status for this contact
+ mir_snprintf(buffer, "Status%d",contact_number);
+
+ // prepare to update metacontact record of subcontat status
+ char *szProto = GetContactProto(hContact);
+
+ WORD status = (!szProto) ? ID_STATUS_OFFLINE : db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+ db_set_w(ccMeta->contactID, META_PROTO, buffer, status);
+
+ // update metacontact's record of nick for this contact
+ if (szProto && !db_get(hContact, szProto, "Nick", &dbv)) {
+ mir_snprintf(buffer, "Nick%d",contact_number);
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dbv);
+
+ mir_snprintf(buffer, "CListName%d",contact_number);
+ if (db_get(hContact, "CList", "MyHandle", &dbv2))
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dbv);
+ else {
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dbv2);
+ db_free(&dbv2);
+ }
+
+ db_free(&dbv);
+ }
+ else {
+ if (!db_get(hContact, "CList", "MyHandle", &dbv)) {
+ mir_snprintf(buffer,"CListName%d",contact_number);
+ db_set(ccMeta->contactID, META_PROTO, buffer, &dbv);
+ db_free(&dbv);
+ }
+ }
+
+ if (options.bSuppressStatus)
+ CallService(MS_IGNORE_IGNORE, hContact, IGNOREEVENT_USERONLINE);
+
+ MCONTACT hMostOnline = Meta_GetMostOnline(ccMeta); // set nick
+ Meta_CopyContactNick(ccMeta, hMostOnline);
+ Meta_FixStatus(ccMeta);
+ }
+
+ return 0;
+}
+
+int Meta_HideMetaContacts(bool bHide)
+{
+ // set status suppression
+ bool bSuppress = bHide ? FALSE : options.bSuppressStatus;
+
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ bool bSet;
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hContact);
+ if (cc->IsSub()) { // show on hide, reverse flag
+ bSet = !bHide;
+ CallService(bSuppress ? MS_IGNORE_IGNORE : MS_IGNORE_UNIGNORE, hContact, IGNOREEVENT_USERONLINE);
+ }
+ else if (cc->IsMeta())
+ bSet = bHide;
+ else
+ continue;
+
+ db_set_b(hContact, "CList", "Hidden", bSet);
+ }
+
+ if (bHide) {
+ for (int i = 0; i < arMetaWindows.getCount(); i++)
+ SendMessage(arMetaWindows[i].m_hWnd, WM_CLOSE, 0, 0);
+ arMetaWindows.destroy();
+ }
+
+ return 0;
+}
+
+int Meta_SuppressStatus(BOOL suppress)
+{
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact))
+ if (db_mc_isSub(hContact))
+ CallService((suppress) ? MS_IGNORE_IGNORE : MS_IGNORE_UNIGNORE, hContact, IGNOREEVENT_USERONLINE);
+
+ return 0;
+}
+
+int Meta_CopyContactNick(DBCachedContact *ccMeta, MCONTACT hContact)
+{
+ if (options.bLockHandle)
+ hContact = Meta_GetContactHandle(ccMeta, 0);
+
+ if (!hContact)
+ return 1;
+
+ char *szProto = GetContactProto(hContact);
+ if (szProto == NULL)
+ return 1;
+
+ if (options.clist_contact_name == CNNT_NICK) {
+ ptrT tszNick(db_get_tsa(hContact, szProto, "Nick"));
+ if (tszNick) {
+ db_set_ts(ccMeta->contactID, META_PROTO, "Nick", tszNick);
+ return 0;
+ }
+ }
+ else if (options.clist_contact_name == CNNT_DISPLAYNAME) {
+ TCHAR *name = cli.pfnGetContactDisplayName(hContact, 0);
+ if (name && mir_tstrcmp(name, TranslateT("(Unknown contact)")) != 0) {
+ db_set_ts(ccMeta->contactID, META_PROTO, "Nick", name);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int Meta_SetAllNicks()
+{
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ DBCachedContact *cc = CheckMeta(hContact);
+ if (cc == NULL)
+ continue;
+ MCONTACT most_online = Meta_GetMostOnline(cc);
+ Meta_CopyContactNick(cc, most_online);
+ Meta_FixStatus(cc);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void SwapValues(MCONTACT hContact, LPCSTR szSetting, int n1, int n2)
+{
+ char buf1[100], buf2[100];
+ mir_snprintf(buf1, SIZEOF(buf1), "%s%d", szSetting, n1);
+ mir_snprintf(buf2, SIZEOF(buf2), "%s%d", szSetting, n2);
+
+ DBVARIANT dbv1, dbv2;
+ int ok1 = !db_get(hContact, META_PROTO, buf1, &dbv1);
+ int ok2 = !db_get(hContact, META_PROTO, buf2, &dbv2);
+ if (ok1) {
+ db_set(hContact, META_PROTO, buf2, &dbv1);
+ db_free(&dbv1);
+ }
+ if (ok2) {
+ db_set(hContact, META_PROTO, buf1, &dbv2);
+ db_free(&dbv2);
+ }
+}
+
+int Meta_SwapContacts(DBCachedContact *cc, int n1, int n2)
+{
+ SwapValues(cc->contactID, "Protocol", n1, n2);
+ SwapValues(cc->contactID, "Status", n1, n2);
+ SwapValues(cc->contactID, "StatusString", n1, n2);
+ SwapValues(cc->contactID, "Login", n1, n2);
+ SwapValues(cc->contactID, "Nick", n1, n2);
+ SwapValues(cc->contactID, "CListName", n1, n2);
+ SwapValues(cc->contactID, "Handle", n1, n2);
+
+ MCONTACT tmp = cc->pSubs[n1];
+ cc->pSubs[n1] = cc->pSubs[n2];
+ cc->pSubs[n2] = tmp;
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void Meta_GetSubNick(MCONTACT hMeta, int i, CMString &tszDest)
+{
+ char idStr[50];
+ mir_snprintf(idStr, SIZEOF(idStr), "Login%d", i);
+
+ DBVARIANT dbv;
+ if(db_get(hMeta, META_PROTO, idStr, &dbv))
+ return;
+ switch (dbv.type) {
+ case DBVT_ASCIIZ:
+ tszDest = dbv.pszVal;
+ break;
+ case DBVT_BYTE:
+ tszDest.Format(_T("%d"), dbv.bVal);
+ break;
+ case DBVT_WORD:
+ tszDest.Format(_T("%d"), dbv.wVal);
+ break;
+ case DBVT_DWORD:
+ tszDest.Format(_T("%d"), dbv.dVal);
+ break;
+ default:
+ tszDest.Empty();
+ }
+ db_free(&dbv);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void Meta_FixStatus(DBCachedContact *ccMeta)
+{
+ WORD status = ID_STATUS_OFFLINE;
+
+ MCONTACT most_online = db_mc_getMostOnline(ccMeta->contactID);
+ if (most_online) {
+ char *szProto = GetContactProto(most_online);
+ if (szProto)
+ status = db_get_w(most_online, szProto, "Status", ID_STATUS_OFFLINE);
+ }
+
+ db_set_w(ccMeta->contactID, META_PROTO, "Status", status);
+ Meta_UpdateSrmmIcon(ccMeta, status);
+}
+
+void Meta_UpdateSrmmIcon(DBCachedContact *ccMeta, int)
+{
+ StatusIconData sid = { sizeof(sid) };
+ sid.szModule = META_PROTO;
+ sid.flags = (ccMeta->IsMeta()) ? 0 : MBF_HIDDEN;
+ Srmm_ModifyIcon(ccMeta->contactID, &sid);
+}
diff --git a/src/mir_app/src/metacontacts.h b/src/mir_app/src/metacontacts.h new file mode 100644 index 0000000000..2c4ff0715c --- /dev/null +++ b/src/mir_app/src/metacontacts.h @@ -0,0 +1,129 @@ +/*
+former MetaContacts Plugin for Miranda IM.
+
+Copyright © 2014 Miranda NG Team
+Copyright © 2004-07 Scott Ellis
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+
+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 MAX_CONTACTS 20
+
+#define META_FILTER "MetaContactsFilter"
+
+INT_PTR TranslateMenuFunc(MCONTACT hContact, int i);
+
+// contact menu items
+void InitMenus();
+extern int mcStatus;
+
+struct MetaSrmmData
+{
+ MCONTACT m_hMeta, m_hSub;
+ HWND m_hWnd;
+};
+extern OBJLIST<MetaSrmmData> arMetaWindows;
+
+INT_PTR Meta_Convert(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_AddTo(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_Edit(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_Delete(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_Default(WPARAM wParam,LPARAM lParam);
+
+INT_PTR Meta_OnOff(WPARAM wParam, LPARAM lParam);
+int Meta_ModifyMenu(WPARAM wParam,LPARAM lParam);
+BOOL Meta_Assign(MCONTACT src, MCONTACT dest, BOOL set_as_default);
+void Meta_RemoveContactNumber(DBCachedContact *cc, int number, bool bUpdateInfo);
+int Meta_SetNick(char *proto);
+int Meta_HideLinkedContacts(void);
+int Meta_GetContactNumber(DBCachedContact *cc, MCONTACT hContact);
+int Meta_HideMetaContacts(bool hide);
+int Meta_SuppressStatus(int suppress);
+int Meta_CopyContactNick(DBCachedContact *cc, MCONTACT hContact);
+int Meta_SetAllNicks();
+int Meta_SwapContacts(DBCachedContact *cc, int contact_number1, int contact_number2);
+void Meta_GetSubNick(MCONTACT hMeta, int i, CMString &tszDest);
+
+MCONTACT Meta_GetMostOnline(DBCachedContact *cc);
+MCONTACT Meta_GetMostOnlineSupporting(DBCachedContact *cc, int pflagnum, unsigned long capability);
+MCONTACT Meta_GetContactHandle(DBCachedContact *cc, int contact_number);
+
+DBCachedContact* CheckMeta(MCONTACT hMeta);
+
+// function to copy history from one contact to another - courtesy JdGordon with mods (thx)
+void Meta_FixStatus(DBCachedContact *ccMeta);
+void Meta_UpdateSrmmIcon(DBCachedContact *ccMeta, int iStatus);
+
+char *Meta_GetUniqueIdentifier(MCONTACT hContact, DWORD *pused);
+
+INT_PTR Meta_GetCaps(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_GetName(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_LoadIcon(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_SetStatus(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_GetStatus(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_SendMessage(WPARAM wParam,LPARAM lParam);
+INT_PTR Meta_ContactMenuFunc(WPARAM wParam, LPARAM lParam);
+
+void Meta_InitServices();
+void Meta_CloseHandles();
+
+enum MenuDisplayNameType {DNT_UID = 0, DNT_DID = 1};
+enum MenuFunctionType {FT_MSG = 0, FT_MENU = 1, FT_INFO = 2};
+enum CListDisplayNameType {CNNT_NICK = 0, CNNT_DISPLAYNAME = 1};
+
+struct MetaOptions
+{
+ bool bLockHandle;
+ bool bSuppressStatus;
+
+ int menu_contact_label;
+ int menu_function;
+ int clist_contact_name;
+ int set_status_from_offline_delay;
+};
+
+extern MetaOptions options;
+
+int Meta_OptInit(WPARAM wParam, LPARAM lParam);
+int Meta_ReadOptions();
+
+// API function headers
+void CreateApiServices();
+
+typedef enum {I_MENUOFF, I_MENU, I_CONVERT, I_ADD, I_EDIT, I_SETDEFAULT, I_REMOVE} IconIndex;
+HICON LoadIconEx(IconIndex i);
+HANDLE GetIconHandle(IconIndex i);
+
+extern HANDLE hEventForceSend, hEventUnforceSend, hSubcontactsChanged;
+extern POINT menuMousePoint;
+
+#define MAX_PROTOCOLS 20
+
+// used for the 'jabber' hack - i.e. hide contacts instead of moving them to the hidden group
+#define JABBER_UNIQUE_ID_SETTING "jid"
+
+// delay setting status from offline - to help reduce innapropriate status notification popups
+#define DEFAULT_SET_STATUS_SLEEP_TIME 15000 // milliseconds
+
+// service from clist_meta_mw, existence means we don't need to hide subcontacts (woohoo - thanks FYR)
+#define MS_CLUI_METASUPPORT "CLUI/MetaContactSupport"
+
+#ifndef MS_CLUI_GETVERSION
+#define MS_CLUI_GETVERSION "CLUI/GetVersion"
+
+#define szDelMsg LPGEN("You are going to remove all the contacts associated with this metacontact.\nThis will delete the metacontact.\n\nProceed anyway?")
+
+#endif
diff --git a/src/mir_app/src/mir_app.def b/src/mir_app/src/mir_app.def new file mode 100644 index 0000000000..c713745838 --- /dev/null +++ b/src/mir_app/src/mir_app.def @@ -0,0 +1,21 @@ +EXPORTS
+
+CallContactService @1
+CallProtoService @2
+RecalculateTime @3
+LoadSkinProtoIcon @4
+LoadSkinIcon @5
+Button_FreeIcon_IcoLib @6
+Button_SetIcon_IcoLib @7
+GetSkinIconHandle @8
+IcoLib_AddNewIcon @9
+IcoLib_GetIcon @10
+IcoLib_GetIconByHandle @11
+IcoLib_GetIconHandle @12
+IcoLib_IsManaged @13
+IcoLib_ReleaseIcon @14
+Window_FreeIcon_IcoLib @15
+Window_SetIcon_IcoLib @16
+Window_SetProtoIcon_IcoLib @17
+ProtoServiceExists @18
+mir_main @19
diff --git a/src/mir_app/src/miranda.cpp b/src/mir_app/src/miranda.cpp new file mode 100644 index 0000000000..15acd9e720 --- /dev/null +++ b/src/mir_app/src/miranda.cpp @@ -0,0 +1,425 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#pragma comment(lib, "version.lib")
+
+int LoadDefaultModules(void);
+void UnloadNewPluginsModule(void);
+void UnloadDefaultModules(void);
+
+pfnDrawThemeTextEx drawThemeTextEx;
+pfnSetWindowThemeAttribute setWindowThemeAttribute;
+pfnBufferedPaintInit bufferedPaintInit;
+pfnBufferedPaintUninit bufferedPaintUninit;
+pfnBeginBufferedPaint beginBufferedPaint;
+pfnEndBufferedPaint endBufferedPaint;
+pfnGetBufferedPaintBits getBufferedPaintBits;
+
+pfnDwmExtendFrameIntoClientArea dwmExtendFrameIntoClientArea;
+pfnDwmIsCompositionEnabled dwmIsCompositionEnabled;
+
+ITaskbarList3 * pTaskbarInterface;
+
+HANDLE hOkToExitEvent, hModulesLoadedEvent;
+HANDLE hShutdownEvent, hPreShutdownEvent;
+static HANDLE hWaitObjects[MAXIMUM_WAIT_OBJECTS-1];
+static char *pszWaitServices[MAXIMUM_WAIT_OBJECTS-1];
+static int waitObjectCount = 0;
+HANDLE hMirandaShutdown;
+HINSTANCE g_hInst;
+DWORD hMainThreadId;
+int hLangpack = 0;
+bool bModulesLoadedFired = false;
+
+BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD, LPVOID)
+{
+ g_hInst = hinstDLL;
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// exception handling
+
+static INT_PTR srvGetExceptionFilter(WPARAM, LPARAM)
+{
+ return (INT_PTR)GetExceptionFilter();
+}
+
+static INT_PTR srvSetExceptionFilter(WPARAM, LPARAM lParam)
+{
+ return (INT_PTR)SetExceptionFilter((pfnExceptionFilter)lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+typedef LONG(WINAPI *pNtQIT)(HANDLE, LONG, PVOID, ULONG, PULONG);
+#define ThreadQuerySetWin32StartAddress 9
+
+INT_PTR MirandaIsTerminated(WPARAM, LPARAM)
+{
+ return WaitForSingleObject(hMirandaShutdown, 0) == WAIT_OBJECT_0;
+}
+
+static void __cdecl compactHeapsThread(void*)
+{
+ Thread_SetName("compactHeapsThread");
+
+ while (!Miranda_Terminated()) {
+ HANDLE hHeaps[256];
+ DWORD hc;
+ SleepEx((1000 * 60) * 5, TRUE); // every 5 minutes
+ hc = GetProcessHeaps(255, (PHANDLE)&hHeaps);
+ if (hc != 0 && hc < 256) {
+ DWORD j;
+ for (j = 0; j < hc; j++)
+ HeapCompact(hHeaps[j], 0);
+ }
+ } //while
+}
+
+void(*SetIdleCallback) (void) = NULL;
+
+static INT_PTR SystemSetIdleCallback(WPARAM, LPARAM lParam)
+{
+ if (lParam && SetIdleCallback == NULL) {
+ SetIdleCallback = (void(*)(void))lParam;
+ return 1;
+ }
+ return 0;
+}
+
+static DWORD dwEventTime = 0;
+void checkIdle(MSG * msg)
+{
+ switch (msg->message) {
+ case WM_MOUSEACTIVATE:
+ case WM_MOUSEMOVE:
+ case WM_CHAR:
+ dwEventTime = GetTickCount();
+ }
+}
+
+static INT_PTR SystemGetIdle(WPARAM, LPARAM lParam)
+{
+ if (lParam) *(DWORD*)lParam = dwEventTime;
+ return 0;
+}
+
+static int SystemShutdownProc(WPARAM, LPARAM)
+{
+ UnloadDefaultModules();
+ return 0;
+}
+
+#define MIRANDA_PROCESS_WAIT_TIMEOUT 60000
+#define MIRANDA_PROCESS_WAIT_RESOLUTION 1000
+#define MIRANDA_PROCESS_WAIT_STEPS (MIRANDA_PROCESS_WAIT_TIMEOUT/MIRANDA_PROCESS_WAIT_RESOLUTION)
+static INT_PTR CALLBACK WaitForProcessDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwnd);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam);
+ SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETRANGE, 0, MAKELPARAM(0, MIRANDA_PROCESS_WAIT_STEPS));
+ SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETSTEP, 1, 0);
+ SetTimer(hwnd, 1, MIRANDA_PROCESS_WAIT_RESOLUTION, NULL);
+ break;
+
+ case WM_TIMER:
+ if (SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_STEPIT, 0, 0) == MIRANDA_PROCESS_WAIT_STEPS)
+ EndDialog(hwnd, 0);
+ if (WaitForSingleObject((HANDLE)GetWindowLongPtr(hwnd, GWLP_USERDATA), 1) != WAIT_TIMEOUT) {
+ SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETPOS, MIRANDA_PROCESS_WAIT_STEPS, 0);
+ EndDialog(hwnd, 0);
+ }
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDCANCEL) {
+ SendDlgItemMessage(hwnd, IDC_PROGRESSBAR, PBM_SETPOS, MIRANDA_PROCESS_WAIT_STEPS, 0);
+ EndDialog(hwnd, 1);
+ }
+ break;
+ }
+ return FALSE;
+}
+
+INT_PTR CheckRestart()
+{
+ LPCTSTR tszPID = CmdLine_GetOption(_T("restart"));
+ if (tszPID) {
+ HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, _ttol(tszPID));
+ if (hProcess) {
+ INT_PTR result = DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_WAITRESTART), NULL, WaitForProcessDlgProc, (LPARAM)hProcess);
+ CloseHandle(hProcess);
+ return result;
+ }
+ }
+ return 0;
+}
+
+static void crtErrorHandler(const wchar_t*, const wchar_t*, const wchar_t*, unsigned, uintptr_t)
+{}
+
+int WINAPI mir_main(LPTSTR cmdLine)
+{
+ hMainThreadId = GetCurrentThreadId();
+
+ _set_invalid_parameter_handler(&crtErrorHandler);
+#ifdef _DEBUG
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+#endif
+
+ CmdLine_Parse(cmdLine);
+ setlocale(LC_ALL, "");
+
+#ifdef _DEBUG
+ if (CmdLine_GetOption(_T("memdebug")))
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif
+
+ HMODULE hDwmApi, hThemeAPI;
+ if (IsWinVerVistaPlus()) {
+ hDwmApi = LoadLibrary(_T("dwmapi.dll"));
+ if (hDwmApi) {
+ dwmExtendFrameIntoClientArea = (pfnDwmExtendFrameIntoClientArea)GetProcAddress(hDwmApi, "DwmExtendFrameIntoClientArea");
+ dwmIsCompositionEnabled = (pfnDwmIsCompositionEnabled)GetProcAddress(hDwmApi, "DwmIsCompositionEnabled");
+ }
+ hThemeAPI = LoadLibrary(_T("uxtheme.dll"));
+ if (hThemeAPI) {
+ drawThemeTextEx = (pfnDrawThemeTextEx)GetProcAddress(hThemeAPI, "DrawThemeTextEx");
+ setWindowThemeAttribute = (pfnSetWindowThemeAttribute)GetProcAddress(hThemeAPI, "SetWindowThemeAttribute");
+ bufferedPaintInit = (pfnBufferedPaintInit)GetProcAddress(hThemeAPI, "BufferedPaintInit");
+ bufferedPaintUninit = (pfnBufferedPaintUninit)GetProcAddress(hThemeAPI, "BufferedPaintUninit");
+ beginBufferedPaint = (pfnBeginBufferedPaint)GetProcAddress(hThemeAPI, "BeginBufferedPaint");
+ endBufferedPaint = (pfnEndBufferedPaint)GetProcAddress(hThemeAPI, "EndBufferedPaint");
+ getBufferedPaintBits = (pfnGetBufferedPaintBits)GetProcAddress(hThemeAPI, "GetBufferedPaintBits");
+ }
+ }
+ else hDwmApi = hThemeAPI = 0;
+
+ if (bufferedPaintInit)
+ bufferedPaintInit();
+
+ OleInitialize(NULL);
+
+ if (IsWinVer7Plus())
+ CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_ALL, IID_ITaskbarList3, (void**)&pTaskbarInterface);
+
+ int result = 0;
+ if (LoadDefaultModules()) {
+ SetEvent(hMirandaShutdown);
+ NotifyEventHooks(hPreShutdownEvent, 0, 0);
+ NotifyEventHooks(hShutdownEvent, 0, 0);
+ UnloadDefaultModules();
+
+ result = 1;
+ }
+ else {
+ InitPathVar();
+ NotifyEventHooks(hModulesLoadedEvent, 0, 0);
+ bModulesLoadedFired = true;
+
+ // ensure that the kernel hooks the SystemShutdownProc() after all plugins
+ HookEvent(ME_SYSTEM_SHUTDOWN, SystemShutdownProc);
+
+ forkthread(compactHeapsThread, 0, NULL);
+ CreateServiceFunction(MS_SYSTEM_SETIDLECALLBACK, SystemSetIdleCallback);
+ CreateServiceFunction(MS_SYSTEM_GETIDLE, SystemGetIdle);
+ dwEventTime = GetTickCount();
+ DWORD myPid = GetCurrentProcessId();
+
+ bool messageloop = true;
+ while (messageloop) {
+ MSG msg;
+ BOOL dying = FALSE;
+ DWORD rc = MsgWaitForMultipleObjectsEx(waitObjectCount, hWaitObjects, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE);
+ if (rc < WAIT_OBJECT_0 + waitObjectCount) {
+ rc -= WAIT_OBJECT_0;
+ CallService(pszWaitServices[rc], (WPARAM)hWaitObjects[rc], 0);
+ }
+ //
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ if (msg.message != WM_QUIT) {
+ HWND h = GetForegroundWindow();
+ DWORD pid = 0;
+ checkIdle(&msg);
+ if (h != NULL && GetWindowThreadProcessId(h, &pid) && pid == myPid && GetClassLongPtr(h, GCW_ATOM) == 32770)
+ if (h != NULL && IsDialogMessage(h, &msg)) /* Wine fix. */
+ continue;
+
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ if (SetIdleCallback != NULL)
+ SetIdleCallback();
+ }
+ else if (!dying) {
+ dying++;
+ SetEvent(hMirandaShutdown);
+ NotifyEventHooks(hPreShutdownEvent, 0, 0);
+
+ // this spins and processes the msg loop, objects and APC.
+ Thread_Wait();
+ NotifyEventHooks(hShutdownEvent, 0, 0);
+ // if the hooks generated any messages, it'll get processed before the second WM_QUIT
+ PostQuitMessage(0);
+ }
+ else if (dying)
+ messageloop = false;
+ }
+ }
+ }
+
+ UnloadNewPluginsModule();
+ UnloadCoreModule();
+ CloseHandle(hMirandaShutdown);
+ FreeLibrary(hDwmApi);
+ FreeLibrary(hThemeAPI);
+
+ if (pTaskbarInterface)
+ pTaskbarInterface->Release();
+
+ OleUninitialize();
+
+ if (bufferedPaintUninit)
+ bufferedPaintUninit();
+ return result;
+}
+
+static INT_PTR OkToExit(WPARAM, LPARAM)
+{
+ return NotifyEventHooks(hOkToExitEvent, 0, 0) == 0;
+}
+
+static INT_PTR GetMirandaVersion(WPARAM, LPARAM)
+{
+ TCHAR filename[MAX_PATH];
+ GetModuleFileName(g_hInst, filename, SIZEOF(filename));
+
+ DWORD unused, verInfoSize = GetFileVersionInfoSize(filename, &unused);
+ PVOID pVerInfo = _alloca(verInfoSize);
+ GetFileVersionInfo(filename, 0, verInfoSize, pVerInfo);
+
+ UINT blockSize;
+ VS_FIXEDFILEINFO *vsffi;
+ VerQueryValue(pVerInfo, _T("\\"), (PVOID*)&vsffi, &blockSize);
+ DWORD ver = (((vsffi->dwProductVersionMS >> 16) & 0xFF) << 24) |
+ ((vsffi->dwProductVersionMS & 0xFF) << 16) |
+ (((vsffi->dwProductVersionLS >> 16) & 0xFF) << 8) |
+ (vsffi->dwProductVersionLS & 0xFF);
+ return (INT_PTR)ver;
+}
+
+static INT_PTR GetMirandaFileVersion(WPARAM, LPARAM lParam)
+{
+ TCHAR filename[MAX_PATH];
+ GetModuleFileName(g_hInst, filename, SIZEOF(filename));
+
+ DWORD unused, verInfoSize = GetFileVersionInfoSize(filename, &unused);
+ PVOID pVerInfo = _alloca(verInfoSize);
+ GetFileVersionInfo(filename, 0, verInfoSize, pVerInfo);
+
+ UINT blockSize;
+ VS_FIXEDFILEINFO *vsffi;
+ VerQueryValue(pVerInfo, _T("\\"), (PVOID*)&vsffi, &blockSize);
+
+ WORD* p = (WORD*)lParam;
+ p[0] = HIWORD(vsffi->dwProductVersionMS);
+ p[1] = LOWORD(vsffi->dwProductVersionMS);
+ p[2] = HIWORD(vsffi->dwProductVersionLS);
+ p[3] = LOWORD(vsffi->dwProductVersionLS);
+ return 0;
+}
+
+static INT_PTR GetMirandaVersionText(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR filename[MAX_PATH], *productVersion;
+ GetModuleFileName(g_hInst, filename, SIZEOF(filename));
+
+ DWORD unused, verInfoSize = GetFileVersionInfoSize(filename, &unused);
+ PVOID pVerInfo = _alloca(verInfoSize);
+ GetFileVersionInfo(filename, 0, verInfoSize, pVerInfo);
+
+ UINT blockSize;
+ VerQueryValue(pVerInfo, _T("\\StringFileInfo\\000004b0\\ProductVersion"), (LPVOID*)&productVersion, &blockSize);
+ strncpy((char*)lParam, _T2A(productVersion), wParam);
+#if defined(_WIN64)
+ strcat_s((char*)lParam, wParam, " x64");
+#endif
+ return 0;
+}
+
+INT_PTR WaitOnHandle(WPARAM wParam, LPARAM lParam)
+{
+ if (waitObjectCount >= MAXIMUM_WAIT_OBJECTS - 1)
+ return 1;
+
+ hWaitObjects[waitObjectCount] = (HANDLE)wParam;
+ pszWaitServices[waitObjectCount] = (char*)lParam;
+ waitObjectCount++;
+ return 0;
+}
+
+static INT_PTR RemoveWait(WPARAM wParam, LPARAM)
+{
+ int i;
+
+ for (i = 0; i < waitObjectCount; i++)
+ if (hWaitObjects[i] == (HANDLE)wParam)
+ break;
+
+ if (i == waitObjectCount)
+ return 1;
+
+ waitObjectCount--;
+ memmove(&hWaitObjects[i], &hWaitObjects[i + 1], sizeof(HANDLE)*(waitObjectCount - i));
+ memmove(&pszWaitServices[i], &pszWaitServices[i + 1], sizeof(char*)*(waitObjectCount - i));
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int LoadSystemModule(void)
+{
+ hMirandaShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ hShutdownEvent = CreateHookableEvent(ME_SYSTEM_SHUTDOWN);
+ hPreShutdownEvent = CreateHookableEvent(ME_SYSTEM_PRESHUTDOWN);
+ hModulesLoadedEvent = CreateHookableEvent(ME_SYSTEM_MODULESLOADED);
+ hOkToExitEvent = CreateHookableEvent(ME_SYSTEM_OKTOEXIT);
+
+ CreateServiceFunction(MS_SYSTEM_TERMINATED, MirandaIsTerminated);
+ CreateServiceFunction(MS_SYSTEM_OKTOEXIT, OkToExit);
+ CreateServiceFunction(MS_SYSTEM_GETVERSION, GetMirandaVersion);
+ CreateServiceFunction(MS_SYSTEM_GETFILEVERSION, GetMirandaFileVersion);
+ CreateServiceFunction(MS_SYSTEM_GETVERSIONTEXT, GetMirandaVersionText);
+ CreateServiceFunction(MS_SYSTEM_WAITONHANDLE, WaitOnHandle);
+ CreateServiceFunction(MS_SYSTEM_REMOVEWAIT, RemoveWait);
+ CreateServiceFunction(MS_SYSTEM_GETEXCEPTFILTER, srvGetExceptionFilter);
+ CreateServiceFunction(MS_SYSTEM_SETEXCEPTFILTER, srvSetExceptionFilter);
+ return 0;
+}
diff --git a/src/mir_app/src/miranda.h b/src/mir_app/src/miranda.h new file mode 100644 index 0000000000..8ceb3d308d --- /dev/null +++ b/src/mir_app/src/miranda.h @@ -0,0 +1,186 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+typedef HRESULT (STDAPICALLTYPE *pfnDrawThemeTextEx)(HTHEME, HDC, int, int, LPCWSTR, int, DWORD, LPRECT, const struct _DTTOPTS *);
+typedef HRESULT (STDAPICALLTYPE *pfnSetWindowThemeAttribute)(HWND, enum WINDOWTHEMEATTRIBUTETYPE, PVOID, DWORD);
+typedef HRESULT (STDAPICALLTYPE *pfnBufferedPaintInit)(void);
+typedef HRESULT (STDAPICALLTYPE *pfnBufferedPaintUninit)(void);
+typedef HANDLE (STDAPICALLTYPE *pfnBeginBufferedPaint)(HDC, RECT *, BP_BUFFERFORMAT, BP_PAINTPARAMS *, HDC *);
+typedef HRESULT (STDAPICALLTYPE *pfnEndBufferedPaint)(HANDLE, BOOL);
+typedef HRESULT (STDAPICALLTYPE *pfnGetBufferedPaintBits)(HANDLE, RGBQUAD **, int *);
+
+extern pfnDrawThemeTextEx drawThemeTextEx;
+extern pfnSetWindowThemeAttribute setWindowThemeAttribute;
+extern pfnBufferedPaintInit bufferedPaintInit;
+extern pfnBufferedPaintUninit bufferedPaintUninit;
+extern pfnBeginBufferedPaint beginBufferedPaint;
+extern pfnEndBufferedPaint endBufferedPaint;
+extern pfnGetBufferedPaintBits getBufferedPaintBits;
+
+extern ITaskbarList3 * pTaskbarInterface;
+
+typedef HRESULT (STDAPICALLTYPE *pfnDwmExtendFrameIntoClientArea)(HWND hwnd, const MARGINS *margins);
+typedef HRESULT (STDAPICALLTYPE *pfnDwmIsCompositionEnabled)(BOOL *);
+
+extern pfnDwmExtendFrameIntoClientArea dwmExtendFrameIntoClientArea;
+extern pfnDwmIsCompositionEnabled dwmIsCompositionEnabled;
+
+/**** database.cpp *********************************************************************/
+
+extern MIDatabase* currDb;
+extern DATABASELINK* currDblink;
+extern LIST<DATABASELINK> arDbPlugins;
+
+int InitIni(void);
+void UninitIni(void);
+
+/**** extraicons.cpp *******************************************************************/
+
+void KillModuleExtraIcons(int hLangpack);
+
+/**** fontService.cpp ******************************************************************/
+
+void KillModuleFonts(int hLangpack);
+void KillModuleColours(int hLangpack);
+void KillModuleEffects(int hLangpack);
+void KillModuleHotkeys(int hLangpack);
+void KillModuleSounds(int hLangpack);
+
+/**** miranda.cpp **********************************************************************/
+
+extern HINSTANCE g_hInst;
+extern DWORD hMainThreadId;
+extern HANDLE hOkToExitEvent, hModulesLoadedEvent, hevLoadModule, hevUnloadModule;
+extern TCHAR mirandabootini[MAX_PATH];
+
+/**** newplugins.cpp *******************************************************************/
+
+char* GetPluginNameByInstance(HINSTANCE hInstance);
+int GetPluginFakeId(const MUUID &uuid, int hLangpack);
+int LoadStdPlugins(void);
+
+/**** path.cpp *************************************************************************/
+
+void InitPathVar(void);
+
+/**** srmm.cpp *************************************************************************/
+
+void KillModuleSrmmIcons(int hLangpack);
+
+/**** utf.cpp **************************************************************************/
+
+__forceinline char* Utf8DecodeA(const char* src)
+{
+ char* tmp = mir_strdup(src);
+ Utf8Decode(tmp, NULL);
+ return tmp;
+}
+
+#pragma optimize("", on)
+
+/**** options.cpp **********************************************************************/
+
+HTREEITEM FindNamedTreeItemAtRoot(HWND hwndTree, const TCHAR* name);
+
+/**** skin2icons.cpp *******************************************************************/
+
+void KillModuleIcons(int hLangpack);
+
+/**** skinicons.cpp ********************************************************************/
+
+HICON LoadIconEx(HINSTANCE hInstance, LPCTSTR lpIconName, BOOL bShared);
+int ImageList_AddIcon_NotShared(HIMAGELIST hIml, LPCTSTR szResource);
+int ImageList_ReplaceIcon_NotShared(HIMAGELIST hIml, int iIndex, HINSTANCE hInstance, LPCTSTR szResource);
+
+int ImageList_AddIcon_IconLibLoaded(HIMAGELIST hIml, int iconId);
+int ImageList_AddIcon_ProtoIconLibLoaded(HIMAGELIST hIml, const char *szProto, int iconId);
+int ImageList_ReplaceIcon_IconLibLoaded(HIMAGELIST hIml, int nIndex, HICON hIcon);
+
+#define Safe_DestroyIcon(hIcon) if (hIcon) DestroyIcon(hIcon)
+
+/**** clistmenus.cpp ********************************************************************/
+
+extern HANDLE hMainMenuObject, hContactMenuObject, hStatusMenuObject;
+extern HANDLE hPreBuildMainMenuEvent, hPreBuildContactMenuEvent;
+extern HANDLE hShutdownEvent, hPreShutdownEvent;
+
+extern const int statusModeList[ MAX_STATUS_COUNT ];
+extern const int skinIconStatusList[ MAX_STATUS_COUNT ];
+extern const int skinIconStatusFlags[ MAX_STATUS_COUNT ];
+
+int TryProcessDoubleClick(MCONTACT hContact);
+
+void KillModuleMenus(int hLangpack);
+
+/**** protocols.cpp *********************************************************************/
+
+#define OFFSET_PROTOPOS 200
+#define OFFSET_VISIBLE 400
+#define OFFSET_ENABLED 600
+#define OFFSET_NAME 800
+
+extern LIST<PROTOACCOUNT> accounts;
+
+INT_PTR ProtoCallService(LPCSTR szModule, const char *szService, WPARAM wParam, LPARAM lParam);
+
+PROTOACCOUNT* Proto_CreateAccount(const char *szModuleName, const char *szBaseProto, const TCHAR *tszAccountName);
+
+PROTOACCOUNT* __fastcall Proto_GetAccount(const char *accName);
+PROTOACCOUNT* __fastcall Proto_GetAccount(MCONTACT hContact);
+
+bool __fastcall Proto_IsAccountEnabled(PROTOACCOUNT *pa);
+bool __fastcall Proto_IsAccountLocked(PROTOACCOUNT *pa);
+
+PROTO_INTERFACE* AddDefaultAccount(const char *szProtoName);
+int FreeDefaultAccount(PROTO_INTERFACE* ppi);
+
+BOOL ActivateAccount(PROTOACCOUNT *pa);
+void EraseAccount(const char *pszProtoName);
+void DeactivateAccount(PROTOACCOUNT *pa, bool bIsDynamic, bool bErase);
+void UnloadAccount(PROTOACCOUNT *pa, bool bIsDynamic, bool bErase);
+void OpenAccountOptions(PROTOACCOUNT *pa);
+
+void LoadDbAccounts(void);
+void WriteDbAccounts(void);
+
+INT_PTR CallProtoServiceInt(MCONTACT hContact, const char* szModule, const char* szService, WPARAM wParam, LPARAM lParam);
+
+/**** utils.cpp ************************************************************************/
+
+void HotkeyToName(TCHAR *buf, int size, BYTE shift, BYTE key);
+WORD GetHotkeyValue(INT_PTR idHotkey);
+int AssertInsideScreen(RECT &rc);
+
+HBITMAP ConvertIconToBitmap(HICON hIcon, HIMAGELIST hIml, int iconId);
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern "C"
+{
+ MIR_CORE_DLL(int) Langpack_MarkPluginLoaded(PLUGININFOEX* pInfo);
+ MIR_CORE_DLL(int) GetSubscribersCount(HANDLE hHook);
+ MIR_CORE_DLL(void) db_setCurrent(MIDatabase* _db);
+
+ MIR_CORE_DLL(PROTOCOLDESCRIPTOR*) Proto_RegisterModule(PROTOCOLDESCRIPTOR *pd);
+};
diff --git a/src/mir_app/src/modules.cpp b/src/mir_app/src/modules.cpp new file mode 100644 index 0000000000..0c7e748552 --- /dev/null +++ b/src/mir_app/src/modules.cpp @@ -0,0 +1,184 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include "plugins.h"
+#include "langpack.h"
+#include "chat.h"
+
+INT_PTR CheckRestart(); // core: IDD_WAITRESTART
+
+int LoadSystemModule(void); // core: m_system.h services
+int LoadNewPluginsModuleInfos(void); // core: preloading plugins
+int LoadNewPluginsModule(void); // core: N.O. plugins
+int LoadNetlibModule(void); // core: network
+int LoadSslModule(void);
+int LoadLangpackModule(void); // core: translation
+int LoadProtocolsModule(void); // core: protocol manager
+int LoadAccountsModule(void); // core: account manager
+int LoadIgnoreModule(void); // protocol filter: ignore
+int LoadDbintfModule(void);
+int LoadEventsModule(void);
+int LoadSrmmModule(void);
+
+int LoadContactsModule(void);
+int LoadContactListModule(void);// ui: clist
+int LoadDatabaseModule(void);
+int LoadMetacontacts(void);
+int LoadOptionsModule(void); // ui: options dialog
+int LoadFindAddModule(void); // ui: search/add users
+int LoadSkinIcons(void);
+int LoadSkinSounds(void);
+int LoadSkinHotkeys(void);
+int LoadUserInfoModule(void); // ui: user info
+int LoadVisibilityModule(void); // ui: visibility control
+
+int LoadPluginOptionsModule(void); // ui: plugin viewer
+int LoadAddContactModule(void); // ui: authcontrol contacts
+int LoadUtilsModule(void); // ui: utils (has a few window classes, like HyperLink)
+int LoadCLCModule(void); // window class: CLC control
+int LoadButtonModule(void); // window class: button class
+int LoadFontserviceModule(void); // ui: font manager
+int LoadIcoLibModule(void); // ui: icons manager
+int LoadServiceModePlugin(void);
+int LoadDefaultServiceModePlugin(void);
+int LoadErrorsModule(void);
+
+void UnloadAccountsModule(void);
+void UnloadClcModule(void);
+void UnloadContactListModule(void);
+void UnloadDatabase(void);
+void UnloadErrorsModule(void);
+void UnloadEventsModule(void);
+void UnloadExtraIconsModule(void);
+void UnloadIcoLibModule(void);
+void UnloadMetacontacts(void);
+void UnloadNetlibModule(void);
+void UnloadNewPlugins(void);
+void UnloadProtocolsModule(void);
+void UnloadSkinSounds(void);
+void UnloadSkinHotkeys(void);
+void UnloadSrmmModule(void);
+void UnloadUtilsModule(void);
+
+int LoadIcoTabsModule();
+int LoadHeaderbarModule();
+int LoadDescButtonModule();
+
+int LoadDefaultModules(void)
+{
+ // load order is very important for these
+ if (LoadSystemModule()) return 1;
+ if (LoadLangpackModule()) return 1; // langpack will be a system module in the new order so this is moved here
+ if (CheckRestart()) return 1;
+ if (LoadUtilsModule()) return 1; //order not important for this, but no dependencies and no point in pluginising
+ if (LoadIcoTabsModule()) return 1;
+ if (LoadHeaderbarModule()) return 1;
+ if (LoadDbintfModule()) return 1;
+ if (LoadEventsModule()) return 1;
+
+ // load database drivers & service plugins without executing their Load()
+ if (LoadNewPluginsModuleInfos()) return 1;
+
+ switch (LoadDefaultServiceModePlugin()) {
+ case SERVICE_CONTINUE: // continue loading Miranda normally
+ case SERVICE_ONLYDB: // load database and go to the message cycle
+ break;
+ case SERVICE_MONOPOLY: // unload database and go to the message cycle
+ return 0;
+ default: // smth went wrong, terminating
+ return 1;
+ }
+
+ // the database will select which db plugin to use, or fail if no profile is selected
+ if (LoadDatabaseModule()) return 1;
+
+ // database is available here
+ if (LoadButtonModule()) return 1;
+ if (LoadIcoLibModule()) return 1;
+ if (LoadSkinIcons()) return 1;
+
+ // if (LoadErrorsModule()) return 1;
+
+ switch (LoadServiceModePlugin()) {
+ case SERVICE_CONTINUE: // continue loading Miranda normally
+ break;
+ case SERVICE_ONLYDB: // load database and go to the message cycle
+ return 0;
+ case SERVICE_MONOPOLY: // unload database and go to the message cycle
+ UnloadDatabase();
+ return 0;
+ default: // smth went wrong, terminating
+ return 1;
+ }
+
+ if (LoadSkinSounds()) return 1;
+ if (LoadSkinHotkeys()) return 1;
+ if (LoadFontserviceModule()) return 1;
+ if (LoadSrmmModule()) return 1;
+ if (LoadChatModule()) return 1;
+ if (LoadDescButtonModule()) return 1;
+ if (LoadOptionsModule()) return 1;
+ if (LoadNetlibModule()) return 1;
+ if (LoadSslModule()) return 1;
+ if (LoadProtocolsModule()) return 1;
+ LoadDbAccounts(); // retrieves the account array from a database
+ if (LoadContactsModule()) return 1;
+ if (LoadContactListModule()) return 1; // prepare contact list interface
+ if (LoadAddContactModule()) return 1;
+ if (LoadMetacontacts()) return 1;
+
+ if (LoadNewPluginsModule()) return 1; // will call Load(void) on everything, clist will load first
+
+ Langpack_SortDuplicates();
+
+ if (LoadAccountsModule()) return 1;
+
+ //order becomes less important below here
+ if (LoadFindAddModule()) return 1;
+ if (LoadIgnoreModule()) return 1;
+ if (LoadVisibilityModule()) return 1;
+ if (LoadStdPlugins()) return 1;
+ return 0;
+}
+
+void UnloadDefaultModules(void)
+{
+ UnloadChatModule();
+ UnloadAccountsModule();
+ UnloadMetacontacts();
+ UnloadNewPlugins();
+ UnloadProtocolsModule();
+ UnloadSkinSounds();
+ UnloadSkinHotkeys();
+ UnloadSrmmModule();
+ // UnloadErrorsModule();
+ UnloadIcoLibModule();
+ UnloadUtilsModule();
+ UnloadExtraIconsModule();
+ UnloadClcModule();
+ UnloadContactListModule();
+ UnloadEventsModule();
+ UnloadNetlibModule();
+}
diff --git a/src/mir_app/src/movetogroup.cpp b/src/mir_app/src/movetogroup.cpp new file mode 100644 index 0000000000..cfc7bee71e --- /dev/null +++ b/src/mir_app/src/movetogroup.cpp @@ -0,0 +1,149 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+HGENMENU hMoveToGroupItem = 0, hPriorityItem = 0, hFloatingItem = 0;
+
+LIST<HANDLE> 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*mir_tstrlen(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 = { 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 = Menu_AddContactMenuItem(&mi);
+ mir_free(mi.ptszName);
+
+ lphGroupsItems.insert((HANDLE*)result);
+}
+
+static int OnContactMenuBuild(WPARAM wParam, LPARAM)
+{
+ int i;
+ OBJLIST<GroupItemSort> groups(10, GroupItemSort::compare);
+
+ if (!hMoveToGroupItem) {
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.position = 100000;
+ mi.pszName = LPGEN("&Move to group");
+ mi.flags = CMIF_ROOTHANDLE;
+ mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_GROUP);
+
+ hMoveToGroupItem = Menu_AddContactMenuItem(&mi);
+ }
+
+ for (i=0; i < lphGroupsItems.getCount(); i++)
+ CallService(MO_REMOVEMENUITEM, (WPARAM)lphGroupsItems[i], 0);
+ lphGroupsItems.destroy();
+
+ ptrT szContactGroup(db_get_tsa(wParam, "CList", "Group"));
+
+ int pos = 1000;
+
+ AddGroupItem(hMoveToGroupItem, TranslateT("<Root group>"), pos, -1, !szContactGroup);
+
+ pos += 100000; // Separator
+
+ for (i=0; ; i++) {
+ char intname[20];
+ _itoa(i, intname, 10);
+
+ DBVARIANT dbv;
+ if (db_get_ts(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 && !mir_tstrcmp(szContactGroup, groups[i].name);
+ AddGroupItem(hMoveToGroupItem, groups[i].name, ++pos, groups[i].position, checked);
+ }
+
+ 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()
+{
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, OnContactMenuBuild);
+ CreateServiceFunction(MTG_MOVE, MTG_DOMOVE);
+}
+
+int UnloadMoveToGroup(void)
+{
+ return 0;
+}
diff --git a/src/mir_app/src/netlib.cpp b/src/mir_app/src/netlib.cpp new file mode 100644 index 0000000000..7453fe5c0a --- /dev/null +++ b/src/mir_app/src/netlib.cpp @@ -0,0 +1,527 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 mir_strcmp(p1->user.szSettingsModule, p2->user.szSettingsModule);
+}
+
+LIST<NetlibUser> netlibUser(5, CompareNetlibUser);
+mir_cs 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);
+}
+
+int GetNetlibHandleType(void *p)
+{
+ __try {
+ return *(int*)p;
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {}
+
+ return NLH_INVALID;
+}
+
+void NetlibInitializeNestedCS(NetlibNestedCriticalSection *nlncs)
+{
+ nlncs->dwOwningThreadId = 0;
+ nlncs->lockCount = 0;
+ nlncs->hMutex = CreateMutex(NULL, FALSE, NULL);
+}
+
+void NetlibDeleteNestedCS(NetlibNestedCriticalSection *nlncs)
+{
+ CloseHandle(nlncs->hMutex);
+}
+
+int NetlibEnterNestedCS(NetlibConnection *nlc, int which)
+{
+ 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(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 (db_get(NULL, szUserModule, szSetting, &dbv) && db_get(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)
+{
+ char *szRet = db_get_sa(NULL, szUserModule, szSetting);
+ if (szRet == NULL)
+ if ((szRet = db_get_sa(NULL, "Netlib", szSetting)) == NULL)
+ return NULL;
+
+ return szRet;
+}
+
+static INT_PTR NetlibRegisterUser(WPARAM, LPARAM lParam)
+{
+ NETLIBUSER *nlu = (NETLIBUSER*)lParam;
+ 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;
+ }
+
+ NetlibUser *thisUser = (NetlibUser*)mir_calloc(sizeof(NetlibUser));
+ thisUser->handleType = NLH_USER;
+ thisUser->user = *nlu;
+
+ int idx;
+ {
+ mir_cslock lck(csNetlibUser);
+ idx = netlibUser.getIndex(thisUser);
+ }
+ if (idx != -1) {
+ mir_free(thisUser);
+ SetLastError(ERROR_DUP_NAME);
+ return 0;
+ }
+
+ 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");
+ 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");
+ thisUser->settings.szProxyAuthPassword = GetNetlibUserSettingString(thisUser->user.szSettingsModule, "NLProxyAuthPassword");
+ 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");
+ thisUser->settings.specifyOutgoingPorts = GetNetlibUserSettingInt(thisUser->user.szSettingsModule, "NLSpecifyOutgoingPorts", 0);
+ thisUser->settings.szOutgoingPorts = GetNetlibUserSettingString(thisUser->user.szSettingsModule, "NLOutgoingPorts");
+ 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);
+
+ mir_cslock lck(csNetlibUser);
+ netlibUser.insert(thisUser);
+ return (INT_PTR)thisUser;
+}
+
+static INT_PTR NetlibGetUserSettings(WPARAM wParam, LPARAM lParam)
+{
+ NETLIBUSERSETTINGS *nlus = (NETLIBUSERSETTINGS*)lParam;
+ NetlibUser *nlu = (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;
+ NetlibUser *nlu = (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)
+{
+ if (wParam == NULL)
+ return 0;
+
+ switch(GetNetlibHandleType((void*)wParam)) {
+ case NLH_USER:
+ {
+ NetlibUser *nlu = (NetlibUser*)wParam;
+ {
+ mir_cslock lck(csNetlibUser);
+ int i = netlibUser.getIndex(nlu);
+ if (i >= 0)
+ netlibUser.remove(i);
+ }
+
+ 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:
+ {
+ 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, nlc->termRequested);
+ 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((void*)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 NetlibStringToAddressSrv(WPARAM wParam, LPARAM lParam)
+{
+ return (INT_PTR)!NetlibStringToAddress((char*)wParam, (SOCKADDR_INET_M*)lParam);
+}
+
+INT_PTR NetlibAddressToStringSrv(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam) {
+ SOCKADDR_INET_M iaddr = {0};
+ iaddr.Ipv4.sin_family = AF_INET;
+ iaddr.Ipv4.sin_addr.s_addr = htonl((unsigned)lParam);
+ return (INT_PTR)NetlibAddressToString(&iaddr);
+ }
+ return (INT_PTR)NetlibAddressToString((SOCKADDR_INET_M*)lParam);
+}
+
+INT_PTR NetlibGetConnectionInfoSrv(WPARAM wParam, LPARAM lParam)
+{
+ NetlibGetConnectionInfo((NetlibConnection*)wParam, (NETLIBCONNINFO*)lParam);
+ return 0;
+}
+
+INT_PTR NetlibGetMyIp(WPARAM wParam, LPARAM)
+{
+ return (INT_PTR)GetMyIp((unsigned)wParam);
+}
+
+INT_PTR NetlibShutdown(WPARAM wParam, LPARAM)
+{
+ if (wParam) {
+ WaitForSingleObject(hConnectionHeaderMutex, INFINITE);
+ switch(GetNetlibHandleType((void*)wParam)) {
+ case NLH_CONNECTION:
+ {
+ NetlibConnection *nlc = (NetlibConnection*)wParam;
+ if (!nlc->termRequested) {
+ 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;
+}
+
+void UnloadNetlibModule(void)
+{
+ if (!bModuleInitialized || hConnectionHeaderMutex == NULL) return;
+
+ NetlibUnloadIeProxy();
+ NetlibUPnPDestroy();
+ NetlibLogShutdown();
+
+ DestroyHookableEvent(hRecvEvent); hRecvEvent = NULL;
+ DestroyHookableEvent(hSendEvent); hSendEvent = NULL;
+
+ for (int i = netlibUser.getCount(); i > 0; i--)
+ NetlibCloseHandle((WPARAM)netlibUser[i-1], 0);
+
+ CloseHandle(hConnectionHeaderMutex);
+ if (hConnectionOpenMutex)
+ CloseHandle(hConnectionOpenMutex);
+ WSACleanup();
+}
+
+int LoadNetlibModule(void)
+{
+ WSADATA wsadata;
+
+ bModuleInitialized = TRUE;
+
+ WSAStartup(MAKEWORD(2, 2), &wsadata);
+
+ HookEvent(ME_OPT_INITIALISE, NetlibOptInitialise);
+
+ 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_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);
+ CreateServiceFunction(MS_NETLIB_STRINGTOADDRESS, NetlibStringToAddressSrv);
+ CreateServiceFunction(MS_NETLIB_ADDRESSTOSTRING, NetlibAddressToStringSrv);
+ CreateServiceFunction(MS_NETLIB_GETCONNECTIONINFO, NetlibGetConnectionInfoSrv);
+ CreateServiceFunction(MS_NETLIB_GETMYIP, NetlibGetMyIp);
+
+ hRecvEvent = CreateHookableEvent(ME_NETLIB_FASTRECV);
+ hSendEvent = CreateHookableEvent(ME_NETLIB_FASTSEND);
+
+ NetlibUPnPInit();
+ NetlibSecurityInit();
+ NetlibLoadIeProxy();
+ return 0;
+}
diff --git a/src/mir_app/src/netlib.h b/src/mir_app/src/netlib.h new file mode 100644 index 0000000000..949d1755b2 --- /dev/null +++ b/src/mir_app/src/netlib.h @@ -0,0 +1,220 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#define NLH_INVALID 0
+#define NLH_USER 'USER'
+#define NLH_CONNECTION 'CONN'
+#define NLH_BOUNDPORT 'BIND'
+#define NLH_PACKETRECVER 'PCKT'
+int GetNetlibHandleType(void*);
+
+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
+{
+ NetlibHTTPProxyPacketQueue *next;
+ PBYTE dataBuffer;
+ int dataBufferLen;
+};
+
+typedef union _SOCKADDR_INET_M {
+ SOCKADDR_IN Ipv4;
+ SOCKADDR_IN6 Ipv6;
+ USHORT si_family;
+} SOCKADDR_INET_M, *PSOCKADDR_INET_M;
+
+struct NetlibConnection
+{
+ int handleType;
+ SOCKET s, s2;
+ bool usingHttpGateway;
+ bool usingDirectHttpGateway;
+ bool proxyAuthNeeded;
+ bool dnsThroughProxy;
+ bool termRequested;
+ NetlibUser *nlu;
+ NETLIBHTTPPROXYINFO nlhpi;
+ PBYTE dataBuffer;
+ int dataBufferLen;
+ CRITICAL_SECTION csHttpSequenceNums;
+ HANDLE hOkToCloseEvent;
+ LONG dontCloseNow;
+ NetlibNestedCriticalSection ncsSend, ncsRecv;
+ HSSL hSsl;
+ NetlibHTTPProxyPacketQueue * pHttpProxyPacketQueue;
+ char *szNewUrl;
+ char *szProxyServer;
+ WORD wProxyPort;
+ int proxyType;
+ int pollingTimeout;
+ unsigned lastPost;
+ NETLIBOPENCONNECTION nloc;
+};
+
+struct NetlibBoundPort {
+ int handleType;
+ SOCKET s;
+ SOCKET s6;
+ WORD wPort;
+ WORD wExPort;
+ NetlibUser *nlu;
+ NETLIBNEWCONNECTIONPROC_V2 pfnNewConnectionV2;
+ HANDLE hThread;
+ void *pExtra;
+};
+
+struct NetlibPacketRecver {
+ int handleType;
+ 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(NetlibNestedCriticalSection *nlncs);
+void NetlibDeleteNestedCS(NetlibNestedCriticalSection *nlncs);
+#define NLNCS_SEND 0
+#define NLNCS_RECV 1
+int NetlibEnterNestedCS(NetlibConnection *nlc, int which);
+void NetlibLeaveNestedCS(NetlibNestedCriticalSection *nlncs);
+INT_PTR NetlibBase64Encode(WPARAM wParam, LPARAM lParam);
+INT_PTR NetlibBase64Decode(WPARAM wParam, LPARAM lParam);
+INT_PTR NetlibHttpUrlEncode(WPARAM wParam, LPARAM lParam);
+
+extern mir_cs csNetlibUser;
+extern LIST<NetlibUser> netlibUser;
+
+//netlibautoproxy.c
+void NetlibLoadIeProxy(void);
+void NetlibUnloadIeProxy(void);
+char* NetlibGetIeProxy(char *szUrl);
+bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps);
+
+//netlibbind.c
+int NetlibFreeBoundPort(NetlibBoundPort *nlbp);
+INT_PTR NetlibBindPort(WPARAM wParam, LPARAM lParam);
+bool BindSocketToPort(const char *szPorts, SOCKET s, SOCKET s6, 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(NetlibConnection *nlc, NetlibUser *nlu, NETLIBOPENCONNECTION *nloc);
+int NetlibHttpGatewayRecv(NetlibConnection *nlc, char *buf, int len, int flags);
+int NetlibHttpGatewayPost(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(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(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);
+
+bool NetlibStringToAddress(const char* str, SOCKADDR_INET_M* addr);
+char* NetlibAddressToString(SOCKADDR_INET_M* addr);
+void NetlibGetConnectionInfo(NetlibConnection* nlc, NETLIBCONNINFO *connInfo);
+NETLIBIPLIST* GetMyIp(unsigned flags);
+
+//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 NetlibDestroySecurityProvider(HANDLE hSecurity);
+HANDLE NetlibInitSecurityProvider(const TCHAR* szProvider, const TCHAR* szPrincipal);
+HANDLE NetlibInitSecurityProvider(const char* szProvider, const char* szPrincipal);
+char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const TCHAR* login, const TCHAR* psw,
+ bool http, unsigned& complete);
+
+static __inline INT_PTR NLSend(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(NetlibConnection *nlc, char *buf, int len, int flags) {
+ NETLIBBUFFER nlb = {buf, len, flags};
+ return NetlibRecv((WPARAM)nlc, (LPARAM)&nlb);
+}
diff --git a/src/mir_app/src/netlibautoproxy.cpp b/src/mir_app/src/netlibautoproxy.cpp new file mode 100644 index 0000000000..92982a9999 --- /dev/null +++ b/src/mir_app/src/netlibautoproxy.cpp @@ -0,0 +1,453 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.h"
+
+#include <wininet.h>
+
+/*
+/////////////////////////////////////////////////////////////////////
+// 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<char> 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, "https://%s", nlc->nloc.szHost);
+ usingSsl = true;
+ }
+ else if (nlc->nloc.flags & (NLOCF_HTTPGATEWAY | NLOCF_HTTP) || nlc->usingHttpGateway)
+ mir_snprintf(szUrl, "http://%s", nlc->nloc.szHost);
+ else
+ {
+ strncpy_s(szUrl, nlc->nloc.szHost, _TRUNCATE);
+ 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 void 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)mir_strlen(param->szUrl),
+ param->szHost, (DWORD)mir_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");
+}
+
+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 (mir_strcmp(proxyBypass[i], "<local>") == 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 = mir_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]) {
+ IeProxyParam param = { szUrl, szHost, NULL };
+ HANDLE hThread = mir_forkthread(NetlibIeProxyThread, ¶m);
+ WaitForSingleObject(hThread, INFINITE);
+ 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);
+ RegQueryValueExA(hSettings, "AutoConfigUrl", NULL, NULL, (BYTE*)szAutoUrlStr, &tValueLen);
+
+ tValueLen = SIZEOF(szProxyBypassStr);
+ RegQueryValueExA(hSettings, "ProxyOverride", NULL, NULL, (BYTE*)szProxyBypassStr, &tValueLen);
+
+ RegCloseKey(hSettings);
+
+ if (bEnabled)
+ {
+ char* szProxy = ltrim(szHostStr);
+ if (szProxy[0] == 0) { enabled = false; return; }
+
+ while(true)
+ {
+ 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 = mir_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;
+ while(true)
+ {
+ 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]);
+
+ mir_free(abuf.lpszScriptBuffer);
+
+ CloseHandle(hIeProxyMutex);
+}
diff --git a/src/mir_app/src/netlibbind.cpp b/src/mir_app/src/netlibbind.cpp new file mode 100644 index 0000000000..77fb25fd97 --- /dev/null +++ b/src/mir_app/src/netlibbind.cpp @@ -0,0 +1,308 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.h"
+
+bool BindSocketToPort(const char *szPorts, SOCKET s, SOCKET s6, int* portn)
+{
+ SOCKADDR_IN sin = {0};
+ sin.sin_family = AF_INET;
+
+ SOCKADDR_IN6 sin6 = {0};
+ sin6.sin6_family = AF_INET6;
+
+ mir_cslock lck(csNetlibUser);
+
+ if (--*portn < 0 && (s != INVALID_SOCKET || s6 != INVALID_SOCKET)) {
+ BindSocketToPort(szPorts, INVALID_SOCKET, INVALID_SOCKET, portn);
+ if (*portn == 0)
+ return false;
+
+ WORD num;
+ CallService(MS_UTILS_GETRANDOM, sizeof(WORD), (LPARAM)&num);
+ *portn = num % *portn;
+ }
+
+ bool before = false;
+ while (true) {
+ 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)
+ return false;
+
+ sin.sin_port = htons((WORD)port);
+ bool bV4Mapped = s == INVALID_SOCKET || bind(s, (SOCKADDR*)&sin, sizeof(sin)) == 0;
+
+ sin6.sin6_port = htons((WORD)port);
+ bool bV6Mapped = s6 == INVALID_SOCKET || bind(s6, (PSOCKADDR)&sin6, sizeof(sin6)) == 0;
+
+ if (bV4Mapped && bV6Mapped) {
+ *portn = portnum + 1;
+ return true;
+ }
+ }
+ }
+ psz = pszEnd;
+ }
+
+ if (*portn < 0) {
+ *portn = portnum;
+ return true;
+ }
+
+ if (*portn >= portnum)
+ *portn = 0;
+ else
+ before = true;
+ }
+}
+
+int NetlibFreeBoundPort(struct NetlibBoundPort *nlbp)
+{
+ closesocket(nlbp->s);
+ closesocket(nlbp->s6);
+ if (nlbp->hThread)
+ WaitForSingleObject(nlbp->hThread, INFINITE);
+ NetlibLogf(nlbp->nlu, "(%u) Port %u closed for incoming connections", nlbp->s, nlbp->wPort);
+ mir_free(nlbp);
+ return 1;
+}
+
+static void NetlibBindAcceptThread(void* param)
+{
+ NetlibBoundPort *nlbp = (NetlibBoundPort*)param;
+ NetlibLogf(nlbp->nlu, "(%u) Port %u opened for incoming connections", nlbp->s, nlbp->wPort);
+
+ while (true) {
+ fd_set r;
+ FD_ZERO(&r);
+ if (nlbp->s != INVALID_SOCKET)
+ FD_SET(nlbp->s, &r);
+ if (nlbp->s6 != INVALID_SOCKET)
+ FD_SET(nlbp->s6, &r);
+ if (select(0, &r, NULL, NULL, NULL) == SOCKET_ERROR) {
+ NetlibLogf(nlbp->nlu, "NetlibBindAcceptThread (%p): select failed (%d)", nlbp->s, GetLastError());
+ break;
+ }
+
+ SOCKADDR_INET_M sin;
+ int sinLen = sizeof(sin);
+ memset(&sin, 0, sizeof(sin));
+
+ SOCKET s;
+ if (FD_ISSET(nlbp->s, &r)) {
+ s = accept(nlbp->s, (sockaddr*)&sin, &sinLen);
+ if (s == INVALID_SOCKET) {
+ NetlibLogf(nlbp->nlu, "NetlibBindAcceptThread (%p): accept V4 failed (%d)", nlbp->s, GetLastError());
+ break;
+ }
+ }
+ else if (FD_ISSET(nlbp->s6, &r)) {
+ s = accept(nlbp->s6, (sockaddr*)&sin, &sinLen);
+ if (s == INVALID_SOCKET) {
+ NetlibLogf(nlbp->nlu, "NetlibBindAcceptThread (%p): accept V6 failed (%d)", nlbp->s, GetLastError());
+ break;
+ }
+ }
+ else s = NULL;
+
+ NetlibLogf(nlbp->nlu, "New incoming connection on port %u from %s (%p)", nlbp->wPort, ptrA(NetlibAddressToString(&sin)), s);
+
+ NetlibConnection *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);
+ NetlibInitializeNestedCS(&nlc->ncsSend);
+ NetlibInitializeNestedCS(&nlc->ncsRecv);
+
+ if (nlbp->pfnNewConnectionV2)
+ nlbp->pfnNewConnectionV2(nlc, ntohl(sin.Ipv4.sin_addr.S_un.S_addr), nlbp->pExtra);
+ }
+
+ NetlibUPnPDeletePortMapping(nlbp->wExPort, "TCP");
+ nlbp->hThread = 0;
+
+ NetlibLogf(nlbp->nlu, "NetlibBindAcceptThread: (%p) thread for port %u closed", nlbp->s, nlbp->wPort);
+}
+
+INT_PTR NetlibBindPort(WPARAM wParam, LPARAM lParam)
+{
+ NETLIBBIND *nlb = (NETLIBBIND*)lParam;
+ NetlibUser *nlu = (NetlibUser*)wParam;
+ struct NetlibBoundPort *nlbp;
+ SOCKADDR_IN sin = { 0 };
+ SOCKADDR_IN6 sin6 = { 0 };
+ int foundPort = 0;
+
+ 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))
+ return 0;
+
+ nlbp = (NetlibBoundPort*)mir_calloc(sizeof(NetlibBoundPort));
+ nlbp->handleType = NLH_BOUNDPORT;
+ nlbp->nlu = nlu;
+ nlbp->pfnNewConnectionV2 = nlb->pfnNewConnectionV2;
+
+ nlbp->s = socket(PF_INET, SOCK_STREAM, 0);
+ nlbp->s6 = socket(PF_INET6, SOCK_STREAM, 0);
+ nlbp->pExtra = nlb->pExtra;
+ if (nlbp->s == INVALID_SOCKET && nlbp->s6 == INVALID_SOCKET) {
+ NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "socket", WSAGetLastError());
+ mir_free(nlbp);
+ return 0;
+ }
+ sin.sin_family = AF_INET;
+ sin6.sin6_family = AF_INET6;
+
+ /* 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, nlbp->s6, &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);
+ sin6.sin6_port = htons(nlb->wPort);
+ }
+
+ if (nlbp->s != INVALID_SOCKET)
+ if (bind(nlbp->s, (PSOCKADDR)&sin, sizeof(sin)) == 0) {
+ SOCKADDR_IN sin = { 0 };
+ int len = sizeof(sin);
+ if (!getsockname(nlbp->s, (PSOCKADDR)&sin, &len))
+ sin6.sin6_port = sin.sin_port;
+ foundPort = 1;
+ }
+
+ if (nlbp->s6 != INVALID_SOCKET)
+ if (bind(nlbp->s6, (PSOCKADDR)&sin6, sizeof(sin6)) == 0)
+ foundPort = 1;
+ }
+ if (!foundPort) {
+ NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "bind", WSAGetLastError());
+LBL_Error:
+ closesocket(nlbp->s);
+ closesocket(nlbp->s6);
+ mir_free(nlbp);
+ return 0;
+ }
+
+ if (nlbp->s != INVALID_SOCKET && listen(nlbp->s, 5)) {
+ NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "listen", WSAGetLastError());
+ goto LBL_Error;
+ }
+
+ if (nlbp->s6 != INVALID_SOCKET && listen(nlbp->s6, 5)) {
+ NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "listen", WSAGetLastError());
+ goto LBL_Error;
+ }
+
+ SOCKADDR_INET_M sinm = { 0 };
+ int len = sizeof(sinm);
+ if (!getsockname(nlbp->s, (PSOCKADDR)&sinm, &len)) {
+ nlb->wPort = ntohs(sinm.Ipv4.sin_port);
+ nlb->dwInternalIP = ntohl(sinm.Ipv4.sin_addr.S_un.S_addr);
+ }
+ else if (!getsockname(nlbp->s6, (PSOCKADDR)&sinm, &len))
+ nlb->wPort = ntohs(sinm.Ipv6.sin6_port);
+ else {
+ NetlibLogf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "getsockname", WSAGetLastError());
+ goto LBL_Error;
+ }
+ nlbp->wPort = nlb->wPort;
+
+ if (nlb->dwInternalIP == 0) {
+ char hostname[64] = "";
+ gethostname(hostname, SIZEOF(hostname));
+
+ PHOSTENT he = gethostbyname(hostname);
+ if (he && he->h_addr)
+ nlb->dwInternalIP = ntohl(*(PDWORD)he->h_addr);
+ }
+
+ DWORD extIP;
+ if (nlu->settings.enableUPnP && NetlibUPnPAddPortMapping(nlb->wPort, "TCP", &nlbp->wExPort, &extIP, true)) {
+ NetlibLogf(NULL, "UPnP port mapping succeeded. Internal Port: %u External Port: %u\n", nlb->wPort, nlbp->wExPort);
+ 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;
+ nlb->wExPort = nlb->wPort;
+ nlb->dwExternalIP = nlb->dwInternalIP;
+ }
+
+ nlbp->hThread = mir_forkthread(NetlibBindAcceptThread, nlbp);
+ return (INT_PTR)nlbp;
+}
diff --git a/src/mir_app/src/netlibhttp.cpp b/src/mir_app/src/netlibhttp.cpp new file mode 100644 index 0000000000..9a2e67dc54 --- /dev/null +++ b/src/mir_app/src/netlibhttp.cpp @@ -0,0 +1,1168 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "../plugins/zlib/src/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 mir_strcmpi(p1->szServer, p2->szServer); }
+};
+
+struct ProxyAuthList : OBJLIST<ProxyAuth>
+{
+ ProxyAuthList() : OBJLIST<ProxyAuth>(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<ProxyAuth>::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);
+ while (true) {
+ 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(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++) {
+ NETLIBHTTPHEADER &p = nlhrReply->headers[i];
+ if (_stricmp(p.szName, hdr) == 0)
+ return p.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++) {
+ NETLIBHTTPHEADER &p = nlhrReply->headers[i];
+ if (_stricmp(p.szName, hdr) == 0) {
+ if (_strnicmp(p.szValue, "Negotiate", 9) == 0)
+ szNegoHdr = p.szValue;
+ else if (_strnicmp(p.szValue, "NTLM", 4) == 0)
+ szNtlmHdr = p.szValue;
+ else if (_strnicmp(p.szValue, "Basic", 5) == 0)
+ szBasicHdr = p.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 = mir_strcmp(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) {
+ mir_cslock lck(csNetlibUser);
+ szLogin = mir_a2t(nlc->nlu->settings.szProxyAuthUser);
+ szPassw = mir_a2t(nlc->nlu->settings.szProxyAuthPassword);
+ }
+
+ szAuthHdr = NtlmCreateResponseFromChallenge(m_hNtlmSecurity,
+ szChallenge, szLogin, szPassw, true, complete);
+
+ if (!szAuthHdr) {
+ NetlibLogf(NULL, "Security login %s failed, user: %S pssw: %S",
+ 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;
+
+ while(true) {
+ 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';
+ if ((peol = strchr(buffer, '\n')) != NULL)
+ break;
+
+ if ((int)mir_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);
+ }
+
+ 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(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)
+{
+ NetlibConnection *nlc = (struct NetlibConnection*)wParam;
+ NETLIBHTTPREQUEST *nlhr = (NETLIBHTTPREQUEST*)lParam;
+ NETLIBHTTPREQUEST *nlhrReply = NULL;
+ HttpSecurityContext httpSecurity;
+
+ struct ResizableCharBuffer httpRequest = {0};
+ char *szHost = NULL, *szNewUrl = NULL;
+ char *pszProxyAuthHdr = NULL, *pszAuthHdr = NULL;
+ int i, doneHostHeader, doneContentLengthHeader, doneProxyAuthHeader, doneAuthHeader;
+ int bytesSent = 0;
+ bool lastFirstLineFail = false;
+
+ if (nlhr == NULL || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->szUrl == NULL || nlhr->szUrl[0] == '\0') {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+
+ int hdrTimeout = (nlhr->timeout) ? nlhr->timeout : HTTPRECVHEADERSTIMEOUT;
+
+ const char *pszRequest;
+ 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;
+
+ const char *pszFullUrl = nlhr->szUrl;
+ const char *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(64);
+ if (cln) *cln = ':';
+ mir_snprintf(szHost, 64, "%s%s", inet_ntoa(*(PIN_ADDR)&ip), cln ? cln : "");
+ }
+ }
+ 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++) {
+ NETLIBHTTPHEADER &p = nlhr->headers[i];
+ if (!mir_strcmpi(p.szName, "Host")) doneHostHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Content-Length")) doneContentLengthHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Proxy-Authorization")) doneProxyAuthHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Authorization")) doneAuthHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Connection")) continue;
+ if (p.szValue == NULL) continue;
+ AppendToCharBuffer(&httpRequest, "%s: %s\r\n", p.szName, p.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)
+ break;
+
+ 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, &nlhr->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;
+ }
+
+ lastFirstLineFail = true;
+ continue;
+ }
+
+ int resultCode = nlhr->resultCode;
+ 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) && (nlhr->flags & NLHRF_REDIRECT))) { // 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 : mir_strlen(pszFullUrl);
+ }
+
+ nlc->szNewUrl = (char*)mir_realloc(nlc->szNewUrl, rlen + mir_strlen(tmpUrl) * 3 + 1);
+
+ strncpy(nlc->szNewUrl, pszFullUrl, rlen);
+ mir_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)
+ replaceStr(pszProxyAuthHdr, NULL);
+
+ if (pszAuthHdr && resultCode != 401 && !doneAuthHeader)
+ replaceStr(pszAuthHdr, NULL);
+
+ if (nlhrReply) {
+ NetlibHttpFreeRequestStruct(0, (LPARAM)nlhrReply);
+ nlhrReply = NULL;
+ }
+ }
+
+ 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) {
+ for (int i=0; i < nlhr->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhr->headers[i];
+ mir_free(p.szName);
+ mir_free(p.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)
+{
+ NetlibConnection *nlc = (struct NetlibConnection*)wParam;
+ if (!NetlibEnterNestedCS(nlc, NLNCS_RECV))
+ return 0;
+
+ char *peol, *pbuffer;
+ int headersCount = 0, bufferSize = 8192;
+
+ DWORD dwRequestTimeoutTime = GetTickCount() + HTTPRECVDATATIMEOUT;
+ NETLIBHTTPREQUEST *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;
+
+ int firstLineLength = 0;
+ if (!HttpPeekFirstResponseLine(nlc, dwRequestTimeoutTime, lParam | MSG_PEEK, &nlhr->resultCode, &nlhr->szResultDescr, &firstLineLength)) {
+ NetlibLeaveNestedCS(&nlc->ncsRecv);
+ NetlibHttpFreeRequestStruct(0, (LPARAM)nlhr);
+ return 0;
+ }
+
+ char *buffer = (char*)mir_alloc(bufferSize + 1);
+ int 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;
+ for (bool headersCompleted = false; !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;
+ }
+ }
+ }
+
+ // Receive 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;
+
+ if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) ||
+ nlhr == NULL || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) ||
+ nlhr->szUrl == NULL || nlhr->szUrl[0] == 0)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ if (nlhr->nlc != NULL && GetNetlibHandleType(nlhr->nlc) != NLH_CONNECTION)
+ nlhr->nlc = NULL;
+
+ 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) {
+ nlhrSend.headers[nlhrSend.headersCount].szName = "User-Agent";
+ nlhrSend.headers[nlhrSend.headersCount].szValue = szUserAgent;
+ ++nlhrSend.headersCount;
+
+ char szMirandaVer[64];
+ CallService(MS_SYSTEM_GETVERSIONTEXT, SIZEOF(szMirandaVer), (LPARAM)szMirandaVer);
+ char *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);
+ nlhr->resultCode = nlhrSend.resultCode;
+ NetlibCloseHandle((WPARAM)nlc, 0);
+ return 0;
+ }
+ if (!doneUserAgentHeader || !doneAcceptEncoding)
+ mir_free(nlhrSend.headers);
+
+ DWORD 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);
+
+ DWORD 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;
+
+ while(true) {
+ 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; i<nlhrReply->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhrReply->headers[i];
+ if (!mir_strcmpi(p.szName, "Content-Length"))
+ dataLen = atoi(p.szValue);
+
+ if (!mir_strcmpi(p.szName, "Content-Encoding")) {
+ cenc = i;
+ if (strstr(p.szValue, "gzip"))
+ cenctype = 1;
+ else if (strstr(p.szValue, "deflate"))
+ cenctype = 2;
+ }
+
+ if (!mir_strcmpi(p.szName, "Connection"))
+ close = !mir_strcmpi(p.szValue, "close");
+
+ if (!mir_strcmpi(p.szName, "Transfer-Encoding") &&
+ !mir_strcmpi(p.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) {
+ while(true) {
+ 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)
+ break;
+
+ 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);
+ }
+
+ nlhrReply->pData[nlhrReply->dataLength] = '\0';
+ }
+
+ if (chunked) {
+ nlhrReply->headers[chunkhdr].szName = (char*)mir_realloc(nlhrReply->headers[chunkhdr].szName, 16);
+ mir_strcpy(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/mir_app/src/netlibhttpproxy.cpp b/src/mir_app/src/netlibhttpproxy.cpp new file mode 100644 index 0000000000..62e7784c64 --- /dev/null +++ b/src/mir_app/src/netlibhttpproxy.cpp @@ -0,0 +1,459 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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)
+{
+ mir_cslock lck(nlc->csHttpSequenceNums);
+ while (pck-- && nlc->pHttpProxyPacketQueue != NULL) {
+ NetlibHTTPProxyPacketQueue *p = nlc->pHttpProxyPacketQueue;
+ nlc->pHttpProxyPacketQueue = nlc->pHttpProxyPacketQueue->next;
+
+ mir_free(p->dataBuffer);
+ mir_free(p);
+ }
+}
+
+static bool NetlibHttpGatewaySend(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)) {
+ mir_cslock lck(nlc->csHttpSequenceNums);
+ mir_snprintf(szUrl, "%s%u", nlc->nlhpi.szHttpGetUrl, nlc->nlhpi.firstGetSequence++);
+ if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE)
+ nlc->nlhpi.firstPostSequence++;
+ 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, "%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 = mir_strcmp(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";
+ return NetlibHttpSendRequest((WPARAM)nlc, (LPARAM)&nlhrSend) != SOCKET_ERROR;
+}
+
+static bool NetlibHttpGatewayStdPost(NetlibConnection *nlc, int &numPackets)
+{
+ int np = 0, len = 0;
+ char *buf;
+ NetlibHTTPProxyPacketQueue *p;
+ {
+ mir_cslock lck(nlc->csHttpSequenceNums);
+
+ for (p = nlc->pHttpProxyPacketQueue; p != NULL && np < nlc->nlhpi.combinePackets; p = p->next) {
+ np++;
+ len += p->dataBufferLen;
+ }
+
+ int dlen = 0;
+ buf = (char*)alloca(len);
+ numPackets = np;
+
+ for (p = nlc->pHttpProxyPacketQueue; np--; p = p->next) {
+ memcpy(buf + dlen, p->dataBuffer, p->dataBufferLen);
+ dlen += p->dataBufferLen;
+ }
+ }
+
+ return NetlibHttpGatewaySend(nlc, reqNewPost, buf, len);
+}
+
+static bool NetlibHttpGatewayOscarPost(NetlibConnection *nlc, const char *buf, int len, int flags)
+{
+ 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);
+
+ mir_cslock lck(nlc->csHttpSequenceNums);
+ nlc->nlhpi.firstPostSequence++;
+ if (nlc->nlhpi.flags & NLHPIF_GETPOSTSAMESEQUENCE)
+ nlc->nlhpi.firstGetSequence++;
+
+ return res;
+}
+
+int NetlibHttpGatewayPost(NetlibConnection *nlc, const char *buf, int len, int flags)
+{
+ 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.
+ */
+
+ NetlibHTTPProxyPacketQueue *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
+ */
+
+ mir_cslock lck(nlc->csHttpSequenceNums);
+ if (nlc->pHttpProxyPacketQueue == NULL)
+ nlc->pHttpProxyPacketQueue = p;
+ else {
+ NetlibHTTPProxyPacketQueue *t = nlc->pHttpProxyPacketQueue;
+ while (t->next != NULL)
+ t = t->next;
+ t->next = p;
+ }
+
+ /*
+ * 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(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 (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(NetlibConnection *nlc, NetlibUser *nlu, NETLIBOPENCONNECTION *nloc)
+{
+ NETLIBHTTPREQUEST *nlhrReply = NULL;
+ {
+ mir_cslock lck(nlc->csHttpSequenceNums);
+ 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;
+ 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)
+{
+ NetlibUser * nu = (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;
+ NetlibConnection *nlc = (struct NetlibConnection*)wParam;
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION) return -1;
+ oldTimeout = nlc->pollingTimeout;
+ nlc->pollingTimeout = lParam;
+ return oldTimeout;
+}
diff --git a/src/mir_app/src/netliblog.cpp b/src/mir_app/src/netliblog.cpp new file mode 100644 index 0000000000..3ac72264d8 --- /dev/null +++ b/src/mir_app/src/netliblog.cpp @@ -0,0 +1,532 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.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;
+ int timeFormat;
+ int showUser;
+ int dumpSent, dumpRecv, dumpProxy, dumpSsl;
+ int textDumps, autoDetectText;
+ CMString tszFile, tszUserFile;
+}
+static logOptions = { 0 };
+
+struct LOGMSG
+{
+ const char* pszHead;
+ const char* pszMsg;
+};
+
+static __int64 mirandaStartTime, perfCounterFreq;
+static int bIsActive = TRUE;
+static HANDLE hLogEvent = NULL;
+static HANDLE hLogger = NULL;
+
+static void InitLog()
+{
+ if (hLogger) {
+ mir_closeLog(hLogger);
+ hLogger = NULL;
+ }
+
+ ptrT szBuf(db_get_tsa(NULL, "Netlib", "File"));
+ if (mir_tstrlen(szBuf)) {
+ logOptions.tszUserFile = szBuf;
+
+ TCHAR path[MAX_PATH];
+ PathToAbsoluteT(VARST(szBuf), path);
+ logOptions.tszFile = path;
+ }
+ else {
+ db_set_ts(NULL, "Netlib", "File", logOptions.tszUserFile = _T("%miranda_logpath%\\netlog.txt"));
+ logOptions.tszFile = Utils_ReplaceVarsT(logOptions.tszUserFile);
+ }
+
+ if (logOptions.toFile)
+ hLogger = mir_createLog("Netlib", LPGENT("Standard netlib log"), logOptions.tszFile, 0);
+}
+
+static const TCHAR* szTimeFormats[] =
+{
+ LPGENT("No times"),
+ LPGENT("Standard hh:mm:ss times"),
+ LPGENT("Times in milliseconds"),
+ LPGENT("Times in microseconds")
+};
+
+static INT_PTR CALLBACK LogOptionsDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ TCHAR str[MAX_PATH];
+
+ 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);
+ {
+ for (int 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.tszUserFile);
+ SetDlgItemText(hwndDlg, IDC_PATH, logOptions.tszFile);
+ CheckDlgButton(hwndDlg, IDC_SHOWTHISDLGATSTART, db_get_b(NULL, "Netlib", "ShowLogOptsAtStart", 0) ? BST_CHECKED : BST_UNCHECKED);
+ {
+ ptrA szRun(db_get_sa(NULL, "Netlib", "RunAtStart"));
+ if (szRun)
+ SetDlgItemTextA(hwndDlg, IDC_RUNATSTART, szRun);
+
+ HWND hwndFilter = GetDlgItem(hwndDlg, IDC_FILTER);
+ SetWindowLongPtr(hwndFilter, GWL_STYLE, GetWindowLongPtr(hwndFilter, GWL_STYLE) | (TVS_NOHSCROLL | TVS_CHECKBOXES));
+
+ TVINSERTSTRUCT tvis = { 0 };
+ tvis.hInsertAfter = TVI_SORT;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_STATE;
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+
+ for (int 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_FILENAME:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ if ((HWND)lParam == GetFocus())
+ CheckDlgButton(hwndDlg, IDC_TOFILE, BST_CHECKED);
+
+ TCHAR path[MAX_PATH];
+ GetWindowText((HWND)lParam, path, SIZEOF(path));
+
+ PathToAbsoluteT(VARST(path), path);
+ SetDlgItemText(hwndDlg, IDC_PATH, path);
+ }
+ break;
+
+ case IDC_FILENAMEBROWSE:
+ case IDC_RUNATSTARTBROWSE:
+ GetWindowText(GetWindow((HWND)lParam, GW_HWNDPREV), str, SIZEOF(str));
+ {
+ TCHAR filter[200];
+ mir_sntprintf(filter, SIZEOF(filter), _T("%s (*)%c*%c"), TranslateT("All files"), 0, 0);
+
+ OPENFILENAME ofn = { 0 };
+ 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");
+ }
+ 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) {
+ memmove(str + 1, str, ((SIZEOF(str) - 2) * sizeof(TCHAR)));
+ str[0] = '"';
+ mir_tstrcat(str, _T("\""));
+ }
+ SetWindowText(GetWindow((HWND)lParam, GW_HWNDPREV), str);
+ }
+ break;
+
+ case IDC_RUNNOW:
+ GetDlgItemText(hwndDlg, IDC_RUNATSTART, str, SIZEOF(str));
+ if (str[0]) {
+ STARTUPINFO si = { sizeof(si) };
+ PROCESS_INFORMATION pi;
+ CreateProcess(NULL, str, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+ }
+ break;
+
+ case IDOK:
+ GetDlgItemText(hwndDlg, IDC_RUNATSTART, str, SIZEOF(str));
+ db_set_ts(NULL, "Netlib", "RunAtStart", str);
+ db_set_b(NULL, "Netlib", "ShowLogOptsAtStart", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SHOWTHISDLGATSTART));
+
+ GetDlgItemText(hwndDlg, IDC_FILENAME, str, SIZEOF(str));
+ logOptions.tszUserFile = rtrimt(str);
+ db_set_ts(NULL, "Netlib", "File", str);
+
+ GetDlgItemText(hwndDlg, IDC_PATH, str, SIZEOF(str));
+ logOptions.tszFile = rtrimt(str);
+
+ db_set_b(NULL, "Netlib", "DumpRecv", logOptions.dumpRecv = IsDlgButtonChecked(hwndDlg, IDC_DUMPRECV));
+ db_set_b(NULL, "Netlib", "DumpSent", logOptions.dumpSent = IsDlgButtonChecked(hwndDlg, IDC_DUMPSENT));
+ db_set_b(NULL, "Netlib", "DumpProxy", logOptions.dumpProxy = IsDlgButtonChecked(hwndDlg, IDC_DUMPPROXY));
+ db_set_b(NULL, "Netlib", "DumpSsl", logOptions.dumpSsl = IsDlgButtonChecked(hwndDlg, IDC_DUMPSSL));
+ db_set_b(NULL, "Netlib", "TextDumps", logOptions.textDumps = IsDlgButtonChecked(hwndDlg, IDC_TEXTDUMPS));
+ db_set_b(NULL, "Netlib", "AutoDetectText", logOptions.autoDetectText = IsDlgButtonChecked(hwndDlg, IDC_AUTODETECTTEXT));
+ db_set_b(NULL, "Netlib", "TimeFormat", logOptions.timeFormat = SendDlgItemMessage(hwndDlg, IDC_TIMEFORMAT, CB_GETCURSEL, 0, 0));
+ db_set_b(NULL, "Netlib", "ShowUser", logOptions.showUser = IsDlgButtonChecked(hwndDlg, IDC_SHOWNAMES));
+ db_set_b(NULL, "Netlib", "ToOutputDebugString", logOptions.toOutputDebugString = IsDlgButtonChecked(hwndDlg, IDC_TOOUTPUTDEBUGSTRING));
+ db_set_b(NULL, "Netlib", "ToFile", logOptions.toFile = IsDlgButtonChecked(hwndDlg, IDC_TOFILE));
+ {
+ 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;
+ db_set_dw(NULL, "Netlib", "NLlog", checked);
+ }
+ else if (tvi.lParam < netlibUser.getCount()) {
+ netlibUser[tvi.lParam]->toLog = checked;
+ db_set_dw(NULL, netlibUser[tvi.lParam]->user.szSettingsModule, "NLlog", checked);
+ }
+
+ tvi.hItem = TreeView_GetNextSibling(hwndFilter, tvi.hItem);
+ }
+ }
+ InitLog();
+ // fall through
+ case IDCANCEL:
+ DestroyWindow(hwndDlg);
+ }
+ 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(g_hInst, 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)
+{
+ if (!bIsActive)
+ return 0;
+
+ DWORD dwOriginalLastError = GetLastError();
+
+ NetlibUser *nlu = (NetlibUser*)wParam;
+ const char *pszMsg = (const char*)lParam;
+ if ((nlu != NULL && GetNetlibHandleType(nlu) != NLH_USER) || pszMsg == NULL) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ /* if the Netlib user handle is NULL, just pretend its not */
+ if (!(nlu != NULL ? nlu->toLog : logOptions.toLog))
+ return 1;
+
+ LARGE_INTEGER liTimeNow;
+ char szTime[32], szHead[128];
+ switch (logOptions.timeFormat) {
+ case TIMEFORMAT_HHMMSS:
+ GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, NULL, NULL, szTime, SIZEOF(szTime));
+ mir_strcat(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;
+ }
+
+ char *szUser = (logOptions.showUser) ? (nlu == NULL ? NULL : nlu->user.szSettingsModule) : NULL;
+ if (szUser)
+ mir_snprintf(szHead, SIZEOF(szHead), "[%s%04X] [%s] ", szTime, GetCurrentThreadId(), szUser);
+ else
+ mir_snprintf(szHead, SIZEOF(szHead), "[%s%04X] ", szTime, GetCurrentThreadId());
+
+ if (logOptions.toOutputDebugString) {
+ if (szHead[0])
+ OutputDebugStringA(szHead);
+ OutputDebugStringA(pszMsg);
+ OutputDebugStringA("\n");
+ }
+
+ if (logOptions.toFile && !logOptions.tszFile.IsEmpty()) {
+ size_t len = mir_strlen(pszMsg);
+ mir_writeLogA(hLogger, "%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n");
+ }
+
+ LOGMSG logMsg = { szHead, pszMsg };
+ NotifyFastHook(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 = Utf8EncodeW(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(NetlibConnection *nlc, PBYTE buf, int len, int sent, int flags)
+{
+ char szTitleLine[128];
+ char *szBuf;
+ 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 || GetSubscribersCount(hLogEvent) != 0 || (logOptions.toFile && !logOptions.tszFile.IsEmpty())))
+ 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);
+ NetlibUser *nlu = nlc ? nlc->nlu : NULL;
+ int 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;
+
+ bool isText = true;
+ if (!logOptions.textDumps)
+ isText = false;
+ else if (!(flags & MSG_DUMPASTEXT)) {
+ if (logOptions.autoDetectText) {
+ for (int i = 0; i < len; i++) {
+ if ((buf[i] < ' ' && buf[i] != '\t' && buf[i] != '\r' && buf[i] != '\n') || buf[i] >= 0x80) {
+ isText = false;
+ break;
+ }
+ }
+ }
+ else isText = false;
+ }
+
+ // Text data
+ if (isText) {
+ int sz = titleLineLen + len + 1;
+ useStack = sz <= 8192;
+ szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz));
+ memcpy(szBuf, szTitleLine, titleLineLen);
+ memcpy(szBuf + titleLineLen, (const char*)buf, len);
+ szBuf[titleLineLen + len] = '\0';
+ }
+ // Binary data
+ else {
+ int line, col, colsInLine;
+ int sz = titleLineLen + ((len + 16) >> 4) * 78 + 1;
+ useStack = sz <= 8192;
+
+ szBuf = (char*)(useStack ? alloca(sz) : mir_alloc(sz));
+ memcpy(szBuf, szTitleLine, titleLineLen);
+ char *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++) {
+ mir_strcpy(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)
+{
+ 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);
+
+ logOptions.dumpRecv = db_get_b(NULL, "Netlib", "DumpRecv", 1);
+ logOptions.dumpSent = db_get_b(NULL, "Netlib", "DumpSent", 1);
+ logOptions.dumpProxy = db_get_b(NULL, "Netlib", "DumpProxy", 1);
+ logOptions.dumpSsl = db_get_b(NULL, "Netlib", "DumpSsl", 0);
+ logOptions.textDumps = db_get_b(NULL, "Netlib", "TextDumps", 1);
+ logOptions.autoDetectText = db_get_b(NULL, "Netlib", "AutoDetectText", 1);
+ logOptions.timeFormat = db_get_b(NULL, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS);
+ logOptions.showUser = db_get_b(NULL, "Netlib", "ShowUser", 1);
+ logOptions.toOutputDebugString = db_get_b(NULL, "Netlib", "ToOutputDebugString", 0);
+ logOptions.toFile = db_get_b(NULL, "Netlib", "ToFile", 0);
+ logOptions.toLog = db_get_dw(NULL, "Netlib", "NLlog", 1);
+
+ InitLog();
+
+ if (db_get_b(NULL, "Netlib", "ShowLogOptsAtStart", 0))
+ NetlibLogShowOptions();
+
+ ptrT szBuf(db_get_tsa(NULL, "Netlib", "RunAtStart"));
+ if (szBuf) {
+ STARTUPINFO si = { sizeof(si) };
+ PROCESS_INFORMATION pi;
+ CreateProcess(NULL, szBuf, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+ }
+}
+
+void NetlibLogShutdown(void)
+{
+ bIsActive = FALSE;
+ DestroyHookableEvent(hLogEvent); hLogEvent = NULL;
+ if (IsWindow(logOptions.hwndOpts))
+ DestroyWindow(logOptions.hwndOpts);
+}
diff --git a/src/mir_app/src/netlibopenconn.cpp b/src/mir_app/src/netlibopenconn.cpp new file mode 100644 index 0000000000..83e34eb428 --- /dev/null +++ b/src/mir_app/src/netlibopenconn.cpp @@ -0,0 +1,890 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.h" +#include "netlib.h" + +extern mir_cs 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(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(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 || !nloc->szHost || !nloc->szHost[0]) return 0; + + size_t nHostLen = mir_strlen(nloc->szHost) + 1; + size_t nUserLen = nlu->settings.szProxyAuthUser ? mir_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(NetlibConnection *nlc, 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 + size_t nUserLen = mir_strlen(nlu->settings.szProxyAuthUser); + size_t nPassLen = mir_strlen(nlu->settings.szProxyAuthPassword); + PBYTE pAuthBuf = (PBYTE)mir_alloc(3 + nUserLen + nPassLen); + pAuthBuf[0] = 1; //auth version + pAuthBuf[1] = (BYTE)nUserLen; + memcpy(pAuthBuf + 2, nlu->settings.szProxyAuthUser, nUserLen); + pAuthBuf[2 + nUserLen] = (BYTE)nPassLen; + memcpy(pAuthBuf + 3 + nUserLen, nlu->settings.szProxyAuthPassword, nPassLen); + if (NLSend(nlc, (char*)pAuthBuf, int(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; + } + } + + size_t nHostLen; + DWORD hostIP; + + if (nlc->dnsThroughProxy) { + hostIP = inet_addr(nloc->szHost); + nHostLen = (hostIP == INADDR_NONE) ? mir_strlen(nloc->szHost) + 1 : 4; + } + else { + hostIP = DnsLookup(nlu, nloc->szHost); + if (hostIP == 0) + return 0; + nHostLen = 4; + } + PBYTE 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] = BYTE(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, int(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(NetlibConnection *nlc, NetlibUser *nlu, NETLIBOPENCONNECTION *nloc) +{ + //rfc2817 + NETLIBHTTPREQUEST nlhrSend = { 0 }; + 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, "%s:%u", nloc->szHost, nloc->wPort); + else { + DWORD ip = DnsLookup(nlu, nloc->szHost); + if (ip == 0) return false; + mir_snprintf(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; + } + + NETLIBHTTPREQUEST *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(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 }; + + // if dwTimeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway + unsigned int dwTimeout = (nloc && (nloc->cbSize == sizeof(NETLIBOPENCONNECTION)) && (nloc->flags & NLOCF_V2) && (nloc->timeout>0)) ? nloc->timeout : 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; + } + + PHOSTENT he; + 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); + he = gethostbyname(nlc->szProxyServer); + } + else { + if (!nloc || !nloc->szHost || nloc->szHost[0] == '[' || strchr(nloc->szHost, ':')) return false; + NetlibLogf(nlc->nlu, "(%p) Connecting to server %s:%d....", nlc, nloc->szHost, nloc->wPort); + + sin.sin_port = htons(nloc->wPort); + he = gethostbyname(nloc->szHost); + } + + for (char** har = he->h_addr_list; *har && !Miranda_Terminated(); ++har) { + sin.sin_addr.s_addr = *(u_long*)*har; + + char* szIp = NetlibAddressToString((SOCKADDR_INET_M*)&sin); + NetlibLogf(nlc->nlu, "(%p) Connecting to ip %s ....", nlc, szIp); + mir_free(szIp); + +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, INVALID_SOCKET, &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) { + rc = 0; + break; + } + + // didn't work, was it cos of nonblocking? + if (WSAGetLastError() != WSAEWOULDBLOCK) { + rc = SOCKET_ERROR; + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + continue; + } + + while (true) { + 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; + } + } + + if (rc == 0) break; + + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + } + + notblocking = 0; + if (nlc->s != INVALID_SOCKET) ioctlsocket(nlc->s, FIONBIO, ¬blocking); + if (rc && lasterr) SetLastError(lasterr); + return rc == 0; +} + +static bool my_connectIPv6(NetlibConnection *nlc, NETLIBOPENCONNECTION *nloc) +{ + if (!nloc) + return false; + + 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; + + NetlibLogf(nlc->nlu, "(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, nloc->szHost, nloc->wPort); + + _itoa(nlc->wProxyPort, szPort, 10); + if (GetAddrInfoA(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->szHost) + return false; + + NetlibLogf(nlc->nlu, "(%p) Connecting to server %s:%d....", nlc, nloc->szHost, nloc->wPort); + + _itoa(nlc->nloc.wPort, szPort, 10); + + if (GetAddrInfoA(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) { + NetlibLogf(nlc->nlu, "(%p) Connecting to ip %s ....", nlc, ptrA(NetlibAddressToString((SOCKADDR_INET_M*)ai->ai_addr))); +retry: + nlc->s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (nlc->s == INVALID_SOCKET) { + FreeAddrInfoA(air); + return false; + } + + // return the socket to non blocking + if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) { + FreeAddrInfoA(air); + return false; + } + + if (nlc->nlu->settings.specifyOutgoingPorts && nlc->nlu->settings.szOutgoingPorts && nlc->nlu->settings.szOutgoingPorts[0]) { + SOCKET s = ai->ai_family == AF_INET ? nlc->s : INVALID_SOCKET; + SOCKET s6 = ai->ai_family == AF_INET6 ? nlc->s : INVALID_SOCKET; + if (!BindSocketToPort(nlc->nlu->settings.szOutgoingPorts, s, s6, &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; + closesocket(nlc->s); + nlc->s = INVALID_SOCKET; + continue; + } + + while (true) { // 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; + } + + FreeAddrInfoA(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 my_connectIPv6(nlc, nloc); +} + +static int NetlibHttpFallbackToDirect(NetlibConnection *nlc, 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; + } + } + } + + while (!my_connect(nlc, nloc)) { + // if connection failed, the state of nlc might be unpredictable + if (GetNetlibHandleType(nlc) == NLH_CONNECTION) { + // Fallback to direct only when using HTTP proxy, as this is what used by companies + // If other type of proxy used it's an indication of security nutcase, leave him alone + if (usingProxy && (nlc->proxyType == PROXYTYPE_HTTPS || nlc->proxyType == PROXYTYPE_HTTP)) { + usingProxy = false; + nlc->proxyType = 0; + NetlibLogf(nlu, "Fallback to direct connection"); + continue; + } + if (nlu->settings.useProxy && !usingProxy && nlu->settings.proxyType == PROXYTYPE_IE && !forceHttps) { + forceHttps = true; + usingProxy = NetlibGetIeProxyConn(nlc, true); + if (usingProxy) + continue; + } + } + 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) +{ + // a connection might be freed already + if (GetNetlibHandleType(nlc) != NLH_CONNECTION) + return false; + + 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); + } + return NetlibDoConnect(nlc); + } + return true; +} + +INT_PTR NetlibOpenConnection(WPARAM wParam, LPARAM lParam) +{ + NETLIBOPENCONNECTION *nloc = (NETLIBOPENCONNECTION*)lParam; + if (nloc == NULL || nloc->cbSize != sizeof(NETLIBOPENCONNECTION) || nloc->szHost == NULL || nloc->wPort == 0) { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + + NetlibUser *nlu = (NetlibUser*)wParam; + if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING)) + return 0; + + NetlibLogf(nlu, "Connection request to %s:%d (Flags %x)....", nloc->szHost, nloc->wPort, nloc->flags); + + NetlibConnection *nlc = (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) { + mir_cslock lck(csNetlibUser); + if (iUPnPCleanup == 0) { + iUPnPCleanup = 1; + forkthread(NetlibUPnPCleanup, 0, NULL); + } + } + + 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/mir_app/src/netlibopts.cpp b/src/mir_app/src/netlibopts.cpp new file mode 100644 index 0000000000..85d17fcee8 --- /dev/null +++ b/src/mir_app/src/netlibopts.cpp @@ -0,0 +1,530 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.h"
+
+struct NetlibTempSettings
+{
+ DWORD flags;
+ char *szSettingsModule;
+ NETLIBUSERSETTINGS settings;
+};
+
+static LIST <NetlibTempSettings> 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[] = {LPGENT("<mixed>"), _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)
+{
+ for (int i = 0; i < cControls; i++)
+ ShowWindow(GetDlgItem(hwndDlg, controls[i]), state);
+}
+
+static void EnableMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state)
+{
+ for (int i = 0; i < cControls; i++)
+ EnableWindow(GetDlgItem(hwndDlg, controls[i]), state);
+}
+
+static void AddProxyTypeItem(HWND hwndDlg, int type, int selectType)
+{
+ int i = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_ADDSTRING, 0, (LPARAM)(type == 0 ? TranslateTS(szProxyTypes[type]) : szProxyTypes[type]));
+ SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_SETITEMDATA, i, type);
+ if (type == selectType)
+ SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_SETCURSEL, i, 0);
+}
+
+static void CopySettingsStruct(NETLIBUSERSETTINGS *dest, NETLIBUSERSETTINGS *source)
+{
+ *dest = *source;
+ if (dest->szIncomingPorts) 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 || mir_strcmpi(*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 = IsDlgButtonChecked(hwndDlg, ctrlId) != BST_CHECKED;
+ CheckDlgButton(hwndDlg, ctrlId, newValue ? BST_CHECKED : BST_UNCHECKED);
+ if (iUser == -1) {
+ for (int i = 0; i < tempSettings.getCount(); i++) {
+ NetlibTempSettings *p = tempSettings[i];
+ if (!(p->flags & NUF_NOOPTIONS))
+ *(int*)(((PBYTE)&p->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 newValueLen = GetWindowTextLength(GetDlgItem(hwndDlg, ctrlId));
+ char *szNewValue = (char*)mir_alloc(newValueLen+1);
+ GetDlgItemTextA(hwndDlg, ctrlId, szNewValue, newValueLen+1);
+ if (iUser == -1) {
+ for (int i = 0; i < tempSettings.getCount(); i++) {
+ NetlibTempSettings *p = tempSettings[i];
+ if (!(p->flags & NUF_NOOPTIONS)) {
+ char **ppszNew = (char**)(((PBYTE)&p->settings) + memberOffset);
+ mir_free(*ppszNew);
+ *ppszNew = mir_strdup(szNewValue);
+ }
+ }
+ mir_free(szNewValue);
+ }
+ else {
+ char **ppszNew = (char**)(((PBYTE)&tempSettings[iUser]->settings) + memberOffset);
+ mir_free(*ppszNew);
+ *ppszNew = szNewValue;
+ }
+}
+
+static void WriteSettingsStructToDb(const char *szSettingsModule, NETLIBUSERSETTINGS *settings, DWORD flags)
+{
+ if (flags & NUF_OUTGOING) {
+ db_set_b(NULL, szSettingsModule, "NLValidateSSL", (BYTE)settings->validateSSL);
+ db_set_b(NULL, szSettingsModule, "NLUseProxy", (BYTE)settings->useProxy);
+ db_set_b(NULL, szSettingsModule, "NLProxyType", (BYTE)settings->proxyType);
+ db_set_s(NULL, szSettingsModule, "NLProxyServer", settings->szProxyServer ? settings->szProxyServer : "");
+ db_set_w(NULL, szSettingsModule, "NLProxyPort", (WORD)settings->wProxyPort);
+ db_set_b(NULL, szSettingsModule, "NLUseProxyAuth", (BYTE)settings->useProxyAuth);
+ db_set_s(NULL, szSettingsModule, "NLProxyAuthUser", settings->szProxyAuthUser ? settings->szProxyAuthUser : "");
+ db_set_s(NULL, szSettingsModule, "NLProxyAuthPassword", settings->szProxyAuthPassword ? settings->szProxyAuthPassword : "");
+ db_set_b(NULL, szSettingsModule, "NLDnsThroughProxy", (BYTE)settings->dnsThroughProxy);
+ db_set_b(NULL, szSettingsModule, "NLSpecifyOutgoingPorts", (BYTE)settings->specifyOutgoingPorts);
+ db_set_s(NULL, szSettingsModule, "NLOutgoingPorts", settings->szOutgoingPorts ? settings->szOutgoingPorts : "");
+ }
+ if (flags & NUF_INCOMING) {
+ db_set_b(NULL, szSettingsModule, "NLEnableUPnP", (BYTE)settings->enableUPnP);
+ db_set_b(NULL, szSettingsModule, "NLSpecifyIncomingPorts", (BYTE)settings->specifyIncomingPorts);
+ db_set_s(NULL, szSettingsModule, "NLIncomingPorts", settings->szIncomingPorts ? settings->szIncomingPorts : "");
+ }
+}
+
+void NetlibSaveUserSettingsStruct(const char *szSettingsModule, NETLIBUSERSETTINGS *settings)
+{
+ mir_cslock lck(csNetlibUser);
+
+ NetlibUser tUser;
+ tUser.user.szSettingsModule = (char*)szSettingsModule;
+ NetlibUser *thisUser = netlibUser.find(&tUser);
+ if (thisUser == NULL)
+ return;
+
+ NetlibFreeUserSettingsStruct(&thisUser->settings);
+ CopySettingsStruct(&thisUser->settings, settings);
+ WriteSettingsStructToDb(thisUser->user.szSettingsModule, &thisUser->settings, thisUser->user.flags);
+
+ NETLIBUSERSETTINGS combinedSettings = { 0 };
+ combinedSettings.cbSize = sizeof(combinedSettings);
+
+ DWORD flags = 0;
+ for (int i = 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);
+}
+
+static INT_PTR CALLBACK DlgProcNetlibOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ int iUser;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ int iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, (LPARAM)TranslateT("<All connections>"));
+ SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETITEMDATA, iItem, (LPARAM)-1);
+ SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETCURSEL, iItem, 0);
+ {
+ mir_cslock lck(csNetlibUser);
+ for (int i = 0; i < netlibUser.getCount(); ++i) {
+ NetlibTempSettings *thisSettings = (NetlibTempSettings*)mir_calloc(sizeof(NetlibTempSettings));
+ thisSettings->flags = netlibUser[i]->user.flags;
+ thisSettings->szSettingsModule = mir_strdup(netlibUser[i]->user.szSettingsModule);
+ CopySettingsStruct(&thisSettings->settings, &netlibUser[i]->settings);
+ tempSettings.insert(thisSettings);
+
+ if (netlibUser[i]->user.flags & NUF_NOOPTIONS)
+ continue;
+ iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, (LPARAM)netlibUser[i]->user.ptszDescriptiveName);
+ SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETITEMDATA, iItem, i);
+ }
+ }
+ }
+
+ SendMessage(hwndDlg, M_REFRESHALL, 0, 0);
+ return TRUE;
+
+ case M_REFRESHALL:
+ iUser = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETCURSEL, 0, 0), 0);
+ {
+ NETLIBUSERSETTINGS settings = { 0 };
+ DWORD flags = 0;
+
+ if (iUser == -1) {
+ settings.cbSize = sizeof(settings);
+ for (int i = 0; i < tempSettings.getCount(); i++) {
+ NetlibTempSettings *p = tempSettings[i];
+ if (!(p->flags & NUF_NOOPTIONS))
+ CombineSettingsStructs(&settings, &flags, &p->settings, p->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 ? BST_CHECKED : BST_UNCHECKED);
+ 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 ? BST_CHECKED : BST_UNCHECKED);
+ SetDlgItemTextA(hwndDlg, IDC_PROXYUSER, settings.szProxyAuthUser ? settings.szProxyAuthUser : "");
+ SetDlgItemTextA(hwndDlg, IDC_PROXYPASS, settings.szProxyAuthPassword ? settings.szProxyAuthPassword : "");
+ CheckDlgButton(hwndDlg, IDC_PROXYDNS, settings.dnsThroughProxy ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(hwndDlg, IDC_VALIDATESSL, settings.validateSSL ? BST_CHECKED : BST_UNCHECKED);
+
+ ShowMultipleControls(hwndDlg, incomingConnectionsControls, SIZEOF(incomingConnectionsControls), flags&NUF_INCOMING ? SW_SHOW : SW_HIDE);
+ CheckDlgButton(hwndDlg, IDC_SPECIFYPORTS, settings.specifyIncomingPorts ? BST_CHECKED : BST_UNCHECKED);
+ SetDlgItemTextA(hwndDlg, IDC_PORTSRANGE, settings.szIncomingPorts ? settings.szIncomingPorts : "");
+
+ CheckDlgButton(hwndDlg, IDC_SPECIFYPORTSO, settings.specifyOutgoingPorts ? BST_CHECKED : BST_UNCHECKED);
+ SetDlgItemTextA(hwndDlg, IDC_PORTSRANGEO, settings.szOutgoingPorts ? settings.szOutgoingPorts : "");
+
+ CheckDlgButton(hwndDlg, IDC_ENABLEUPNP, settings.enableUPnP ? BST_CHECKED : BST_UNCHECKED);
+
+ NetlibFreeUserSettingsStruct(&settings);
+ SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0);
+ }
+ break;
+
+ case M_REFRESHENABLING:
+ TCHAR str[80];
+ {
+ int 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) {
+ for (int i = 0; i < tempSettings.getCount(); i++) {
+ NetlibTempSettings *p = tempSettings[i];
+ if (!p->settings.useProxy ||
+ p->flags & NUF_NOOPTIONS || !(p->flags & NUF_OUTGOING))
+ continue;
+
+ if (p->settings.proxyType == PROXYTYPE_SOCKS4) enableUser = 1;
+ else {
+ enableAuth = 1;
+ if (p->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:
+ 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 = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETCURSEL, 0, 0), 0);
+ if (iUser == -1) {
+ if (newValue == 0) return 0;
+ for (int i=0; i < tempSettings.getCount(); i++) {
+ NetlibTempSettings *p = tempSettings[i];
+ if (p->flags & NUF_NOOPTIONS) continue;
+ if (newValue == PROXYTYPE_HTTP && !(p->flags & (NUF_HTTPCONNS | NUF_HTTPGATEWAY)))
+ p->settings.proxyType = PROXYTYPE_HTTPS;
+ else if (newValue == PROXYTYPE_HTTPS && p->flags & NUF_NOHTTPSOPTION)
+ p->settings.proxyType = PROXYTYPE_HTTP;
+ else p->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 = GetDlgItemInt(hwndDlg, LOWORD(wParam), NULL, FALSE);
+ if (iUser == -1) {
+ for (int i = 0; i < tempSettings.getCount(); i++) {
+ NetlibTempSettings *p = tempSettings[i];
+ if (!(p->flags & NUF_NOOPTIONS))
+ p->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:
+ for (iUser = 0; iUser < tempSettings.getCount(); iUser++)
+ NetlibSaveUserSettingsStruct(tempSettings[iUser]->szSettingsModule,
+ &tempSettings[iUser]->settings);
+ return TRUE;
+ }
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i = 0; i < tempSettings.getCount(); ++i) {
+ NetlibTempSettings *p = tempSettings[i];
+ mir_free(p->szSettingsModule);
+ NetlibFreeUserSettingsStruct(&p->settings);
+ mir_free(tempSettings[i]);
+ }
+ tempSettings.destroy();
+ break;
+ }
+ return FALSE;
+}
+
+int NetlibOptInitialise(WPARAM wParam, LPARAM)
+{
+ int optionsCount = 0;
+ {
+ mir_cslock lck(csNetlibUser);
+ for (int i=0; i < netlibUser.getCount(); i++)
+ if (!(netlibUser[i]->user.flags & NUF_NOOPTIONS))
+ ++optionsCount;
+ }
+
+ if (optionsCount == 0)
+ return 0;
+
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.position = 900000000;
+ odp.hInstance = g_hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_NETLIB);
+ odp.pszTitle = LPGEN("Network");
+ odp.pfnDlgProc = DlgProcNetlibOpts;
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/netlibpktrecver.cpp b/src/mir_app/src/netlibpktrecver.cpp new file mode 100644 index 0000000000..ca23de0327 --- /dev/null +++ b/src/mir_app/src/netlibpktrecver.cpp @@ -0,0 +1,89 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.h"
+
+INT_PTR NetlibPacketRecverCreate(WPARAM wParam, LPARAM lParam)
+{
+ 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;
+
+ 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 {
+ memmove(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;
+ }
+ }
+
+ INT_PTR 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/mir_app/src/netlibsecurity.cpp b/src/mir_app/src/netlibsecurity.cpp new file mode 100644 index 0000000000..1471d611b9 --- /dev/null +++ b/src/mir_app/src/netlibsecurity.cpp @@ -0,0 +1,427 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.h"
+
+#define SECURITY_WIN32
+#include <security.h>
+#include <rpcdce.h>
+
+#pragma comment(lib, "secur32.lib")
+
+struct NtlmHandleType
+{
+ CtxtHandle hClientContext;
+ CredHandle hClientCredential;
+ TCHAR* szProvider;
+ TCHAR* szPrincipal;
+ unsigned cbMaxToken;
+ bool hasDomain;
+};
+
+struct NTLM_String
+{
+ WORD len;
+ WORD allocedSpace;
+ DWORD offset;
+};
+
+struct NtlmType2packet
+{
+ char sign[8];
+ DWORD type; // == 2
+ NTLM_String targetName;
+ DWORD flags;
+ BYTE challenge[8];
+ BYTE context[8];
+ NTLM_String targetInfo;
+};
+
+static unsigned ntlmCnt = 0;
+static mir_cs csSec;
+
+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);
+}
+
+HANDLE NetlibInitSecurityProvider(const TCHAR* szProvider, const TCHAR* szPrincipal)
+{
+ HANDLE hSecurity = NULL;
+
+ if (mir_tstrcmpi(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;
+ }
+
+ mir_cslock lck(csSec);
+
+ PSecPkgInfo ntlmSecurityPackageInfo;
+ bool isGSSAPI = mir_tstrcmpi(szProvider, _T("GSSAPI")) == 0;
+ const TCHAR *szProviderC = isGSSAPI ? _T("Kerberos") : szProvider;
+ SECURITY_STATUS sc = QuerySecurityPackageInfo((LPTSTR)szProviderC, &ntlmSecurityPackageInfo);
+ if (sc == SEC_E_OK) {
+ NtlmHandleType* hNtlm;
+
+ hSecurity = hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType));
+ hNtlm->cbMaxToken = ntlmSecurityPackageInfo->cbMaxToken;
+ FreeContextBuffer(ntlmSecurityPackageInfo);
+
+ hNtlm->szProvider = mir_tstrdup(szProvider);
+ hNtlm->szPrincipal = mir_tstrdup(szPrincipal ? szPrincipal : _T(""));
+ SecInvalidateHandle(&hNtlm->hClientContext);
+ SecInvalidateHandle(&hNtlm->hClientCredential);
+ ntlmCnt++;
+ }
+ return hSecurity;
+}
+
+HANDLE NetlibInitSecurityProvider(const char* szProvider, const char* szPrincipal)
+{
+ return NetlibInitSecurityProvider(_A2T(szProvider), _A2T(szPrincipal));
+}
+
+void NetlibDestroySecurityProvider(HANDLE hSecurity)
+{
+ if (hSecurity == NULL)
+ return;
+
+ mir_cslock lck(csSec);
+
+ if (ntlmCnt != 0) {
+ NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity;
+ if (hNtlm != NULL) {
+ if (SecIsValidHandle(&hNtlm->hClientContext))
+ DeleteSecurityContext(&hNtlm->hClientContext);
+ if (SecIsValidHandle(&hNtlm->hClientCredential))
+ FreeCredentialsHandle(&hNtlm->hClientCredential);
+ mir_free(hNtlm->szProvider);
+ mir_free(hNtlm->szPrincipal);
+ mir_free(hNtlm);
+ }
+
+ --ntlmCnt;
+ }
+}
+
+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 = 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 = 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 = 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;
+ }
+
+ return mir_base64_encode(response, ressz);
+}
+
+char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const TCHAR* login, const TCHAR* psw, bool http, unsigned& complete)
+{
+ if (hSecurity == NULL || ntlmCnt == 0)
+ return NULL;
+
+ SecBufferDesc outputBufferDescriptor, inputBufferDescriptor;
+ SecBuffer outputSecurityToken, inputSecurityToken;
+ TimeStamp tokenExpiration;
+ ULONG contextAttributes;
+ char *szOutputToken;
+
+ NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity;
+ if (mir_tstrcmpi(hNtlm->szProvider, _T("Basic"))) {
+ bool isGSSAPI = mir_tstrcmpi(hNtlm->szProvider, _T("GSSAPI")) == 0;
+ TCHAR *szProvider = isGSSAPI ? _T("Kerberos") : hNtlm->szProvider;
+ bool hasChallenge = szChallenge != NULL && szChallenge[0] != '\0';
+ if (hasChallenge) {
+ unsigned tokenLen;
+ BYTE *token = (BYTE*)mir_base64_decode(szChallenge, &tokenLen);
+ if (token == NULL)
+ return NULL;
+
+ if (isGSSAPI && complete)
+ return CompleteGssapi(hSecurity, token, tokenLen);
+
+ inputBufferDescriptor.cBuffers = 1;
+ inputBufferDescriptor.pBuffers = &inputSecurityToken;
+ inputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+ inputSecurityToken.BufferType = SECBUFFER_TOKEN;
+ inputSecurityToken.cbBuffer = tokenLen;
+ inputSecurityToken.pvBuffer = token;
+
+ // try to decode the domain name from the NTLM challenge
+ if (login != NULL && login[0] != '\0' && !hNtlm->hasDomain) {
+ NtlmType2packet* pkt = (NtlmType2packet*)token;
+ if (!strncmp(pkt->sign, "NTLMSSP", 8) && pkt->type == 2) {
+
+ wchar_t* domainName = (wchar_t*)&token[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);
+
+ if (domainLen) {
+ size_t newLoginLen = mir_tstrlen(login) + domainLen + 1;
+ TCHAR *newLogin = (TCHAR*)alloca(newLoginLen * sizeof(TCHAR));
+
+ _tcsncpy(newLogin, domainName, domainLen);
+ newLogin[domainLen] = '\\';
+ mir_tstrcpy(newLogin + domainLen + 1, login);
+
+ char* szChl = NtlmCreateResponseFromChallenge(hSecurity, NULL, newLogin, psw, http, complete);
+ mir_free(szChl);
+ }
+ }
+ }
+ }
+ else {
+ if (SecIsValidHandle(&hNtlm->hClientContext))
+ DeleteSecurityContext(&hNtlm->hClientContext);
+ if (SecIsValidHandle(&hNtlm->hClientCredential))
+ FreeCredentialsHandle(&hNtlm->hClientCredential);
+
+ SEC_WINNT_AUTH_IDENTITY auth;
+
+ if (login != NULL && login[0] != '\0') {
+ memset(&auth, 0, sizeof(auth));
+
+ NetlibLogf(NULL, "Security login requested, user: %S pssw: %s", login, psw ? "(exist)" : "(no psw)");
+
+ const TCHAR* loginName = login;
+ const TCHAR* domainName = _tcschr(login, '\\');
+ size_t domainLen = 0;
+ size_t loginLen = mir_tstrlen(loginName);
+ if (domainName != NULL) {
+ loginName = domainName + 1;
+ loginLen = mir_tstrlen(loginName);
+ domainLen = domainName - login;
+ domainName = login;
+ }
+ else if ((domainName = _tcschr(login, '@')) != NULL) {
+ loginName = login;
+ loginLen = domainName - login;
+ domainLen = mir_tstrlen(++domainName);
+ }
+
+ auth.User = (PWORD)loginName;
+ auth.UserLength = (ULONG)loginLen;
+ auth.Password = (PWORD)psw;
+ auth.PasswordLength = (ULONG)mir_tstrlen(psw);
+ auth.Domain = (PWORD)domainName;
+ auth.DomainLength = (ULONG)domainLen;
+ auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+
+ hNtlm->hasDomain = domainLen != 0;
+ }
+
+ SECURITY_STATUS sc = 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);
+
+ SECURITY_STATUS sc = 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 = CompleteAuthToken(&hNtlm->hClientContext, &outputBufferDescriptor);
+
+ if (sc != SEC_E_OK && sc != SEC_I_CONTINUE_NEEDED) {
+ ReportSecError(sc, __LINE__);
+ return NULL;
+ }
+
+ szOutputToken = mir_base64_encode((PBYTE)outputSecurityToken.pvBuffer, outputSecurityToken.cbBuffer);
+ }
+ else {
+ if (!login || !psw) return NULL;
+
+ char *szLogin = mir_t2a(login);
+ char *szPassw = mir_t2a(psw);
+
+ size_t authLen = mir_strlen(szLogin) + mir_strlen(szPassw) + 5;
+ char *szAuth = (char*)alloca(authLen);
+
+ int len = mir_snprintf(szAuth, authLen, "%s:%s", szLogin, szPassw);
+ szOutputToken = mir_base64_encode((BYTE*)szAuth, len);
+ complete = true;
+
+ mir_free(szPassw);
+ mir_free(szLogin);
+ }
+
+ if (szOutputToken == NULL)
+ return NULL;
+
+ if (!http)
+ return szOutputToken;
+
+ ptrA szProvider(mir_t2a(hNtlm->szProvider));
+ size_t resLen = mir_strlen(szOutputToken) + mir_strlen(szProvider) + 10;
+ char *result = (char*)mir_alloc(resLen);
+ mir_snprintf(result, resLen, "%s %s", szProvider, szOutputToken);
+ mir_free(szOutputToken);
+ 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 == NULL || req->cbSize < sizeof(*req))
+ return 0;
+
+ if (req->flags & NNR_UNICODE)
+ return (INT_PTR)NetlibInitSecurityProvider(req->szProviderName, req->szPrincipal);
+ return (INT_PTR)NetlibInitSecurityProvider((char*)req->szProviderName, (char*)req->szPrincipal);
+}
+
+static INT_PTR DestroySecurityProviderService(WPARAM, LPARAM lParam)
+{
+ NetlibDestroySecurityProvider((HANDLE)lParam);
+ return 0;
+}
+
+static INT_PTR NtlmCreateResponseService(WPARAM wParam, LPARAM lParam)
+{
+ NETLIBNTLMREQUEST *req = (NETLIBNTLMREQUEST*)lParam;
+ if (req == NULL)
+ return 0;
+
+ unsigned complete = 0;
+ char *response = NtlmCreateResponseFromChallenge((HANDLE)wParam, req->szChallenge, _A2T(req->userName), _A2T(req->password), false, complete);
+ return (INT_PTR)response;
+}
+
+static INT_PTR NtlmCreateResponseService2(WPARAM wParam, LPARAM lParam)
+{
+ NETLIBNTLMREQUEST2 *req = (NETLIBNTLMREQUEST2*)lParam;
+ if (req == NULL || req->cbSize < sizeof(*req))
+ return 0;
+
+ if (req->flags & NNR_UNICODE)
+ return (INT_PTR)NtlmCreateResponseFromChallenge((HANDLE)wParam, req->szChallenge, req->szUserName, req->szPassword, false, req->complete);
+
+ return (INT_PTR)NtlmCreateResponseFromChallenge((HANDLE)wParam, req->szChallenge, _A2T((char*)req->szUserName), _A2T((char*)req->szPassword), false, req->complete);
+}
+
+void NetlibSecurityInit(void)
+{
+ 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);
+}
diff --git a/src/mir_app/src/netlibsock.cpp b/src/mir_app/src/netlibsock.cpp new file mode 100644 index 0000000000..ee1cfde144 --- /dev/null +++ b/src/mir_app/src/netlibsock.cpp @@ -0,0 +1,313 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "netlib.h"
+
+extern HANDLE hConnectionHeaderMutex, hSendEvent, hRecvEvent;
+
+INT_PTR NetlibSend(WPARAM wParam, LPARAM lParam)
+{
+ NetlibConnection *nlc = (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);
+
+ NETLIBNOTIFY nln = { nlb, result };
+ NotifyFastHook(hSendEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user);
+
+ return result;
+}
+
+INT_PTR NetlibRecv(WPARAM wParam, LPARAM lParam)
+{
+ NetlibConnection *nlc = (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) {
+ NETLIBNOTIFY nln = { nlb, recvResult };
+ NotifyFastHook(hRecvEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user);
+ }
+ return recvResult;
+}
+
+static int ConnectionListToSocketList(HANDLE *hConns, fd_set *fd, int& pending)
+{
+ FD_ZERO(fd);
+ for (int i = 0; hConns[i] && hConns[i] != INVALID_HANDLE_VALUE && i < FD_SETSIZE; i++) {
+ NetlibConnection *nlcCheck = (NetlibConnection*)hConns[i];
+ if (nlcCheck->handleType != 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 */
+ NetlibConnection *conn = NULL;
+ int j;
+ for (j = 0; j < FD_SETSIZE; j++) {
+ conn = (NetlibConnection*)nls->hReadConns[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; j < FD_SETSIZE; j++) {
+ conn = (NetlibConnection*)nls->hWriteConns[j];
+ if (conn == NULL || conn == INVALID_HANDLE_VALUE) break;
+ nls->hWriteStatus[j] = FD_ISSET(conn->s, &writefd);
+ }
+ for (j = 0; j < FD_SETSIZE; j++) {
+ conn = (NetlibConnection*)nls->hExceptConns[j];
+ if (conn == NULL || conn == INVALID_HANDLE_VALUE) break;
+ nls->hExceptStatus[j] = FD_ISSET(conn->s, &exceptfd);
+ }
+ ReleaseMutex(hConnectionHeaderMutex);
+ return rc;
+}
+
+bool NetlibStringToAddress(const char* str, SOCKADDR_INET_M* addr)
+{
+ if (!str) return false;
+
+ int len = sizeof(SOCKADDR_INET_M);
+ return !WSAStringToAddressA((char*)str, AF_INET6, NULL, (PSOCKADDR)addr, &len);
+}
+
+char* NetlibAddressToString(SOCKADDR_INET_M* addr)
+{
+ char saddr[128];
+ DWORD len = sizeof(saddr);
+ if (!WSAAddressToStringA((PSOCKADDR)addr, sizeof(*addr), NULL, saddr, &len))
+ return mir_strdup(saddr);
+
+ if (addr->si_family == AF_INET) {
+ char *szIp = inet_ntoa(addr->Ipv4.sin_addr);
+ if (addr->Ipv4.sin_port != 0) {
+ mir_snprintf(saddr, SIZEOF(saddr), "%s:%d", szIp, htons(addr->Ipv4.sin_port));
+ return mir_strdup(saddr);
+ }
+ return mir_strdup(szIp);
+ }
+ return NULL;
+}
+
+void NetlibGetConnectionInfo(NetlibConnection* nlc, NETLIBCONNINFO *connInfo)
+{
+ if (!nlc || !connInfo || connInfo->cbSize < sizeof(NETLIBCONNINFO)) return;
+
+ SOCKADDR_INET_M sin = { 0 };
+ int len = sizeof(sin);
+ if (!getsockname(nlc->s, (PSOCKADDR)&sin, &len)) {
+ connInfo->wPort = ntohs(sin.Ipv4.sin_port);
+ connInfo->dwIpv4 = sin.si_family == AF_INET ? htonl(sin.Ipv4.sin_addr.s_addr) : 0;
+
+ char *szTmp = NetlibAddressToString(&sin);
+ strncpy(connInfo->szIpPort, szTmp, sizeof(connInfo->szIpPort));
+ connInfo->szIpPort[sizeof(connInfo->szIpPort) - 1] = 0;
+ mir_free(szTmp);
+ }
+}
+
+inline bool IsAddrGlobal(const IN6_ADDR *a)
+{
+ unsigned char High = a->s6_bytes[0] & 0xf0;
+ return High != 0 && High != 0xf0;
+}
+
+static NETLIBIPLIST* GetMyIpv6(unsigned flags)
+{
+ addrinfo *air = NULL, *ai, hints = { 0 };
+ const char *szMyHost = "";
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+
+ if (GetAddrInfoA(szMyHost, NULL, &hints, &air))
+ return NULL;
+
+ unsigned n = 0;
+ for (ai = air; ai; ai = ai->ai_next) {
+ SOCKADDR_INET_M *iaddr = (SOCKADDR_INET_M*)ai->ai_addr;
+ if (ai->ai_family == AF_INET || (ai->ai_family == AF_INET6 && (!(flags & 1) || IsAddrGlobal(&iaddr->Ipv6.sin6_addr))))
+ ++n;
+ }
+
+ NETLIBIPLIST *addr = (NETLIBIPLIST*)mir_calloc(n * 64 + 4);
+ addr->cbNum = n;
+
+ unsigned i = 0;
+ for (ai = air; ai; ai = ai->ai_next) {
+ SOCKADDR_INET_M *iaddr = (SOCKADDR_INET_M*)ai->ai_addr;
+ if (ai->ai_family == AF_INET ||
+ (ai->ai_family == AF_INET6 &&
+ (!(flags & 1) || IsAddrGlobal(&iaddr->Ipv6.sin6_addr))))
+ {
+ char *szIp = NetlibAddressToString(iaddr);
+ if (szIp)
+ strncpy_s(addr->szIp[i++], szIp, _TRUNCATE);
+ mir_free(szIp);
+ }
+ }
+ FreeAddrInfoA(air);
+ return addr;
+}
+
+static NETLIBIPLIST* GetMyIpv4(void)
+{
+ char hostname[256] = "";
+
+ gethostname(hostname, sizeof(hostname));
+ PHOSTENT he = gethostbyname(hostname);
+
+ unsigned n;
+ for (n = 0; he->h_addr_list[n]; ++n)
+ ;
+
+ NETLIBIPLIST *addr = (NETLIBIPLIST*)mir_calloc(n * 64 + 4);
+ addr->cbNum = n;
+
+ for (unsigned i = 0; i < n; i++)
+ strncpy_s(addr->szIp[i], inet_ntoa(*(PIN_ADDR)he->h_addr_list[i]), _TRUNCATE);
+
+ return addr;
+}
+
+NETLIBIPLIST* GetMyIp(unsigned flags)
+{
+ return GetMyIpv6(flags);
+}
diff --git a/src/mir_app/src/netlibupnp.cpp b/src/mir_app/src/netlibupnp.cpp new file mode 100644 index 0000000000..55fe46152a --- /dev/null +++ b/src/mir_app/src/netlibupnp.cpp @@ -0,0 +1,822 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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[] =
+ "<serviceType>%s</serviceType>";
+
+static const char soap_action[] =
+ "<?xml version = \"1.0\"?>\r\n"
+ "<s:Envelope\r\n"
+ " xmlns:s = \"http://schemas.xmlsoap.org/soap/envelope/\"\r\n"
+ " s:encodingStyle = \"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"
+ " <s:Body>\r\n"
+ " <u:%s xmlns:u = \"%s\">\r\n"
+ "%s"
+ " </u:%s>\r\n"
+ " </s:Body>\r\n"
+ "</s:Envelope>\r\n";
+
+static const char soap_query[] =
+ "<s:Envelope\r\n"
+ " xmlns:s = \"http://schemas.xmlsoap.org/soap/envelope/\"\r\n"
+ " s:encodingStyle = \"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"
+ " <s:Body>\r\n"
+ " <u:QueryStateVariable xmlns:u = \"urn:schemas-upnp-org:control-1-0\">\r\n"
+ " <u:varName>%s</u:varName>\r\n"
+ " </u:QueryStateVariable>\r\n"
+ " </s:Body>\r\n"
+ "</s:Envelope>\r\n";
+
+static const char add_port_mapping[] =
+ " <NewRemoteHost></NewRemoteHost>\r\n"
+ " <NewExternalPort>%i</NewExternalPort>\r\n"
+ " <NewProtocol>%s</NewProtocol>\r\n"
+ " <NewInternalPort>%i</NewInternalPort>\r\n"
+ " <NewInternalClient>%s</NewInternalClient>\r\n"
+ " <NewEnabled>1</NewEnabled>\r\n"
+ " <NewPortMappingDescription>Miranda</NewPortMappingDescription>\r\n"
+ " <NewLeaseDuration>0</NewLeaseDuration>\r\n";
+
+static const char delete_port_mapping[] =
+ " <NewRemoteHost></NewRemoteHost>\r\n"
+ " <NewExternalPort>%i</NewExternalPort>\r\n"
+ " <NewProtocol>%s</NewProtocol>\r\n";
+
+static const char get_port_mapping[] =
+ " <NewPortMappingIndex>%i</NewPortMappingIndex>\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 += mir_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 + mir_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];
+
+ if (sock == INVALID_SOCKET)
+ return;
+
+ FD_ZERO(&rfd);
+ FD_SET(sock, &rfd);
+
+ bool opened = false;
+ switch (select(1, &rfd, NULL, NULL, &tv)) {
+ 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 = false;
+
+ 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();
+
+ while (true) {
+ 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;
+ }
+ }
+ strncpy_s(szConnHost, szHost, _TRUNCATE);
+ 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;
+ while (true) {
+ 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, mir_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, "<NewExternalIPAddress", ">", "<", 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, "<URLBase>", "</URLBase>", szTemp, sizeof(szTemp));
+ strncpy(szCtlUrl, szTemp[0] ? szTemp : szUrl, sizeof(szCtlUrl));
+ szCtlUrl[sizeof(szCtlUrl) - 1] = 0;
+
+ mir_snprintf(szTemp, search_device, szDev);
+ txtParseParam(szData, szTemp, "<controlURL>", "</controlURL>", 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 + mir_strlen(szCtlUrl);
+ }
+ else { // relative URI rel_path
+ size_t ctlCLen = mir_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 (mir_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, "<errorCode>", "</errorCode>", 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*)
+{
+ // upnp is disabled globally, no need for a cleanup
+ if (db_get_b(NULL, "Netlib", "NLEnableUPnP", 1) == 0)
+ return;
+
+ {
+ int incoming = 0;
+ mir_cslock lck(csNetlibUser);
+ for (int i = 0; i < netlibUser.getCount(); i++)
+ if (netlibUser[i]->user.flags & NUF_INCOMING) {
+ incoming = 1;
+ break;
+ }
+
+ if (!incoming)
+ return;
+ }
+
+ if (findUPnPGateway()) {
+ char *szData = (char*)alloca(4096);
+ char buf[50], lip[50];
+ unsigned j = 0, k, num = 100;
+
+ strncpy_s(lip, inet_ntoa(locIP.sin_addr), _TRUNCATE);
+
+ WaitForSingleObject(portListMutex, INFINITE);
+
+ if (httpTransact(szCtlUrl, szData, 4096, "PortMappingNumberOfEntries", ControlQuery) == 200 &&
+ txtParseParam(szData, "QueryStateVariableResponse", "<return>", "<", buf, sizeof(buf)))
+ num = atol(buf);
+
+ WORD ports[30];
+ for (unsigned i = 0; i < num && !Miranda_Terminated(); i++) {
+ mir_snprintf(szData, 4096, get_port_mapping, i);
+
+ ReleaseMutex(portListMutex);
+ WaitForSingleObject(portListMutex, INFINITE);
+
+ if (httpTransact(szCtlUrl, szData, 4096, "GetGenericPortMappingEntry", ControlAction) != 200)
+ break;
+
+ if (!txtParseParam(szData, "<NewPortMappingDescription", ">", "<", buf, sizeof(buf)) || mir_strcmp(buf, "Miranda") != 0)
+ continue;
+
+ if (!txtParseParam(szData, "<NewInternalClient", ">", "<", buf, sizeof(buf)) || mir_strcmp(buf, lip) != 0)
+ continue;
+
+ if (txtParseParam(szData, "<NewExternalPort", ">", "<", buf, sizeof(buf))) {
+ WORD mport = (WORD)atol(buf);
+
+ if (j >= SIZEOF(ports))
+ break;
+
+ for (k = 0; k < numports; ++k)
+ if (portList[k] == mport)
+ break;
+
+ if (k >= numports)
+ ports[j++] = mport;
+ }
+ }
+
+ ReleaseMutex(portListMutex);
+
+ for (unsigned i = 0; i < j && !Miranda_Terminated(); i++)
+ NetlibUPnPDeletePortMapping(ports[i], "TCP");
+ }
+}
+
+void NetlibUPnPInit(void)
+{
+ numports = 0;
+ numportsAlloc = 10;
+ portList = (WORD*)mir_alloc(sizeof(WORD)*numportsAlloc);
+
+ portListMutex = CreateMutex(NULL, FALSE, NULL);
+}
+
+void NetlibUPnPDestroy(void)
+{
+ mir_free(portList);
+ CloseHandle(portListMutex);
+}
diff --git a/src/mir_app/src/newplugins.cpp b/src/mir_app/src/newplugins.cpp new file mode 100644 index 0000000000..dafeb4d964 --- /dev/null +++ b/src/mir_app/src/newplugins.cpp @@ -0,0 +1,898 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include "plugins.h"
+#include "profilemanager.h"
+#include "langpack.h"
+
+void LoadExtraIconsModule();
+
+extern bool bModulesLoadedFired;
+
+static int sttComparePluginsByName(const pluginEntry* p1, const pluginEntry* p2)
+{
+ return mir_tstrcmpi(p1->pluginname, p2->pluginname);
+}
+
+LIST<pluginEntry>
+ pluginList(10, sttComparePluginsByName),
+ servicePlugins(5, sttComparePluginsByName),
+ clistPlugins(5, sttComparePluginsByName);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define MAX_MIR_VER ULONG_MAX
+
+static BOOL bModuleInitialized = FALSE;
+
+TCHAR mirandabootini[MAX_PATH];
+static DWORD mirandaVersion;
+static int sttFakeID = -100;
+static HANDLE hPluginListHeap = NULL;
+static int askAboutIgnoredPlugins;
+
+static pluginEntry *plugin_freeimg, *plugin_crshdmp, *serviceModePlugin, *plugin_ssl;
+
+#define PLUGINDISABLELIST "PluginDisable"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// basic functions
+
+int equalUUID(const MUUID& u1, const MUUID& u2)
+{
+ return memcmp(&u1, &u2, sizeof(MUUID)) ? 0 : 1;
+}
+
+bool hasMuuid(const MUUID* p, const MUUID& uuid)
+{
+ if (p == NULL)
+ return false;
+
+ for (int i = 0; !equalUUID(miid_last, p[i]); i++)
+ if (equalUUID(uuid, p[i]))
+ return true;
+
+ return false;
+}
+
+bool hasMuuid(const BASIC_PLUGIN_INFO& bpi, const MUUID& uuid)
+{
+ if (bpi.Interfaces)
+ return hasMuuid(bpi.Interfaces, uuid);
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// banned plugins
+
+static const MUUID pluginBannedList[] =
+{
+ { 0x9d6c3213, 0x02b4, 0x4fe1, { 0x92, 0xe6, 0x52, 0x6d, 0xe2, 0x4f, 0x8d, 0x65 } }, // old chat
+ { 0x240a91dc, 0x9464, 0x457a, { 0x97, 0x87, 0xff, 0x1e, 0xa8, 0x8e, 0x77, 0xe3 } }, // old clist
+ { 0x657fe89b, 0xd121, 0x40c2, { 0x8a, 0xc9, 0xb9, 0xfa, 0x57, 0x55, 0xb3, 0x0c } }, // old srmm
+ { 0x112f7d30, 0xcd19, 0x4c74, { 0xa0, 0x3b, 0xbf, 0xbb, 0x76, 0xb7, 0x5b, 0xc4 } }, // extraicons
+ { 0x72765a6f, 0xb017, 0x42f1, { 0xb3, 0x0f, 0x5e, 0x09, 0x41, 0x27, 0x3a, 0x3f } }, // flashavatars
+ { 0x1394a3ab, 0x2585, 0x4196, { 0x8f, 0x72, 0x0e, 0xae, 0xc2, 0x45, 0x0e, 0x11 } }, // db3x
+ { 0x28ff9b91, 0x3e4d, 0x4f1c, { 0xb4, 0x7c, 0xc6, 0x41, 0xb0, 0x37, 0xff, 0x40 } }, // dbx_mmap_sa
+ { 0x28f45248, 0x8c9c, 0x4bee, { 0x93, 0x07, 0x7b, 0xcf, 0x3e, 0x12, 0xbf, 0x99 } }, // dbx_tree
+ { 0x4c4a27cf, 0x5e64, 0x4242, { 0xa3, 0x32, 0xb9, 0x8b, 0x08, 0x24, 0x3e, 0x89 } }, // metacontacts
+ { 0x9c448c61, 0xfc3f, 0x42f9, { 0xb9, 0xf0, 0x4a, 0x30, 0xe1, 0xcf, 0x86, 0x71 } }, // skypekit based skype
+ { 0x49c2cf54, 0x7898, 0x44de, { 0xbe, 0x3a, 0x6d, 0x2e, 0x4e, 0xf9, 0x00, 0x79 } } // firstrun
+};
+
+static bool isPluginBanned(const MUUID& u1)
+{
+ for (int i = 0; i < SIZEOF(pluginBannedList); i++)
+ if (equalUUID(pluginBannedList[i], u1))
+ return true;
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default plugins
+
+static MuuidReplacement pluginDefault[] =
+{
+ { MIID_UIUSERINFO, _T("stduserinfo"), NULL }, // 0
+ { MIID_SRURL, _T("stdurl"), NULL }, // 1
+ { MIID_SREMAIL, _T("stdemail"), NULL }, // 2
+ { MIID_SRAUTH, _T("stdauth"), NULL }, // 3
+ { MIID_SRFILE, _T("stdfile"), NULL }, // 4
+ { MIID_UIHELP, _T("stdhelp"), NULL }, // 5
+ { MIID_UIHISTORY, _T("stduihist"), NULL }, // 6
+ { MIID_IDLE, _T("stdidle"), NULL }, // 7
+ { MIID_AUTOAWAY, _T("stdautoaway"), NULL }, // 8
+ { MIID_USERONLINE, _T("stduseronline"), NULL }, // 9
+ { MIID_SRAWAY, _T("stdaway"), NULL }, // 10
+ { MIID_CLIST, _T("stdclist"), NULL }, // 11
+ { MIID_CHAT, _T("stdchat"), NULL }, // 12
+ { MIID_SRMM, _T("stdmsg"), NULL } // 13
+};
+
+int getDefaultPluginIdx(const MUUID &muuid)
+{
+ for (int i = 0; i < SIZEOF(pluginDefault); i++)
+ if (equalUUID(muuid, pluginDefault[i].uuid))
+ return i;
+
+ return -1;
+}
+
+int LoadStdPlugins()
+{
+ for (int i = 0; i < SIZEOF(pluginDefault); i++) {
+ if (pluginDefault[i].pImpl)
+ continue;
+
+ if (!LoadCorePlugin(pluginDefault[i]))
+ return 1;
+ }
+
+ if (pluginDefault[13].pImpl == NULL)
+ MessageBox(NULL, TranslateT("No messaging plugins loaded. Please install/enable one of the messaging plugins, for instance, \"StdMsg.dll\""), _T("Miranda NG"), MB_OK | MB_ICONWARNING);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// global functions
+
+char* GetPluginNameByInstance(HINSTANCE hInstance)
+{
+ if (pluginList.getCount() == 0)
+ return NULL;
+
+ for (int i = 0; i < pluginList.getCount(); i++) {
+ pluginEntry *p = pluginList[i];
+ if (p->bpi.pluginInfo && p->bpi.hInst == hInstance)
+ return p->bpi.pluginInfo->shortName;
+ }
+ return NULL;
+}
+
+MIR_APP_DLL(int) GetPluginLangByInstance(HINSTANCE hInstance)
+{
+ if (pluginList.getCount() == 0)
+ return NULL;
+
+ for (int i = 0; i < pluginList.getCount(); i++) {
+ pluginEntry *p = pluginList[i];
+ if (p->bpi.pluginInfo && p->bpi.hInst == hInstance)
+ return p->hLangpack;
+ }
+ return NULL;
+}
+
+int GetPluginFakeId(const MUUID &uuid, int hLangpack)
+{
+ for (int i = 0; i < pluginList.getCount(); i++) {
+ pluginEntry *p = pluginList[i];
+ if (!p->bpi.hInst)
+ continue;
+
+ if (equalUUID(p->bpi.pluginInfo->uuid, uuid))
+ return p->hLangpack = (hLangpack) ? hLangpack : --sttFakeID;
+ }
+
+ return 0;
+}
+
+MUUID miid_last = MIID_LAST;
+MUUID miid_chat = MIID_CHAT;
+MUUID miid_srmm = MIID_SRMM;
+MUUID miid_clist = MIID_CLIST;
+MUUID miid_database = MIID_DATABASE;
+MUUID miid_protocol = MIID_PROTOCOL;
+MUUID miid_servicemode = MIID_SERVICEMODE;
+MUUID miid_crypto = MIID_CRYPTO;
+MUUID miid_ssl = MIID_SSL;
+
+static bool validInterfaceList(MUUID *piface)
+{
+ if (piface == NULL)
+ return true;
+
+ if (equalUUID(miid_last, piface[0]))
+ return false;
+
+ return true;
+}
+
+static int checkPI(BASIC_PLUGIN_INFO* bpi, PLUGININFOEX* pi)
+{
+ if (pi == NULL)
+ return FALSE;
+
+ if (bpi->InfoEx == NULL || pi->cbSize != sizeof(PLUGININFOEX))
+ return FALSE;
+
+ if (!validInterfaceList(bpi->Interfaces) || isPluginBanned(pi->uuid))
+ return FALSE;
+
+ if (pi->shortName == NULL || pi->description == NULL || pi->author == NULL ||
+ pi->authorEmail == NULL || pi->copyright == NULL || pi->homepage == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+int checkAPI(TCHAR* plugin, BASIC_PLUGIN_INFO* bpi, DWORD mirandaVersion, int checkTypeAPI)
+{
+ HINSTANCE 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->InfoEx = (Miranda_Plugin_InfoEx)GetProcAddress(h, "MirandaPluginInfoEx");
+
+ // if they were present
+ if (!bpi->Load || !bpi->Unload || !bpi->InfoEx) {
+LBL_Error:
+ FreeLibrary(h);
+ return 0;
+ }
+
+ bpi->Interfaces = (MUUID*)GetProcAddress(h, "MirandaInterfaces");
+ if (bpi->Interfaces == NULL) {
+ typedef MUUID * (__cdecl * Miranda_Plugin_Interfaces) (void);
+ Miranda_Plugin_Interfaces pFunc = (Miranda_Plugin_Interfaces)GetProcAddress(h, "MirandaPluginInterfaces");
+ if (pFunc)
+ bpi->Interfaces = pFunc();
+ }
+
+ PLUGININFOEX* pi = bpi->InfoEx(mirandaVersion);
+ if (!checkPI(bpi, pi))
+ goto LBL_Error;
+
+ bpi->pluginInfo = pi;
+ // basic API is present
+ if (checkTypeAPI == CHECKAPI_NONE) {
+LBL_Ok:
+ bpi->hInst = h;
+ return 1;
+ }
+ // check clist ?
+ if (checkTypeAPI == CHECKAPI_CLIST) {
+ bpi->clistlink = (CList_Initialise)GetProcAddress(h, "CListInitialise");
+ if ((pi->flags & UNICODE_AWARE) && bpi->clistlink)
+ goto LBL_Ok;
+ }
+ goto LBL_Error;
+}
+
+// perform any API related tasks to freeing
+void Plugin_Uninit(pluginEntry *p)
+{
+ // if the basic API check had passed, call Unload if Load(void) was ever called
+ if (p->pclass & PCLASS_LOADED) {
+ p->bpi.Unload();
+ p->pclass &= ~PCLASS_LOADED;
+ }
+
+ // release the library
+ HINSTANCE hInst = p->bpi.hInst;
+ if (hInst != NULL) {
+ // we need to kill all resources which belong to that DLL before calling FreeLibrary
+ KillModuleEventHooks(hInst);
+ KillModuleServices(hInst);
+ UnregisterModule(hInst);
+
+ FreeLibrary(hInst);
+ memset(&p->bpi, 0, sizeof(p->bpi));
+ }
+
+ if (p == plugin_crshdmp)
+ plugin_crshdmp = NULL;
+ pluginList.remove(p);
+}
+
+int Plugin_UnloadDyn(pluginEntry *p)
+{
+ if (p->bpi.hInst) {
+ if (CallPluginEventHook(p->bpi.hInst, hOkToExitEvent, 0, 0) != 0)
+ return FALSE;
+
+ KillModuleSubclassing(p->bpi.hInst);
+
+ CallPluginEventHook(p->bpi.hInst, hPreShutdownEvent, 0, 0);
+ CallPluginEventHook(p->bpi.hInst, hShutdownEvent, 0, 0);
+
+ KillModuleEventHooks(p->bpi.hInst);
+ KillModuleServices(p->bpi.hInst);
+ }
+
+ int hLangpack = p->hLangpack;
+ if (hLangpack != 0) {
+ KillModuleMenus(hLangpack);
+ KillModuleFonts(hLangpack);
+ KillModuleColours(hLangpack);
+ KillModuleEffects(hLangpack);
+ KillModuleIcons(hLangpack);
+ KillModuleHotkeys(hLangpack);
+ KillModuleSounds(hLangpack);
+ KillModuleExtraIcons(hLangpack);
+ KillModuleSrmmIcons(hLangpack);
+ }
+
+ NotifyFastHook(hevUnloadModule, (WPARAM)p->bpi.pluginInfo, (LPARAM)p->bpi.hInst);
+
+ Plugin_Uninit(p);
+
+ // mark default plugins to be loaded
+ if (!(p->pclass & PCLASS_CORE))
+ for (int i = 0; i < SIZEOF(pluginDefault); i++)
+ if (pluginDefault[i].pImpl == p)
+ pluginDefault[i].pImpl = NULL;
+
+ return TRUE;
+}
+
+// returns true if the given file is <anything>.dll exactly
+static int valid_library_name(TCHAR *name)
+{
+ TCHAR *dot = _tcsrchr(name, '.');
+ if (dot != NULL && mir_tstrcmpi(dot + 1, _T("dll")) == 0)
+ if (dot[4] == 0)
+ return 1;
+
+ return 0;
+}
+
+void enumPlugins(SCAN_PLUGINS_CALLBACK cb, WPARAM wParam, LPARAM lParam)
+{
+ // get miranda's exe path
+ TCHAR exe[MAX_PATH];
+ GetModuleFileName(NULL, exe, SIZEOF(exe));
+ TCHAR *p = _tcsrchr(exe, '\\'); if (p) *p = 0;
+
+ // create the search filter
+ TCHAR search[MAX_PATH];
+ mir_sntprintf(search, SIZEOF(search), _T("%s\\Plugins\\*.dll"), exe);
+
+ // FFFN will return filenames for things like dot dll+ or dot dllx
+ WIN32_FIND_DATA ffd;
+ HANDLE hFind = FindFirstFile(search, &ffd);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return;
+
+ do {
+ if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && valid_library_name(ffd.cFileName))
+ cb(&ffd, exe, wParam, lParam);
+ } while (FindNextFile(hFind, &ffd));
+ FindClose(hFind);
+}
+
+pluginEntry* OpenPlugin(TCHAR *tszFileName, TCHAR *dir, TCHAR *path)
+{
+ pluginEntry *p = (pluginEntry*)HeapAlloc(hPluginListHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, sizeof(pluginEntry));
+ _tcsncpy_s(p->pluginname, tszFileName, _TRUNCATE);
+
+ // add it to the list anyway
+ pluginList.insert(p);
+
+ TCHAR tszFullPath[MAX_PATH];
+ mir_sntprintf(tszFullPath, SIZEOF(tszFullPath), _T("%s\\%s\\%s"), path, dir, tszFileName);
+
+ // map dll into the memory and check its exports
+ bool bIsPlugin = false;
+ mir_ptr<MUUID> pIds(GetPluginInterfaces(tszFullPath, bIsPlugin));
+ if (!bIsPlugin) {
+ p->pclass |= PCLASS_FAILED; // piece of shit
+ return p;
+ }
+
+ // plugin declared that it's a database or a cryptor. load it asap!
+ bool bIsDb = hasMuuid(pIds, miid_database);
+ if (bIsDb || hasMuuid(pIds, miid_crypto)) {
+ BASIC_PLUGIN_INFO bpi;
+ if (checkAPI(tszFullPath, &bpi, mirandaVersion, CHECKAPI_NONE)) {
+ // plugin is valid
+ p->pclass |= ((bIsDb ? PCLASS_DB : PCLASS_CRYPT) | PCLASS_BASICAPI);
+ // copy the dblink stuff
+ p->bpi = bpi;
+
+ RegisterModule(p->bpi.hInst);
+ if (bpi.Load() != 0)
+ p->pclass |= PCLASS_FAILED;
+ else
+ p->pclass |= PCLASS_LOADED;
+ }
+ else p->pclass |= PCLASS_FAILED;
+ }
+ // plugin declared that it's a contact list. mark it for the future load
+ else if (hasMuuid(pIds, miid_clist)) {
+ // keep a note of this plugin for later
+ clistPlugins.insert(p);
+ p->pclass |= PCLASS_CLIST;
+ }
+ // plugin declared that it's a ssl provider. mark it for the future load
+ else if (hasMuuid(pIds, miid_ssl)) {
+ plugin_ssl = p;
+ p->pclass |= PCLASS_LAST;
+ }
+ // plugin declared that it's a service mode plugin.
+ // load it for a profile manager's window
+ else if (hasMuuid(pIds, miid_servicemode)) {
+ BASIC_PLUGIN_INFO bpi;
+ if (checkAPI(tszFullPath, &bpi, mirandaVersion, CHECKAPI_NONE)) {
+ p->pclass |= (PCLASS_OK | PCLASS_BASICAPI);
+ p->bpi = bpi;
+ if (hasMuuid(bpi, miid_servicemode)) {
+ p->pclass |= (PCLASS_SERVICE);
+ servicePlugins.insert(p);
+ }
+ }
+ else
+ // didn't have basic APIs or DB exports - failed.
+ p->pclass |= PCLASS_FAILED;
+ }
+ return p;
+}
+
+void SetPluginOnWhiteList(const TCHAR* pluginname, int allow)
+{
+ db_set_b(NULL, PLUGINDISABLELIST, _strlwr(_T2A(pluginname)), allow == 0);
+}
+
+// returns 1 if the plugin should be enabled within this profile, filename is always lower case
+int isPluginOnWhiteList(const TCHAR* pluginname)
+{
+ int rc = db_get_b(NULL, PLUGINDISABLELIST, _strlwr(_T2A(pluginname)), 0);
+ if (rc != 0 && askAboutIgnoredPlugins) {
+ TCHAR buf[256];
+ mir_sntprintf(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;
+}
+
+bool TryLoadPlugin(pluginEntry *p, bool bDynamic)
+{
+ TCHAR exe[MAX_PATH], tszFullPath[MAX_PATH];
+ GetModuleFileName(NULL, exe, SIZEOF(exe));
+ TCHAR* slice = _tcsrchr(exe, '\\');
+ if (slice)
+ *slice = 0;
+
+ if (!(p->pclass & (PCLASS_LOADED | PCLASS_DB | PCLASS_CLIST))) {
+ if (!bDynamic && !isPluginOnWhiteList(p->pluginname))
+ return false;
+
+ if (!(p->pclass & PCLASS_BASICAPI)) {
+ BASIC_PLUGIN_INFO bpi;
+ mir_sntprintf(tszFullPath, SIZEOF(tszFullPath), _T("%s\\%s\\%s"), exe, (p->pclass & PCLASS_CORE) ? _T("Core") : _T("Plugins"), p->pluginname);
+ if (!checkAPI(tszFullPath, &bpi, mirandaVersion, CHECKAPI_NONE)) {
+ p->pclass |= PCLASS_FAILED;
+ return false;
+ }
+
+ p->bpi = bpi;
+ p->pclass |= PCLASS_OK | PCLASS_BASICAPI;
+ }
+
+ if (p->bpi.Interfaces) {
+ MUUID *piface = p->bpi.Interfaces;
+ for (int i = 0; !equalUUID(miid_last, piface[i]); i++) {
+ int idx = getDefaultPluginIdx(piface[i]);
+ if (idx != -1 && pluginDefault[idx].pImpl) {
+ if (!bDynamic) { // this place is already occupied, skip & disable
+ SetPluginOnWhiteList(p->pluginname, 0);
+ return false;
+ }
+
+ // we're loading new implementation dynamically, let the old one die
+ if (!(p->pclass & PCLASS_CORE))
+ Plugin_UnloadDyn(pluginDefault[idx].pImpl);
+ }
+ }
+ }
+
+ RegisterModule(p->bpi.hInst);
+ if (p->bpi.Load() != 0)
+ return false;
+
+ p->pclass |= PCLASS_LOADED;
+ if (p->bpi.Interfaces) {
+ MUUID *piface = p->bpi.Interfaces;
+ for (int i = 0; !equalUUID(miid_last, piface[i]); i++) {
+ int idx = getDefaultPluginIdx(piface[i]);
+ if (idx != -1)
+ pluginDefault[idx].pImpl = p;
+ }
+ }
+ }
+ else if (p->bpi.hInst != NULL) {
+ RegisterModule(p->bpi.hInst);
+ p->pclass |= PCLASS_LOADED;
+ }
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Core plugins support
+
+static TCHAR tszCoreErr[] = LPGENT("Core plugin '%s' cannot be loaded or missing. Miranda will exit now");
+
+bool LoadCorePlugin(MuuidReplacement& mr)
+{
+ TCHAR exe[MAX_PATH], tszPlugName[MAX_PATH];
+ GetModuleFileName(NULL, exe, SIZEOF(exe));
+ TCHAR *p = _tcsrchr(exe, '\\'); if (p) *p = 0;
+
+ mir_sntprintf(tszPlugName, SIZEOF(tszPlugName), _T("%s.dll"), mr.stdplugname);
+ pluginEntry* pPlug = OpenPlugin(tszPlugName, _T("Core"), exe);
+ if (pPlug->pclass & PCLASS_FAILED) {
+LBL_Error:
+ MessageBox(NULL, CMString(FORMAT, TranslateTS(tszCoreErr), mr.stdplugname), TranslateT("Fatal error"), MB_OK | MB_ICONSTOP);
+
+ Plugin_UnloadDyn(pPlug);
+ mr.pImpl = NULL;
+ return false;
+ }
+
+ pPlug->pclass |= PCLASS_CORE;
+
+ if (!TryLoadPlugin(pPlug, true))
+ goto LBL_Error;
+
+ if (bModulesLoadedFired) {
+ if (CallPluginEventHook(pPlug->bpi.hInst, hModulesLoadedEvent, 0, 0) != 0)
+ goto LBL_Error;
+
+ NotifyEventHooks(hevLoadModule, (WPARAM)pPlug->bpi.pluginInfo, (LPARAM)pPlug->bpi.hInst);
+ }
+ mr.pImpl = pPlug;
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Contact list plugins support
+
+static bool loadClistModule(TCHAR* exe, pluginEntry *p)
+{
+ BASIC_PLUGIN_INFO bpi;
+ if (checkAPI(exe, &bpi, mirandaVersion, CHECKAPI_CLIST)) {
+ p->bpi = bpi;
+ p->pclass |= PCLASS_LAST | PCLASS_OK | PCLASS_BASICAPI;
+ RegisterModule(p->bpi.hInst);
+ if (bpi.clistlink() == 0) {
+ p->bpi = bpi;
+ p->pclass |= PCLASS_LOADED;
+ pluginDefault[11].pImpl = p;
+
+ LoadExtraIconsModule();
+ return true;
+ }
+ Plugin_Uninit(p);
+ }
+ return false;
+}
+
+static pluginEntry* getCListModule(TCHAR *exe)
+{
+ TCHAR tszFullPath[MAX_PATH];
+
+ for (int i = 0; i < clistPlugins.getCount(); i++) {
+ pluginEntry *p = clistPlugins[i];
+ if (!isPluginOnWhiteList(p->pluginname))
+ continue;
+
+ mir_sntprintf(tszFullPath, SIZEOF(tszFullPath), _T("%s\\Plugins\\%s"), exe, p->pluginname);
+ if (loadClistModule(tszFullPath, p))
+ return p;
+ }
+
+ MuuidReplacement& stdClist = pluginDefault[11];
+ if (LoadCorePlugin(stdClist)) {
+ mir_sntprintf(tszFullPath, SIZEOF(tszFullPath), _T("%s\\Core\\%s.dll"), exe, stdClist.stdplugname);
+ if (loadClistModule(tszFullPath, stdClist.pImpl))
+ return stdClist.pImpl;
+ }
+
+ return NULL;
+}
+
+int UnloadPlugin(TCHAR* buf, int bufLen)
+{
+ for (int i = pluginList.getCount() - 1; i >= 0; i--) {
+ pluginEntry *p = pluginList[i];
+ if (!mir_tstrcmpi(p->pluginname, buf)) {
+ GetModuleFileName(p->bpi.hInst, buf, bufLen);
+ Plugin_Uninit(p);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Service plugins functions
+
+void SetServiceModePlugin(pluginEntry *p)
+{
+ serviceModePlugin = p;
+}
+
+static int LaunchServicePlugin(pluginEntry *p)
+{
+ // plugin load failed - terminating Miranda
+ if (!(p->pclass & PCLASS_LOADED)) {
+ if (p->bpi.Load() != ERROR_SUCCESS) {
+ Plugin_Uninit(p);
+ return SERVICE_FAILED;
+ }
+ p->pclass |= PCLASS_LOADED;
+ }
+
+ INT_PTR res = CallService(MS_SERVICEMODE_LAUNCH, 0, 0);
+ if (res != CALLSERVICE_NOTFOUND)
+ return res;
+
+ MessageBox(NULL, TranslateT("Unable to load plugin in service mode!"), p->pluginname, MB_ICONSTOP);
+ Plugin_Uninit(p);
+ return SERVICE_FAILED;
+}
+
+int LoadDefaultServiceModePlugin()
+{
+ LPCTSTR param = CmdLine_GetOption(_T("svc"));
+ if (param == NULL || *param == 0)
+ return SERVICE_CONTINUE;
+
+ size_t cbLen = mir_tstrlen(param);
+ for (int i = 0; i < servicePlugins.getCount(); i++) {
+ pluginEntry *p = servicePlugins[i];
+ if (!_tcsnicmp(p->pluginname, param, cbLen)) {
+ int res = LaunchServicePlugin(p);
+ if (res == SERVICE_ONLYDB) // load it later
+ serviceModePlugin = p;
+ return res;
+ }
+ }
+
+ return SERVICE_CONTINUE;
+}
+
+int LoadServiceModePlugin()
+{
+ return (serviceModePlugin == NULL) ? SERVICE_CONTINUE : LaunchServicePlugin(serviceModePlugin);
+}
+
+void EnsureCheckerLoaded(bool bEnable)
+{
+ for (int i = 0; i < pluginList.getCount(); i++) {
+ pluginEntry *p = pluginList[i];
+ if (mir_tstrcmpi(p->pluginname, _T("dbchecker.dll")))
+ continue;
+
+ if (bEnable) {
+ if (!(p->pclass & PCLASS_LOADED)) {
+ if (p->bpi.Load() != ERROR_SUCCESS)
+ Plugin_Uninit(p);
+ else {
+ p->pclass |= PCLASS_LOADED;
+ servicePlugins.remove(p);
+ }
+ }
+ }
+ else Plugin_Uninit(p);
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int LoadSslModule(void)
+{
+ if (plugin_ssl != NULL) {
+ if (!TryLoadPlugin(plugin_ssl, false)) {
+ Plugin_Uninit(plugin_ssl);
+ return 1;
+ }
+ }
+ else {
+ MuuidReplacement stdSsl = { MIID_SSL, _T("stdssl"), NULL };
+ if (!LoadCorePlugin(stdSsl))
+ return 1;
+ }
+
+ mir_getSI(&si);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Event hook to unload all non-core plugins
+// hooked very late, after all the internal plugins, blah
+
+void UnloadNewPlugins(void)
+{
+ // unload everything but the special db/clist plugins
+ for (int i = pluginList.getCount() - 1; i >= 0; i--) {
+ pluginEntry *p = pluginList[i];
+ if (!(p->pclass & PCLASS_LAST) && (p->pclass & PCLASS_OK))
+ Plugin_Uninit(p);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Loads all plugins
+
+int LoadNewPluginsModule(void)
+{
+ int i;
+
+ // make full path to the plugin
+ TCHAR exe[MAX_PATH], fullPath[MAX_PATH];
+ GetModuleFileName(NULL, exe, SIZEOF(exe));
+ TCHAR *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 (plugin_crshdmp != NULL && isPluginOnWhiteList(plugin_crshdmp->pluginname))
+ if (!TryLoadPlugin(plugin_crshdmp, false))
+ Plugin_Uninit(plugin_crshdmp);
+
+ // if freeimage is present, load it to provide the basic core functions
+ if (plugin_freeimg != NULL) {
+ BASIC_PLUGIN_INFO bpi;
+ mir_sntprintf(fullPath, SIZEOF(fullPath), _T("%s\\Plugins\\%s"), exe, plugin_freeimg->pluginname);
+ if (checkAPI(fullPath, &bpi, mirandaVersion, CHECKAPI_NONE)) {
+ plugin_freeimg->bpi = bpi;
+ plugin_freeimg->pclass |= PCLASS_OK | PCLASS_BASICAPI;
+ if (bpi.Load() == 0)
+ plugin_freeimg->pclass |= PCLASS_LOADED;
+ else
+ Plugin_Uninit(plugin_freeimg);
+ }
+ }
+
+ // first load the clist cos alot of plugins need that to be present at Load(void)
+ pluginEntry* clist = getCListModule(exe);
+
+ /* 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 (clistPlugins.getCount())
+ 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 NG"), MB_OK | MB_ICONERROR);
+ else
+ MessageBox(NULL, TranslateT("Can't find a contact list plugin! You need StdClist or any other contact list plugin."), _T("Miranda NG"), MB_OK | MB_ICONERROR);
+ return 1;
+ }
+
+ /* enable and disable as needed */
+ for (i = 0; i < clistPlugins.getCount(); i++)
+ SetPluginOnWhiteList(clistPlugins[i]->pluginname, clist != clistPlugins[i] ? 0 : 1);
+
+ /* now loop thru and load all the other plugins, do this in one pass */
+ for (i = 0; i < pluginList.getCount(); i++) {
+ pluginEntry *p = pluginList[i];
+ if (!TryLoadPlugin(p, false)) {
+ Plugin_Uninit(p);
+ i--;
+ }
+ }
+
+ HookEvent(ME_OPT_INITIALISE, PluginOptionsInit);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Plugins module initialization
+// called before anything real is loaded, incl. database
+
+static BOOL scanPluginsDir(WIN32_FIND_DATA *fd, TCHAR *path, WPARAM, LPARAM)
+{
+ pluginEntry *p = OpenPlugin(fd->cFileName, _T("Plugins"), path);
+ if (!(p->pclass & PCLASS_FAILED)) {
+ if (plugin_freeimg == NULL && mir_tstrcmpi(fd->cFileName, _T("advaimg.dll")) == 0) {
+ plugin_freeimg = p;
+ p->pclass |= PCLASS_LAST;
+ }
+
+ if (plugin_crshdmp == NULL && mir_tstrcmpi(fd->cFileName, _T("crashdumper.dll")) == 0) {
+ plugin_crshdmp = p;
+ p->pclass |= PCLASS_LAST;
+ }
+ }
+
+ return TRUE;
+}
+
+int LoadNewPluginsModuleInfos(void)
+{
+ bModuleInitialized = TRUE;
+
+ LoadPluginOptions();
+
+ hPluginListHeap = HeapCreate(HEAP_NO_SERIALIZE, 0, 0);
+ mirandaVersion = (DWORD)CallService(MS_SYSTEM_GETVERSION, 0, 0);
+
+ // remember where the mirandaboot.ini goes
+ PathToAbsoluteT(_T("mirandaboot.ini"), mirandabootini);
+
+ // look for all *.dll's
+ enumPlugins(scanPluginsDir, 0, 0);
+
+ MuuidReplacement stdCrypt = { MIID_CRYPTO, _T("stdcrypt"), NULL };
+ return !LoadCorePlugin(stdCrypt);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Plugins module unloading
+// called at the end of module chain unloading, just modular engine left at this point
+
+void UnloadDatabase(void)
+{
+ if (currDb != NULL) {
+ db_setCurrent(NULL);
+ currDblink->Unload(currDb);
+ currDb = NULL;
+ currDblink = NULL;
+ }
+
+ UninitIni();
+}
+
+void UnloadNewPluginsModule(void)
+{
+ if (!bModuleInitialized)
+ return;
+
+ UnloadPluginOptions();
+
+ // unload everything but the DB
+ for (int i = pluginList.getCount() - 1; i >= 0; i--) {
+ pluginEntry *p = pluginList[i];
+ if (!(p->pclass & (PCLASS_DB | PCLASS_CRYPT)) && p != plugin_crshdmp)
+ Plugin_Uninit(p);
+ }
+
+ if (plugin_crshdmp)
+ Plugin_Uninit(plugin_crshdmp);
+
+ UnloadDatabase();
+
+ for (int k = pluginList.getCount() - 1; k >= 0; k--) {
+ pluginEntry *p = pluginList[k];
+ Plugin_Uninit(p);
+ }
+
+ if (hPluginListHeap) HeapDestroy(hPluginListHeap);
+ hPluginListHeap = 0;
+}
diff --git a/src/mir_app/src/openurl.cpp b/src/mir_app/src/openurl.cpp new file mode 100644 index 0000000000..1ad7b508ad --- /dev/null +++ b/src/mir_app/src/openurl.cpp @@ -0,0 +1,82 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include <ctype.h>
+
+struct TOpenUrlInfo
+{
+ TOpenUrlInfo(TCHAR *_url, int _bNew) :
+ szUrl(_url),
+ newWindow(_bNew)
+ {}
+
+ ptrT szUrl;
+ int newWindow;
+};
+
+static void OpenURLThread(void *arg)
+{
+ TOpenUrlInfo *hUrlInfo = (TOpenUrlInfo*)arg;
+
+ // wack a protocol on it
+ CMString tszUrl;
+ if ((isalpha(hUrlInfo->szUrl[0]) && hUrlInfo->szUrl[1] == ':') || hUrlInfo->szUrl[0] == '\\')
+ tszUrl.Format(_T("file:///%s"), hUrlInfo->szUrl);
+ else {
+ int i;
+ for (i = 0; _istalpha(hUrlInfo->szUrl[i]); i++);
+ if (hUrlInfo->szUrl[i] == ':')
+ tszUrl = hUrlInfo->szUrl;
+ else if (!_tcsnicmp(hUrlInfo->szUrl, _T("ftp."), 4))
+ tszUrl.Format(_T("ftp://%s"), hUrlInfo->szUrl);
+ else
+ tszUrl.Format(_T("http://%s"), hUrlInfo->szUrl);
+ }
+
+ // check user defined browser for opening urls
+ ptrT tszBrowser(db_get_tsa(NULL, "Miranda", "OpenUrlBrowser"));
+ if (tszBrowser)
+ ShellExecute(NULL, _T("open"), tszBrowser, tszUrl, NULL, (hUrlInfo->newWindow) ? SW_NORMAL : SW_SHOWDEFAULT);
+ else
+ ShellExecute(NULL, _T("open"), tszUrl, NULL, NULL, (hUrlInfo->newWindow) ? SW_NORMAL : SW_SHOWDEFAULT);
+
+ delete hUrlInfo;
+}
+
+static INT_PTR OpenURL(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam == 0)
+ return 1;
+
+ TOpenUrlInfo *hUrlInfo = new TOpenUrlInfo((wParam & OUF_UNICODE) ? mir_wstrdup((WCHAR*)lParam) : mir_a2t((char*)lParam), wParam & OUF_NEWWINDOW);
+ forkthread(OpenURLThread, 0, hUrlInfo);
+ return 0;
+}
+
+int InitOpenUrl(void)
+{
+ CreateServiceFunction(MS_UTILS_OPENURL, OpenURL);
+ return 0;
+}
diff --git a/src/mir_app/src/options.cpp b/src/mir_app/src/options.cpp new file mode 100644 index 0000000000..4bdec9d37f --- /dev/null +++ b/src/mir_app/src/options.cpp @@ -0,0 +1,1327 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+Copyright (c) 2007 Artem Shpynov
+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 "stdafx.h"
+#include "filter.h"
+
+#define FILTER_TIMEOUT_TIMER 10012
+
+#define ALL_MODULES_FILTER LPGEN("<all modules>")
+#define CORE_MODULES_FILTER LPGEN("<core modules>")
+
+int LangpackOptionsInit(WPARAM, LPARAM);
+
+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;
+
+struct OptionsPageInit
+{
+ int pageCount;
+ OPTIONSDIALOGPAGE *odp;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class COptionPageDialog : public CDlgBase
+{
+ DLGPROC m_wndProc;
+ LPARAM m_lParam;
+
+public:
+ COptionPageDialog(HINSTANCE hInst, int idDialog, DLGPROC pProc, LPARAM lParam) :
+ CDlgBase(hInst, idDialog),
+ m_wndProc(pProc),
+ m_lParam(lParam)
+ {
+ }
+
+ virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
+ {
+ if (msg == WM_INITDIALOG)
+ lParam = m_lParam;
+
+ LRESULT res = m_wndProc(m_hwnd, msg, wParam, lParam);
+
+ if (msg == WM_DESTROY)
+ m_hwnd = NULL;
+
+ return res;
+ }
+};
+
+struct OptionsPageData : public MZeroedObject
+{
+ OptionsPageData(OPTIONSDIALOGPAGE *src)
+ {
+ if (src->hInstance != NULL && src->pszTemplate != NULL)
+ pDialog = new COptionPageDialog(src->hInstance, (int)src->pszTemplate, src->pfnDlgProc, src->dwInitParam);
+ else
+ pDialog = src->pDialog;
+ assert(pDialog != NULL);
+
+ flags = src->flags;
+ hLangpack = src->hLangpack;
+
+ if (src->flags & ODPF_UNICODE)
+ ptszTitle = mir_tstrdup(src->ptszTitle);
+ else
+ ptszTitle = mir_a2t(src->pszTitle);
+
+ if (src->flags & ODPF_UNICODE)
+ ptszGroup = mir_tstrdup(src->ptszGroup);
+ else
+ ptszGroup = mir_a2t(src->pszGroup);
+
+ if (src->flags & ODPF_UNICODE)
+ ptszTab = mir_tstrdup(src->ptszTab);
+ else
+ ptszTab = mir_a2t(src->pszTab);
+ }
+
+ ~OptionsPageData()
+ {
+ if (pDialog && getHwnd() != NULL)
+ DestroyWindow(getHwnd());
+ }
+
+ CDlgBase *pDialog;
+ int hLangpack;
+ ptrT ptszTitle, ptszGroup, ptszTab;
+ HTREEITEM hTreeItem;
+ int changed;
+ int height;
+ int width;
+ DWORD flags;
+ BOOL insideTab;
+
+ __forceinline HWND getHwnd() const { return pDialog->GetHwnd(); }
+ __forceinline HINSTANCE getInst() const { return pDialog->GetInst(); }
+
+ __forceinline TCHAR* getString(TCHAR *ptszStr)
+ {
+ if (flags & ODPF_DONTTRANSLATE)
+ return ptszStr;
+ return TranslateTH(hLangpack, ptszStr);
+ }
+};
+
+struct OptionsDlgData : public MZeroedObject
+{
+ OptionsDlgData() :
+ arOpd(10)
+ {}
+
+ int currentPage;
+ HTREEITEM hCurrentPage;
+ LIST<OptionsPageData> arOpd;
+ RECT rcDisplay;
+ RECT rcTab;
+ HFONT hBoldFont;
+ TCHAR szFilterString[1024];
+ HANDLE hPluginLoad, hPluginUnload;
+
+ OptionsPageData* getCurrent() const
+ { return (currentPage == -1) ? NULL : arOpd[currentPage];
+ }
+};
+
+HTREEITEM FindNamedTreeItemAtRoot(HWND hwndTree, const TCHAR* name)
+{
+ TCHAR str[128];
+ TVITEM tvi;
+ 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 (!mir_tstrcmpi(str, name))
+ return tvi.hItem;
+
+ tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem);
+ }
+ return NULL;
+}
+
+static HTREEITEM FindNamedTreeItemAtChildren(HWND hwndTree, HTREEITEM hItem, const TCHAR* name)
+{
+ TCHAR str[128];
+ TVITEM tvi;
+ 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 (!mir_tstrcmpi(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 (!mir_tstrcmp(szClass, _T("Button")) && (GetWindowLongPtr(hwnd, GWL_STYLE) & 0x0F) == BS_GROUPBOX)
+ SendMessage(hwnd, WM_SETFONT, lParam, 0);
+ 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 (SendDlgItemMessageA(hdlg, IDC_PAGETREE, TVM_GETITEMA, 0, (LPARAM)&tvi)) {
+ mir_snprintf(buf, "%s%s", OPTSTATE_PREFIX, str);
+ db_set_b(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)
+
+#define HM_MODULELOAD (WM_USER+12)
+#define HM_MODULEUNLOAD (WM_USER+13)
+
+static void ThemeDialogBackground(HWND hwnd, BOOL tabbed)
+{
+ EnableThemeDialogTexture(hwnd, (tabbed ? ETDT_ENABLE : ETDT_DISABLE) | ETDT_USETABTEXTURE);
+}
+
+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_s(buffer, size, dllName, _TRUNCATE);
+ return buffer;
+}
+
+PageHash GetPluginPageHash(const OptionsPageData *page)
+{
+ return mir_hashstrT(page->ptszGroup) + mir_hashstrT(page->ptszTitle) + mir_hashstrT(page->ptszTab);
+}
+
+static HWND CreateOptionWindow(const OptionsPageData *opd, HWND hWndParent)
+{
+ opd->pDialog->SetParent(hWndParent);
+ opd->pDialog->Create();
+ return opd->pDialog->GetHwnd();
+}
+
+static void FindFilterStrings(int enableKeywordFiltering, int current, HWND hWndParent, const OptionsPageData *page)
+{
+ HWND hWnd = 0;
+ if (enableKeywordFiltering) {
+ if (current)
+ hWnd = page->getHwnd();
+ else {
+ hWnd = CreateOptionWindow(page, hWndParent);
+ ShowWindow(hWnd, SW_HIDE); // make sure it's hidden
+ }
+ }
+
+ DWORD key = GetPluginPageHash(page); // get the plugin page hash
+
+ TCHAR pluginName[MAX_PATH];
+ char *temp = GetPluginNameByInstance(page->getInst());
+ GetDialogStrings(enableKeywordFiltering, key, GetPluginName(page->getInst(), pluginName, SIZEOF(pluginName)), hWnd, page->ptszGroup, page->ptszTitle, page->ptszTab, _A2T(temp));
+
+ if (enableKeywordFiltering && !current)
+ DestroyWindow(hWnd); // destroy the page, we're done with it
+}
+
+static int MatchesFilter(const OptionsPageData *page, TCHAR *szFilterString)
+{
+ return ContainsFilterString(GetPluginPageHash(page), szFilterString);
+}
+
+static LRESULT CALLBACK OptionsFilterSubclassProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ if (message != WM_PAINT && message != WM_PRINT)
+ return mir_callNextSubclass(hWnd, OptionsFilterSubclassProc, message, wParam, lParam);
+
+ if (GetFocus() == hWnd || GetWindowTextLength(hWnd))
+ return mir_callNextSubclass(hWnd, OptionsFilterSubclassProc, message, wParam, lParam);
+
+ RECT rc;
+ GetClientRect(hWnd, &rc);
+
+ PAINTSTRUCT paint;
+ HDC hdc = (message == WM_PAINT) ? BeginPaint(hWnd, &paint) : (HDC)wParam;
+
+ TCHAR buf[255];
+ if (bSearchState == 1 && FilterLoadProgress < 100 && FilterLoadProgress > 0)
+ mir_sntprintf(buf, TranslateT("Loading... %d%%"), FilterLoadProgress);
+ else
+ mir_sntprintf(buf, TranslateT("Search"));
+
+ bool bDrawnByTheme = false;
+
+ int oldMode = SetBkMode(hdc, TRANSPARENT);
+
+ 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, OptionsDlgData *dat, int i)
+{
+ OptionsPageData *opd = dat->arOpd[i];
+ if (dat->szFilterString[0] && !MatchesFilter(opd, dat->szFilterString))
+ return false;
+ return true;
+}
+
+static BOOL IsAeroMode()
+{
+ BOOL result;
+ return dwmIsCompositionEnabled && (dwmIsCompositionEnabled(&result) == S_OK) && result;
+}
+
+static void FreeOptionsData(OptionsPageInit* popi)
+{
+ for (int 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);
+}
+
+static LRESULT CALLBACK AeroPaintSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+static void AeroPaintControl(HWND hwnd, HDC hdc, UINT msg, LPARAM lpFlags)
+{
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+
+ 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;
+
+ BYTE *pBits;
+ HBITMAP hBmp = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, (void **)&pBits, NULL, 0);
+ HBITMAP hOldBmp = (HBITMAP)SelectObject(tempDC, hBmp);
+
+ // paint
+ SetPropA(hwnd, "Miranda.AeroRender.Active", (HANDLE)TRUE);
+ mir_callNextSubclass(hwnd, AeroPaintSubclassProc, 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)
+{
+ 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, msg, lParam);
+ return TRUE;
+
+ case WM_DESTROY:
+ RemovePropA(hwnd, "Miranda.AeroRender.Active");
+ break;
+
+ case WM_PAINT:
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(hwnd, &ps);
+ AeroPaintControl(hwnd, hdc, WM_PRINT, PRF_CLIENT | PRF_NONCLIENT);
+ EndPaint(hwnd, &ps);
+ return TRUE;
+ }
+ return mir_callNextSubclass(hwnd, AeroPaintSubclassProc, msg, wParam, lParam);
+}
+
+static void CALLBACK FilterSearchTimerFunc(HWND hwnd, UINT, UINT_PTR, DWORD)
+{
+ OptionsDlgData *dat = (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, GetModuleHandleA("mir_app.dll"), 0); // Fake window to keep option page focused
+
+ if (FilterPage < dat->arOpd.getCount())
+ FindFilterStrings(TRUE, dat->currentPage == FilterPage, hFilterSearchWnd, dat->arOpd[FilterPage]);
+
+ FilterPage++;
+ FilterLoadProgress = FilterPage * 100 / ((dat->arOpd.getCount()) ? dat->arOpd.getCount() : FilterPage);
+ if (FilterPage >= dat->arOpd.getCount()) {
+ 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(HWND hDlg, OptionsDlgData* dat)
+{
+ HINSTANCE *KnownInstances = (HINSTANCE*)alloca(sizeof(HINSTANCE)*dat->arOpd.getCount());
+ int countKnownInst = 0;
+ SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_RESETCONTENT, 0, 0);
+ int index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_ADDSTRING, 0, (LPARAM)TranslateT(ALL_MODULES_FILTER));
+ SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_SETITEMDATA, (WPARAM)index, 0);
+ index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_ADDSTRING, 0, (LPARAM)TranslateT(CORE_MODULES_FILTER));
+ SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_SETITEMDATA, (WPARAM)index, (LPARAM)g_hInst);
+
+ for (int i = 0; i < dat->arOpd.getCount(); i++) {
+ OptionsPageData *opd = dat->arOpd[i];
+ FindFilterStrings(FALSE, FALSE, hDlg, opd); // only modules name (fast enougth)
+
+ HINSTANCE inst = opd->getInst();
+ if (inst == g_hInst)
+ continue;
+
+ int j;
+ for (j = 0; j < countKnownInst; j++)
+ if (KnownInstances[j] == inst)
+ break;
+ if (j != countKnownInst)
+ continue;
+
+ KnownInstances[countKnownInst] = inst;
+ countKnownInst++;
+
+ TCHAR tszModuleName[MAX_PATH];
+ GetModuleFileName(inst, tszModuleName, SIZEOF(tszModuleName));
+
+ TCHAR *dllName = mir_a2t(GetPluginNameByInstance(inst));
+ if (!dllName) dllName = mir_tstrdup(_tcsrchr(tszModuleName, _T('\\')));
+ if (!dllName) dllName = mir_tstrdup(tszModuleName);
+ if (dllName) {
+ index = SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_ADDSTRING, 0, (LPARAM)dllName);
+ SendDlgItemMessage(hDlg, IDC_KEYWORD_FILTER, (UINT)CB_SETITEMDATA, (WPARAM)index, (LPARAM)inst);
+ mir_free(dllName);
+ }
+ }
+
+ FilterLoadProgress = 100;
+}
+
+static void RebuildPageTree(HWND hdlg, OptionsDlgData *dat)
+{
+ 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)
+ BOOL bRemoveFocusFromFilter = FALSE;
+ if (mir_tstrcmp(dat->szFilterString, TranslateT(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 (mir_tstrcmp(dat->szFilterString, TranslateT(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_s(dat->szFilterString, pos, _TRUNCATE);
+ }
+ else {
+ int sel = SendDlgItemMessage(hdlg, IDC_KEYWORD_FILTER, (UINT)CB_GETCURSEL, 0, 0);
+ if (sel != -1) {
+ HINSTANCE hinst = (HINSTANCE)SendDlgItemMessage(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_s(dat->szFilterString, pos, _TRUNCATE);
+ }
+ }
+
+ _tcslwr_locale(dat->szFilterString); //all strings are stored as lowercase ... make sure filter string is lowercase too
+
+ HWND hwndTree = GetDlgItem(hdlg, IDC_PAGETREE);
+ SendMessage(hwndTree, WM_SETREDRAW, FALSE, 0);
+
+ HWND oldWnd = NULL;
+ HWND oldTab = NULL;
+ CMString fullTitle;
+
+ OptionsPageData *opd = dat->getCurrent();
+ if (opd != NULL) {
+ oldWnd = opd->getHwnd();
+ if (opd->insideTab)
+ oldTab = GetDlgItem(hdlg, IDC_TAB);
+ }
+
+ dat->hCurrentPage = NULL;
+
+ TreeView_SelectItem(hwndTree, NULL);
+ TreeView_DeleteAllItems(hwndTree);
+
+ TVINSERTSTRUCT tvis;
+ 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 (int i = 0; i < dat->arOpd.getCount(); i++) {
+ if (!CheckPageShow(hdlg, dat, i))
+ continue;
+
+ opd = dat->arOpd[i];
+ TCHAR *ptszGroup = TranslateTH(opd->hLangpack, opd->ptszGroup);
+ TCHAR *ptszTitle = opd->getString(opd->ptszTitle), *useTitle;
+ TCHAR *ptszTab = TranslateTH(opd->hLangpack, opd->ptszTab);
+
+ tvis.hParent = NULL;
+ useTitle = ptszTitle;
+
+ if (ptszGroup != NULL) {
+ tvis.hParent = FindNamedTreeItemAtRoot(hwndTree, ptszGroup);
+ if (tvis.hParent == NULL) {
+ tvis.item.lParam = -1;
+ tvis.item.pszText = ptszGroup;
+ 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 (ptszTab != 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;
+ opd->hTreeItem = TreeView_InsertItem(hwndTree, &tvis);
+ if (i == dat->currentPage)
+ dat->hCurrentPage = opd->hTreeItem;
+ }
+
+ char str[128];
+ TVITEMA tvi;
+ 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)) {
+ char buf[130];
+ mir_snprintf(buf, "%s%s", OPTSTATE_PREFIX, str);
+ if (!db_get_b(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) {
+ opd = dat->getCurrent();
+ if (opd && oldWnd != opd->getHwnd()) {
+ ShowWindow(oldWnd, SW_HIDE);
+ if (oldTab && (opd == NULL || !opd->insideTab))
+ ShowWindow(oldTab, SW_HIDE);
+ }
+ }
+
+ if (dat->szFilterString[0] == 0) // Clear the keyword combo box
+ SetDlgItemText(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
+
+ SendMessage(hwndTree, WM_SETREDRAW, TRUE, 0);
+ TreeView_EnsureVisible(hwndTree, dat->hCurrentPage);
+}
+
+static BOOL IsInsideTab(HWND hdlg, OptionsDlgData *dat, int i)
+{
+ OptionsPageData *opd = dat->arOpd[i];
+ int pages = 0;
+ if (opd->ptszTab != NULL) {
+ // Count tabs to calc position
+ for (int j = 0; j < dat->arOpd.getCount() && pages < 2; j++) {
+ OptionsPageData* opd2 = dat->arOpd[j];
+ if (!CheckPageShow(hdlg, dat, j)) continue;
+ if (mir_tstrcmp(opd2->ptszTitle, opd->ptszTitle) || mir_tstrcmp(opd2->ptszGroup, opd->ptszGroup))
+ continue;
+ pages++;
+ }
+ }
+ return (pages > 1);
+}
+
+static void LoadOptionsModule(HWND hdlg, OptionsDlgData *dat, HINSTANCE hInst)
+{
+ OptionsPageInit opi = { 0 };
+ CallPluginEventHook(hInst, hOptionsInitEvent, (WPARAM)&opi, 0);
+ if (opi.pageCount == 0)
+ return;
+
+ for (int i = 0; i < opi.pageCount; i++) {
+ OptionsPageData *opd = new OptionsPageData(&opi.odp[i]);
+ if (opd->pDialog == NULL) // smth went wrong
+ delete opd;
+ else
+ dat->arOpd.insert(opd);
+ }
+
+ FreeOptionsData(&opi);
+ PostMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);
+}
+
+static void UnloadOptionsModule(HWND hdlg, OptionsDlgData *dat, HINSTANCE hInst)
+{
+ bool bToRebuildTree = false;
+
+ for (int i = dat->arOpd.getCount() - 1; i >= 0; i--) {
+ OptionsPageData *opd = dat->arOpd[i];
+ if (opd->getInst() != hInst)
+ continue;
+
+ if (dat->currentPage > i)
+ dat->currentPage--;
+
+ dat->arOpd.remove(i);
+ delete opd;
+ bToRebuildTree = true;
+ }
+
+ if (bToRebuildTree)
+ PostMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);
+}
+
+static INT_PTR CALLBACK OptionsDlgProc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ OptionsPageData *opd;
+ OptionsDlgData *dat = (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:
+ TranslateDialogDefault(hdlg);
+
+ if (!ServiceExists(MS_MODERNOPT_SHOW))
+ ShowWindow(GetDlgItem(hdlg, IDC_MODERN), FALSE);
+
+ dat = new OptionsDlgData;
+ SetWindowLongPtr(hdlg, GWLP_USERDATA, (LONG_PTR)dat);
+
+ Utils_RestoreWindowPositionNoSize(hdlg, NULL, "Options", "");
+ Window_SetIcon_IcoLib(hdlg, SKINICON_OTHER_OPTIONS);
+ EnableWindow(GetDlgItem(hdlg, IDC_APPLY), FALSE);
+ {
+ COMBOBOXINFO cbi;
+ cbi.cbSize = sizeof(COMBOBOXINFO);
+ GetComboBoxInfo(GetDlgItem(hdlg, IDC_KEYWORD_FILTER), &cbi);
+ mir_subclassWindow(cbi.hwndItem, OptionsFilterSubclassProc);
+
+ if (IsAeroMode()) {
+ mir_subclassWindow(cbi.hwndCombo, AeroPaintSubclassProc);
+ mir_subclassWindow(cbi.hwndItem, AeroPaintSubclassProc);
+ }
+
+ PROPSHEETHEADER *psh = (PROPSHEETHEADER*)lParam;
+ SetWindowText(hdlg, psh->pszCaption);
+
+ LOGFONT lf;
+ dat->hBoldFont = (HFONT)SendDlgItemMessage(hdlg, IDC_APPLY, WM_GETFONT, 0, 0);
+ GetObject(dat->hBoldFont, sizeof(lf), &lf);
+ lf.lfWeight = FW_BOLD;
+ dat->hBoldFont = CreateFontIndirect(&lf);
+
+ dat->hPluginLoad = HookEventMessage(ME_SYSTEM_MODULELOAD, hdlg, HM_MODULELOAD);
+ dat->hPluginUnload = HookEventMessage(ME_SYSTEM_MODULEUNLOAD, hdlg, HM_MODULEUNLOAD);
+ dat->currentPage = -1;
+
+ ptrT lastPage, lastGroup, lastTab;
+ OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)psh->pStartPage;
+ if (ood->pszPage == NULL) {
+ lastPage = db_get_tsa(NULL, "Options", "LastPage");
+
+ if (ood->pszGroup == NULL)
+ lastGroup = db_get_tsa(NULL, "Options", "LastGroup");
+ else
+ lastGroup = mir_a2t(ood->pszGroup);
+ }
+ else {
+ lastPage = mir_a2t(ood->pszPage);
+ lastGroup = mir_a2t(ood->pszGroup);
+ }
+
+ if (ood->pszTab == NULL)
+ lastTab = db_get_tsa(NULL, "Options", "LastTab");
+ else
+ lastTab = mir_a2t(ood->pszTab);
+
+ OPTIONSDIALOGPAGE *odp = (OPTIONSDIALOGPAGE*)psh->ppsp;
+ for (UINT i = 0; i < psh->nPages; i++, odp++) {
+ OptionsPageData *opd = new OptionsPageData(odp);
+ if (opd->pDialog == NULL) // smth went wrong
+ delete opd;
+ else
+ dat->arOpd.insert(opd);
+
+ if (!mir_tstrcmp(lastPage, odp->ptszTitle) && !mir_tstrcmp(lastGroup, odp->ptszGroup))
+ if ((ood->pszTab == NULL && dat->currentPage == -1) || !mir_tstrcmp(lastTab, odp->ptszTab))
+ dat->currentPage = (int)i;
+ }
+
+ GetWindowRect(GetDlgItem(hdlg, IDC_STNOPAGE), &dat->rcDisplay);
+ MapWindowPoints(NULL, hdlg, (LPPOINT)&dat->rcDisplay, 2);
+
+ // Add an item to count in height
+ TCITEM tie;
+ 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);
+
+ FillFilterCombo(hdlg, dat);
+ PostMessage(hdlg, DM_REBUILDPAGETREE, 0, 0);
+ }
+ return TRUE;
+
+ case DM_REBUILDPAGETREE:
+ RebuildPageTree(hdlg, dat);
+ break;
+
+ case HM_MODULELOAD:
+ LoadOptionsModule(hdlg, dat, (HINSTANCE)lParam);
+ break;
+
+ case HM_MODULEUNLOAD:
+ UnloadOptionsModule(hdlg, dat, (HINSTANCE)lParam);
+ break;
+
+ case PSM_CHANGED:
+ EnableWindow(GetDlgItem(hdlg, IDC_APPLY), TRUE);
+
+ opd = dat->getCurrent();
+ if (opd)
+ opd->changed = 1;
+
+ 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:
+ opd = dat->getCurrent();
+ if (opd && opd->getHwnd() != NULL) {
+ PSHNOTIFY pshn;
+ pshn.hdr.code = PSN_KILLACTIVE;
+ pshn.hdr.hwndFrom = dat->arOpd[dat->currentPage]->getHwnd();
+ pshn.hdr.idFrom = 0;
+ pshn.lParam = 0;
+ if (SendMessage(dat->arOpd[dat->currentPage]->getHwnd(), WM_NOTIFY, 0, (LPARAM)&pshn)) {
+ SetWindowLongPtr(hdlg, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+ }
+ break;
+
+ case TCN_SELCHANGE:
+ case TVN_SELCHANGED:
+ ShowWindow(GetDlgItem(hdlg, IDC_STNOPAGE), SW_HIDE);
+
+ opd = dat->getCurrent();
+ if (opd && opd->getHwnd() != NULL)
+ ShowWindow(opd->getHwnd(), SW_HIDE);
+
+ if (wParam != IDC_TAB) {
+ TVITEM tvi;
+ tvi.hItem = dat->hCurrentPage = TreeView_GetSelection(hwndTree);
+ if (tvi.hItem == NULL) {
+ ShowWindow(GetDlgItem(hdlg, IDC_TAB), SW_HIDE);
+ 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;
+ tie.mask = TCIF_PARAM;
+ TabCtrl_GetItem(GetDlgItem(hdlg, IDC_TAB), TabCtrl_GetCurSel(GetDlgItem(hdlg, IDC_TAB)), &tie);
+ dat->currentPage = tie.lParam;
+
+ TVITEM tvi;
+ tvi.hItem = dat->hCurrentPage;
+ tvi.mask = TVIF_PARAM;
+ tvi.lParam = dat->currentPage;
+ TreeView_SetItem(hwndTree, &tvi);
+ }
+
+ opd = dat->getCurrent();
+ if (opd == NULL) {
+ ShowWindow(GetDlgItem(hdlg, IDC_STNOPAGE), SW_SHOW);
+ break;
+ }
+ if (opd->getHwnd() == NULL) {
+ CreateOptionWindow(opd, hdlg);
+ if (opd->flags & ODPF_BOLDGROUPS)
+ EnumChildWindows(opd->getHwnd(), BoldGroupTitlesEnumChildren, (LPARAM)dat->hBoldFont);
+
+ RECT rcPage;
+ GetClientRect(opd->getHwnd(), &rcPage);
+ int w = opd->width = rcPage.right;
+ int h = opd->height = rcPage.bottom;
+
+ RECT rc;
+ GetWindowRect(opd->getHwnd(), &rc);
+
+ opd->insideTab = IsInsideTab(hdlg, dat, dat->currentPage);
+ if (opd->insideTab) {
+ SetWindowPos(opd->getHwnd(), HWND_TOP, (dat->rcTab.left + dat->rcTab.right - w) >> 1, dat->rcTab.top, w, h, 0);
+ ThemeDialogBackground(opd->getHwnd(), TRUE);
+ }
+ else {
+ SetWindowPos(opd->getHwnd(), HWND_TOP, (dat->rcDisplay.left + dat->rcDisplay.right - w) >> 1, (dat->rcDisplay.top + dat->rcDisplay.bottom - h) >> 1, w, h, 0);
+ ThemeDialogBackground(opd->getHwnd(), FALSE);
+ }
+ }
+
+ if (wParam != IDC_TAB) {
+ opd->insideTab = IsInsideTab(hdlg, dat, dat->currentPage);
+ if (opd->insideTab) {
+ // Make tabbed pane
+ int pages = 0, sel = 0;
+ HWND hwndTab = GetDlgItem(hdlg, IDC_TAB);
+ TabCtrl_DeleteAllItems(hwndTab);
+
+ TCITEM tie;
+ tie.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
+ tie.iImage = -1;
+ for (int i = 0; i < dat->arOpd.getCount(); i++) {
+ if (!CheckPageShow(hdlg, dat, i))
+ continue;
+
+ OptionsPageData *p = dat->arOpd[i];
+ if (mir_tstrcmp(opd->ptszTitle, p->ptszTitle) || mir_tstrcmp(opd->ptszGroup, p->ptszGroup))
+ continue;
+
+ tie.pszText = TranslateTH(p->hLangpack, p->ptszTab);
+ tie.lParam = i;
+ TabCtrl_InsertItem(hwndTab, pages, &tie);
+ if (!mir_tstrcmp(opd->ptszTab, p->ptszTab))
+ sel = pages;
+ pages++;
+ }
+ TabCtrl_SetCurSel(hwndTab, sel);
+ ShowWindow(hwndTab, opd->insideTab ? SW_SHOW : SW_HIDE);
+ }
+
+ ThemeDialogBackground(opd->getHwnd(), opd->insideTab);
+ }
+
+ ShowWindow(opd->getHwnd(), SW_SHOW);
+ if (((LPNMTREEVIEW)lParam)->action == TVC_BYMOUSE)
+ PostMessage(hdlg, DM_FOCUSPAGE, 0, 0);
+ else
+ SetFocus(hwndTree);
+ }
+ }
+ break;
+
+ case DM_FOCUSPAGE:
+ if (dat->currentPage != -1)
+ SetFocus(dat->arOpd[dat->currentPage]->getHwnd());
+ 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_MODERN:
+ db_set_b(NULL, "Options", "Expert", 0);
+ SaveOptionsTreeState(hdlg);
+ PostMessage(hdlg, WM_CLOSE, 0, 0);
+ CallService(MS_MODERNOPT_SHOW, 0, 0);
+ break;
+
+ case IDCANCEL:
+ {
+ PSHNOTIFY pshn;
+ pshn.hdr.idFrom = 0;
+ pshn.lParam = 0;
+ pshn.hdr.code = PSN_RESET;
+ for (int i = 0; i < dat->arOpd.getCount(); i++) {
+ OptionsPageData *p = dat->arOpd[i];
+ if (p->getHwnd() == NULL || !p->changed)
+ continue;
+ pshn.hdr.hwndFrom = p->getHwnd();
+ SendMessage(p->getHwnd(), WM_NOTIFY, 0, (LPARAM)&pshn);
+ }
+ DestroyWindow(hdlg);
+ }
+ break;
+
+ case IDOK:
+ case IDC_APPLY:
+ if (LOWORD(wParam) == IDOK && GetParent(GetFocus()) == GetDlgItem(hdlg, IDC_KEYWORD_FILTER))
+ return TRUE;
+
+ PSHNOTIFY pshn;
+ EnableWindow(GetDlgItem(hdlg, IDC_APPLY), FALSE);
+ SetFocus(hwndTree);
+
+ opd = dat->getCurrent();
+ if (opd != NULL) {
+ pshn.hdr.idFrom = 0;
+ pshn.lParam = LOWORD(wParam);
+ pshn.hdr.code = PSN_KILLACTIVE;
+ pshn.hdr.hwndFrom = opd->getHwnd();
+ if (SendMessage(opd->getHwnd(), WM_NOTIFY, 0, (LPARAM)&pshn))
+ break;
+ }
+
+ pshn.hdr.code = PSN_APPLY;
+ for (int i = 0; i < dat->arOpd.getCount(); i++) {
+ OptionsPageData *p = dat->arOpd[i];
+ if (p->getHwnd() == NULL || !p->changed) continue;
+ p->changed = 0;
+ pshn.hdr.hwndFrom = p->getHwnd();
+ if (SendMessage(p->getHwnd(), WM_NOTIFY, 0, (LPARAM)&pshn) == PSNRET_INVALID_NOCHANGEPAGE) {
+ dat->hCurrentPage = p->hTreeItem;
+ TreeView_SelectItem(hwndTree, dat->hCurrentPage);
+ if (opd)
+ opd->pDialog->Show(SW_HIDE);
+ dat->currentPage = i;
+ if (opd)
+ opd->pDialog->Show();
+ return 0;
+ }
+ }
+
+ if (LOWORD(wParam) == IDOK)
+ DestroyWindow(hdlg);
+ }
+ break;
+
+ case WM_DESTROY:
+ if (FilterTimerId)
+ KillTimer(hdlg, FilterTimerId);
+ DestroyWindow(hFilterSearchWnd);
+ ClearFilterStrings();
+ dat->szFilterString[0] = 0;
+
+ UnhookEvent(dat->hPluginLoad);
+ UnhookEvent(dat->hPluginUnload);
+
+ SaveOptionsTreeState(hdlg);
+ Window_FreeIcon_IcoLib(hdlg);
+
+ opd = dat->getCurrent();
+ if (opd) {
+ if (opd->ptszTab)
+ db_set_ts(NULL, "Options", "LastTab", opd->ptszTab);
+ else
+ db_unset(NULL, "Options", "LastTab");
+ if (opd->ptszGroup)
+ db_set_ts(NULL, "Options", "LastGroup", opd->ptszGroup);
+ else
+ db_unset(NULL, "Options", "LastGroup");
+ db_set_ts(NULL, "Options", "LastPage", opd->ptszTitle);
+ }
+ else {
+ db_unset(NULL, "Options", "LastTab");
+ db_unset(NULL, "Options", "LastGroup");
+ db_unset(NULL, "Options", "LastPage");
+ }
+
+ Utils_SaveWindowPosition(hdlg, NULL, "Options", "");
+
+ for (int i = 0; i < dat->arOpd.getCount(); i++)
+ delete dat->arOpd[i];
+
+ DeleteObject(dat->hBoldFont);
+ delete dat;
+ hwndOptions = NULL;
+
+ CallService(MS_MODERNOPT_RESTORE, 0, 0);
+ break;
+ }
+ return FALSE;
+}
+
+void OpenAccountOptions(PROTOACCOUNT *pa)
+{
+ if (pa->ppro == NULL)
+ return;
+
+ OptionsPageInit opi = { 0 };
+ pa->ppro->OnEvent(EV_PROTO_ONOPTIONS, (WPARAM)&opi, 0);
+ if (opi.pageCount == 0)
+ return;
+
+ TCHAR tszTitle[100];
+ mir_sntprintf(tszTitle, SIZEOF(tszTitle), TranslateT("%s options"), pa->tszAccountName);
+
+ OPENOPTIONSDIALOG ood = { sizeof(ood) };
+ ood.pszGroup = LPGEN("Network");
+ ood.pszPage = mir_t2a(pa->tszAccountName);
+
+ PROPSHEETHEADER psh = { 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(g_hInst, MAKEINTRESOURCE(IDD_OPTIONSPAGE), NULL, OptionsDlgProc, (LPARAM)&psh);
+ mir_free((void*)ood.pszPage);
+ FreeOptionsData(&opi);
+}
+
+static void OpenOptionsNow(int hLangpack, const char *pszGroup, const char *pszPage, const char *pszTab, bool bSinglePage = false)
+{
+ if (IsWindow(hwndOptions)) {
+ ShowWindow(hwndOptions, SW_RESTORE);
+ SetForegroundWindow(hwndOptions);
+ if (pszPage != NULL) {
+ ptrT ptszPage(mir_a2t(pszPage));
+ HTREEITEM hItem = NULL;
+ if (pszGroup != NULL) {
+ ptrT ptszGroup(mir_a2t(pszGroup));
+ hItem = FindNamedTreeItemAtRoot(GetDlgItem(hwndOptions, IDC_PAGETREE), TranslateTH(hLangpack, ptszGroup));
+ if (hItem != NULL)
+ hItem = FindNamedTreeItemAtChildren(GetDlgItem(hwndOptions, IDC_PAGETREE), hItem, TranslateTH(hLangpack, ptszPage));
+ }
+ else hItem = FindNamedTreeItemAtRoot(GetDlgItem(hwndOptions, IDC_PAGETREE), TranslateTH(hLangpack, ptszPage));
+
+ if (hItem != NULL)
+ TreeView_SelectItem(GetDlgItem(hwndOptions, IDC_PAGETREE), hItem);
+ }
+ }
+ else {
+ OptionsPageInit opi = { 0 };
+ NotifyEventHooks(hOptionsInitEvent, (WPARAM)&opi, 0);
+ if (opi.pageCount == 0)
+ return;
+
+ OPENOPTIONSDIALOG ood = { 0 };
+ ood.cbSize = sizeof(ood);
+ ood.pszGroup = pszGroup;
+ ood.pszPage = pszPage;
+ ood.pszTab = pszTab;
+
+ PROPSHEETHEADER psh = { 0 };
+ psh.dwSize = sizeof(psh);
+ psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
+ psh.nPages = opi.pageCount;
+ psh.pStartPage = (LPCTSTR)&ood; // more structure misuse
+ psh.pszCaption = TranslateT("Miranda NG options");
+ psh.ppsp = (PROPSHEETPAGE*)opi.odp; // blatent misuse of the structure, but what the hell
+
+ hwndOptions = CreateDialogParam(g_hInst,
+ MAKEINTRESOURCE(bSinglePage ? IDD_OPTIONSPAGE : IDD_OPTIONS),
+ NULL, OptionsDlgProc, (LPARAM)&psh);
+
+ FreeOptionsData(&opi);
+ }
+}
+
+static INT_PTR OpenOptions(WPARAM wParam, LPARAM lParam)
+{
+ OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)lParam;
+ if (ood == NULL || ood->cbSize != sizeof(OPENOPTIONSDIALOG))
+ return 1;
+
+ OpenOptionsNow((int)wParam, ood->pszGroup, ood->pszPage, ood->pszTab);
+ return 0;
+}
+
+static INT_PTR OpenOptionsPage(WPARAM wParam, LPARAM lParam)
+{
+ OPENOPTIONSDIALOG *ood = (OPENOPTIONSDIALOG*)lParam;
+ if (ood == NULL || ood->cbSize != sizeof(OPENOPTIONSDIALOG))
+ return 1;
+
+ OpenOptionsNow((int)wParam, ood->pszGroup, ood->pszPage, ood->pszTab, true);
+ return (INT_PTR)hwndOptions;
+}
+
+static INT_PTR OpenOptionsDialog(WPARAM, LPARAM)
+{
+ if (hwndOptions || !ServiceExists(MS_MODERNOPT_SHOW))
+ OpenOptionsNow(NULL, 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;
+ OptionsPageInit *opi = (OptionsPageInit*)wParam;
+ if (odp == NULL || opi == NULL)
+ return 1;
+
+ opi->odp = (OPTIONSDIALOGPAGE*)mir_realloc(opi->odp, sizeof(OPTIONSDIALOGPAGE)*(opi->pageCount + 1));
+ dst = opi->odp + opi->pageCount;
+ memcpy(dst, odp, sizeof(OPTIONSDIALOGPAGE));
+
+ if (odp->ptszTitle != NULL) {
+ if (odp->flags & ODPF_UNICODE)
+ dst->ptszTitle = mir_wstrdup(odp->ptszTitle);
+ else {
+ dst->ptszTitle = mir_a2u(odp->pszTitle);
+ dst->flags |= ODPF_UNICODE;
+ }
+ }
+
+ if (odp->ptszGroup != NULL) {
+ if (odp->flags & ODPF_UNICODE)
+ dst->ptszGroup = mir_wstrdup(odp->ptszGroup);
+ else {
+ dst->ptszGroup = mir_a2t(odp->pszGroup);
+ dst->flags |= ODPF_UNICODE;
+ }
+ }
+
+ if (odp->ptszTab != NULL) {
+ if (odp->flags & ODPF_UNICODE)
+ dst->ptszTab = mir_wstrdup(odp->ptszTab);
+ else {
+ dst->ptszTab = mir_a2t(odp->pszTab);
+ dst->flags |= ODPF_UNICODE;
+ }
+ }
+
+ if ((DWORD_PTR)odp->pszTemplate & 0xFFFF0000)
+ dst->pszTemplate = mir_strdup(odp->pszTemplate);
+
+ opi->pageCount++;
+ return 0;
+}
+
+static int OptModulesLoaded(WPARAM, LPARAM)
+{
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_OPTIONS);
+ mi.position = 1900000000;
+ mi.pszName = LPGEN("&Options...");
+ mi.pszService = "Options/OptionsCommand";
+ Menu_AddMainMenuItem(&mi);
+ return 0;
+}
+
+int ShutdownOptionsModule(WPARAM, LPARAM)
+{
+ if (IsWindow(hwndOptions)) {
+ DestroyWindow(hwndOptions);
+ hwndOptions = NULL;
+ }
+ return 0;
+}
+
+int LoadOptionsModule(void)
+{
+ hwndOptions = NULL;
+ hOptionsInitEvent = CreateHookableEvent(ME_OPT_INITIALISE);
+ HookEvent(ME_OPT_INITIALISE, LangpackOptionsInit);
+
+ CreateServiceFunction("Opt/AddPage", AddOptionsPage);
+ CreateServiceFunction("Opt/OpenOptions", OpenOptions);
+ CreateServiceFunction("Opt/OpenOptionsPage", OpenOptionsPage);
+ CreateServiceFunction("Options/OptionsCommand", OpenOptionsDialog);
+ HookEvent(ME_SYSTEM_MODULESLOADED, OptModulesLoaded);
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, ShutdownOptionsModule);
+ return 0;
+}
diff --git a/src/mir_app/src/options_ei.cpp b/src/mir_app/src/options_ei.cpp new file mode 100644 index 0000000000..dbac3f75c2 --- /dev/null +++ b/src/mir_app/src/options_ei.cpp @@ -0,0 +1,472 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+#define ICON_SIZE 16
+
+int SortFunc(const ExtraIcon *p1, const ExtraIcon *p2);
+
+struct intlist
+{
+ intlist() : count(0), data(0) {}
+ ~intlist() { mir_free(data); }
+
+ void add(int val)
+ {
+ data = (int*)mir_realloc(data, sizeof(int)*(count + 1));
+ data[count++] = val;
+ }
+
+ int count;
+ int *data;
+};
+
+static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM)
+{
+ intlist *a = (intlist*)lParam1;
+ intlist *b = (intlist*)lParam2;
+ return SortFunc(registeredExtraIcons[a->data[0] - 1], registeredExtraIcons[b->data[0] - 1]);
+}
+
+// Functions //////////////////////////////////////////////////////////////////////////////////////
+
+BOOL ScreenToClient(HWND hWnd, LPRECT lpRect)
+{
+ POINT pt;
+ pt.x = lpRect->left;
+ pt.y = lpRect->top;
+
+ BOOL ret = ScreenToClient(hWnd, &pt);
+ if (!ret)
+ return ret;
+
+ lpRect->left = pt.x;
+ lpRect->top = pt.y;
+
+ pt.x = lpRect->right;
+ pt.y = lpRect->bottom;
+
+ ret = ScreenToClient(hWnd, &pt);
+
+ lpRect->right = pt.x;
+ lpRect->bottom = pt.y;
+
+ return ret;
+}
+
+static void RemoveExtraIcons(int slot)
+{
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact))
+ Clist_SetExtraIcon(hContact, slot, INVALID_HANDLE_VALUE);
+}
+
+class CExtraIconOptsDlg : public CDlgBase
+{
+ intlist* Tree_GetIDs(HTREEITEM hItem)
+ {
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ m_tree.GetItem(&tvi);
+ return (intlist*)tvi.lParam;
+ }
+
+ HTREEITEM Tree_AddExtraIcon(BaseExtraIcon *extra, bool selected, HTREEITEM hAfter = TVI_LAST)
+ {
+ intlist *ids = new intlist();
+ ids->add(extra->getID());
+
+ TVINSERTSTRUCT tvis = { 0 };
+ tvis.hInsertAfter = hAfter;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.iSelectedImage = tvis.item.iImage = extra->getID();
+ tvis.item.lParam = (LPARAM)ids;
+ tvis.item.pszText = (LPTSTR)extra->getDescription();
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(selected ? 2 : 1);
+ return m_tree.InsertItem(&tvis);
+ }
+
+ HTREEITEM Tree_AddExtraIconGroup(intlist &group, bool selected, HTREEITEM hAfter = TVI_LAST)
+ {
+ intlist *ids = new intlist();
+ CMString desc;
+ int img = 0;
+ for (int i = 0; i < group.count; i++) {
+ BaseExtraIcon *extra = registeredExtraIcons[group.data[i] - 1];
+ ids->add(extra->getID());
+
+ if (img == 0 && !IsEmpty(extra->getDescIcon()))
+ img = extra->getID();
+
+ if (i > 0)
+ desc += _T(" / ");
+ desc += extra->getDescription();
+ }
+
+ TVINSERTSTRUCT tvis = { 0 };
+ tvis.hInsertAfter = hAfter;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.iSelectedImage = tvis.item.iImage = img;
+ tvis.item.lParam = (LPARAM)ids;
+ tvis.item.pszText = (TCHAR*)desc.c_str();
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(selected ? 2 : 1);
+ return m_tree.InsertItem(&tvis);
+ }
+
+ void GroupSelectedItems()
+ {
+ LIST<_TREEITEM> toRemove(1);
+ intlist ids;
+ bool selected = false;
+ HTREEITEM hPlace = NULL;
+
+ // Find items
+ HTREEITEM hItem = m_tree.GetRoot();
+ TVITEMEX tvi = { 0 };
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT | TVIF_STATE;
+ while (hItem) {
+ if (m_tree.IsSelected(hItem)) {
+ if (hPlace == NULL)
+ hPlace = hItem;
+
+ tvi.hItem = hItem;
+ m_tree.GetItem(&tvi);
+
+ intlist *iids = (intlist*)tvi.lParam;
+ for (int i = 0; i < iids->count; i++)
+ ids.add(iids->data[i]);
+
+ if ((tvi.state & INDEXTOSTATEIMAGEMASK(3)) == INDEXTOSTATEIMAGEMASK(2))
+ selected = true;
+
+ toRemove.insert(hItem);
+ }
+
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ if (hPlace != NULL) {
+ // Add new
+ HTREEITEM hNew = Tree_AddExtraIconGroup(ids, selected, hPlace);
+
+ // Remove old
+ for (int i = 0; i < toRemove.getCount(); i++) {
+ delete Tree_GetIDs(toRemove[i]);
+ m_tree.DeleteItem(toRemove[i]);
+ }
+
+ // Select
+ m_tree.UnselectAll();
+ m_tree.SelectItem(hNew);
+ }
+ }
+
+ void UngroupSelectedItems()
+ {
+ HTREEITEM hItem = m_tree.GetSelection();
+ if (hItem == NULL)
+ return;
+
+ intlist *ids = Tree_GetIDs(hItem);
+ if (ids->count < 2)
+ return;
+
+ bool selected = m_tree.IsSelected(hItem);
+
+ for (int i = ids->count - 1; i >= 0; i--) {
+ BaseExtraIcon *extra = registeredExtraIcons[ids->data[i] - 1];
+ Tree_AddExtraIcon(extra, selected, hItem);
+ }
+
+ delete ids;
+ m_tree.DeleteItem(hItem);
+
+ m_tree.UnselectAll();
+ }
+
+ int ShowPopup(int popup)
+ {
+ // Fix selection
+ HTREEITEM hSelected = m_tree.GetDropHilight();
+ HTREEITEM hItem = m_tree.GetRoot();
+ while (hItem) {
+ if (hItem != hSelected && m_tree.IsSelected(hItem))
+ m_tree.DropHilite(hItem);
+
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ HMENU menu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_OPT_POPUP));
+ HMENU submenu = GetSubMenu(menu, popup);
+ TranslateMenu(submenu);
+
+ DWORD pos = GetMessagePos();
+ int ret = TrackPopupMenu(submenu, TPM_TOPALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_LEFTALIGN, LOWORD(pos), HIWORD(pos), 0, m_hwnd, NULL);
+
+ DestroyMenu(menu);
+
+ // Revert selection
+ hItem = m_tree.GetRoot();
+ while (hItem) {
+ if (hItem != hSelected && m_tree.IsSelected(hItem))
+ m_tree.DropUnhilite(hItem);
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ return ret;
+ }
+
+ CCtrlTreeView m_tree;
+
+public:
+ CExtraIconOptsDlg() :
+ CDlgBase(g_hInst, IDD_EI_OPTIONS),
+ m_tree(this, IDC_EXTRAORDER)
+ {
+ m_tree.SetFlags(MTREE_DND | MTREE_MULTISELECT);
+ }
+
+ virtual void OnInitDialog()
+ {
+ int numSlots = GetNumberOfSlots();
+ if (numSlots < (int)registeredExtraIcons.getCount()) {
+ HWND label = GetDlgItem(m_hwnd, IDC_MAX_ICONS_L);
+ SetWindowText(label, CMString(FORMAT, TranslateT("*only the first %d icons will be shown"), numSlots));
+ ShowWindow(label, SW_SHOW);
+ }
+
+ int cx = GetSystemMetrics(SM_CXSMICON);
+ HIMAGELIST hImageList = ImageList_Create(cx, cx, ILC_COLOR32 | ILC_MASK, 2, 2);
+
+ HICON hBlankIcon = (HICON)LoadImage(g_hInst, MAKEINTRESOURCE(IDI_BLANK), IMAGE_ICON, cx, cx, 0);
+ ImageList_AddIcon(hImageList, hBlankIcon);
+
+ for (int i = 0; i < registeredExtraIcons.getCount(); i++) {
+ ExtraIcon *extra = registeredExtraIcons[i];
+
+ HICON hIcon = Skin_GetIcon(extra->getDescIcon());
+ if (hIcon == NULL)
+ ImageList_AddIcon(hImageList, hBlankIcon);
+ else {
+ ImageList_AddIcon(hImageList, hIcon);
+ Skin_ReleaseIcon(hIcon);
+ }
+ }
+ m_tree.SetImageList(hImageList, TVSIL_NORMAL);
+ DestroyIcon(hBlankIcon);
+
+ for (int k = 0; k < extraIconsBySlot.getCount(); k++) {
+ ExtraIcon *extra = extraIconsBySlot[k];
+
+ if (extra->getType() == EXTRAICON_TYPE_GROUP) {
+ ExtraIconGroup *group = (ExtraIconGroup *)extra;
+ intlist ids;
+ for (int j = 0; j < group->items.getCount(); j++)
+ ids.add(group->items[j]->getID());
+ Tree_AddExtraIconGroup(ids, extra->isEnabled());
+ }
+ else Tree_AddExtraIcon((BaseExtraIcon *)extra, extra->isEnabled());
+ }
+
+ TVSORTCB sort = { 0 };
+ sort.hParent = NULL;
+ sort.lParam = 0;
+ sort.lpfnCompare = CompareFunc;
+ m_tree.SortChildrenCB(&sort, 0);
+ }
+
+ virtual void OnApply()
+ {
+ // Store old slots
+ int *oldSlots = new int[registeredExtraIcons.getCount()];
+ int lastUsedSlot = -1;
+ for (int i = 0; i < registeredExtraIcons.getCount(); i++) {
+ if (extraIconsByHandle[i] == registeredExtraIcons[i])
+ oldSlots[i] = registeredExtraIcons[i]->getSlot();
+ else
+ // Remove old slot for groups to re-set images
+ oldSlots[i] = -1;
+ lastUsedSlot = MAX(lastUsedSlot, registeredExtraIcons[i]->getSlot());
+ }
+ lastUsedSlot = MIN(lastUsedSlot, GetNumberOfSlots());
+
+ // Get user data and create new groups
+ LIST<ExtraIconGroup> groups(1);
+
+ BYTE pos = 0;
+ int firstEmptySlot = 0;
+ HTREEITEM ht = m_tree.GetRoot();
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE;
+ tvi.stateMask = TVIS_STATEIMAGEMASK;
+ while (ht) {
+ tvi.hItem = ht;
+ m_tree.GetItem(&tvi);
+
+ intlist*ids = (intlist*)tvi.lParam;
+ if (ids == NULL || ids->count < 1)
+ continue; // ???
+
+ bool enabled = ((tvi.state & INDEXTOSTATEIMAGEMASK(3)) == INDEXTOSTATEIMAGEMASK(2));
+ int slot = (enabled ? firstEmptySlot++ : -1);
+ if (slot >= GetNumberOfSlots())
+ slot = -1;
+
+ if (ids->count == 1) {
+ BaseExtraIcon *extra = registeredExtraIcons[ids->data[0] - 1];
+ extra->setPosition(pos++);
+ extra->setSlot(slot);
+ }
+ else {
+ char name[128];
+ mir_snprintf(name, "__group_%d", groups.getCount());
+
+ ExtraIconGroup *group = new ExtraIconGroup(name);
+
+ for (int i = 0; i < ids->count; i++) {
+ BaseExtraIcon *extra = registeredExtraIcons[ids->data[i] - 1];
+ extra->setPosition(pos++);
+
+ group->addExtraIcon(extra);
+ }
+
+ group->setSlot(slot);
+ groups.insert(group);
+ }
+
+ ht = m_tree.GetNextSibling(ht);
+ }
+
+ // Store data
+ for (int i = 0; i < registeredExtraIcons.getCount(); i++) {
+ BaseExtraIcon *extra = registeredExtraIcons[i];
+
+ char setting[512];
+ mir_snprintf(setting, "Position_%s", extra->getName());
+ db_set_w(NULL, MODULE_NAME, setting, extra->getPosition());
+
+ mir_snprintf(setting, "Slot_%s", extra->getName());
+ db_set_w(NULL, MODULE_NAME, setting, extra->getSlot());
+ }
+
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)MODULE_NAME "Groups");
+ db_set_w(NULL, MODULE_NAME "Groups", "Count", groups.getCount());
+ for (int k = 0; k < groups.getCount(); k++) {
+ ExtraIconGroup *group = groups[k];
+
+ char setting[512];
+ mir_snprintf(setting, "%d_count", k);
+ db_set_w(NULL, MODULE_NAME "Groups", setting, (WORD)group->items.getCount());
+
+ for (int j = 0; j < group->items.getCount(); j++) {
+ BaseExtraIcon *extra = group->items[j];
+
+ mir_snprintf(setting, "%d_%d", k, j);
+ db_set_s(NULL, MODULE_NAME "Groups", setting, extra->getName());
+ }
+ }
+
+ // Clean removed slots
+ for (int j = firstEmptySlot; j <= lastUsedSlot; j++)
+ RemoveExtraIcons(j);
+
+ // Apply icons to new slots
+ RebuildListsBasedOnGroups(groups);
+ for (int n = 0; n < extraIconsBySlot.getCount(); n++) {
+ ExtraIcon *extra = extraIconsBySlot[n];
+ if (extra->getType() != EXTRAICON_TYPE_GROUP)
+ if (oldSlots[((BaseExtraIcon *)extra)->getID() - 1] == extra->getSlot())
+ continue;
+
+ if (extra->isEnabled())
+ extra->applyIcons();
+ }
+
+ delete[] oldSlots;
+ }
+
+ virtual void OnDestroy()
+ {
+ HTREEITEM hItem = m_tree.GetRoot();
+ while (hItem) {
+ delete Tree_GetIDs(hItem);
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ ImageList_Destroy(m_tree.GetImageList(TVSIL_NORMAL));
+ }
+
+ virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
+ {
+ if (msg == WM_NOTIFY) {
+ LPNMHDR lpnmhdr = (LPNMHDR)lParam;
+ if (lpnmhdr->idFrom == IDC_EXTRAORDER && lpnmhdr->code == NM_RCLICK) {
+ HTREEITEM hSelected = m_tree.GetDropHilight();
+ if (hSelected != NULL && !m_tree.IsSelected(hSelected)) {
+ m_tree.UnselectAll();
+ m_tree.SelectItem(hSelected);
+ }
+
+ int sels = m_tree.GetNumSelected();
+ if (sels > 1) {
+ if (ShowPopup(0) == ID_GROUP) {
+ GroupSelectedItems();
+ NotifyChange();
+ }
+ }
+ else if (sels == 1) {
+ HTREEITEM hItem = m_tree.GetSelection();
+ intlist *ids = Tree_GetIDs(hItem);
+ if (ids->count > 1) {
+ if (ShowPopup(1) == ID_UNGROUP) {
+ UngroupSelectedItems();
+ NotifyChange();
+ }
+ }
+ }
+ }
+ }
+
+ return CDlgBase::DlgProc(msg, wParam, lParam);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int InitOptionsCallback(WPARAM wParam, LPARAM)
+{
+ if (GetNumberOfSlots() < 1)
+ return 0;
+
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.pszGroup = LPGEN("Contact list");
+ odp.pszTitle = LPGEN("Extra icons");
+ odp.pszTab = LPGEN("General");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pDialog = new CExtraIconOptsDlg();
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/path.cpp b/src/mir_app/src/path.cpp new file mode 100644 index 0000000000..e0b795574c --- /dev/null +++ b/src/mir_app/src/path.cpp @@ -0,0 +1,458 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "profilemanager.h"
+
+#include "..\..\..\plugins\ExternalAPI\m_folders.h"
+
+extern TCHAR g_profileDir[MAX_PATH], g_shortProfileName[MAX_PATH];
+
+static HANDLE hAvatarFolder;
+static TCHAR tszAvatarRoot[MAX_PATH];
+
+static INT_PTR pathToRelative(WPARAM wParam, LPARAM lParam)
+{
+ return PathToRelative((char*)wParam, (char*)lParam);
+}
+
+static INT_PTR pathToAbsolute(WPARAM wParam, LPARAM lParam)
+{
+ return PathToAbsolute((char*)wParam, (char*)lParam);
+}
+
+static INT_PTR createDirTree(WPARAM, LPARAM lParam)
+{
+ if (lParam == 0)
+ return 1;
+
+ return CreateDirectoryTree((char*)lParam);
+}
+
+static INT_PTR pathToRelativeW(WPARAM wParam, LPARAM lParam)
+{
+ return PathToRelativeW((WCHAR*)wParam, (WCHAR*)lParam );
+}
+
+static INT_PTR pathToAbsoluteW(WPARAM wParam, LPARAM lParam)
+{
+ return PathToAbsoluteW((WCHAR*)wParam, (WCHAR*)lParam, NULL);
+}
+
+static INT_PTR createDirTreeW(WPARAM, LPARAM lParam)
+{
+ if (lParam == 0)
+ return 1;
+
+ return CreateDirectoryTreeW((WCHAR*)lParam);
+}
+
+TCHAR *GetContactID(MCONTACT hContact)
+{
+ TCHAR *theValue = {0};
+ char *szProto = GetContactProto(hContact);
+ if (db_get_b(hContact, szProto, "ChatRoom", 0) == 1) {
+ DBVARIANT dbv;
+ if (!db_get_ts(hContact, szProto, "ChatRoomID", &dbv)) {
+ theValue = (TCHAR *)mir_tstrdup(dbv.ptszVal);
+ db_free(&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*) { 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 *, MCONTACT hContact)
+{
+ return mir_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, hContact, 0));
+}
+
+static __forceinline char *GetContactIDX(char *, MCONTACT 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*)
+{
+ char result[512];
+ if (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 *)
+{
+ return mir_t2a(g_shortProfileName);
+}
+
+static __forceinline char *GetPathVarX(char *, int code)
+{
+ TCHAR szFullPath[MAX_PATH];
+
+ switch(code) {
+ case 1:
+ if (hAvatarFolder != NULL)
+ _tcsncpy_s(szFullPath, tszAvatarRoot, _TRUNCATE);
+ else
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\AvatarCache"), g_profileDir, g_shortProfileName);
+ break;
+ case 2:
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\Logs"), g_profileDir, g_shortProfileName);
+ break;
+ case 3:
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s"), g_profileDir, g_shortProfileName);
+ break;
+ }
+ return makeFileName(szFullPath);
+}
+
+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*, 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 *, MCONTACT hContact)
+{
+ return mir_tstrdup((TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, hContact, GCDNF_TCHAR));
+}
+
+static __forceinline TCHAR *GetContactIDX(TCHAR *, MCONTACT 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*)
+{
+ TCHAR result[512];
+ if (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 *)
+{
+ return mir_tstrdup(g_shortProfileName);
+}
+
+static __forceinline TCHAR *GetPathVarX(TCHAR *, int code)
+{
+ TCHAR szFullPath[MAX_PATH];
+
+ switch(code) {
+ case 1:
+ if (hAvatarFolder != NULL)
+ _tcsncpy_s(szFullPath, tszAvatarRoot, _TRUNCATE);
+ else
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\AvatarCache"), g_profileDir, g_shortProfileName);
+ break;
+ case 2:
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s\\Logs"), g_profileDir, g_shortProfileName);
+ break;
+ case 3:
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\%s"), g_profileDir, g_shortProfileName);
+ break;
+ }
+ return mir_tstrdup(szFullPath);
+}
+
+template<typename XCHAR>
+XCHAR *GetInternalVariable(XCHAR *key, size_t keyLength, MCONTACT 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, GetContactProto(hContact));
+ else if (!_xcscmp(theKey, XSTR(key, "accountname"))) {
+ PROTOACCOUNT *acc = ProtoGetAccount(GetContactProto(hContact));
+ if (acc != NULL)
+ theValue = mir_a2x(key, _T2A(acc->tszAccountName));
+ }
+ 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_profilesdir")))
+ 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<typename XCHAR>
+XCHAR *GetVariableFromArray(REPLACEVARSARRAY *vars, XCHAR *key, size_t keyLength, MCONTACT 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<typename XCHAR>
+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++;
+ }
+ if (varStart)
+ length += (p - varStart)+1;
+
+ 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;
+ }
+
+ if (varStart) {
+ size_t len = p - varStart + 1;
+ _xcsncpy(q, varStart-1, len);
+ q += len;
+ }
+
+ *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<WCHAR>((WCHAR *)wParam, data);
+
+ return (INT_PTR)ReplaceVariables<char>((char *)wParam, data);
+}
+
+int InitPathUtils(void)
+{
+ CreateServiceFunction(MS_UTILS_PATHTORELATIVE, pathToRelative);
+ CreateServiceFunction(MS_UTILS_PATHTORELATIVEW, pathToRelativeW);
+
+ CreateServiceFunction(MS_UTILS_PATHTOABSOLUTE, pathToAbsolute);
+ CreateServiceFunction(MS_UTILS_PATHTOABSOLUTEW, pathToAbsoluteW);
+
+ CreateServiceFunction(MS_UTILS_CREATEDIRTREE, createDirTree);
+ CreateServiceFunction(MS_UTILS_CREATEDIRTREEW, createDirTreeW);
+
+ CreateServiceFunction(MS_UTILS_REPLACEVARS, replaceVars);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int OnFoldersChanged(WPARAM, LPARAM)
+{
+ mir_sntprintf(tszAvatarRoot, SIZEOF(tszAvatarRoot), _T("%s\\%s\\AvatarCache"), g_profileDir, g_shortProfileName);
+
+ TCHAR tmpVar[MAX_PATH];
+ if (!FoldersGetCustomPathT(hAvatarFolder, tmpVar, SIZEOF(tmpVar), tszAvatarRoot))
+ _tcsncpy_s(tszAvatarRoot, tmpVar, _TRUNCATE);
+ return 0;
+}
+
+void InitPathVar()
+{
+ mir_sntprintf(tszAvatarRoot, SIZEOF(tszAvatarRoot), _T("%s\\%s\\AvatarCache"), g_profileDir, g_shortProfileName);
+ if (hAvatarFolder = FoldersRegisterCustomPathT( LPGEN("Avatars"), LPGEN("Avatars root folder"), tszAvatarRoot)) {
+ TCHAR tmpVar[MAX_PATH];
+ if (!FoldersGetCustomPathT(hAvatarFolder, tmpVar, SIZEOF(tmpVar), tszAvatarRoot))
+ _tcsncpy_s(tszAvatarRoot, tmpVar, _TRUNCATE);
+ HookEvent(ME_FOLDERS_PATH_CHANGED, OnFoldersChanged);
+ }
+}
diff --git a/src/mir_app/src/pluginopts.cpp b/src/mir_app/src/pluginopts.cpp new file mode 100644 index 0000000000..9f62b32c6e --- /dev/null +++ b/src/mir_app/src/pluginopts.cpp @@ -0,0 +1,575 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include <m_version.h>
+
+#include "plugins.h"
+
+extern MUUID miid_clist, miid_database, miid_protocol;
+HANDLE hevLoadModule, hevUnloadModule;
+
+static bool bOldMode = false;
+static CMString szFilter;
+static UINT_PTR timerID;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Plugins options page dialog
+
+struct PluginListItemData
+{
+ TCHAR fileName[MAX_PATH];
+ HINSTANCE hInst;
+ int flags, stdPlugin;
+ char* author;
+ char* authorEmail;
+ char* description;
+ char* copyright;
+ char* homepage;
+ MUUID uuid;
+};
+
+static int sttSortPlugins(const PluginListItemData *p1, const PluginListItemData *p2)
+{
+ return mir_tstrcmp(p1->fileName, p2->fileName);
+}
+
+static LIST<PluginListItemData> arPluginList(10, sttSortPlugins);
+
+static BOOL dialogListPlugins(WIN32_FIND_DATA *fd, TCHAR *path, WPARAM, LPARAM lParam)
+{
+ TCHAR buf[MAX_PATH];
+ mir_sntprintf(buf, _T("%s\\Plugins\\%s"), path, fd->cFileName);
+ HINSTANCE hInst = GetModuleHandle(buf);
+
+ BASIC_PLUGIN_INFO pi;
+ if (checkAPI(buf, &pi, MIRANDA_VERSION_CORE, CHECKAPI_NONE) == 0)
+ return TRUE;
+
+ PluginListItemData *dat = (PluginListItemData*)mir_alloc(sizeof(PluginListItemData));
+ dat->hInst = hInst;
+ dat->flags = pi.pluginInfo->flags;
+
+ dat->stdPlugin = 0;
+ if (pi.Interfaces) {
+ MUUID *piface = pi.Interfaces;
+ for (int i=0; !equalUUID(miid_last, piface[i]); i++) {
+ int idx = getDefaultPluginIdx( piface[i] );
+ if (idx != -1 ) {
+ dat->stdPlugin |= (1 << idx);
+ break;
+ }
+ }
+ }
+
+ CharLower(fd->cFileName);
+ _tcsncpy_s(dat->fileName, fd->cFileName, _TRUNCATE);
+
+ HWND hwndList = (HWND)lParam;
+
+ LVITEM it = { 0 };
+ // column 1: Checkbox + Enable/disabled icons
+ it.mask = LVIF_PARAM | LVIF_IMAGE;
+ it.iImage = (hInst != NULL) ? 2 : 3;
+ bool bNoCheckbox = (dat->flags & STATIC_PLUGIN) != 0;
+ if (bNoCheckbox|| hasMuuid(pi, miid_clist) || hasMuuid(pi, miid_protocol))
+ it.iImage += 2;
+ it.lParam = (LPARAM)dat;
+ int iRow = ListView_InsertItem(hwndList, &it);
+
+ if (isPluginOnWhiteList(fd->cFileName))
+ ListView_SetItemState(hwndList, iRow, bNoCheckbox ? 0x3000 : 0x2000, LVIS_STATEIMAGEMASK);
+
+ if (iRow != -1) {
+ // column 2: Unicode/ANSI icon + filename
+ it.mask = LVIF_IMAGE | LVIF_TEXT;
+ it.iItem = iRow;
+ it.iSubItem = 1;
+ it.iImage = (dat->flags & UNICODE_AWARE) ? 0 : 1;
+ it.pszText = fd->cFileName;
+ ListView_SetItem(hwndList, &it);
+
+ 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);
+ // column 3: plugin short name
+ if (shortNameT) {
+ ListView_SetItemText(hwndList, iRow, 2, shortNameT);
+ mir_free(shortNameT);
+ }
+
+ // column4: version number
+ DWORD unused, verInfoSize = GetFileVersionInfoSize(buf, &unused);
+ if (verInfoSize != 0) {
+ UINT blockSize;
+ VS_FIXEDFILEINFO *fi;
+ void *pVerInfo = mir_alloc(verInfoSize);
+ GetFileVersionInfo(buf, 0, verInfoSize, pVerInfo);
+ VerQueryValue(pVerInfo, _T("\\"), (LPVOID*)&fi, &blockSize);
+ mir_sntprintf(buf, _T("%d.%d.%d.%d"), HIWORD(fi->dwProductVersionMS),
+ LOWORD(fi->dwProductVersionMS), HIWORD(fi->dwProductVersionLS), LOWORD(fi->dwProductVersionLS));
+ mir_free(pVerInfo);
+ }
+ else
+ mir_sntprintf(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, 3, buf);
+ arPluginList.insert(dat);
+ }
+ else
+ mir_free(dat);
+ FreeLibrary(pi.hInst);
+ return TRUE;
+}
+
+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 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 ++;
+ }
+}
+
+static bool LoadPluginDynamically(PluginListItemData *dat)
+{
+ TCHAR exe[MAX_PATH];
+ GetModuleFileName(NULL, exe, SIZEOF(exe));
+ TCHAR *p = _tcsrchr(exe, '\\'); if (p) *p = 0;
+
+ pluginEntry* pPlug = OpenPlugin(dat->fileName, _T("Plugins"), exe);
+ if (pPlug->pclass & PCLASS_FAILED) {
+LBL_Error:
+ Plugin_UnloadDyn(pPlug);
+ return false;
+ }
+
+ if (!TryLoadPlugin(pPlug, true))
+ goto LBL_Error;
+
+ if (CallPluginEventHook(pPlug->bpi.hInst, hModulesLoadedEvent, 0, 0) != 0)
+ goto LBL_Error;
+
+ dat->hInst = pPlug->bpi.hInst;
+ NotifyFastHook(hevLoadModule, (WPARAM)pPlug->bpi.pluginInfo, (LPARAM)pPlug->bpi.hInst);
+ return true;
+}
+
+static bool UnloadPluginDynamically(PluginListItemData *dat)
+{
+ pluginEntry *p = pluginList.find((pluginEntry*)dat->fileName);
+ if (p) {
+ if (!Plugin_UnloadDyn(p))
+ return false;
+
+ dat->hInst = NULL;
+ }
+ return true;
+}
+
+static LRESULT CALLBACK PluginListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg) {
+ case WM_CHAR:
+ if (wParam == '\b') {
+ if (szFilter.GetLength() > 0)
+ szFilter.Truncate(szFilter.GetLength() - 1);
+ }
+ else {
+ szFilter.AppendChar(wParam);
+
+ for (int i = 0; i < arPluginList.getCount(); i++) {
+ PluginListItemData *p = arPluginList[i];
+ if (!_tcsnicmp(szFilter, p->fileName, szFilter.GetLength())) {
+ LVFINDINFO lvfi;
+ lvfi.flags = LVFI_PARAM;
+ lvfi.lParam = (LPARAM)p;
+ int idx = ListView_FindItem(hwnd, 0, &lvfi);
+ if (idx != -1) {
+ ListView_SetItemState(hwnd, idx, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
+ ListView_EnsureVisible(hwnd, idx, FALSE);
+ if (timerID != 0)
+ KillTimer(hwnd, timerID);
+ timerID = SetTimer(hwnd, 1, 1500, 0);
+ return TRUE;
+ }
+ }
+ }
+
+ szFilter.Truncate(szFilter.GetLength() - 1);
+ MessageBeep((UINT)-1);
+ }
+ return TRUE;
+
+ case WM_TIMER:
+ if (wParam == 1) {
+ KillTimer(hwnd, timerID);
+ timerID = 0;
+ szFilter.Empty();
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ LVHITTESTINFO hi;
+ hi.pt.x = LOWORD(lParam);
+ hi.pt.y = HIWORD(lParam);
+ ListView_SubItemHitTest(hwnd, &hi);
+ // Dynamically load/unload a plugin
+ if ((hi.iSubItem == 0) && (hi.flags & LVHT_ONITEMICON)) {
+ LVITEM lvi = {0};
+ lvi.mask = LVIF_IMAGE | LVIF_PARAM;
+ lvi.stateMask = -1;
+ lvi.iItem = hi.iItem;
+ lvi.iSubItem = 0;
+ if (ListView_GetItem(hwnd, &lvi)) {
+ lvi.mask = LVIF_IMAGE;
+ PluginListItemData *dat = (PluginListItemData*)lvi.lParam;
+ if (lvi.iImage == 3) {
+ // load plugin
+ if (LoadPluginDynamically(dat)) {
+ lvi.iImage = 2;
+ ListView_SetItem(hwnd, &lvi);
+ }
+ }
+ else if (lvi.iImage == 2) {
+ // unload plugin
+ if (UnloadPluginDynamically(dat)) {
+ lvi.iImage = 3;
+ ListView_SetItem(hwnd, &lvi);
+ }
+ }
+ LoadStdPlugins();
+ }
+ }
+ }
+
+ return mir_callNextSubclass(hwnd, PluginListWndProc, msg, wParam, lParam);
+}
+
+static int CALLBACK SortPlugins(WPARAM i1, LPARAM i2, LPARAM)
+{
+ PluginListItemData *p1 = (PluginListItemData*)i1, *p2 = (PluginListItemData*)i2;
+ return mir_tstrcmp(p1->fileName, p2->fileName);
+}
+
+static TCHAR *latin2t(const char *p)
+{
+ if (p == NULL)
+ return mir_tstrdup( _T(""));
+
+ return mir_a2t_cp(p, 1250);
+}
+
+INT_PTR CALLBACK DlgPluginOpt(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ timerID = 0;
+ {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_PLUGLIST);
+ mir_subclassWindow(hwndList, PluginListWndProc);
+
+ HIMAGELIST hIml = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 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);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_LOADEDGRAY);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_NOTLOADEDGRAY);
+ ListView_SetImageList(hwndList, hIml, LVSIL_SMALL);
+
+ LVCOLUMN col;
+ col.mask = LVCF_TEXT | LVCF_WIDTH;
+ col.pszText = _T("");
+ col.cx = 40;
+ ListView_InsertColumn(hwndList, 0, &col);
+
+ col.pszText = TranslateT("Plugin");
+ col.cx = 180;
+ ListView_InsertColumn(hwndList, 1, &col);
+
+ col.pszText = TranslateT("Name");
+ col.cx = 180;//max = 220;
+ ListView_InsertColumn(hwndList, 2, &col);
+
+ col.pszText = TranslateT("Version");
+ col.cx = 75;
+ ListView_InsertColumn(hwndList, 3, &col);
+
+ ListView_SetExtendedListViewStyleEx(hwndList, 0, LVS_EX_SUBITEMIMAGES | LVS_EX_CHECKBOXES | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT);
+ // scan the plugin dir for plugins, cos
+ arPluginList.destroy();
+ szFilter.Empty();
+ enumPlugins(dialogListPlugins, (WPARAM)hwndDlg, (LPARAM)hwndList);
+ // sort out the headers
+
+ ListView_SetColumnWidth(hwndList, 1, LVSCW_AUTOSIZE); // dll name
+ int w = ListView_GetColumnWidth(hwndList, 1);
+ if (w > 110) {
+ ListView_SetColumnWidth(hwndList, 1, w = 110);
+ }
+ int max = w < 110 ? 189 + 110 - w : 189;
+ ListView_SetColumnWidth(hwndList, 3, LVSCW_AUTOSIZE); // short name
+ w = ListView_GetColumnWidth(hwndList, 2);
+ if (w > max)
+ ListView_SetColumnWidth(hwndList, 2, max);
+
+ ListView_SortItems(hwndList, SortPlugins, (LPARAM)hwndDlg);
+ }
+ return TRUE;
+
+ case WM_NOTIFY:
+ if (lParam) {
+ NMLISTVIEW *hdr = (NMLISTVIEW *)lParam;
+ if (hdr->hdr.code == LVN_ITEMCHANGED && IsWindowVisible(hdr->hdr.hwndFrom)) {
+ if (hdr->uOldState != 0 && (hdr->uNewState == 0x1000 || hdr->uNewState == 0x2000)) {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_PLUGLIST);
+
+ LVITEM it;
+ it.mask = LVIF_PARAM | LVIF_STATE;
+ it.iItem = hdr->iItem;
+ if (!ListView_GetItem(hwndList, &it))
+ break;
+
+ PluginListItemData *dat = (PluginListItemData*)it.lParam;
+ if (dat->flags & STATIC_PLUGIN) {
+ ListView_SetItemState(hwndList, hdr->iItem, 0x3000, LVIS_STATEIMAGEMASK);
+ return FALSE;
+ }
+ // find all another standard plugins by mask and disable them
+ if ((hdr->uNewState == 0x2000) && dat->stdPlugin != 0) {
+ for (int iRow = 0; iRow != -1; iRow = ListView_GetNextItem(hwndList, iRow, LVNI_ALL)) {
+ if (iRow != hdr->iItem) { // skip the plugin we're standing on
+ LVITEM dt;
+ dt.mask = LVIF_PARAM;
+ dt.iItem = iRow;
+ if (ListView_GetItem(hwndList, &dt)) {
+ PluginListItemData *dat2 = (PluginListItemData*)dt.lParam;
+ if (dat2->stdPlugin & dat->stdPlugin) {// mask differs
+ // the lParam is unset, so when the check is unset the clist block doesnt trigger
+ int lParam = dat2->stdPlugin;
+ dat2->stdPlugin = 0;
+ ListView_SetItemState(hwndList, iRow, 0x1000, LVIS_STATEIMAGEMASK);
+ dat2->stdPlugin = lParam;
+ }
+ }
+ }
+ }
+ }
+
+ if (bOldMode)
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RESTART), TRUE); // this here only in "ghazan mode"
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+
+ if (hdr->iItem != -1) {
+ 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;
+
+ TCHAR buf[1024];
+ ListView_GetItemText(hwndList, hdr->iItem, 2, buf, SIZEOF(buf));
+ SetDlgItemText(hwndDlg, IDC_PLUGININFOFRAME, sel ? buf : _T(""));
+
+ ptrT tszAuthor(latin2t(sel ? dat->author : NULL));
+ SetDlgItemText(hwndDlg, IDC_PLUGINAUTHOR, tszAuthor);
+
+ ptrT tszEmail(latin2t(sel ? dat->authorEmail : NULL));
+ SetDlgItemText(hwndDlg, IDC_PLUGINEMAIL, tszEmail);
+
+ ptrT p(Langpack_PcharToTchar(dat->description));
+ SetDlgItemText(hwndDlg, IDC_PLUGINLONGINFO, sel ? p : _T(""));
+
+ ptrT tszCopyright(latin2t(sel ? dat->copyright : NULL));
+ SetDlgItemText(hwndDlg, IDC_PLUGINCPYR, tszCopyright);
+
+ ptrT tszUrl(latin2t(sel ? dat->homepage : NULL));
+ SetDlgItemText(hwndDlg, IDC_PLUGINURL, tszUrl);
+
+ if (!equalUUID(miid_last, dat->uuid)) {
+ char szUID[128];
+ uuidToString(dat->uuid, szUID, sizeof(szUID));
+ SetDlgItemTextA(hwndDlg, IDC_PLUGINPID, sel ? szUID : "");
+ }
+ else
+ SetDlgItemText(hwndDlg, IDC_PLUGINPID, sel ? TranslateT("<none>") : _T(""));
+ }
+ }
+ }
+
+ if (hdr->hdr.code == PSN_APPLY) {
+ bool needRestart = false;
+ TCHAR bufRestart[1024];
+ int bufLen = mir_sntprintf(bufRestart, SIZEOF(bufRestart), _T("%s\n"), TranslateT("Miranda NG must be restarted to apply changes for these plugins:"));
+
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_PLUGLIST);
+ for (int iRow = 0; iRow != -1;) {
+ TCHAR buf[1024];
+ ListView_GetItemText(hwndList, iRow, 1, buf, SIZEOF(buf));
+ int iState = ListView_GetItemState(hwndList, iRow, LVIS_STATEIMAGEMASK);
+ SetPluginOnWhiteList(buf, (iState & 0x2000) ? 1 : 0);
+
+ if (!bOldMode && iState != 0x3000) {
+ LVITEM lvi = { 0 };
+ lvi.mask = LVIF_IMAGE | LVIF_PARAM;
+ lvi.stateMask = -1;
+ lvi.iItem = iRow;
+ lvi.iSubItem = 0;
+ if (ListView_GetItem(hwndList, &lvi)) {
+ lvi.mask = LVIF_IMAGE;
+
+ PluginListItemData *dat = (PluginListItemData*)lvi.lParam;
+ if (iState == 0x2000) {
+ // enabling plugin
+ if (lvi.iImage == 3 || lvi.iImage == 5) {
+ if (lvi.iImage == 3 && LoadPluginDynamically(dat)) {
+ lvi.iImage = 2;
+ ListView_SetItem(hwndList, &lvi);
+ }
+ else {
+ bufLen += mir_sntprintf(bufRestart + bufLen, SIZEOF(bufRestart) - bufLen, _T(" - %s\n"), buf);
+ needRestart = true;
+ }
+ }
+ }
+ else {
+ // disabling plugin
+ if (lvi.iImage == 2 || lvi.iImage == 4) {
+ if (lvi.iImage == 2 && UnloadPluginDynamically(dat)) {
+ lvi.iImage = 3;
+ ListView_SetItem(hwndList, &lvi);
+ }
+ else {
+ bufLen += mir_sntprintf(bufRestart + bufLen, SIZEOF(bufRestart) - bufLen, _T(" - %s\n"), buf);
+ needRestart = true;
+ }
+ }
+ }
+ }
+ }
+
+ iRow = ListView_GetNextItem(hwndList, iRow, LVNI_ALL);
+ }
+ LoadStdPlugins();
+
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RESTART), needRestart);
+ if (needRestart) {
+ mir_sntprintf(bufRestart + bufLen, SIZEOF(bufRestart) - bufLen, _T("\n%s"), TranslateT("Do you want to restart it now?"));
+ if (MessageBox(NULL, bufRestart, _T("Miranda NG"), MB_ICONWARNING | MB_YESNO) == IDYES)
+ CallService(MS_SYSTEM_RESTART, 1, 0);
+ }
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ if (HIWORD(wParam) == STN_CLICKED) {
+ switch (LOWORD(wParam)) {
+ case IDC_GETMOREPLUGINS:
+ CallService(MS_UTILS_OPENURL, 0, (LPARAM) "http://miranda-ng.org/downloads/");
+ break;
+
+ case IDC_PLUGINEMAIL:
+ case IDC_PLUGINURL:
+ char buf[512];
+ char *p = &buf[7];
+ mir_strcpy(buf, "mailto:");
+ if (GetDlgItemTextA(hwndDlg, LOWORD(wParam), p, SIZEOF(buf) - 7))
+ CallService(MS_UTILS_OPENURL, 0, (LPARAM)(LOWORD(wParam) == IDC_PLUGINEMAIL ? buf : p));
+ break;
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ arPluginList.destroy();
+ RemoveAllItems(GetDlgItem(hwndDlg, IDC_PLUGLIST));
+ break;
+ }
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int PluginOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.hInstance = g_hInst;
+ odp.pfnDlgProc = DlgPluginOpt;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_PLUGINS);
+ odp.position = 1300000000;
+ odp.pszTitle = LPGEN("Plugins");
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+void LoadPluginOptions()
+{
+ bOldMode = db_get_b(NULL, "Options", "OldPluginSettings", false) != 0;
+
+ hevLoadModule = CreateHookableEvent(ME_SYSTEM_MODULELOAD);
+ hevUnloadModule = CreateHookableEvent(ME_SYSTEM_MODULEUNLOAD);
+}
+
+void UnloadPluginOptions()
+{
+ DestroyHookableEvent(hevLoadModule);
+ DestroyHookableEvent(hevUnloadModule);
+}
diff --git a/src/mir_app/src/plugins.h b/src/mir_app/src/plugins.h new file mode 100644 index 0000000000..35ae742b60 --- /dev/null +++ b/src/mir_app/src/plugins.h @@ -0,0 +1,84 @@ +
+// returns true if the API exports were good, otherwise, passed in data is returned
+#define CHECKAPI_NONE 0
+#define CHECKAPI_CLIST 1
+
+// block these plugins
+#define DEFMOD_REMOVED_UIPLUGINOPTS 21
+#define DEFMOD_REMOVED_PROTOCOLNETLIB 22
+
+// basic export prototypes
+typedef int (__cdecl * Miranda_Plugin_Load) (void);
+typedef int (__cdecl * Miranda_Plugin_Unload) (void);
+// version control
+typedef PLUGININFOEX * (__cdecl * Miranda_Plugin_InfoEx) (DWORD mirandaVersion);
+// prototype for clists
+typedef int (__cdecl * CList_Initialise) (void);
+
+// can all be NULL
+struct BASIC_PLUGIN_INFO
+{
+ HINSTANCE hInst;
+ Miranda_Plugin_Load Load;
+ Miranda_Plugin_Unload Unload;
+ Miranda_Plugin_InfoEx InfoEx;
+ CList_Initialise clistlink;
+ PLUGININFOEX * pluginInfo; // must be freed if hInst = = NULL then its a copy
+ MUUID *Interfaces; // array of supported interfaces
+};
+
+#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(void) 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
+#define PCLASS_CORE 0x200 // a plugin from the /Core directory
+#define PCLASS_CRYPT 0x400 // crypto provider
+
+struct pluginEntry
+{
+ TCHAR pluginname[64];
+ unsigned int pclass; // PCLASS_*
+ int hLangpack;
+ BASIC_PLUGIN_INFO bpi;
+};
+
+extern LIST<pluginEntry> pluginList, servicePlugins, clistPlugins;
+extern MUUID miid_last;
+
+int PluginOptionsInit(WPARAM, LPARAM);
+void LoadPluginOptions();
+void UnloadPluginOptions();
+
+int isPluginOnWhiteList(const TCHAR* pluginname);
+void SetPluginOnWhiteList(const TCHAR* pluginname, int allow);
+
+int getDefaultPluginIdx(const MUUID& muuid);
+bool hasMuuid(const BASIC_PLUGIN_INFO&, const MUUID&);
+bool hasMuuid(const MUUID* pFirst, const MUUID&);
+int equalUUID(const MUUID& u1, const MUUID& u2);
+int checkAPI(TCHAR* plugin, BASIC_PLUGIN_INFO* bpi, DWORD mirandaVersion, int checkTypeAPI);
+
+pluginEntry* OpenPlugin(TCHAR *tszFileName, TCHAR *dir, TCHAR *path);
+
+bool TryLoadPlugin(pluginEntry *p, bool bDynamic);
+void Plugin_Uninit(pluginEntry *p);
+int Plugin_UnloadDyn(pluginEntry *p);
+
+typedef BOOL (*SCAN_PLUGINS_CALLBACK) (WIN32_FIND_DATA * fd, TCHAR *path, WPARAM wParam, LPARAM lParam);
+void enumPlugins(SCAN_PLUGINS_CALLBACK cb, WPARAM wParam, LPARAM lParam);
+
+struct MuuidReplacement
+{
+ MUUID uuid; // default interface plugin
+ TCHAR* stdplugname;
+ pluginEntry* pImpl; // replacement plugin
+};
+
+bool LoadCorePlugin( MuuidReplacement& );
+
+MUUID* GetPluginInterfaces(const TCHAR* ptszFileName, bool& bIsPlugin);
\ No newline at end of file diff --git a/src/mir_app/src/profilemanager.cpp b/src/mir_app/src/profilemanager.cpp new file mode 100644 index 0000000000..7f7138be2f --- /dev/null +++ b/src/mir_app/src/profilemanager.cpp @@ -0,0 +1,656 @@ +/* + +Miranda NG: the free IM client for Microsoft* Windows* + +Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org), +Copyright (c) 2000-12 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 "stdafx.h" +#include "plugins.h" +#include "langpack.h" +#include "profilemanager.h" +#include <sys/stat.h> + +#pragma warning(disable : 4512) + +void EnsureCheckerLoaded(bool); + +#define WM_INPUTCHANGED (WM_USER + 0x3000) +#define WM_FOCUSTEXTBOX (WM_USER + 0x3001) + +typedef BOOL (__cdecl *ENUMPROFILECALLBACK) (TCHAR *tszFullPath, TCHAR *profile, LPARAM lParam); + +void SetServiceModePlugin(pluginEntry *p); + +///////////////////////////////////////////////////////////////////////////////////////// +// Profile creator + +static int findProfiles(TCHAR *szProfileDir, ENUMPROFILECALLBACK callback, LPARAM lParam) +{ + // find in Miranda NG profile subfolders + TCHAR searchspec[MAX_PATH]; + mir_sntprintf(searchspec, SIZEOF(searchspec), _T("%s\\*.*"), szProfileDir); + + WIN32_FIND_DATA ffd; + HANDLE hFind = FindFirstFile(searchspec, &ffd); + if (hFind == INVALID_HANDLE_VALUE) + return 0; + + do { + // find all subfolders except "." and ".." + if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && mir_tstrcmp(ffd.cFileName, _T(".")) && mir_tstrcmp(ffd.cFileName, _T(".."))) { + TCHAR buf[MAX_PATH], profile[MAX_PATH]; + mir_sntprintf(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 mir_callNextSubclass(edit, ProfileNameValidate, msg, wParam, lParam); +} + +class CCreateProfileDlg : public CDlgBase +{ + CCtrlButton &m_btnOk; + PROFILEMANAGERDATA *m_pd; + + int CreateProfile(TCHAR *profile, DATABASELINK *link) + { + 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, + TranslateT("The profile '%s' already exists. Do you want to move it to the Recycle Bin?\n\nWARNING: The profile will be deleted if Recycle Bin is disabled.\nWARNING: A profile may contain confidential information and should be properly deleted."), + file); + if (MessageBox(m_hwnd, 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, _T("%s\0"), profile); + if (SHFileOperation(&sf) != 0) { + mir_sntprintf(buf, TranslateT("Couldn't move '%s' to the Recycle Bin. Please select another profile name."), file); + MessageBox(m_hwnd, 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); + if ((err = link->makeDatabase(profile)) != ERROR_SUCCESS) { + mir_sntprintf(buf, TranslateT("Unable to create the profile '%s', the error was %x"), file, err); + MessageBox(m_hwnd, buf, TranslateT("Problem creating profile"), MB_ICONERROR | MB_OK); + return 0; + } + + // the profile has been created! + g_bDbCreated = true; + return 1; + } + + CCtrlCombo m_driverList; + CCtrlEdit m_profileName; + CCtrlBase m_warning; + +public: + CCreateProfileDlg(CCtrlButton &_btn, PROFILEMANAGERDATA *_pd) : + CDlgBase(g_hInst, IDD_PROFILE_NEW), + m_btnOk(_btn), + m_pd(_pd), + m_driverList(this, IDC_PROFILEDRIVERS), + m_profileName(this, IDC_PROFILENAME), + m_warning(this, IDC_NODBDRIVERS) + {} + + virtual void OnInitDialog() + { + // what, no plugins?! + if (arDbPlugins.getCount() == 0) { + m_driverList.Enable(false); + m_profileName.Enable(false); + ShowWindow(m_warning.GetHwnd(), TRUE); + } + else { + for (int i = 0; i < arDbPlugins.getCount(); i++) { + DATABASELINK *p = arDbPlugins[i]; + m_driverList.AddString(TranslateTS(p->szFullName), (LPARAM)p); + } + } + + // default item + m_driverList.SetCurSel(0); + + // subclass the profile name box + mir_subclassWindow(m_profileName.GetHwnd(), ProfileNameValidate); + + // decide if there is a default profile name given in the INI and if it should be used + if (m_pd->noProfiles || (shouldAutoCreate(m_pd->ptszProfile) && _taccess(m_pd->ptszProfile, 0))) { + TCHAR *profile = _tcsrchr(m_pd->ptszProfile, '\\'); + if (profile) ++profile; + else profile = m_pd->ptszProfile; + + TCHAR *p = _tcsrchr(profile, '.'); + TCHAR c = 0; + if (p) { c = *p; *p = 0; } + + m_profileName.SetText(profile); + if (c) *p = c; + } + + // focus on the textbox + PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0); + } + + virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) + { + switch (msg) { + case WM_FOCUSTEXTBOX: + SetFocus(m_profileName.GetHwnd()); + break; + + case WM_INPUTCHANGED: // when input in the edit box changes + NotifyChange(); + m_btnOk.Enable(GetWindowTextLength(m_profileName.GetHwnd()) > 0); + break; + + case WM_SHOWWINDOW: + if (wParam) { + m_btnOk.SetText(TranslateT("&Create")); + SendMessage(m_hwnd, WM_INPUTCHANGED, 0, 0); + } + break; + } + return CDlgBase::DlgProc(msg, wParam, lParam); + } + + virtual void OnApply() + { + LRESULT curSel = m_driverList.GetCurSel(); + if (curSel == -1) + return; // should never happen + + ptrT szName(m_profileName.GetText()); + if (szName == 0) + return; + + // profile placed in "profile_name" subfolder + mir_sntprintf(m_pd->ptszProfile, MAX_PATH, _T("%s\\%s\\%s.dat"), m_pd->ptszProfileDir, szName, szName); + m_pd->newProfile = 1; + m_pd->dblink = (DATABASELINK *)m_driverList.GetItemData(curSel); + + if (CreateProfile(m_pd->ptszProfile, m_pd->dblink) == 0) + SetWindowLongPtr(m_hwnd, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); + else + m_pd->bRun = true; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// Profile selector + +class CChooseProfileDlg : public CDlgBase +{ + CCtrlButton &m_btnOk; + PROFILEMANAGERDATA *m_pd; + HANDLE m_hFileNotify; + + struct ProfileEnumData + { + ProfileEnumData(CCtrlListView &_list, TCHAR *_profile) : + list(_list), + szProfile(_profile) + {} + + CCtrlListView &list; + TCHAR* szProfile; + }; + + static BOOL EnumProfilesForList(TCHAR *tszFullPath, TCHAR *profile, LPARAM lParam) + { + ProfileEnumData *ped = (ProfileEnumData*)lParam; + CCtrlListView &list = ped->list; + + TCHAR sizeBuf[64]; + bool bFileLocked = true; + + TCHAR *p = _tcsrchr(profile, '.'); + mir_tstrcpy(sizeBuf, _T("0 KB")); + if (p != NULL) *p = 0; + + LVITEM item = { 0 }; + item.mask = LVIF_TEXT | LVIF_IMAGE; + item.pszText = profile; + item.iItem = 0; + + struct _stat statbuf; + if (_tstat(tszFullPath, &statbuf) == 0) { + if (statbuf.st_size > 1000000) { + mir_sntprintf(sizeBuf, SIZEOF(sizeBuf), _T("%.3lf"), (double)statbuf.st_size / 1048576.0); + mir_tstrcpy(sizeBuf + 5, _T(" MB")); + } + else { + mir_sntprintf(sizeBuf, SIZEOF(sizeBuf), _T("%.3lf"), (double)statbuf.st_size / 1024.0); + mir_tstrcpy(sizeBuf + 5, _T(" KB")); + } + bFileLocked = !fileExist(tszFullPath); + } + + DATABASELINK *dblink; + switch (touchDatabase(tszFullPath, &dblink)) { + case ERROR_SUCCESS: + item.iImage = bFileLocked; + break; + + case EGROKPRF_OBSOLETE: + item.iImage = 2; + break; + + default: + item.iImage = 3; + } + + int iItem = list.InsertItem(&item); + if (mir_tstrcmpi(ped->szProfile, tszFullPath) == 0) + list.SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + + list.SetItemText(iItem, 2, sizeBuf); + + if (dblink != NULL) { + if (bFileLocked) // file locked + list.SetItemText(iItem, 1, TranslateT("<In use>")); + else + list.SetItemText(iItem, 1, TranslateTS(dblink->szFullName)); + } + else list.SetItemText(iItem, 1, TranslateT("<Unknown format>")); + + return TRUE; + } + + void CheckProfile(int iItem) + { + if (iItem < 0) + return; + + TCHAR profile[MAX_PATH], fullName[MAX_PATH]; + LVITEM item = { 0 }; + item.mask = LVIF_TEXT | LVIF_IMAGE; + item.iItem = iItem; + item.pszText = profile; + item.cchTextMax = SIZEOF(profile); + if (!m_profileList.GetItem(&item)) + return; + + mir_sntprintf(fullName, SIZEOF(fullName), _T("%s\\%s\\%s.dat"), m_pd->ptszProfileDir, profile, profile); + CallService(MS_DB_CHECKPROFILE, (WPARAM)fullName, item.iImage == 2); + } + + void DeleteProfile(int iItem) + { + 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 (!m_profileList.GetItem(&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 NG"), MB_YESNO | MB_TASKMODAL | MB_ICONWARNING)) + return; + + mir_sntprintf(profilef, SIZEOF(profilef), _T("%s\\%s%c"), m_pd->ptszProfileDir, profile, 0); + + SHFILEOPSTRUCT sf = { 0 }; + sf.wFunc = FO_DELETE; + sf.pFrom = profilef; + sf.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_ALLOWUNDO; + SHFileOperation(&sf); + m_profileList.DeleteItem(item.iItem); + } + + void CheckRun() + { + m_btnOk.Enable(m_profileList.GetSelectedCount() == 1); + + TCHAR profile[MAX_PATH]; + LVITEM item = { 0 }; + item.mask = LVIF_TEXT | LVIF_IMAGE; + item.iItem = m_profileList.GetNextItem(-1, LVNI_SELECTED | LVNI_ALL); + item.pszText = profile; + item.cchTextMax = SIZEOF(profile); + if (!m_profileList.GetItem(&item)) + return; + + switch(item.iImage) { + case 3: + m_btnOk.Enable(false); + return; + + case 2: + m_btnOk.SetText(TranslateT("&Convert")); + m_pd->bRun = false; + break; + + default: + m_btnOk.SetText(TranslateT("&Run")); + m_pd->bRun = true; + } + + // profile is placed in "profile_name" subfolder + + TCHAR tmpPath[MAX_PATH]; + mir_sntprintf(tmpPath, SIZEOF(tmpPath), _T("%s\\%s.dat"), m_pd->ptszProfileDir, profile); + if (_taccess(tmpPath, 2)) + mir_sntprintf(m_pd->ptszProfile, MAX_PATH, _T("%s\\%s\\%s.dat"), m_pd->ptszProfileDir, profile, profile); + else + _tcsncpy_s(m_pd->ptszProfile, MAX_PATH, tmpPath, _TRUNCATE); + } + + void ExecuteMenu(LPARAM lParam) + { + LVHITTESTINFO lvht = { 0 }; + lvht.pt.x = GET_X_LPARAM(lParam); + lvht.pt.y = GET_Y_LPARAM(lParam); + ScreenToClient(m_profileList.GetHwnd(), &lvht.pt); + + if (m_profileList.HitTest(&lvht) == -1) + return; + + if (lvht.iItem == -1) + return; + + LVITEM tvi = { 0 }; + tvi.mask = LVIF_IMAGE; + tvi.iItem = lvht.iItem; + if (!m_profileList.GetItem(&tvi)) + return; + + bool bConvert = (tvi.iImage == 2); + + lvht.pt.x = GET_X_LPARAM(lParam); + lvht.pt.y = GET_Y_LPARAM(lParam); + + HMENU hMenu = CreatePopupMenu(); + if (tvi.iImage < 2) { + AppendMenu(hMenu, MF_STRING, 1, TranslateT("Run")); + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + } + if (tvi.iImage != 3 && ServiceExists(MS_DB_CHECKPROFILE)) { + if (bConvert) + AppendMenu(hMenu, MF_STRING, 2, TranslateT("Convert database")); + else + AppendMenu(hMenu, MF_STRING, 2, TranslateT("Check database")); + AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); + } + AppendMenu(hMenu, MF_STRING, 3, TranslateT("Delete")); + int index = TrackPopupMenu(hMenu, TPM_RETURNCMD, lvht.pt.x, lvht.pt.y, 0, m_hwnd, NULL); + switch (index) { + case 1: + SendMessage(GetParent(m_hwndParent), WM_COMMAND, IDOK, 0); + break; + + case 2: + CheckProfile(lvht.iItem); + break; + + case 3: + DeleteProfile(lvht.iItem); + break; + } + DestroyMenu(hMenu); + } + + CCtrlListView m_profileList; + +public: + CChooseProfileDlg(CCtrlButton &_btn, PROFILEMANAGERDATA *_pd) : + CDlgBase(g_hInst, IDD_PROFILE_SELECTION), + m_btnOk(_btn), + m_pd(_pd), + m_profileList(this, IDC_PROFILELIST) + { + m_profileList.OnItemChanged = Callback(this, &CChooseProfileDlg::list_OnItemChanged); + m_profileList.OnKeyDown = Callback(this, &CChooseProfileDlg::list_OnKeyDown); + m_profileList.OnGetInfoTip = Callback(this, &CChooseProfileDlg::list_OnGetTip); + m_profileList.OnDoubleClick = Callback(this, &CChooseProfileDlg::list_OnDblClick); + } + + virtual void OnInitDialog() + { + // set columns + LVCOLUMN col; + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = TranslateT("Profile"); + col.cx = 100; + m_profileList.InsertColumn(0, &col); + + col.pszText = TranslateT("Driver"); + col.cx = 150 - GetSystemMetrics(SM_CXVSCROLL); + m_profileList.InsertColumn(1, &col); + + col.pszText = TranslateT("Size"); + col.cx = 60; + m_profileList.InsertColumn(2, &col); + + // icons + HIMAGELIST hImgList = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 2, 1); + ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_USERDETAILS)); + ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_DELETE)); + ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_MWARNING)); + ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_MFATAL)); + + // LV will destroy the image list + m_profileList.SetImageList(hImgList, LVSIL_SMALL); + m_profileList.SetExtendedListViewStyle(m_profileList.GetExtendedListViewStyle() | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT); + + // find all the profiles + ProfileEnumData ped(m_profileList, m_pd->ptszProfile); + findProfiles(m_pd->ptszProfileDir, EnumProfilesForList, (LPARAM)&ped); + PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0); + + m_hFileNotify = FindFirstChangeNotification(m_pd->ptszProfileDir, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE); + if (m_hFileNotify != INVALID_HANDLE_VALUE) + SetTimer(m_hwnd, 0, 1200, NULL); + } + + virtual void OnDestroy() + { + KillTimer(m_hwnd, 0); + FindCloseChangeNotification(m_hFileNotify); + } + + void list_OnItemChanged(CCtrlListView::TEventInfo*) + { + CheckRun(); + } + + void list_OnKeyDown(CCtrlListView::TEventInfo *evt) + { + if (evt->nmlvkey->wVKey == VK_DELETE) + DeleteProfile(m_profileList.GetNextItem(-1, LVNI_SELECTED | LVNI_ALL)); + } + + void list_OnGetTip(CCtrlListView::TEventInfo *evt) + { + if (auto pTip = evt->nmlvit) { + TCHAR profilename[MAX_PATH], tszFullPath[MAX_PATH]; + struct _stat statbuf; + m_profileList.GetItemText(pTip->iItem, 0, profilename, SIZEOF(profilename)); + mir_sntprintf(tszFullPath, SIZEOF(tszFullPath), _T("%s\\%s\\%s.dat"), m_pd->ptszProfileDir, profilename, profilename); + _tstat(tszFullPath, &statbuf); + mir_sntprintf(pTip->pszText, pTip->cchTextMax, _T("%s\n%s: %s\n%s: %s"), tszFullPath, TranslateT("Created"), rtrimt(NEWTSTR_ALLOCA(_tctime(&statbuf.st_ctime))), TranslateT("Modified"), rtrimt(NEWTSTR_ALLOCA(_tctime(&statbuf.st_mtime)))); + } + } + + void list_OnDblClick(CCtrlListView::TEventInfo*) + { + CheckRun(); + EndDialog(GetParent(m_hwndParent), 1); + } + + virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) + { + switch (msg) { + case WM_TIMER: + if (WaitForSingleObject(m_hFileNotify, 0) == WAIT_OBJECT_0) { + m_profileList.DeleteAllItems(); + ProfileEnumData ped(m_profileList, m_pd->ptszProfile); + findProfiles(m_pd->ptszProfileDir, EnumProfilesForList, (LPARAM)&ped); + FindNextChangeNotification(m_hFileNotify); + } + break; + + case WM_FOCUSTEXTBOX: + SetFocus(m_profileList.GetHwnd()); + if (m_pd->ptszProfile[0] == 0 || m_profileList.GetSelectedCount() == 0) + m_profileList.SetItemState(0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + break; + + case WM_SHOWWINDOW: + if (wParam) + CheckRun(); + break; + + case WM_CONTEXTMENU: + ExecuteMenu(lParam); + break; + } + + return CDlgBase::DlgProc(msg, wParam, lParam); + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// Tab manager + its envelope + +class CProfileManager : public CDlgBase +{ + PROFILEMANAGERDATA *m_pd; + + CCtrlPages m_tab; + CCtrlButton m_btnOk; + CCtrlCombo m_servicePlugs; + CCtrlBase m_warning; + +public: + CProfileManager(PROFILEMANAGERDATA *_pd) : + CDlgBase(g_hInst, IDD_PROFILEMANAGER), + m_btnOk(this, IDOK), + m_pd(_pd), + m_tab(this, IDC_TABS), + m_servicePlugs(this, IDC_SM_COMBO), + m_warning(this, IDC_SM_LABEL) + { + m_btnOk.OnClick = Callback(this, &CProfileManager::onOk); + + m_tab.AddPage(LPGENT("My profiles"), NULL, new CChooseProfileDlg(m_btnOk, m_pd)); + m_tab.AddPage(LPGENT("New profile"), NULL, new CCreateProfileDlg(m_btnOk, m_pd)); + } + + virtual void OnInitDialog() + { + SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadImage(g_hInst, MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0)); + SendMessage(m_hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadImage(g_hInst, MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0)); + + if (m_pd->noProfiles || shouldAutoCreate(m_pd->ptszProfile)) + m_tab.ActivatePage(1); + + // service mode combobox + if (servicePlugins.getCount() == 0) { + ShowWindow(m_warning.GetHwnd(), FALSE); + ShowWindow(m_servicePlugs.GetHwnd(), FALSE); + } + else { + m_servicePlugs.AddStringA("", -1); + m_servicePlugs.SetCurSel(0); + + for (int i = 0; i < servicePlugins.getCount(); i++) { + pluginEntry *p = servicePlugins[i]; + m_servicePlugs.AddString(TranslateTS(p->pluginname), i); + } + } + } + + virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) + { + switch (msg) { + case WM_CTLCOLORSTATIC: + switch (GetDlgCtrlID((HWND)lParam)) { + case IDC_WHITERECT: + SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + } + break; + } + return CDlgBase::DlgProc(msg, wParam, lParam); + } + + virtual void OnDestroy() + { + LRESULT curSel = m_servicePlugs.GetCurSel(); + if (curSel != -1) { + int idx = m_servicePlugs.GetItemData(curSel); + if (idx != -1) + SetServiceModePlugin(servicePlugins[idx]); + } + + DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0)); + DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0)); + } + + void onOk(CCtrlButton*) + { + EndDialog(m_hwnd, 1); + } +}; + +int getProfileManager(PROFILEMANAGERDATA *pd) +{ + EnsureCheckerLoaded(true); + + return CProfileManager(pd).DoModal(); +} diff --git a/src/mir_app/src/profilemanager.h b/src/mir_app/src/profilemanager.h new file mode 100644 index 0000000000..21650375a2 --- /dev/null +++ b/src/mir_app/src/profilemanager.h @@ -0,0 +1,45 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+struct PROFILEMANAGERDATA
+{
+ TCHAR *ptszProfile; // in/out
+ TCHAR *ptszProfileDir; // in/out
+ BOOL noProfiles; // in
+
+ BOOL bRun; // out
+ BOOL newProfile; // out
+ DATABASELINK *dblink; // out
+};
+
+char* makeFileName(const TCHAR *tszOriginalName);
+int touchDatabase(const TCHAR *tszProfile, DATABASELINK **pDblink);
+int getProfileManager(PROFILEMANAGERDATA *pd);
+int getProfilePath(TCHAR *buf, size_t cch);
+int isValidProfileName(const TCHAR *name);
+bool fileExist(const TCHAR *fname);
+bool shouldAutoCreate(TCHAR *szProfile);
+
+extern TCHAR g_profileDir[MAX_PATH], g_profileName[MAX_PATH], g_shortProfileName[MAX_PATH];
+extern bool g_bDbCreated;
diff --git a/src/mir_app/src/protoaccs.cpp b/src/mir_app/src/protoaccs.cpp new file mode 100644 index 0000000000..6aa601dfbf --- /dev/null +++ b/src/mir_app/src/protoaccs.cpp @@ -0,0 +1,453 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include "clc.h"
+
+bool CheckProtocolOrder(void);
+void BuildProtoMenus();
+
+HICON Proto_GetIcon(PROTO_INTERFACE *ppro, int iconIndex);
+
+static BOOL bModuleInitialized = FALSE;
+static HANDLE hHooks[4];
+
+static int CompareAccounts(const PROTOACCOUNT* p1, const PROTOACCOUNT* p2)
+{
+ return mir_strcmp(p1->szModuleName, p2->szModuleName);
+}
+
+LIST<PROTOACCOUNT> accounts(10, CompareAccounts);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int EnumDbModules(const char *szModuleName, DWORD, LPARAM)
+{
+ DBVARIANT dbv;
+ if (!db_get_s(NULL, szModuleName, "AM_BaseProto", &dbv)) {
+ if (!Proto_GetAccount(szModuleName)) {
+ PROTOACCOUNT *pa = (PROTOACCOUNT*)mir_calloc(sizeof(PROTOACCOUNT));
+ pa->cbSize = sizeof(*pa);
+ 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);
+ }
+ db_free(&dbv);
+ }
+ return 0;
+}
+
+void LoadDbAccounts(void)
+{
+ DBVARIANT dbv;
+ int ver = db_get_dw(NULL, "Protocols", "PrVer", -1);
+ int count = db_get_dw(NULL, "Protocols", "ProtoCount", 0);
+
+ for (int i = 0; i < count; i++) {
+ char buf[10];
+ _itoa(i, buf, 10);
+ if (db_get_s(NULL, "Protocols", buf, &dbv))
+ continue;
+
+ PROTOACCOUNT *pa = (PROTOACCOUNT*)mir_calloc(sizeof(PROTOACCOUNT));
+ if (pa == NULL) {
+ db_free(&dbv);
+ continue;
+ }
+ pa->cbSize = sizeof(*pa);
+ pa->szModuleName = mir_strdup(dbv.pszVal);
+ db_free(&dbv);
+
+ _itoa(OFFSET_VISIBLE + i, buf, 10);
+ pa->bIsVisible = db_get_dw(NULL, "Protocols", buf, 1) != 0;
+
+ _itoa(OFFSET_PROTOPOS + i, buf, 10);
+ pa->iOrder = db_get_dw(NULL, "Protocols", buf, 1);
+
+ if (ver >= 4) {
+ db_free(&dbv);
+ _itoa(OFFSET_NAME + i, buf, 10);
+ if (!db_get_ts(NULL, "Protocols", buf, &dbv)) {
+ pa->tszAccountName = mir_tstrdup(dbv.ptszVal);
+ db_free(&dbv);
+ }
+
+ _itoa(OFFSET_ENABLED + i, buf, 10);
+ pa->bIsEnabled = db_get_dw(NULL, "Protocols", buf, 1) != 0;
+
+ if (!db_get_s(NULL, pa->szModuleName, "AM_BaseProto", &dbv)) {
+ pa->szProtoName = mir_strdup(dbv.pszVal);
+ db_free(&dbv);
+ }
+ }
+ else pa->bIsEnabled = true;
+
+ if (!pa->szProtoName) {
+ pa->szProtoName = mir_strdup(pa->szModuleName);
+ db_set_s(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()
+{
+ // 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) {
+ for (int i = 0; i < param.arrlen; i++) {
+ db_unset(0, "Protocols", param.pszSettingName[i]);
+ mir_free(param.pszSettingName[i]);
+ }
+ mir_free(param.pszSettingName);
+ }
+
+ // write new data
+ for (int i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *pa = accounts[i];
+
+ char buf[20];
+ _itoa(i, buf, 10);
+ db_set_s(NULL, "Protocols", buf, pa->szModuleName);
+
+ _itoa(OFFSET_PROTOPOS + i, buf, 10);
+ db_set_dw(NULL, "Protocols", buf, pa->iOrder);
+
+ _itoa(OFFSET_VISIBLE + i, buf, 10);
+ db_set_dw(NULL, "Protocols", buf, pa->bIsVisible);
+
+ _itoa(OFFSET_ENABLED + i, buf, 10);
+ db_set_dw(NULL, "Protocols", buf, pa->bIsEnabled);
+
+ _itoa(OFFSET_NAME + i, buf, 10);
+ db_set_ts(NULL, "Protocols", buf, pa->tszAccountName);
+ }
+
+ db_unset(0, "Protocols", "ProtoCount");
+ db_set_dw(0, "Protocols", "ProtoCount", accounts.getCount());
+ db_set_dw(0, "Protocols", "PrVer", 4);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int OnContactDeleted(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact) {
+ PROTOACCOUNT *pa = Proto_GetAccount(hContact);
+ if (Proto_IsAccountEnabled(pa) && pa->ppro)
+ pa->ppro->OnEvent(EV_PROTO_ONCONTACTDELETED, hContact, lParam);
+ }
+ return 0;
+}
+
+static int OnDbSettingsChanged(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact) {
+ PROTOACCOUNT *pa = Proto_GetAccount(hContact);
+ if (Proto_IsAccountEnabled(pa) && pa->ppro)
+ pa->ppro->OnEvent(EV_PROTO_DBSETTINGSCHANGED, hContact, 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 && !db_get_b(NULL, "FirstRun", "AccManager", 0)) {
+ db_set_b(NULL, "FirstRun", "AccManager", 1);
+ CallService(MS_PROTO_SHOWACCMGR, 0, 0);
+ }
+ // This is for pack creators with a profile with predefined accounts
+ else if (db_get_b(NULL, "FirstRun", "ForceShowAccManager", 0)) {
+ CallService(MS_PROTO_SHOWACCMGR, 0, 0);
+ db_unset(NULL, "FirstRun", "ForceShowAccManager");
+ }
+ 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))
+ if (pa->ppro->OnEvent(EV_PROTO_ONREADYTOEXIT, 0, 0) != TRUE)
+ return 1;
+ }
+
+ for (int i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *pa = accounts[i];
+ if (pa->ppro && Proto_IsAccountEnabled(pa))
+ pa->ppro->OnEvent(EV_PROTO_ONEXIT, 0, 0);
+ }
+
+ return 0;
+}
+
+int LoadAccountsModule(void)
+{
+ bModuleInitialized = TRUE;
+
+ for (int i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *pa = accounts[i];
+ pa->bDynDisabled = !Proto_IsProtocolLoaded(pa->szProtoName);
+ if (pa->ppro)
+ continue;
+
+ if (!Proto_IsAccountEnabled(pa))
+ continue;
+
+ if (!ActivateAccount(pa))
+ pa->bDynDisabled = TRUE;
+ }
+
+ hHooks[0] = HookEvent(ME_SYSTEM_MODULESLOADED, InitializeStaticAccounts);
+ hHooks[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, UninitializeStaticAccounts);
+ hHooks[2] = HookEvent(ME_DB_CONTACT_DELETED, OnContactDeleted);
+ hHooks[3] = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, OnDbSettingsChanged);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static HANDLE CreateProtoServiceEx(const char* szModule, const char* szService, MIRANDASERVICEOBJ pFunc, void* param)
+{
+ char tmp[100];
+ mir_snprintf(tmp, "%s%s", szModule, szService);
+ return CreateServiceFunctionObj(tmp, pFunc, param);
+}
+
+BOOL ActivateAccount(PROTOACCOUNT *pa)
+{
+ PROTOCOLDESCRIPTOR* ppd = Proto_IsProtocolLoaded(pa->szProtoName);
+ if (ppd == NULL)
+ return FALSE;
+
+ if (ppd->fnInit == NULL)
+ return FALSE;
+
+ PROTO_INTERFACE *ppi = ppd->fnInit(pa->szModuleName, pa->tszAccountName);
+ if (ppi == NULL)
+ return FALSE;
+
+ pa->ppro = ppi;
+ ppi->m_iDesiredStatus = ppi->m_iStatus = ID_STATUS_OFFLINE;
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct DeactivationThreadParam
+{
+ PROTO_INTERFACE *ppro;
+ pfnUninitProto fnUninit;
+ bool bIsDynamic, bErase;
+};
+
+pfnUninitProto GetProtocolDestructor(char *szProto);
+
+static int DeactivationThread(DeactivationThreadParam* param)
+{
+ PROTO_INTERFACE *p = (PROTO_INTERFACE*)param->ppro;
+ p->SetStatus(ID_STATUS_OFFLINE);
+
+ char *szModuleName = NEWSTR_ALLOCA(p->m_szModuleName);
+
+ if (param->bIsDynamic) {
+ while (p->OnEvent(EV_PROTO_ONREADYTOEXIT, 0, 0) != TRUE)
+ SleepEx(100, TRUE);
+
+ 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;
+ if (bIsDynamic)
+ mir_forkthread((pThreadFunc)DeactivationThread, param);
+ else
+ DeactivationThread(param);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void EraseAccount(const char* pszModuleName)
+{
+ // remove protocol contacts first
+ for (MCONTACT hContact = db_find_first(pszModuleName); hContact != NULL;) {
+ MCONTACT hNext = db_find_next(hContact, pszModuleName);
+ CallService(MS_DB_CONTACT_DELETE, hContact, 0);
+ hContact = hNext;
+ }
+
+ // 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()
+{
+ if (!bModuleInitialized) return;
+
+ for (int i = accounts.getCount() - 1; i >= 0; i--) {
+ PROTOACCOUNT *pa = accounts[i];
+ UnloadAccount(pa, false, false);
+ accounts.remove(i);
+ }
+ accounts.destroy();
+
+ for (int i = 0; i < SIZEOF(hHooks); i++)
+ UnhookEvent(hHooks[i]);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+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()
+{
+ RebuildMenuOrder();
+ BuildProtoMenus();
+}
diff --git a/src/mir_app/src/protochains.cpp b/src/mir_app/src/protochains.cpp new file mode 100644 index 0000000000..e7f4348fe0 --- /dev/null +++ b/src/mir_app/src/protochains.cpp @@ -0,0 +1,253 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include <m_protomod.h>
+
+extern LIST<PROTOCOLDESCRIPTOR> filters;
+
+static int GetProtocolP(MCONTACT hContact, char *szBuf, int cbLen)
+{
+ if (currDb == NULL)
+ return 1;
+
+ DBCachedContact *cc = currDb->m_cache->GetCachedContact(hContact);
+ if (cc && cc->szProto != NULL) {
+ strncpy(szBuf, cc->szProto, cbLen);
+ szBuf[cbLen - 1] = 0;
+ return 0;
+ }
+
+ DBVARIANT dbv;
+ dbv.type = DBVT_ASCIIZ;
+ dbv.pszVal = szBuf;
+ dbv.cchVal = cbLen;
+
+ int res = currDb->GetContactSettingStatic(hContact, "Protocol", "p", &dbv);
+ if (res == 0) {
+ if (cc == NULL)
+ cc = currDb->m_cache->AddContactToCache(hContact);
+
+ cc->szProto = currDb->m_cache->GetCachedSetting(NULL, szBuf, 0, (int)mir_strlen(szBuf));
+ }
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(INT_PTR) CallContactService(MCONTACT hContact, const char *szProtoService, WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR ret;
+ CCSDATA ccs = { hContact, szProtoService, wParam, lParam };
+
+ for (int i = 0; i < filters.getCount(); i++) {
+ if ((ret = CallProtoServiceInt(hContact, filters[i]->szName, szProtoService, i + 1, (LPARAM)&ccs)) != CALLSERVICE_NOTFOUND) {
+ //chain was started, exit
+ return ret;
+ }
+ }
+
+ char szProto[40];
+ if (GetProtocolP((MCONTACT)hContact, szProto, sizeof(szProto)))
+ return 1;
+
+ PROTOACCOUNT *pa = Proto_GetAccount(szProto);
+ if (pa == NULL || pa->ppro == NULL)
+ return 1;
+
+ if (pa->bOldProto)
+ ret = CallProtoServiceInt(hContact, szProto, szProtoService, (WPARAM)(-1), (LPARAM)&ccs);
+ else
+ ret = CallProtoServiceInt(hContact, szProto, szProtoService, wParam, lParam);
+ if (ret == CALLSERVICE_NOTFOUND)
+ ret = 1;
+
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR Proto_CallContactService(WPARAM wParam, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ INT_PTR ret;
+
+ if (wParam == (WPARAM)(-1))
+ return 1;
+
+ for (int i = wParam; i < filters.getCount(); i++) {
+ if ((ret = CallProtoServiceInt(NULL, filters[i]->szName, ccs->szProtoService, i + 1, lParam)) != CALLSERVICE_NOTFOUND) {
+ //chain was started, exit
+ return ret;
+ }
+ }
+
+ char szProto[40];
+ if (GetProtocolP((MCONTACT)ccs->hContact, szProto, sizeof(szProto)))
+ return 1;
+
+ PROTOACCOUNT *pa = Proto_GetAccount(szProto);
+ if (pa == NULL || pa->ppro == NULL)
+ return 1;
+
+ if (pa->bOldProto)
+ ret = CallProtoServiceInt(ccs->hContact, szProto, ccs->szProtoService, (WPARAM)(-1), (LPARAM)ccs);
+ else
+ ret = CallProtoServiceInt(ccs->hContact, szProto, ccs->szProtoService, ccs->wParam, ccs->lParam);
+ if (ret == CALLSERVICE_NOTFOUND)
+ ret = 1;
+
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR Proto_RecvChain(WPARAM wParam, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ INT_PTR ret;
+
+ if (wParam == (WPARAM)(-1)) return 1; //shouldn't happen - sanity check
+ if (wParam == 0) { //begin processing by finding end of chain
+ if (GetCurrentThreadId() != hMainThreadId) // restart this function in the main thread
+ return CallServiceSync(MS_PROTO_CHAINRECV, wParam, lParam);
+
+ wParam = filters.getCount();
+ }
+ else wParam--;
+
+ for (int i = wParam - 1; i >= 0; i--)
+ if ((ret = CallProtoServiceInt(NULL, filters[i]->szName, ccs->szProtoService, i + 1, lParam)) != CALLSERVICE_NOTFOUND)
+ //chain was started, exit
+ return ret;
+
+ //end of chain, call network protocol again
+ char szProto[40];
+ if (GetProtocolP((MCONTACT)ccs->hContact, szProto, sizeof(szProto)))
+ return 1;
+
+ PROTOACCOUNT *pa = Proto_GetAccount(szProto);
+ if (pa == NULL || pa->ppro == NULL)
+ return 1;
+
+ if (pa->bOldProto)
+ ret = CallProtoServiceInt(ccs->hContact, szProto, ccs->szProtoService, (WPARAM)(-1), (LPARAM)ccs);
+ else
+ ret = CallProtoServiceInt(ccs->hContact, szProto, ccs->szProtoService, ccs->wParam, ccs->lParam);
+ if (ret == CALLSERVICE_NOTFOUND)
+ ret = 1;
+
+ return ret;
+}
+
+PROTOACCOUNT* __fastcall Proto_GetAccount(MCONTACT hContact)
+{
+ if (hContact == NULL)
+ return NULL;
+
+ char szProto[40];
+ if (GetProtocolP((MCONTACT)hContact, szProto, sizeof(szProto)))
+ return NULL;
+
+ return Proto_GetAccount(szProto);
+}
+
+static INT_PTR Proto_GetContactBaseProto(WPARAM wParam, LPARAM)
+{
+ PROTOACCOUNT *pa = Proto_GetAccount(wParam);
+ return (INT_PTR)(Proto_IsAccountEnabled(pa) ? pa->szModuleName : NULL);
+}
+
+static INT_PTR Proto_GetContactBaseAccount(WPARAM wParam, LPARAM)
+{
+ PROTOACCOUNT *pa = Proto_GetAccount(wParam);
+ return (INT_PTR)(pa ? pa->szModuleName : NULL);
+}
+
+static INT_PTR Proto_IsProtoOnContact(WPARAM wParam, LPARAM lParam)
+{
+ char *szProto = (char*)lParam;
+ if (szProto == NULL)
+ return 0;
+
+ char szContactProto[40];
+ if (!GetProtocolP(wParam, szContactProto, sizeof(szContactProto)))
+ if (!_stricmp(szProto, szContactProto))
+ return -1;
+
+ for (int i = 0; i < filters.getCount(); i++)
+ if (!mir_strcmp(szProto, filters[i]->szName))
+ return i + 1;
+
+ return 0;
+}
+
+static INT_PTR Proto_AddToContact(WPARAM wParam, LPARAM lParam)
+{
+ char *szProto = (char*)lParam;
+ PROTOCOLDESCRIPTOR *pd = Proto_IsProtocolLoaded(szProto);
+ if (pd == NULL) {
+ PROTOACCOUNT *pa = Proto_GetAccount(szProto);
+ if (pa) {
+ db_set_s(wParam, "Protocol", "p", szProto);
+ return 0;
+ }
+ return 1;
+ }
+
+ if (pd->type == PROTOTYPE_PROTOCOL || pd->type == PROTOTYPE_VIRTUAL)
+ db_set_s(wParam, "Protocol", "p", szProto);
+
+ return 0;
+}
+
+static INT_PTR Proto_RemoveFromContact(WPARAM wParam, LPARAM lParam)
+{
+ switch (Proto_IsProtoOnContact(wParam, lParam)) {
+ case 0:
+ return 1;
+ case -1:
+ db_unset(wParam, "Protocol", "p");
+ }
+
+ return 0;
+}
+
+int LoadProtoChains(void)
+{
+ if (!db_get_b(NULL, "Compatibility", "Filters", 0)) {
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)"_Filters");
+ db_set_b(NULL, "Compatibility", "Filters", 1);
+ }
+
+ CreateServiceFunction(MS_PROTO_CALLCONTACTSERVICE, Proto_CallContactService);
+ CreateServiceFunction(MS_PROTO_CHAINSEND, Proto_CallContactService);
+ CreateServiceFunction(MS_PROTO_CHAINRECV, Proto_RecvChain);
+ 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/mir_app/src/protocolorder.cpp b/src/mir_app/src/protocolorder.cpp new file mode 100644 index 0000000000..3f6af51b04 --- /dev/null +++ b/src/mir_app/src/protocolorder.cpp @@ -0,0 +1,231 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "clc.h"
+
+struct ProtocolData
+{
+ char *RealName;
+ int enabled;
+};
+
+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;
+}
+
+static bool ProtoToInclude(PROTOACCOUNT *pa)
+{
+ if (!Proto_IsAccountEnabled(pa))
+ return false;
+
+ PROTOCOLDESCRIPTOR *pd = Proto_IsProtocolLoaded(pa->szProtoName);
+ return (pd != NULL && pd->type == PROTOTYPE_PROTOCOL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CProtocolOrderOpts : public CDlgBase
+{
+ void FillTree()
+ {
+ m_order.DeleteAllItems();
+
+ 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 < accounts.getCount(); i++) {
+ int idx = cli.pfnGetAccountIndexByPos(i);
+ if (idx == -1)
+ continue;
+
+ PROTOACCOUNT *pa = accounts[idx];
+ if (!ProtoToInclude(pa))
+ continue;
+
+ ProtocolData *PD = (ProtocolData*)mir_alloc(sizeof(ProtocolData));
+ PD->RealName = pa->szModuleName;
+ PD->enabled = Proto_IsAccountEnabled(pa) && isProtoSuitable(pa->ppro);
+
+ tvis.item.lParam = (LPARAM)PD;
+ tvis.item.pszText = pa->tszAccountName;
+ tvis.item.iImage = tvis.item.iSelectedImage = PD->enabled ? pa->bIsVisible : 100;
+ m_order.InsertItem(&tvis);
+ }
+ }
+
+ bool m_bDragging;
+ HTREEITEM m_hDragItem;
+
+ CCtrlTreeView m_order;
+ CCtrlButton m_btnReset;
+
+public:
+ CProtocolOrderOpts() :
+ CDlgBase(g_hInst, IDD_OPT_PROTOCOLORDER),
+ m_order(this, IDC_PROTOCOLORDER),
+ m_btnReset(this, IDC_RESETPROTOCOLDATA),
+ m_bDragging(false),
+ m_hDragItem(NULL)
+ {
+ m_btnReset.OnClick = Callback(this, &CProtocolOrderOpts::onReset_Click);
+
+ m_order.SetFlags(MTREE_CHECKBOX | MTREE_DND);
+ m_order.OnDeleteItem = Callback(this, &CProtocolOrderOpts::onOrder_DeleteItem);
+ }
+
+ virtual void OnInitDialog()
+ {
+ 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);
+ m_order.SetImageList(himlCheckBoxes, TVSIL_NORMAL);
+
+ FillTree();
+ }
+
+ virtual void OnApply()
+ {
+ int idx = 0;
+
+ TVITEMEX tvi;
+ tvi.hItem = m_order.GetRoot();
+ tvi.mask = TVIF_PARAM | TVIF_HANDLE | TVIF_IMAGE;
+ while (tvi.hItem != NULL) {
+ m_order.GetItem(&tvi);
+
+ if (tvi.lParam != 0) {
+ ProtocolData *ppd = (ProtocolData*)tvi.lParam;
+ PROTOACCOUNT *pa = Proto_GetAccount(ppd->RealName);
+ if (pa != NULL) {
+ while (idx < accounts.getCount() && !ProtoToInclude(accounts[idx]))
+ idx++;
+ pa->iOrder = idx++;
+ if (ppd->enabled)
+ pa->bIsVisible = tvi.iImage != 0;
+ }
+ }
+
+ tvi.hItem = m_order.GetNextSibling(tvi.hItem);
+ }
+
+ WriteDbAccounts();
+ cli.pfnReloadProtoMenus();
+ cli.pfnTrayIconIconsChanged();
+ cli.pfnClcBroadcast(INTM_RELOADOPTIONS, 0, 0);
+ cli.pfnClcBroadcast(INTM_INVALIDATE, 0, 0);
+ }
+
+ virtual void OnDestroy()
+ {
+ ImageList_Destroy(m_order.GetImageList(TVSIL_NORMAL));
+ }
+
+ void onReset_Click(CCtrlButton*)
+ {
+ for (int i = 0; i < accounts.getCount(); i++)
+ accounts[i]->iOrder = i;
+
+ FillTree();
+ NotifyChange();
+ }
+
+ void onOrder_DeleteItem(CCtrlTreeView::TEventInfo *env)
+ {
+ NMTREEVIEW *pnmtv = env->nmtv;
+ if (pnmtv)
+ mir_free((ProtocolData*)pnmtv->itemOld.lParam);
+ }
+};
+
+int ProtocolOrderOptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.position = -10000000;
+ odp.groupPosition = 1000000;
+ odp.pszTitle = LPGEN("Accounts");
+ odp.pszGroup = LPGEN("Contact list");
+ odp.pDialog = new CProtocolOrderOpts();
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/protocols.cpp b/src/mir_app/src/protocols.cpp new file mode 100644 index 0000000000..2d18d58757 --- /dev/null +++ b/src/mir_app/src/protocols.cpp @@ -0,0 +1,511 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+int LoadProtoChains(void);
+int LoadProtoOptions(void);
+
+HANDLE hAccListChanged;
+static HANDLE hTypeEvent;
+static BOOL bModuleInitialized = FALSE;
+
+struct TServiceListItem
+{
+ const char *name;
+ int id;
+};
+
+static int __cdecl CompareServiceItems(const void *p1, const void *p2)
+{ return strcmp(((TServiceListItem*)p1)->name, ((TServiceListItem*)p2)->name);
+}
+
+static TServiceListItem serviceItems[] =
+{
+ { PS_ADDTOLIST, 1 },
+ { PS_ADDTOLISTBYEVENT, 2 },
+ { PS_AUTHALLOW, 3 },
+ { PS_AUTHDENY, 4 },
+ { PSR_AUTH, 5 },
+ { PSS_AUTHREQUEST, 6 },
+ { PSS_FILEALLOW, 8 },
+ { PSS_FILECANCEL, 9 },
+ { PSS_FILEDENY, 10 },
+ { PS_FILERESUME, 11 },
+ { PS_GETCAPS, 12 },
+ { PS_LOADICON, 13 },
+ { PSS_GETINFO, 14 },
+ { PS_BASICSEARCH, 15 },
+ { PS_SEARCHBYEMAIL, 16 },
+ { PS_SEARCHBYNAME, 17 },
+ { PS_SEARCHBYADVANCED, 18 },
+ { PS_CREATEADVSEARCHUI, 19 },
+ { PSR_CONTACTS, 20 },
+ { PSR_FILE, 21 },
+ { PSR_MESSAGE, 22 },
+ { PSR_URL, 23 },
+ { PSS_CONTACTS, 24 },
+ { PSS_FILE, 25 },
+ { PSS_MESSAGE, 26 },
+ { PSS_URL, 27 },
+ { PSS_SETAPPARENTMODE, 28 },
+ { PS_SETSTATUS, 29 },
+ { PSS_GETAWAYMSG, 30 },
+ { PSR_AWAYMSG, 31 },
+ { PS_SETAWAYMSG, 33 },
+ { PSS_USERISTYPING, 34 },
+ { PS_GETNAME, 35 },
+ { PS_GETSTATUS, 36 }
+};
+
+//------------------------------------------------------------------------------------
+
+static int CompareProtos(const PROTOCOLDESCRIPTOR *p1, const PROTOCOLDESCRIPTOR *p2)
+{
+ if (p1->type != p2->type)
+ return p1->type - p2->type;
+
+ return mir_strcmp(p1->szName, p2->szName);
+}
+
+LIST<PROTOCOLDESCRIPTOR> filters(10, CompareProtos);
+
+//------------------------------------------------------------------------------------
+
+void FreeFilesMatrix(TCHAR ***files);
+
+INT_PTR srvProto_IsLoaded(WPARAM, LPARAM lParam)
+{
+ return (INT_PTR)Proto_IsProtocolLoaded((char*)lParam);
+}
+
+static PROTO_INTERFACE* defInitProto(const char* szModule, const TCHAR*)
+{
+ return AddDefaultAccount(szModule);
+}
+
+static INT_PTR srvProto_RegisterModule(WPARAM, LPARAM lParam)
+{
+ PROTOCOLDESCRIPTOR *pd = (PROTOCOLDESCRIPTOR*)lParam;
+ if (pd->cbSize != sizeof(PROTOCOLDESCRIPTOR) && pd->cbSize != PROTOCOLDESCRIPTOR_V3_SIZE)
+ return 1;
+
+ PROTOCOLDESCRIPTOR *p = Proto_RegisterModule(pd);
+ if (p == NULL)
+ return 2;
+
+ if (p->fnInit == NULL && (p->type == PROTOTYPE_PROTOCOL || p->type == PROTOTYPE_VIRTUAL)) {
+ // let's create a new container
+ PROTO_INTERFACE* ppi = AddDefaultAccount(pd->szName);
+ if (ppi) {
+ ppi->m_iVersion = (pd->cbSize == PROTOCOLDESCRIPTOR_V3_SIZE) ? 1 : 2;
+ PROTOACCOUNT *pa = Proto_GetAccount(pd->szName);
+ if (pa == NULL) {
+ pa = (PROTOACCOUNT*)mir_calloc(sizeof(PROTOACCOUNT));
+ pa->cbSize = sizeof(PROTOACCOUNT);
+ 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->bIsVirtual = (p->type == PROTOTYPE_VIRTUAL);
+ pa->ppro = ppi;
+ p->fnInit = defInitProto;
+ p->fnUninit = FreeDefaultAccount;
+ }
+ }
+
+ if (p->type != PROTOTYPE_PROTOCOL && p->type != PROTOTYPE_VIRTUAL)
+ filters.insert(p);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Basic core services
+
+static INT_PTR Proto_RecvMessage(WPARAM, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ PROTORECVEVENT *pre = (PROTORECVEVENT*)ccs->lParam;
+ if (pre->szMessage == NULL)
+ return NULL;
+
+ ptrA pszTemp;
+ mir_ptr<BYTE> pszBlob;
+
+ DBEVENTINFO dbei = { 0 };
+ dbei.cbSize = sizeof(dbei);
+ dbei.flags = DBEF_UTF;
+ dbei.szModule = GetContactProto(ccs->hContact);
+ dbei.timestamp = pre->timestamp;
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.cbBlob = (DWORD)mir_strlen(pre->szMessage) + 1;
+ dbei.pBlob = (PBYTE)pre->szMessage;
+
+ if (pre->cbCustomDataSize != 0) {
+ pszBlob = (PBYTE)mir_alloc(dbei.cbBlob + pre->cbCustomDataSize);
+ memcpy(pszBlob, dbei.pBlob, dbei.cbBlob);
+ memcpy((PBYTE)pszBlob + dbei.cbBlob, pre->pCustomData, pre->cbCustomDataSize);
+ dbei.pBlob = pszBlob;
+ dbei.cbBlob += pre->cbCustomDataSize;
+ }
+
+ if (pre->flags & PREF_CREATEREAD)
+ dbei.flags |= DBEF_READ;
+ if (pre->flags & PREF_SENT)
+ dbei.flags |= DBEF_SENT;
+
+ return (INT_PTR)db_event_add(ccs->hContact, &dbei);
+}
+
+static INT_PTR Proto_AuthRecv(WPARAM wParam, LPARAM lParam)
+{
+ PROTORECVEVENT* pre = (PROTORECVEVENT*)lParam;
+
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ dbei.szModule = (char*)wParam;
+ dbei.timestamp = pre->timestamp;
+ dbei.flags = DBEF_UTF | pre->flags & (PREF_CREATEREAD ? DBEF_READ : 0);
+ dbei.eventType = EVENTTYPE_AUTHREQUEST;
+ dbei.cbBlob = pre->lParam;
+ dbei.pBlob = (PBYTE)pre->szMessage;
+ return (INT_PTR)db_event_add(NULL, &dbei);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// User Typing Notification services
+
+static int Proto_ValidTypingContact(MCONTACT hContact, char *szProto)
+{
+ if (!hContact || !szProto)
+ return 0;
+
+ return (CallProtoServiceInt(NULL, 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 = GetContactProto(wParam);
+ if (!szProto)
+ return 0;
+
+ if (Proto_ValidTypingContact(wParam, szProto))
+ CallProtoServiceInt(NULL, szProto, PSS_USERISTYPING, wParam, lParam);
+ }
+
+ return 0;
+}
+
+static INT_PTR Proto_ContactIsTyping(WPARAM wParam, LPARAM lParam)
+{
+ int type = (int)lParam;
+ char *szProto = GetContactProto(wParam);
+ if (!szProto)
+ return 0;
+
+ if (CallService(MS_IGNORE_ISIGNORED, wParam, IGNOREEVENT_TYPINGNOTIFY))
+ return 0;
+
+ if (type < PROTOTYPE_CONTACTTYPING_OFF)
+ return 0;
+
+ if (Proto_ValidTypingContact(wParam, szProto))
+ NotifyEventHooks(hTypeEvent, wParam, lParam);
+
+ return 0;
+}
+
+void Proto_SetStatus(const char *szProto, unsigned status)
+{
+ if (CallProtoServiceInt(NULL, szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND) {
+ ptrT tszAwayMsg((TCHAR*)CallService(MS_AWAYMSG_GETSTATUSMSGT, status, (LPARAM)szProto));
+ CallProtoServiceInt(NULL, szProto, PS_SETAWAYMSG, status, tszAwayMsg);
+ }
+ CallProtoServiceInt(NULL, szProto, PS_SETSTATUS, status, 0);
+}
+
+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;
+}
+
+HICON Proto_GetIcon(PROTO_INTERFACE *ppro, int iconIndex)
+{
+ if (LOWORD(iconIndex) != PLI_PROTOCOL)
+ return NULL;
+
+ if (iconIndex & PLIF_ICOLIBHANDLE)
+ return (HICON)ppro->m_hProtoIcon;
+
+ bool big = (iconIndex & PLIF_SMALL) == 0;
+ HICON hIcon = Skin_GetIconByHandle(ppro->m_hProtoIcon, big);
+ if (iconIndex & PLIF_ICOLIB)
+ return hIcon;
+
+ HICON hIcon2 = CopyIcon(hIcon);
+ Skin_ReleaseIcon(hIcon);
+ return hIcon2;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// 0.8.0+ - accounts
+
+PROTOACCOUNT* __fastcall Proto_GetAccount(const char* accName)
+{
+ if (accName == NULL)
+ return NULL;
+
+ int idx;
+ PROTOACCOUNT temp;
+ temp.szModuleName = (char*)accName;
+ if ((idx = accounts.getIndex(&temp)) == -1)
+ return NULL;
+
+ return accounts[idx];
+}
+
+static INT_PTR srvProto_CreateAccount(WPARAM, LPARAM lParam)
+{
+ ACC_CREATE *p = (ACC_CREATE*)lParam;
+ if (p == NULL)
+ return NULL;
+
+ PROTOACCOUNT *pa = Proto_CreateAccount(p->pszInternal, p->pszBaseProto, p->ptszAccountName);
+ if (pa) {
+ WriteDbAccounts();
+ NotifyEventHooks(hAccListChanged, PRAC_ADDED, (LPARAM)pa);
+ }
+ return (INT_PTR)pa;
+}
+
+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 && db_get_b(NULL, pa->szModuleName, "LockMainStatus", 0) != 0;
+}
+
+static INT_PTR srvProto_IsAccountLocked(WPARAM, LPARAM lParam)
+{
+ return (INT_PTR)Proto_IsAccountLocked(Proto_GetAccount((char*)lParam));
+}
+
+static INT_PTR Proto_BroadcastAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA*)lParam;
+ return ProtoBroadcastAck(ack->szModule, ack->hContact, ack->type, ack->result, ack->hProcess, ack->lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) ProtoServiceExists(const char *szModule, const char *szService)
+{
+ if (szModule == NULL || szService == NULL)
+ return false;
+
+ TServiceListItem *item = (TServiceListItem*)bsearch(&szService, serviceItems, _countof(serviceItems), sizeof(serviceItems[0]), CompareServiceItems);
+ if (item != NULL)
+ return true;
+
+ char str[MAXMODULELABELLENGTH * 2];
+ strncpy_s(str, szModule, _TRUNCATE);
+ strncat_s(str, szService, _TRUNCATE);
+ return ServiceExists(str);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(INT_PTR) CallProtoService(const char* szModule, const char* szService, WPARAM wParam, LPARAM lParam)
+{
+ return CallProtoServiceInt(NULL, szModule, szService, wParam, lParam);
+}
+
+INT_PTR CallProtoServiceInt(MCONTACT hContact, const char *szModule, const char *szService, WPARAM wParam, LPARAM lParam)
+{
+ PROTOACCOUNT *pa = Proto_GetAccount(szModule);
+ if (pa && !pa->bOldProto) {
+ PROTO_INTERFACE *ppi = pa->ppro;
+ if (ppi != NULL && ppi->m_iVersion > 1) {
+ TServiceListItem *item = (TServiceListItem*)bsearch(&szService, serviceItems, _countof(serviceItems), sizeof(serviceItems[0]), CompareServiceItems);
+ if (item) {
+ switch (item->id) {
+ case 1: return (INT_PTR)ppi->AddToList(wParam, (PROTOSEARCHRESULT*)lParam);
+ case 2: return (INT_PTR)ppi->AddToListByEvent(LOWORD(wParam), HIWORD(wParam), (MEVENT)lParam);
+ case 3: return (INT_PTR)ppi->Authorize((MEVENT)wParam);
+ case 4: return (INT_PTR)ppi->AuthDeny((MEVENT)wParam, (TCHAR*)lParam);
+ case 5: return (INT_PTR)ppi->AuthRecv(hContact, (PROTORECVEVENT*)lParam);
+ case 6: return (INT_PTR)ppi->AuthRequest(hContact, (TCHAR*)lParam);
+ case 8: return (INT_PTR)ppi->FileAllow(hContact, (HANDLE)wParam, (TCHAR*)lParam);
+ case 9: return (INT_PTR)ppi->FileCancel(hContact, (HANDLE)wParam);
+ case 10: return (INT_PTR)ppi->FileDeny(hContact, (HANDLE)wParam, (TCHAR*)lParam);
+ case 11: {
+ PROTOFILERESUME *pfr = (PROTOFILERESUME*)lParam;
+ return (INT_PTR)ppi->FileResume((HANDLE)wParam, &pfr->action, (const TCHAR**)&pfr->szFilename);
+ }
+
+ case 12: return (INT_PTR)ppi->GetCaps(wParam, lParam);
+ case 13: return (INT_PTR)Proto_GetIcon(ppi, wParam);
+ case 14: return (INT_PTR)ppi->GetInfo(hContact, wParam);
+ case 15: return (INT_PTR)ppi->SearchBasic((TCHAR*)lParam);
+ case 16: return (INT_PTR)ppi->SearchByEmail((TCHAR*)lParam);
+ case 17: {
+ PROTOSEARCHBYNAME* psbn = (PROTOSEARCHBYNAME*)lParam;
+ 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, (PROTORECVFILET*)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), (MCONTACT*)lParam);
+ case 25: 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 33: return (INT_PTR)ppi->SetAwayMsg(wParam, (TCHAR*)lParam);
+ case 34: return (INT_PTR)ppi->UserIsTyping(wParam, lParam);
+ case 35: mir_strncpy((char*)lParam, ppi->m_szModuleName, wParam); return 0;
+ case 36:
+ return ppi->m_iStatus;
+ }
+ }
+ }
+ }
+
+ return ProtoCallService(szModule, szService, wParam, lParam);
+}
+
+INT_PTR ProtoCallService(const char *szModule, const char *szService, WPARAM wParam, LPARAM lParam)
+{
+ if (szModule == NULL || szService == NULL)
+ return false;
+
+ char str[MAXMODULELABELLENGTH * 2];
+ strncpy_s(str, szModule, _TRUNCATE);
+ strncat_s(str, szService, _TRUNCATE);
+ return CallService(str, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int LoadProtocolsModule(void)
+{
+ bModuleInitialized = TRUE;
+
+ if (LoadProtoChains())
+ return 1;
+
+ qsort(serviceItems, _countof(serviceItems), sizeof(serviceItems[0]), CompareServiceItems);
+
+ 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_REGISTERMODULE, srvProto_RegisterModule);
+ CreateServiceFunction(MS_PROTO_SELFISTYPING, Proto_SelfIsTyping);
+ CreateServiceFunction(MS_PROTO_CONTACTISTYPING, Proto_ContactIsTyping);
+
+ CreateServiceFunction(MS_PROTO_RECVMSG, Proto_RecvMessage);
+ CreateServiceFunction(MS_PROTO_AUTHRECV, Proto_AuthRecv);
+
+ CreateServiceFunction("Proto/EnumProtocols", Proto_EnumAccounts);
+ CreateServiceFunction(MS_PROTO_ENUMACCOUNTS, Proto_EnumAccounts);
+ CreateServiceFunction(MS_PROTO_CREATEACCOUNT, srvProto_CreateAccount);
+ CreateServiceFunction(MS_PROTO_GETACCOUNT, srvProto_GetAccount);
+
+ CreateServiceFunction(MS_PROTO_ISACCOUNTENABLED, srvProto_IsAccountEnabled);
+ CreateServiceFunction(MS_PROTO_ISACCOUNTLOCKED, srvProto_IsAccountLocked);
+
+ return LoadProtoOptions();
+}
+
+void UnloadProtocolsModule()
+{
+ if (!bModuleInitialized) return;
+
+ if (hAccListChanged) {
+ DestroyHookableEvent(hAccListChanged);
+ hAccListChanged = NULL;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+pfnUninitProto GetProtocolDestructor(char *szProto)
+{
+ PROTOCOLDESCRIPTOR *p = Proto_IsProtocolLoaded(szProto);
+ return (p == NULL) ? NULL : p->fnUninit;
+}
diff --git a/src/mir_app/src/protodir.cpp b/src/mir_app/src/protodir.cpp new file mode 100644 index 0000000000..dbeac1afee --- /dev/null +++ b/src/mir_app/src/protodir.cpp @@ -0,0 +1,243 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 {
+ mir_cs 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 = mir_strcmp(x->proto, y->proto);
+ if ( rc == 0 ) {
+ // same id? id's might be missing
+ if ( x->id && y->id ) rc = mir_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 mir_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;
+ mir_cslock lck(cd->csLock);
+ if ( List_GetIndex(&cd->protoNameCache, proto, &index) ) szCache = cd->protoNameCache.items[index];
+ else {
+ szCache = HeapAlloc(hCacheHeap, HEAP_NO_SERIALIZE, mir_strlen(proto)+1);
+ mir_strcpy(szCache, proto);
+ List_Insert(&cd->protoNameCache, szCache, index);
+ }
+ 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="";
+ mir_cslock lck(cd->csLock);
+ if ( List_GetIndex(&cd->protoCache, &e, &index) ) {
+ contactEntry * p = cd->protoCache.items[index];
+ szCache = p->proto;
+ }
+ 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 = "";
+ mir_cslock lck(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);
+ }
+ } 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)
+{
+ 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);
+}
+
+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/mir_app/src/protoint.cpp b/src/mir_app/src/protoint.cpp new file mode 100644 index 0000000000..c440c5c22d --- /dev/null +++ b/src/mir_app/src/protoint.cpp @@ -0,0 +1,304 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+char** __fastcall Proto_FilesMatrixA(TCHAR **files);
+
+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;
+}
+
+struct DEFAULT_PROTO_INTERFACE : public PROTO_INTERFACE
+{
+ MCONTACT __cdecl AddToList(int flags, PROTOSEARCHRESULT *psr)
+ {
+ return (MCONTACT)ProtoCallService(m_szModuleName, PS_ADDTOLIST, flags, (LPARAM)psr);
+ }
+
+ MCONTACT __cdecl AddToListByEvent(int flags, int iContact, MEVENT hDbEvent)
+ {
+ return (MCONTACT)ProtoCallService(m_szModuleName, PS_ADDTOLISTBYEVENT, MAKELONG(flags, iContact), hDbEvent);
+ }
+
+ int __cdecl Authorize(MEVENT hDbEvent)
+ {
+ return (int)ProtoCallService(m_szModuleName, PS_AUTHALLOW, (WPARAM)hDbEvent, 0);
+ }
+
+ int __cdecl AuthDeny(MEVENT hDbEvent, const TCHAR *szReason)
+ {
+ if (m_iVersion > 1)
+ return (int)ProtoCallService(m_szModuleName, PS_AUTHDENY, hDbEvent, (LPARAM)szReason);
+
+ return (int)ProtoCallService(m_szModuleName, PS_AUTHDENY, hDbEvent, _T2A(szReason));
+ }
+
+ int __cdecl AuthRecv(MCONTACT hContact, PROTORECVEVENT *evt)
+ {
+ CCSDATA ccs = { hContact, PSR_AUTH, 0, (LPARAM)evt };
+ return (int)ProtoCallService(m_szModuleName, PSR_AUTH, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl AuthRequest(MCONTACT hContact, const TCHAR *szMessage)
+ {
+ CCSDATA ccs = { hContact, PSS_AUTHREQUEST, 0, (LPARAM)szMessage };
+ if (m_iVersion > 1)
+ return (int)ProtoCallService(m_szModuleName, PSS_AUTHREQUEST, 0, (LPARAM)&ccs);
+
+ ccs.lParam = (LPARAM)mir_t2a(szMessage);
+ int res = (int)ProtoCallService(m_szModuleName, PSS_AUTHREQUEST, 0, (LPARAM)&ccs);
+ mir_free((char*)ccs.lParam);
+ return res;
+ }
+
+ HANDLE __cdecl FileAllow(MCONTACT hContact, HANDLE hTransfer, const TCHAR* szPath)
+ {
+ CCSDATA ccs = { hContact, PSS_FILEALLOW, (WPARAM)hTransfer, (LPARAM)szPath };
+ if (m_iVersion > 1)
+ return (HANDLE)ProtoCallService(m_szModuleName, PSS_FILEALLOW, 0, (LPARAM)&ccs);
+
+ ccs.lParam = (LPARAM)mir_t2a(szPath);
+ HANDLE res = (HANDLE)ProtoCallService(m_szModuleName, PSS_FILEALLOW, 0, (LPARAM)&ccs);
+ mir_free((char*)ccs.lParam);
+ return res;
+ }
+
+ int __cdecl FileCancel(MCONTACT hContact, HANDLE hTransfer)
+ {
+ CCSDATA ccs = { hContact, PSS_FILECANCEL, (WPARAM)hTransfer, 0 };
+ return (int)ProtoCallService(m_szModuleName, PSS_FILECANCEL, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl FileDeny(MCONTACT hContact, HANDLE hTransfer, const TCHAR* szReason)
+ {
+ CCSDATA ccs = { hContact, PSS_FILEDENY, (WPARAM)hTransfer, (LPARAM)szReason };
+ if (m_iVersion > 1)
+ return (int)ProtoCallService(m_szModuleName, PSS_FILEDENY, 0, (LPARAM)&ccs);
+
+ ccs.lParam = (LPARAM)mir_t2a(szReason);
+ int res = (int)ProtoCallService(m_szModuleName, PSS_FILEDENY, 0, (LPARAM)&ccs);
+ mir_free((char*)ccs.lParam);
+ return res;
+ }
+
+ int __cdecl FileResume(HANDLE hTransfer, int* action, const TCHAR** szFilename)
+ {
+ PROTOFILERESUME pfr = { *action, *szFilename };
+ if (m_iVersion > 1)
+ return (int)ProtoCallService(m_szModuleName, PS_FILERESUME, (WPARAM)hTransfer, (LPARAM)&pfr);
+
+ pfr.szFilename = (TCHAR*)mir_t2a(pfr.szFilename);
+ int res = (int)ProtoCallService(m_szModuleName, PS_FILERESUME, (WPARAM)hTransfer, (LPARAM)&pfr);
+ mir_free((TCHAR*)*szFilename);
+ *action = pfr.action; *szFilename = (TCHAR*)pfr.szFilename;
+
+ return res;
+ }
+
+ DWORD_PTR __cdecl GetCaps(int type, MCONTACT hContact)
+ {
+ return (DWORD_PTR)ProtoCallService(m_szModuleName, PS_GETCAPS, type, hContact);
+ }
+
+ HICON __cdecl GetIcon(int iconIndex)
+ {
+ return (HICON)ProtoCallService(m_szModuleName, PS_LOADICON, iconIndex, 0);
+ }
+
+ int __cdecl GetInfo(MCONTACT hContact, int flags)
+ {
+ CCSDATA ccs = { hContact, PSS_GETINFO, flags, 0 };
+ return ProtoCallService(m_szModuleName, PSS_GETINFO, 0, (LPARAM)&ccs);
+ }
+
+ HANDLE __cdecl SearchBasic(const TCHAR* id)
+ {
+ if (m_iVersion > 1)
+ return (HANDLE)ProtoCallService(m_szModuleName, PS_BASICSEARCH, 0, (LPARAM)id);
+
+ return (HANDLE)ProtoCallService(m_szModuleName, PS_BASICSEARCH, 0, _T2A(id));
+ }
+
+ HANDLE __cdecl SearchByEmail(const TCHAR* email)
+ {
+ if (m_iVersion > 1)
+ return (HANDLE)ProtoCallService(m_szModuleName, PS_SEARCHBYEMAIL, 0, (LPARAM)email);
+ return (HANDLE)ProtoCallService(m_szModuleName, PS_SEARCHBYEMAIL, 0, _T2A(email));
+ }
+
+ HANDLE __cdecl SearchByName(const TCHAR* nick, const TCHAR* firstName, const TCHAR* lastName)
+ {
+ PROTOSEARCHBYNAME psn;
+ psn.pszNick = (TCHAR*)mir_t2a(nick);
+ psn.pszFirstName = (TCHAR*)mir_t2a(firstName);
+ psn.pszLastName = (TCHAR*)mir_t2a(lastName);
+ HANDLE res = (HANDLE)ProtoCallService(m_szModuleName, PS_SEARCHBYNAME, 0, (LPARAM)&psn);
+ mir_free(psn.pszNick);
+ mir_free(psn.pszFirstName);
+ mir_free(psn.pszLastName);
+ return res;
+
+ }
+
+ HWND __cdecl SearchAdvanced(HWND owner)
+ {
+ return (HWND)ProtoCallService(m_szModuleName, PS_SEARCHBYADVANCED, 0, (LPARAM)owner);
+ }
+
+ HWND __cdecl CreateExtendedSearchUI(HWND owner)
+ {
+ return (HWND)ProtoCallService(m_szModuleName, PS_CREATEADVSEARCHUI, 0, (LPARAM)owner);
+ }
+
+ int __cdecl RecvContacts(MCONTACT hContact, PROTORECVEVENT* evt)
+ {
+ CCSDATA ccs = { hContact, PSR_CONTACTS, 0, (LPARAM)evt };
+ return (int)ProtoCallService(m_szModuleName, PSR_CONTACTS, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl RecvFile(MCONTACT hContact, PROTORECVFILET* evt)
+ {
+ CCSDATA ccs = { hContact, PSR_FILE, 0, (LPARAM)evt };
+ return ProtoCallService(m_szModuleName, PSR_FILE, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl RecvMsg(MCONTACT hContact, PROTORECVEVENT* evt)
+ {
+ CCSDATA ccs = { hContact, PSR_MESSAGE, 0, (LPARAM)evt };
+ return (int)ProtoCallService(m_szModuleName, PSR_MESSAGE, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl RecvUrl(MCONTACT hContact, PROTORECVEVENT* evt)
+ {
+ CCSDATA ccs = { hContact, PSR_URL, 0, (LPARAM)evt };
+ return (int)ProtoCallService(m_szModuleName, PSR_URL, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl SendContacts(MCONTACT hContact, int flags, int nContacts, MCONTACT *hContactsList)
+ {
+ CCSDATA ccs = { hContact, PSS_CONTACTS, MAKEWPARAM(flags, nContacts), (LPARAM)hContactsList };
+ return (int)ProtoCallService(m_szModuleName, PSS_CONTACTS, 0, (LPARAM)&ccs);
+ }
+
+ HANDLE __cdecl SendFile(MCONTACT hContact, const TCHAR* szDescription, TCHAR** ppszFiles)
+ {
+ CCSDATA ccs = { hContact, PSS_FILE, (WPARAM)szDescription, (LPARAM)ppszFiles };
+
+ if (m_iVersion > 1)
+ return (HANDLE)ProtoCallService(m_szModuleName, PSS_FILE, 0, (LPARAM)&ccs);
+
+ ccs.wParam = (WPARAM)mir_t2a(szDescription);
+ ccs.lParam = (LPARAM)Proto_FilesMatrixA(ppszFiles);
+ HANDLE res = (HANDLE)ProtoCallService(m_szModuleName, PSS_FILE, 0, (LPARAM)&ccs);
+ if (res == 0) FreeFilesMatrix((TCHAR***)&ccs.lParam);
+ mir_free((char*)ccs.wParam);
+ return res;
+ }
+
+ int __cdecl SendMsg(MCONTACT hContact, const char* msg)
+ {
+ CCSDATA ccs = { hContact, PSS_MESSAGE, 0, (LPARAM)msg };
+ return (int)ProtoCallService(m_szModuleName, PSS_MESSAGE, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl SendUrl(MCONTACT hContact, int flags, const char* url)
+ {
+ CCSDATA ccs = { hContact, PSS_URL, flags, (LPARAM)url };
+ return (int)ProtoCallService(m_szModuleName, PSS_URL, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl SetApparentMode(MCONTACT hContact, int mode)
+ {
+ CCSDATA ccs = { hContact, PSS_SETAPPARENTMODE, mode, 0 };
+ return (int)ProtoCallService(m_szModuleName, PSS_SETAPPARENTMODE, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl SetStatus(int iNewStatus)
+ {
+ return (int)ProtoCallService(m_szModuleName, PS_SETSTATUS, iNewStatus, 0);
+ }
+
+ HANDLE __cdecl GetAwayMsg(MCONTACT hContact)
+ {
+ CCSDATA ccs = { hContact, PSS_GETAWAYMSG, 0, 0 };
+ return (HANDLE)ProtoCallService(m_szModuleName, PSS_GETAWAYMSG, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl RecvAwayMsg(MCONTACT hContact, int statusMode, PROTORECVEVENT* evt)
+ {
+ CCSDATA ccs = { hContact, PSR_AWAYMSG, statusMode, (LPARAM)evt };
+ return (int)ProtoCallService(m_szModuleName, PSR_AWAYMSG, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl SetAwayMsg(int iStatus, const TCHAR *msg)
+ {
+ if (m_iVersion > 1)
+ return (int)ProtoCallService(m_szModuleName, PS_SETAWAYMSG, iStatus, (LPARAM)msg);
+ return (int)ProtoCallService(m_szModuleName, PS_SETAWAYMSG, iStatus, _T2A(msg));
+ }
+
+ int __cdecl UserIsTyping(MCONTACT hContact, int type)
+ {
+ CCSDATA ccs = { hContact, PSS_USERISTYPING, hContact, type };
+ return ProtoCallService(m_szModuleName, PSS_USERISTYPING, 0, (LPARAM)&ccs);
+ }
+
+ int __cdecl OnEvent(PROTOEVENTTYPE, WPARAM, LPARAM)
+ {
+ return 1;
+ }
+};
+
+// creates the default protocol container for compatibility with the old plugins
+
+PROTO_INTERFACE* AddDefaultAccount(const char *szProtoName)
+{
+ PROTO_INTERFACE* ppi = new DEFAULT_PROTO_INTERFACE;
+ ppi->m_szModuleName = 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_tszUserName);
+ delete ppi;
+ return 0;
+}
diff --git a/src/mir_app/src/protoopts.cpp b/src/mir_app/src/protoopts.cpp new file mode 100644 index 0000000000..8e072db725 --- /dev/null +++ b/src/mir_app/src/protoopts.cpp @@ -0,0 +1,1062 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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)
+
+bool CheckProtocolOrder(void);
+
+#define errMsg \
+TranslateT("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 \
+TranslateT("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")
+// is upgradeMsg in use in any place?
+#define legacyMsg \
+TranslateT("This account uses legacy protocol plugin. \
+Use Miranda NG options dialogs to change its preferences.")
+
+#define welcomeMsg \
+TranslateT("Welcome to Miranda NG'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);
+
+PROTOACCOUNT* Proto_CreateAccount(const char *szModuleName, const char *szBaseProto, const TCHAR *tszAccountName)
+{
+ PROTOACCOUNT *pa = (PROTOACCOUNT*)mir_calloc(sizeof(PROTOACCOUNT));
+ if (pa == NULL)
+ return NULL;
+
+ pa->cbSize = sizeof(PROTOACCOUNT);
+ pa->bIsEnabled = pa->bIsVisible = true;
+ pa->iOrder = accounts.getCount();
+ pa->szProtoName = mir_strdup(szBaseProto);
+
+ // if the internal name is empty, generate new one
+ if (mir_strlen(szModuleName) == 0) {
+ char buf[100];
+ int count = 1;
+ while (true) {
+ mir_snprintf(buf, "%s_%d", szBaseProto, count++);
+ if (ptrA(db_get_sa(NULL, buf, "AM_BaseProto")) == NULL)
+ break;
+ }
+ pa->szModuleName = mir_strdup(buf);
+ }
+ else pa->szModuleName = mir_strdup(szModuleName);
+
+ pa->tszAccountName = mir_tstrdup(tszAccountName);
+
+ db_set_s(NULL, pa->szModuleName, "AM_BaseProto", szBaseProto);
+ accounts.insert(pa);
+
+ if (ActivateAccount(pa)) {
+ pa->ppro->OnEvent(EV_PROTO_ONLOAD, 0, 0);
+ if (!db_get_b(NULL, "CList", "MoveProtoMenus", true))
+ pa->ppro->OnEvent(EV_PROTO_ONMENU, 0, 0);
+ }
+
+ return pa;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Account edit form
+// Gets PROTOACCOUNT* as a parameter, or NULL to edit a new one
+
+struct AccFormDlgParam
+{
+ int action;
+ PROTOACCOUNT *pa;
+};
+
+static bool FindAccountByName(const char *szModuleName)
+{
+ if (!mir_strlen(szModuleName))
+ return false;
+
+ for (int i = 0; i < accounts.getCount(); i++)
+ if (_stricmp(szModuleName, accounts[i]->szModuleName) == 0)
+ return true;
+
+ return false;
+}
+
+static bool OnCreateAccount(HWND hwndDlg)
+{
+ AccFormDlgParam* param = (AccFormDlgParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ PROTOACCOUNT *pa = param->pa;
+
+ TCHAR tszAccName[256];
+ GetDlgItemText(hwndDlg, IDC_ACCNAME, tszAccName, SIZEOF(tszAccName));
+ rtrimt(tszAccName);
+ if (tszAccName[0] == 0) {
+ MessageBox(hwndDlg, TranslateT("Account name must be filled."), TranslateT("Account error"), MB_ICONERROR | MB_OK);
+ return false;
+ }
+
+ if (param->action == PRAC_ADDED) {
+ char buf[200];
+ GetDlgItemTextA(hwndDlg, IDC_ACCINTERNALNAME, buf, SIZEOF(buf));
+ if (FindAccountByName(rtrim(buf))) {
+ MessageBox(hwndDlg, TranslateT("Account name has to be unique. Please enter unique name."), TranslateT("Account error"), MB_ICONERROR | MB_OK);
+ return false;
+ }
+ }
+
+ if (param->action == PRAC_UPGRADED) {
+ BOOL oldProto = pa->bOldProto;
+ TCHAR szPlugin[MAX_PATH];
+ mir_sntprintf(szPlugin, SIZEOF(szPlugin), _T("%s.dll"), _A2T(pa->szProtoName));
+ int 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);
+ }
+ param->action = PRAC_ADDED;
+ }
+
+ if (param->action == PRAC_ADDED) {
+ char buf[200];
+ GetDlgItemTextA(hwndDlg, IDC_PROTOTYPECOMBO, buf, SIZEOF(buf));
+ char *szBaseProto = NEWSTR_ALLOCA(buf);
+
+ GetDlgItemTextA(hwndDlg, IDC_ACCINTERNALNAME, buf, SIZEOF(buf));
+ rtrim(buf);
+
+ pa = Proto_CreateAccount(buf, szBaseProto, tszAccName);
+ }
+ else replaceStrT(pa->tszAccountName, tszAccName);
+
+ WriteDbAccounts();
+ NotifyEventHooks(hAccListChanged, param->action, (LPARAM)pa);
+
+ SendMessage(GetParent(hwndDlg), WM_MY_REFRESH, 0, 0);
+ return true;
+}
+
+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;
+ CallService(MS_PROTO_ENUMPROTOS, (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:
+ if (OnCreateAccount(hwndDlg))
+ 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
+{
+ int iItem;
+ RECT rcCheck;
+ HWND hwndEdit;
+};
+
+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 mir_callNextSubclass(hwnd, sttEditSubclassProc, 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, g_hInst, NULL);
+ mir_subclassWindow(dat->hwndEdit, 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, -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 mir_callNextSubclass(hwnd, AccListWndProc, 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;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)dat);
+ mir_subclassWindow(hwnd, AccListWndProc);
+ }
+ else {
+ struct TAccListData *dat = (struct TAccListData *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ 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) {
+ 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);
+ SetDlgItemText(hwndDlg, IDC_TXT_INFO, TranslateT("Account is disabled. Please activate it to access options."));
+ }
+ else {
+ HWND hwnd = (HWND)CallProtoService(pa->szModuleName, PS_CREATEACCMGRUI, 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);
+ SetDlgItemText(hwndDlg, IDC_TXT_INFO, 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);
+ SetDlgItemText(hwndDlg, IDC_TXT_INFO, welcomeMsg);
+}
+
+INT_PTR CALLBACK AccMgrDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ struct TAccMgrData *dat = (struct TAccMgrData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_ACCLIST);
+ PROTOACCOUNT *pa;
+ int idx;
+ PSHNOTIFY pshn;
+
+ switch (message) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ Window_SetIcon_IcoLib(hwndDlg, SKINICON_OTHER_ACCMGR);
+
+ dat = (TAccMgrData *)mir_alloc(sizeof(TAccMgrData));
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);
+
+ 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;
+ 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 hdc = GetDC(hwndDlg);
+ HFONT hfnt = (HFONT)SelectObject(hdc, dat->hfntTitle);
+
+ TEXTMETRIC tm;
+ 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:
+ 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;
+
+ int tmp;
+ 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 hIcon = LoadSkinnedIcon(tmp);
+ DrawIconEx(lps->hDC, lps->rcItem.left, lps->rcItem.top, hIcon, cxIcon, cyIcon, 0, hbrBack, DI_NORMAL);
+ IcoLib_ReleaseIcon(hIcon, 0);
+
+ lps->rcItem.left += cxIcon + 2;
+
+ if (acc->ppro) {
+ hIcon = Skin_GetIconByHandle(acc->ppro->m_hProtoIcon);
+ DrawIconEx(lps->hDC, lps->rcItem.left, lps->rcItem.top, hIcon, cxIcon, cyIcon, 0, hbrBack, DI_NORMAL);
+ Skin_ReleaseIcon(hIcon);
+ }
+ lps->rcItem.left += cxIcon + 2;
+
+ int length = SendDlgItemMessage(hwndDlg, IDC_ACCLIST, LB_GETTEXTLEN, lps->itemID, 0);
+ int size = max(length + 1, 256);
+ TCHAR *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: %S"), TranslateT("Protocol"), acc->szProtoName);
+ length = (int)mir_tstrlen(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 = (char *)acc->ppro->GetCaps(PFLAG_UNIQUEIDTEXT, 0);
+ TCHAR *tszIdName = szIdName ? mir_a2t(szIdName) : mir_tstrdup(TranslateT("Account ID"));
+
+ CONTACTINFO ci = { 0 };
+ 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("<unknown>"));
+ mir_free(tszIdName);
+ }
+ else mir_sntprintf(text, size, TranslateT("Protocol is not loaded."));
+
+ length = (int)mir_tstrlen(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:
+ dat->iSelected = -1;
+ {
+ int i = ListBox_GetCurSel(hwndList);
+ PROTOACCOUNT *acc = (i == LB_ERR) ? NULL : (PROTOACCOUNT *)ListBox_GetItemData(hwndList, i);
+
+ SendMessage(hwndList, LB_RESETCONTENT, 0, 0);
+ for (i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *p = accounts[i];
+ PROTOCOLDESCRIPTOR *pd = Proto_IsProtocolLoaded(p->szProtoName);
+ if (pd != NULL && pd->type != PROTOTYPE_PROTOCOL)
+ continue;
+
+ int iItem = SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)p->tszAccountName);
+ SendMessage(hwndList, LB_SETITEMDATA, iItem, (LPARAM)p);
+
+ if (p == acc)
+ ListBox_SetCurSel(hwndList, iItem);
+ }
+
+ dat->iSelected = ListBox_GetCurSel(hwndList); // -1 if error = > nothing selected in our case
+ if (dat->iSelected >= 0)
+ sttSelectItem(dat, hwndList, 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) {
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(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) {
+ 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"));
+
+ 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:
+ 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:
+ 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 (!db_get_b(NULL, "CList", "MoveProtoMenus", TRUE))
+ pa->ppro->OnEvent(EV_PROTO_ONMENU, 0, 0);
+ }
+ }
+ else {
+ DWORD dwStatus = CallProtoServiceInt(NULL, pa->szModuleName, PS_GETSTATUS, 0, 0);
+ if (dwStatus >= ID_STATUS_ONLINE) {
+ TCHAR buf[200];
+ mir_sntprintf(buf, TranslateT("Account %s is being disabled"), pa->tszAccountName);
+ if (IDNO == ::MessageBox(hwndDlg,
+ TranslateT("Account is online. Disable account?"),
+ buf, MB_ICONWARNING | MB_DEFBUTTON2 | MB_YESNO)) {
+ 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);
+ 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;
+
+ case IDC_ADD:
+ {
+ AccFormDlgParam param = { PRAC_ADDED, NULL };
+ if (IDOK == DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_ACCFORM), hwndDlg, AccFormDlgProc, (LPARAM)¶m))
+ SendMessage(hwndDlg, WM_MY_REFRESH, 0, 0);
+ }
+ break;
+
+ case IDC_EDIT:
+ idx = ListBox_GetCurSel(hwndList);
+ if (idx != -1)
+ PostMessage(hwndList, WM_MY_RENAME, 0, 0);
+ break;
+
+ case IDC_REMOVE:
+ idx = ListBox_GetCurSel(hwndList);
+ if (idx != -1) {
+ pa = (PROTOACCOUNT*)ListBox_GetItemData(hwndList, idx);
+ TCHAR buf[200];
+ mir_sntprintf(buf, TranslateT("Account %s is being deleted"), pa->tszAccountName);
+ if (pa->bOldProto) {
+ MessageBox(hwndDlg, TranslateT("You need to disable plugin to delete this account"), buf, MB_ICONERROR | MB_OK);
+ break;
+ }
+ if (IDYES == MessageBox(hwndDlg, errMsg, buf, MB_ICONWARNING | MB_DEFBUTTON2 | MB_YESNO)) {
+ // lock controls to avoid changes during remove process
+ ListBox_SetCurSel(hwndList, -1);
+ sttUpdateAccountInfo(hwndDlg, dat);
+ EnableWindow(hwndList, FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), FALSE);
+
+ ListBox_SetItemData(hwndList, 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(hwndList, TRUE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), TRUE);
+ }
+ }
+ break;
+
+ case IDC_OPTIONS:
+ idx = ListBox_GetCurSel(hwndList);
+ if (idx != -1) {
+ pa = (PROTOACCOUNT*)ListBox_GetItemData(hwndList, idx);
+ if (pa->bOldProto) {
+ OPENOPTIONSDIALOG ood;
+ ood.cbSize = sizeof(ood);
+ ood.pszGroup = "Network";
+ ood.pszPage = pa->szModuleName;
+ ood.pszTab = NULL;
+ Options_Open(&ood);
+ }
+ else OpenAccountOptions(pa);
+ }
+ break;
+
+ case IDC_UPGRADE:
+ idx = ListBox_GetCurSel(hwndList);
+ if (idx != -1) {
+ AccFormDlgParam param = { PRAC_UPGRADED, (PROTOACCOUNT*)ListBox_GetItemData(hwndList, idx) };
+ DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_ACCFORM), hwndDlg, AccFormDlgProc, (LPARAM)¶m);
+ }
+ break;
+
+ case IDC_LNK_NETWORK:
+ pshn.hdr.idFrom = 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";
+ Options_Open(&ood);
+ }
+ break;
+
+ case IDC_LNK_ADDONS:
+ CallService(MS_UTILS_OPENURL, OUF_NEWWINDOW, (LPARAM)"http://miranda-ng.org/");
+ break;
+
+ case IDOK:
+ pshn.hdr.idFrom = 0;
+ pshn.hdr.code = PSN_APPLY;
+ pshn.hdr.hwndFrom = hwndDlg;
+ SendMessage(hwndDlg, WM_NOTIFY, 0, (LPARAM)&pshn);
+ DestroyWindow(hwndDlg);
+ break;
+
+ case IDCANCEL:
+ pshn.hdr.idFrom = 0;
+ pshn.hdr.code = PSN_RESET;
+ pshn.hdr.hwndFrom = hwndDlg;
+ SendMessage(hwndDlg, WM_NOTIFY, 0, (LPARAM)&pshn);
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ break;
+
+ case PSM_CHANGED:
+ idx = ListBox_GetCurSel(hwndList);
+ if (idx != -1) {
+ pa = (PROTOACCOUNT *)ListBox_GetItemData(hwndList, idx);
+ if (pa) {
+ pa->bAccMgrUIChanged = TRUE;
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->idFrom == 0) {
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_APPLY:
+ pshn.hdr.idFrom = 0;
+ pshn.hdr.code = PSN_APPLY;
+ for (int i = 0; i < accounts.getCount(); i++) {
+ pa = accounts[i];
+ if (pa->hwndAccMgrUI && pa->bAccMgrUIChanged) {
+ pshn.hdr.hwndFrom = pa->hwndAccMgrUI;
+ SendMessage(pa->hwndAccMgrUI, WM_NOTIFY, 0, (LPARAM)&pshn);
+ pa->bAccMgrUIChanged = FALSE;
+ }
+ }
+ return TRUE;
+
+ case PSN_RESET:
+ pshn.hdr.idFrom = 0;
+ pshn.hdr.code = PSN_RESET;
+ for (int i = 0; i < accounts.getCount(); i++) {
+ pa = accounts[i];
+ if (pa->hwndAccMgrUI && pa->bAccMgrUIChanged) {
+ pshn.hdr.hwndFrom = pa->hwndAccMgrUI;
+ SendMessage(pa->hwndAccMgrUI, WM_NOTIFY, 0, (LPARAM)&pshn);
+ pa->bAccMgrUIChanged = FALSE;
+ }
+ }
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ for (int i = 0; i < accounts.getCount(); i++) {
+ pa = accounts[i];
+ pa->bAccMgrUIChanged = FALSE;
+ if (pa->hwndAccMgrUI) {
+ DestroyWindow(pa->hwndAccMgrUI);
+ pa->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(g_hInst, MAKEINTRESOURCE(IDD_ACCMGR), NULL, AccMgrDlgProc, 0);
+
+ ShowWindow(hAccMgr, SW_RESTORE);
+ SetForegroundWindow(hAccMgr);
+ SetActiveWindow(hAccMgr);
+ return 0;
+}
+
+int OptProtosLoaded(WPARAM, LPARAM)
+{
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_ACCMGR);
+ mi.position = 1900000000;
+ mi.pszName = LPGEN("&Accounts...");
+ mi.pszService = MS_PROTO_SHOWACCMGR;
+ Menu_AddMainMenuItem(&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/mir_app/src/resizer.cpp b/src/mir_app/src/resizer.cpp new file mode 100644 index 0000000000..ce27ee8079 --- /dev/null +++ b/src/mir_app/src/resizer.cpp @@ -0,0 +1,158 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#pragma pack(2)
+
+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;i<itemCount;i++) {
+ if ((UINT_PTR)pWord&2) pWord++; //dword align
+
+ if (extendedDlg) {
+ pItemEx = (START_OF_DLGITEMTEMPLATEEX*)pWord;
+ pWord = (PWORD)(pItemEx+1);
+
+ urc.wId = pItemEx->id;
+ 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;
+ }
+ HWND hCtrl = GetDlgItem(urd->hwndDlg, extendedDlg ? pItemEx->id : pItem->id);
+ if (NULL != hCtrl) /* Wine fix. */
+ hDwp = DeferWindowPos(hDwp, hCtrl, 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/mir_app/src/resource.h b/src/mir_app/src/resource.h new file mode 100644 index 0000000000..7fd2a39ca5 --- /dev/null +++ b/src/mir_app/src/resource.h @@ -0,0 +1,605 @@ +//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by resource.rc
+//
+#define IDC_AUTHICON 1
+#define IDC_NOTOALL 3
+#define IDC_APPLY 3
+#define IDC_DECIDELATER 3
+#define IDD_OPT_LANGUAGES 101
+#define IDI_MIRANDA 102
+#define IDD_ABOUT 103
+#define IDI_SMS 103
+#define IDI_ONLINE 104
+#define IDI_OFFLINE 105
+#define IDD_OPT_FONTS 107
+#define IDD_OPT_GENMENU 108
+#define IDD_OPT_PROTOCOLORDER 109
+#define IDD_OPT_ICOLIB 110
+#define IDD_ICOLIB_IMPORT 111
+#define IDD_MODERNOPT_FONTS 112
+#define IDI_LOADED_GRAY 113
+#define IDI_NOTLOADED_GRAY 114
+#define IDD_ADDED 115
+#define IDD_ENTER_STRING 116
+
+#define IDD_URLSEND 119
+#define IDD_URLRECV 120
+#define IDD_AUTHREQ 121
+#define IDD_DETAILS 125
+#define IDD_HISTORY 127
+#define IDI_AWAY 128
+#define IDI_FREE4CHAT 129
+#define IDI_INVISIBLE 130
+#define IDI_NA 131
+#define IDI_LOAD 132
+#define IDD_OPT_SOUND 134
+#define IDI_RECVMSG 136
+#define IDI_URL 138
+#define IDD_MODERNOPT_ACCOUNTS 139
+#define IDD_MODERNOPT_MODULES 140
+#define IDD_MODERNOPT_STATUS 141
+#define IDD_MODERNOPT_IDLE 142
+#define IDD_MODERNOPT_IGNORE 143
+#define IDI_MCMENUOFF 144
+#define IDI_MCMENU 145
+#define IDI_MCCONVERT 146
+#define IDI_MCADD 147
+#define IDI_MCEDIT 148
+#define IDI_MCSETDEFAULT 149
+#define IDI_MCREMOVE 150
+#define IDD_METASELECT 151
+#define IDD_METAEDIT 152
+#define IDD_METAOPTIONS 153
+#define IDI_DND 158
+#define IDI_OCCUPIED 159
+#define IDI_USERDETAILS 160
+#define IDI_FINDUSER 161
+#define IDI_HELP 162
+#define IDI_OPTIONS 163
+#define IDI_MFATAL 164
+#define IDI_MERROR 165
+#define IDI_MWARNING 166
+#define IDI_MINFO 167
+#define IDI_MIRANDAWEBSITE 172
+#define IDI_RENAME 173
+#define IDI_HISTORY 174
+#define IDI_DELETE 175
+#define IDR_CONTEXT 180
+#define IDR_ICOLIB_CONTEXT 181
+#define IDC_DROP 183
+#define IDI_ALWAYSVIS 184
+#define IDD_EI_OPTIONS 185
+#define IDI_NEVERVIS 186
+#define IDI_CHAT 187
+#define IDI_MALE 188
+#define IDI_FEMALE 189
+#define IDR_OPT_POPUP 191
+#define IDD_HISTORY_FIND 192
+#define IDI_SENDEMAIL 193
+#define IDD_FILERECV 194
+#define IDD_COLORCHOOSER 195
+#define IDD_PROFILEMANAGER 197
+#define IDI_MAINMENU 198
+#define IDR_CLISTMENU 199
+#define IDI_BLANK 200
+#define IDD_FINDADD 201
+#define IDI_USERONLINE 201
+#define IDI_GROUPSHUT 202
+#define IDD_OPTIONS 203
+#define IDI_GROUPOPEN 203
+#define IDD_FILESEND 205
+#define IDI_NOTICK 205
+#define IDD_OPT_PLUGINS 206
+#define IDI_TICK 206
+#define IDD_OPT_ICONS 207
+#define IDI_FILE 207
+#define IDI_ADDCONTACT 210
+#define IDI_SMALLDOT 211
+#define IDI_FILLEDBLOB 212
+#define IDD_READAWAYMSG 213
+#define IDI_EMPTYBLOB 213
+#define IDD_OPT_IGNORE 214
+#define IDC_HYPERLINKHAND 214
+#define IDD_OPT_VISIBILITY 215
+#define IDC_DROPUSER 215
+#define IDD_SETAWAYMSG 216
+#define IDI_DETAILSLOGO 216
+#define IDD_OPT_AWAYMSG 217
+#define IDI_UNICODE 218
+#define IDI_ANSI 219
+#define IDD_INFO_SUMMARY 220
+#define IDI_LOADED 220
+#define IDD_INFO_CONTACT 221
+#define IDR_CREDITS 221
+#define IDI_NOTLOADED 221
+#define IDD_INFO_BACKGROUND 222
+#define IDD_INFO_NOTES 223
+#define IDD_ADDEMAIL 226
+#define IDD_ICONINDEX 227
+#define IDD_INFO_LOCATION 231
+#define IDD_INFO_WORK 232
+#define IDD_ADDPHONE 233
+#define IDD_INSTALLINI 235
+#define IDD_WARNINICHANGE 236
+#define IDD_INIIMPORTDONE 237
+#define IDD_OPT_NETLIB 246
+#define IDD_NETLIBLOGOPTS 247
+#define IDD_FILETRANSFERINFO 249
+#define IDD_OPT_FILETRANSFER 250
+#define IDD_FILEEXISTS 251
+#define IDD_DELETECONTACT 254
+#define IDD_ADDCONTACT 257
+#define IDD_OPT_CONTACT 261
+#define IDI_DOWNARROW 264
+#define IDD_OPT_IDLE 268
+#define IDD_PROFILE_SELECTION 269
+#define IDD_PROFILE_NEW 270
+#define IDI_TYPING 274
+#define IDD_UPDATE_NOTIFY 275
+#define IDD_OPT_UPDATENOTIFY 276
+#define IDD_OPT_KEYBINDINGS 277
+#define IDD_OPT_HOTKEYS 278
+#define IDI_UNDO 279
+#define IDI_WINDOW 280
+#define IDI_WINDOWS 281
+#define IDI_ACCMGR 282
+#define IDD_ACCMGR 283
+#define IDD_ACCFORM 284
+#define IDD_WAITRESTART 285
+#define IDD_FTMGR 286
+#define IDD_FTPAGE 287
+#define IDI_JOINCHAT 289
+#define IDD_CHOOSE_FONT_EFFECT 289
+#define IDI_LEAVECHAT 290
+#define IDI_STATUS_LOCKED 291
+#define IDI_SHOWHIDE 292
+#define IDI_EXIT 293
+#define IDD_ERROR_LIST 300
+#define IDD_OPT_ERRORS 301
+#define IDD_OPTIONSPAGE 318
+#define IDI_MOVETOGROUP 319
+#define IDI_ON 320
+#define IDI_OFF 322
+#define IDI_FRAME 323
+#define IDI_AUTH_ADD 324
+#define IDI_AUTH_GRANT 325
+#define IDI_AUTH_REQUEST 326
+#define IDI_AUTH_REVOKE 327
+#define IDI_ONTHEPHONE 1002
+#define IDC_MESSAGE 1002
+#define IDC_LANGINFOFRAME 1002
+#define IDI_OUTTOLUNCH 1003
+#define IDC_LANGAUTHORSLABEL 1003
+#define IDC_AUTOCLOSE 1004
+#define IDC_LANGAUTHORS 1004
+#define IDC_FROM 1005
+#define IDC_AUTOMIN 1005
+#define IDC_LANGEMAILLABEL 1005
+#define IDC_DATE 1006
+#define IDC_DUMPRECV 1006
+#define IDC_LANGEMAIL 1006
+#define IDC_AUTOCLEAR 1007
+#define IDC_LANGMODUSINGLABEL 1007
+#define IDC_MSG 1008
+#define IDC_PROXYDNS 1008
+#define IDC_LANGMODUSING 1008
+#define IDC_NAME 1009
+#define IDC_PROXYTYPE 1009
+#define IDC_LANGDATELABEL 1009
+#define IDC_STATIC23 1010
+#define IDC_NAMEVAL 1010
+#define IDC_LNK_NETWORK 1010
+#define IDC_LANGDATE 1010
+#define IDC_LNK_ADDONS 1011
+#define IDC_RELOAD 1011
+#define IDC_SPECIFYPORTS 1013
+#define IDC_ST_ENTERMSG 1013
+#define IDC_ST_ENTERURL 1014
+#define IDC_SPECIFYPORTSO 1014
+#define IDC_LANGLOCALE 1014
+#define IDC_ENABLEUPNP 1015
+#define IDC_VALIDATESSL 1016
+#define IDC_ONLYAVAIL 1017
+#define IDC_MORELANG 1017
+#define IDC_METALIST 1018
+#define IDC_CHK_SRT 1019
+#define IDC_LST_CONTACTS 1020
+#define IDC_VALIDATE 1021
+#define IDC_BTN_REM 1022
+#define IDC_BTN_SETDEFAULT 1023
+#define IDC_BTN_SETOFFLINE 1024
+#define IDC_BTN_UP 1025
+#define IDC_BTN_DOWN 1026
+#define IDC_CHK_FORCEDEFAULT 1027
+#define IDC_ED_NAME 1028
+#define IDC_ED_DEFAULT 1029
+#define IDC_SHOWNAMES 1031
+#define IDC_ABOUT 1032
+#define IDC_MYNOTES 1033
+#define IDC_URLS 1037
+#define IDC_COLORTEXT 1038
+#define IDC_REPLY 1039
+#define IDC_URL 1041
+#define IDC_FONTGROUP 1042
+#define IDC_BTN_RESET 1045
+#define IDC_REASON 1046
+#define IDC_BTN_UNDO 1047
+#define IDC_DENYREASON 1047
+#define IDC_EMAIL 1048
+#define IDC_NAMENICK 1049
+#define IDC_NAMEFIRST 1050
+#define IDC_NAMELAST 1051
+#define IDC_NICK 1053
+#define IDC_BTN_EXPORT 1054
+#define IDC_FONTLIST 1056
+#define IDC_CHOOSEFONT 1057
+#define IDC_EFFECT 1058
+#define IDC_EFFECT_STATIC 1059
+#define IDC_GENDER 1060
+#define IDC_CITY 1061
+#define IDC_MARITAL 1061
+#define IDC_STATE 1062
+#define IDC_COUNTRY 1063
+#define IDC_AGE 1064
+#define IDC_ZIP 1064
+#define IDC_PHONE 1065
+#define IDC_STREET 1065
+#define IDC_COMPANY 1066
+#define IDC_LANGUAGE1 1066
+#define IDC_TIMEZONE 1067
+#define IDC_DEPARTMENT 1067
+#define IDC_LOCALTIME 1068
+#define IDC_DETAILS 1069
+#define IDC_POSITION 1069
+#define IDC_LANGUAGE2 1069
+#define IDC_ADD 1070
+#define IDC_LANGUAGE3 1070
+#define IDC_TIMEZONESELECT 1071
+#define IDC_MOREOPTIONS 1071
+#define IDC_USERMENU 1071
+#define IDC_SLOT_L 1075
+#define IDC_SLOT 1076
+#define IDC_MAX_ICONS_L 1077
+#define IDC_EDIT 1078
+#define IDC_LIST 1079
+#define IDC_HISTORY 1080
+#define IDC_MENUOBJECTS 1081
+#define IDC_MENUITEMS 1082
+#define IDC_NOTSUPPORTWARNING 1083
+#define IDC_INSERTSEPARATOR 1084
+#define IDC_GENMENU_SERVICE 1085
+#define IDC_GENMENU_CUSTOMNAME 1086
+#define IDC_GENMENU_SET 1087
+#define IDC_GENMENU_DEFAULT 1089
+#define IDC_CANVAS 1094
+#define IDC_CANVAS2 1095
+#define IDC_TXT_MULTILINE 1096
+#define IDC_TXT_PASSWORD 1097
+#define IDC_TXT_COMBO 1098
+#define IDC_TXT_RICHEDIT 1099
+#define IDC_BUILDTIME 1108
+#define IDC_CREDITSFILE 1109
+#define IDC_NUMBER 1113
+#define IDC_FINDWHAT 1131
+#define IDC_FIND 1132
+#define IDC_FILE 1133
+#define IDC_PROFILELIST 1134
+#define IDC_EFFECT_COMBO 1140
+#define IDC_TABS 1141
+#define IDC_RESULTS 1142
+#define IDC_STATUS 1144
+#define IDC_USEPROXY 1148
+#define IDC_PROXYAUTH 1149
+#define IDC_PROXYHOST 1150
+#define IDC_PROXYPORT 1151
+#define IDC_PROXYUSER 1152
+#define IDC_PROXYPASS 1153
+#define IDC_STATIC12 1155
+#define IDC_STATIC21 1156
+#define IDC_STATIC22 1157
+#define IDC_STATIC31 1158
+#define IDC_STATIC32 1159
+#define IDC_CHANGE 1164
+#define IDC_PREVIEW 1165
+#define IDC_CHOOSE 1169
+#define IDC_TO 1170
+#define IDC_VERSION 1179
+#define IDC_ICONSET 1183
+#define IDC_BROWSE 1184
+#define IDC_RUNATSTARTBROWSE 1185
+#define IDC_PAGETREE 1186
+#define IDC_RUNNOW 1186
+#define IDC_RETRIEVING 1193
+#define IDC_GETMORE 1200
+#define IDC_VISIBLEICON 1204
+#define IDC_INVISIBLEICON 1205
+#define IDC_FILEICON 1206
+#define IDC_ONLINEICON 1207
+#define IDC_FILENAMES 1208
+#define IDC_ALLICON 1208
+#define IDC_DONTREPLY 1209
+#define IDC_NONEICON 1209
+#define IDC_USEPREVIOUS 1210
+#define IDC_TYPINGICON 1210
+#define IDC_NODIALOG 1211
+#define IDC_USESPECIFIC 1212
+#define IDC_FILEDIR 1213
+#define IDC_TRANSFERCOMPLETED 1214
+#define IDC_ALLFILESPROGRESS 1217
+#define IDC_WHITERECT 1221
+#define IDC_ALLSPEED 1221
+#define IDC_FIRSTNAME 1224
+#define IDC_LASTNAME 1225
+#define IDC_DOBDAY 1226
+#define IDC_DOBMONTH 1227
+#define IDC_WEBPAGE 1228
+#define IDC_DOBYEAR 1228
+#define IDC_UPDATING 1231
+#define IDC_NAMEORDER 1234
+#define IDC_RECONNECTREQD 1239
+#define IDC_IMPORT 1241
+#define IDC_TOMAIN 1243
+#define IDC_TOPROTO 1244
+#define IDC_PROTOLIST 1245
+#define IDC_TODEFICON 1246
+#define IDC_IMPORTMULTI 1247
+#define IDC_EFFECT_COLOUR1 1266
+#define IDC_BKGCOLOUR 1269
+#define IDC_EFFECT_COLOUR2 1269
+#define IDC_FILENAME 1271
+#define IDC_FONTCOLOUR 1282
+#define IDC_INTERESTS 1305
+#define IDC_EMAILS 1306
+#define IDC_PAST 1307
+#define IDC_PHONES 1308
+#define IDC_SMS 1310
+#define IDC_AREA 1312
+#define IDC_UPDATE 1313
+#define IDC_ININAME 1333
+#define IDC_VIEWINI 1334
+#define IDC_SECURITYINFO 1335
+#define IDC_SETTINGNAME 1336
+#define IDC_NEWVALUE 1337
+#define IDC_WARNNOMORE 1338
+#define IDC_DELETE 1339
+#define IDC_RECYCLE 1340
+#define IDC_NEWNAME 1341
+#define IDC_MOVE 1342
+#define IDC_LEAVE 1343
+#define IDC_MODERN 1346
+#define IDC_CATEGORYLIST 1366
+#define IDC_LOADICONS 1369
+#define IDC_STICONSGROUP 1371
+#define IDC_MSGICON 1375
+#define IDC_URLICON 1376
+#define IDC_STNOPAGE 1377
+#define IDC_STCHECKMARKS 1380
+#define IDC_STATUSBAR 1389
+#define IDC_PROTOIDGROUP 1392
+#define IDC_BYPROTOID 1393
+#define IDC_PROTOID 1394
+#define IDC_EMAILGROUP 1395
+#define IDC_BYEMAIL 1396
+#define IDC_STNAMENICK 1397
+#define IDC_NAMEGROUP 1398
+#define IDC_BYNAME 1399
+#define IDC_STNAMEFIRST 1400
+#define IDC_STNAMELAST 1401
+#define IDC_ADVANCEDGROUP 1402
+#define IDC_BYADVANCED 1403
+#define IDC_ADVANCED 1404
+#define IDC_PROTOCOLORDER 1405
+#define IDC_TINYEXTENDEDGROUP 1406
+#define IDC_RESETPROTOCOLDATA 1407
+#define IDC_BYCUSTOM 1408
+#define IDC_PROTOCOLORDERWARNING 1409
+#define IDC_CHK_SUPPRESSSTATUS 1413
+#define IDC_RAD_UID 1414
+#define IDC_RAD_DID 1415
+#define IDC_RAD_MSG 1416
+#define IDC_RAD_MENU 1417
+#define IDC_RAD_INFO 1418
+#define IDC_RAD_NICK 1419
+#define IDC_RAD_NAME 1420
+#define IDC_CHK_LOCKHANDLE 1421
+#define IDC_PROG 1433
+#define IDC_STSIMPLERIGHT 1440
+#define IDC_NETLIBUSERS 1443
+#define IDC_STOFTENPORT 1445
+#define IDC_STATIC52 1447
+#define IDC_STATIC43 1448
+#define IDC_LOGOPTIONS 1449
+#define IDC_PORTSRANGE 1450
+#define IDC_PORTSRANGEO 1452
+#define IDC_STATIC54 1453
+#define IDC_TOOUTPUTDEBUGSTRING 1455
+#define IDC_TOFILE 1456
+#define IDC_RUNATSTART 1458
+#define IDC_DUMPSENT 1464
+#define IDC_DUMPPROXY 1466
+#define IDC_TEXTDUMPS 1467
+#define IDC_AUTODETECTTEXT 1468
+#define IDC_TIMEFORMAT 1469
+#define IDC_FILENAMEBROWSE 1470
+#define IDC_SHOWTHISDLGATSTART 1471
+#define IDC_DUMPSSL 1473
+#define IDC_FILEDIRBROWSE 1475
+#define IDC_SCANCMDLINEBROWSE 1476
+#define IDC_ALLTRANSFERRED 1477
+#define IDC_OPENFOLDER 1478
+#define IDC_OPENFILE 1479
+#define IDC_TOTALSIZE 1480
+#define IDC_CONTACT 1480
+#define IDC_AUTOACCEPT 1484
+#define IDC_SCANCMDLINE 1485
+#define IDC_WARNBEFOREOPENING 1488
+#define IDC_SCANDURINGDL 1489
+#define IDC_SCANAFTERDL 1490
+#define IDC_NOSCANNER 1491
+#define IDC_ST_CMDLINE 1492
+#define IDC_ST_CMDLINEHELP 1493
+#define IDC_PROPERTIES 1496
+#define IDC_RESUME 1497
+#define IDC_EXISTINGICON 1499
+#define IDC_RESUMEALL 1500
+#define IDC_OVERWRITE 1501
+#define IDC_OVERWRITEALL 1502
+#define IDC_SKIP 1503
+#define IDC_EXISTINGSIZE 1506
+#define IDC_EXISTINGDATE 1507
+#define IDC_EXISTINGTYPE 1508
+#define IDC_NEWICON 1509
+#define IDC_NEWSIZE 1510
+#define IDC_NEWDATE 1511
+#define IDC_NEWTYPE 1512
+#define IDC_SAVEAS 1513
+#define IDC_AUTORENAME 1514
+#define IDC_ASK 1516
+#define IDC_RENAME 1519
+#define IDC_VIRUSSCANNERGROUP 1520
+#define IDC_HIDE 1534
+#define IDC_TOPLINE 1535
+#define IDC_MYHANDLE 1540
+#define IDC_GROUP 1541
+#define IDC_ADDED 1542
+#define IDC_AUTH 1543
+#define IDC_OPEN_WINDOW 1544
+#define IDC_DELETEHISTORY 1560
+#define IDC_AUTHREQ 1577
+#define IDC_PROTOCOL 1580
+#define IDC_CONTRIBLINK 1586
+#define IDC_DEVS 1589
+#define IDC_TXT_TITLE1 1592
+#define IDC_TXT_TITLE2 1593
+#define IDC_TXT_TITLE3 1594
+#define IDC_TXT_TITLE4 1595
+#define IDC_TXT_TITLE5 1596
+#define IDC_TXT_TITLE6 1597
+#define IDC_TXT_TITLE7 1598
+#define IDC_TXT_TITLE8 1599
+#define IDC_PREVIEWSMALL 1600
+#define IDC_PREVIEWGENERAL 1601
+#define IDC_PREVIEWHEADER 1602
+#define IDC_CHOOSEFONTHEADER 1603
+#define IDC_CHOOSEFONTGENERAL 1604
+#define IDC_CHOOSEFONTSMALL 1605
+#define IDC_IDLEONWINDOWS 1637
+#define IDC_IDLEONMIRANDA 1638
+#define IDC_SCREENSAVER 1642
+#define IDC_LOCKED 1643
+#define IDC_IDLESHORT 1644
+#define IDC_UPGRADE 1645
+#define IDC_FULLSCREEN 1645
+#define IDC_IDLE1STTIME 1646
+#define IDC_IDLESOUNDSOFF 1647
+#define IDC_IDLEPRIVATE 1649
+#define IDC_AASTATUS 1650
+#define IDC_AASHORTIDLE 1651
+#define IDC_SOUNDTREE 1657
+#define IDC_LOCATION 1659
+#define IDC_SGROUP 1660
+#define IDC_SLOC 1661
+#define IDC_PROFILENAME 1673
+#define IDC_PROFILEDRIVERS 1674
+#define IDC_PLUGLIST 1676
+#define IDC_PLUGINLONGINFO 1677
+#define IDC_PLUGINAUTHOR 1679
+#define IDC_PLUGININFOFRAME 1680
+#define IDC_PLUGINCPYR 1681
+#define IDC_PLUGINURL 1682
+#define IDC_PLUGINPID 1683
+#define IDC_PLUGINEMAIL 1684
+#define IDC_IDLESPIN 1687
+#define IDC_NODBDRIVERS 1690
+#define IDC_IDLESTATUSLOCK 1691
+#define IDC_RESTART 1692
+#define IDC_TAB 1693
+#define IDC_IDLETERMINAL 1694
+#define IDC_ENABLESOUNDS 1695
+#define IDC_NEWVERSIONLABEL 1696
+#define IDC_CURRENTVERSION 1697
+#define IDC_DOWNLOAD 1699
+#define IDC_ENABLEUPDATES 1700
+#define IDC_ENABLEALPHA 1701
+#define IDC_RESETMENU 1702
+#define IDC_KEYWORD_FILTER 1704
+#define IDC_FILTER 1706
+#define IDC_PATH 1707
+#define IDC_LV_HOTKEYS 1708
+#define IDC_HOTKEY 1709
+#define IDC_SM_COMBO 1710
+#define IDC_SM_LABEL 1711
+#define IDC_REMOVE 1712
+#define IDC_ACCLIST 1713
+#define IDC_ACCNAME 1714
+#define IDC_PROTOTYPECOMBO 1715
+#define IDC_ACCINTERNALNAME 1716
+#define IDC_OPTIONS 1717
+#define IDC_CLEAR 1718
+#define IDC_PROGRESSBAR 1719
+#define IDC_TXT_ACCOUNT 1720
+#define IDC_TXT_ADDITIONAL 1721
+#define IDC_TXT_INFO 1722
+#define IDC_CONTACTNAME 1724
+#define IDC_FRAME 1725
+#define IDC_LST_STATUS 1726
+#define IDC_ALLPRECENTS 1727
+#define IDC_ENABLE_KEYWORDFILTERING 1729
+#define IDC_BKGCOLOUR_STATIC 1730
+#define IDC_LST_ERRORS 1731
+#define IDC_LV_ERRORS 1733
+#define IDC_HEADERBAR 1734
+#define IDC_LV_LEGEND 1735
+#define IDC_ENABLEBETA 1737
+#define IDC_ENABLESTABLE 1738
+#define IDC_STORELASTPROFILE 1739
+#define IDC_RADIO1 1740
+#define IDC_RADIO2 1741
+#define IDC_ADDCHECK 1742
+#define IDC_GETMOREPLUGINS 1744
+#define IDC_DISABLEMENUICONS 1745
+#define IDC_COMBO1 1746
+#define IDC_LANGUAGES 1746
+#define IDC_EFFECT_COLOUR_TEXT1 1853
+#define IDC_EFFECT_COLOUR_SPIN1 1854
+#define IDC_EXTRAORDER 1889
+#define IDC_EFFECT_COLOUR_TEXT2 11803
+#define IDC_EFFECT_COLOUR_SPIN2 11806
+#define IDI_SEARCHALL 32548
+#define ID_ICQ_EXIT 40001
+#define IDM_COPY 40001
+#define ID_RESET 40002
+#define POPUP_HIDEEMPTYGROUPS 40003
+#define POPUP_NEWSUBGROUP 40004
+#define POPUP_HIDEOFFLINE 40005
+#define POPUP_GROUPHIDEOFFLINE 40006
+#define POPUP_HIDEOFFLINEROOT 40007
+#define POPUP_DISABLEGROUPS 40008
+#define IDC_SENDMESSAGE 40009
+#define IDM_COPYALL 40011
+#define IDM_SELECTALL 40012
+#define IDM_CLEAR 40013
+#define IDM_OPENNEW 40014
+#define IDM_OPENEXISTING 40015
+#define IDM_COPYLINK 40016
+#define POPUP_HIDEMIRANDA 40017
+#define ID_CANCELCHANGE 40018
+#define ID_TRAY_HIDE 40038
+#define ID_TRAY_EXIT 40040
+#define POPUP_NEWGROUP 40050
+#define POPUP_RENAMEGROUP 40052
+#define POPUP_DELETEGROUP 40053
+#define ID_GROUP 40066
+#define ID_UNGROUP 40067
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 329
+#define _APS_NEXT_COMMAND_VALUE 40018
+#define _APS_NEXT_CONTROL_VALUE 1747
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/src/mir_app/src/searchresults.cpp b/src/mir_app/src/searchresults.cpp new file mode 100644 index 0000000000..af1bf873b6 --- /dev/null +++ b/src/mir_app/src/searchresults.cpp @@ -0,0 +1,372 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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];
+
+ struct FindAddDlgData *dat = (struct FindAddDlgData*)GetWindowLongPtr(GetParent(hwndResults), GWLP_USERDATA);
+ int columnCount = Header_GetItemCount(ListView_GetHeader(hwndResults));
+ if (columnCount != NUM_COLUMNID) return;
+ ListView_GetColumnOrderArray(hwndResults, columnCount, columnOrder);
+ for (int i = 0; i < NUM_COLUMNID; i++) {
+ char szSetting[32];
+ mir_snprintf(szSetting, "ColOrder%d", i);
+ db_set_b(NULL, "FindAdd", szSetting, (BYTE)columnOrder[i]);
+ mir_snprintf(szSetting, "ColWidth%d", i);
+ db_set_w(NULL, "FindAdd", szSetting, (WORD)ListView_GetColumnWidth(hwndResults, i));
+ }
+ db_set_b(NULL, "FindAdd", "SortColumn", (BYTE)dat->iLastColumnSortIndex);
+ db_set_b(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];
+ bool colOrdersValid;
+
+ defaultColumnSizes[COLUMNID_PROTO] = GetSystemMetrics(SM_CXSMICON) + 4;
+ FindAddDlgData *dat = (FindAddDlgData*)GetWindowLongPtr(GetParent(hwndResults), GWLP_USERDATA);
+
+ columnCount = NUM_COLUMNID;
+ colOrdersValid = true;
+ for (int 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) {
+ bNeedsFree = TRUE;
+ lvc.pszText = mir_a2t((char*)CallProtoServiceInt(NULL, szProto, PS_GETCAPS, PFLAG_UNIQUEIDTEXT, 0));
+ }
+ else lvc.pszText = _T("ID");
+ }
+ else lvc.mask &= ~LVCF_TEXT;
+ mir_snprintf(szSetting, "ColWidth%d", i);
+ lvc.cx = db_get_w(NULL, "FindAdd", szSetting, defaultColumnSizes[i]);
+ ListView_InsertColumn(hwndResults, i, (LPARAM)&lvc);
+
+ if (bNeedsFree)
+ mir_free(lvc.pszText);
+ }
+ mir_snprintf(szSetting, "ColOrder%d", i);
+ columnOrder[i] = db_get_b(NULL, "FindAdd", szSetting, -1);
+ if (columnOrder[i] == -1 || columnOrder[i] >= NUM_COLUMNID)
+ colOrdersValid = false;
+ }
+
+ if (colOrdersValid)
+ ListView_SetColumnOrderArray(hwndResults, columnCount, columnOrder);
+
+ dat->iLastColumnSortIndex = db_get_b(NULL, "FindAdd", "SortColumn", COLUMNID_NICK);
+ if (dat->iLastColumnSortIndex >= columnCount) dat->iLastColumnSortIndex = COLUMNID_NICK;
+ dat->bSortAscending = db_get_b(NULL, "FindAdd", "SortAscending", TRUE);
+
+ hdi.mask = HDI_FORMAT;
+ hdi.fmt = HDF_LEFT | HDF_STRING | (dat->bSortAscending ? HDF_SORTDOWN : HDF_SORTUP);
+ 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);
+ struct ListSearchResult *lsr1, *lsr2;
+ HWND hList = GetDlgItem((HWND)lParamSort, IDC_RESULTS);
+
+ int sortMultiplier = dat->bSortAscending ? 1 : -1;
+ int 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 mir_strcmp(lsr1->szProto, lsr2->szProto)*sortMultiplier;
+ case COLUMNID_HANDLE:
+ return mir_tstrcmpi(lsr1->psr.id.t, lsr2->psr.id.t)*sortMultiplier;
+ case COLUMNID_NICK:
+ return mir_tstrcmpi(lsr1->psr.nick.t, lsr2->psr.nick.t)*sortMultiplier;
+ case COLUMNID_FIRST:
+ return mir_tstrcmpi(lsr1->psr.firstName.t, lsr2->psr.firstName.t)*sortMultiplier;
+ case COLUMNID_LAST:
+ return mir_tstrcmpi(lsr1->psr.lastName.t, lsr2->psr.lastName.t)*sortMultiplier;
+ case COLUMNID_EMAIL:
+ return mir_tstrcmpi(lsr1->psr.email.t, lsr2->psr.email.t)*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 mir_tstrcmpi(szText1, szText2)*sortMultiplier;
+ }
+ return 0;
+}
+
+void FreeSearchResults(HWND hwndResults)
+{
+ LV_ITEM lvi;
+ for (lvi.iItem = ListView_GetItemCount(hwndResults) - 1; lvi.iItem >= 0; lvi.iItem--) {
+ lvi.mask = LVIF_PARAM;
+ ListView_GetItem(hwndResults, &lvi);
+ struct ListSearchResult *lsr = (struct ListSearchResult*)lvi.lParam;
+ if (lsr == NULL) continue;
+ mir_free(lsr->psr.id.t);
+ mir_free(lsr->psr.email.t);
+ mir_free(lsr->psr.nick.t);
+ mir_free(lsr->psr.firstName.t);
+ mir_free(lsr->psr.lastName.t);
+ 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,
+ TranslateT("Could not start a search on '%s', there was a problem - is %s connected?"),
+ protoName, protoName);
+ mir_free((char*)arg);
+ }
+ else mir_tstrncpy(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)
+{
+ if (szProto == NULL) {
+ int failures = 0;
+ dat->searchCount = 0;
+ dat->search = (struct ProtoSearchInfo*)mir_calloc(sizeof(struct ProtoSearchInfo) * accounts.getCount());
+ for (int i = 0; i < accounts.getCount(); i++) {
+ PROTOACCOUNT *pa = accounts[i];
+ if (!Proto_IsAccountEnabled(pa)) continue;
+ DWORD caps = (DWORD)CallProtoServiceInt(NULL, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (!(caps&requiredCapability)) continue;
+ dat->search[dat->searchCount].hProcess = (HANDLE)CallProtoServiceInt(NULL, 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)CallProtoServiceInt(NULL, 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;
+}
+
+void SetStatusBarSearchInfo(HWND hwndStatus, struct FindAddDlgData *dat)
+{
+ CMString str;
+
+ if (dat->searchCount != 0) {
+ str = TranslateT("Searching");
+ for (int i = 0; i < dat->searchCount; i++) {
+ PROTOACCOUNT *pa = Proto_GetAccount(dat->search[i].szProto);
+ if (!pa)
+ continue;
+
+ str.Append(i ? _T(", ") : _T(" "));
+ str.Append(pa->tszAccountName);
+ }
+ }
+ else str = TranslateT("Idle");
+
+ SendMessage(hwndStatus, SB_SETTEXT, 0, (LPARAM)str.c_str());
+}
+
+struct ProtoResultsSummary {
+ const char *szProto;
+ int count;
+};
+
+void SetStatusBarResultInfo(HWND hwndDlg)
+{
+ HWND hwndResults = GetDlgItem(hwndDlg, IDC_RESULTS);
+ CMString str;
+
+ int total = ListView_GetItemCount(hwndResults);
+ if (total != 0) {
+ LV_ITEM lvi;
+ struct ProtoResultsSummary *subtotal = NULL;
+ int subtotalCount = 0;
+ for (lvi.iItem = total - 1; lvi.iItem >= 0; lvi.iItem--) {
+ lvi.mask = LVIF_PARAM;
+ ListView_GetItem(hwndResults, &lvi);
+ struct ListSearchResult *lsr = (struct ListSearchResult*)lvi.lParam;
+ if (lsr == NULL)
+ continue;
+
+ int i = 0;
+ while (i < subtotalCount) {
+ if (subtotal[i].szProto == lsr->szProto) {
+ subtotal[i].count++;
+ break;
+ }
+ i++;
+ }
+ if (i == subtotalCount) {
+ subtotal = (struct ProtoResultsSummary*)mir_realloc(subtotal, sizeof(struct ProtoResultsSummary)*(subtotalCount + 1));
+ subtotal[subtotalCount].szProto = lsr->szProto;
+ subtotal[subtotalCount++].count = 1;
+ }
+ }
+ if (subtotalCount == 1) {
+ PROTOACCOUNT *pa = Proto_GetAccount(subtotal[0].szProto);
+ if (pa == NULL) {
+ mir_free(subtotal);
+ return;
+ }
+ else if (total == 1)
+ str.AppendFormat(TranslateT("1 %s user found"), pa->tszAccountName);
+ else
+ str.AppendFormat(TranslateT("%d %s users found"), total, pa->tszAccountName);
+ }
+ else {
+ str.AppendFormat(TranslateT("%d users found ("), total);
+ for (int i = 0; i < subtotalCount; i++) {
+ PROTOACCOUNT *pa = Proto_GetAccount(subtotal[i].szProto);
+ if (pa == NULL)
+ continue;
+
+ if (i)
+ str.Append(_T(", "));
+
+ str.AppendFormat(_T("%d %s"), subtotal[i].count, pa->tszAccountName);
+ }
+ str.AppendChar(')');
+ }
+ mir_free(subtotal);
+ }
+ else str = TranslateT("No users found");
+
+ SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_SETTEXT, 2, (LPARAM)str.c_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)
+{
+ 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);
+ struct ListSearchResult *lsr = (struct ListSearchResult*)lvi.lParam;
+
+ HMENU hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_CONTEXT));
+ HMENU hPopupMenu = GetSubMenu(hMenu, 4);
+ TranslateMenu(hPopupMenu);
+ int commandId = TrackPopupMenu(hPopupMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD, x, y, 0, hwndDlg, NULL);
+ switch (commandId) {
+ case IDC_ADD:
+ {
+ ADDCONTACTSTRUCT acs = { 0 };
+ acs.handleType = HANDLE_SEARCHRESULT;
+ acs.szProto = lsr->szProto;
+ acs.psr = &lsr->psr;
+ CallService(MS_ADDCONTACT_SHOW, (WPARAM)hwndDlg, (LPARAM)&acs);
+ }
+ break;
+ case IDC_DETAILS:
+ {
+ MCONTACT hContact = (MCONTACT)CallProtoServiceInt(NULL, lsr->szProto, PS_ADDTOLIST, PALF_TEMPORARY, (LPARAM)&lsr->psr);
+ CallService(MS_USERINFO_SHOWDIALOG, hContact, 0);
+ }
+ break;
+ case IDC_SENDMESSAGE:
+ {
+ MCONTACT hContact = (MCONTACT)CallProtoServiceInt(NULL, lsr->szProto, PS_ADDTOLIST, PALF_TEMPORARY, (LPARAM)&lsr->psr);
+ CallService(MS_MSG_SENDMESSAGE, hContact, 0);
+ }
+ break;
+ }
+ DestroyMenu(hPopupMenu);
+ DestroyMenu(hMenu);
+}
diff --git a/src/mir_app/src/services.cpp b/src/mir_app/src/services.cpp new file mode 100644 index 0000000000..85f6837853 --- /dev/null +++ b/src/mir_app/src/services.cpp @@ -0,0 +1,501 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include "FontService.h"
+
+#define FontID_OLDSIZE (offsetof(FontID, backgroundGroup))
+#define FontIDW_OLDSIZE (offsetof(FontIDW, backgroundGroup))
+
+void ConvertFontSettings(FontSettings *fs, FontSettingsW *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);
+}
+
+bool ConvertFontID(FontID *fid, FontIDW *fidw)
+{
+ if (fid->cbSize != sizeof(FontID) && fid->cbSize != FontID_OLDSIZE)
+ return false;
+
+ memset(fidw, 0, sizeof(FontIDW));
+ fidw->cbSize = sizeof(FontIDW);
+ strncpy_s(fidw->dbSettingsGroup, fid->dbSettingsGroup, _TRUNCATE);
+ strncpy_s(fidw->prefix, fid->prefix, _TRUNCATE);
+ 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_OLDSIZE) {
+ MultiByteToWideChar(code_page, 0, fid->backgroundGroup, -1, fidw->backgroundGroup, 64);
+ MultiByteToWideChar(code_page, 0, fid->backgroundName, -1, fidw->backgroundName, 64);
+ }
+ return true;
+}
+
+bool ConvertColourID(ColourID *cid, ColourIDW *cidw)
+{
+ if (cid->cbSize != sizeof(ColourID))
+ return false;
+
+ cidw->cbSize = sizeof(ColourIDW);
+
+ strncpy_s(cidw->dbSettingsGroup, cid->dbSettingsGroup, _TRUNCATE);
+ strncpy_s(cidw->setting, cid->setting, _TRUNCATE);
+ 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);
+ return true;
+}
+
+bool ConvertEffectID(EffectID *eid, EffectIDW *eidw)
+{
+ if (eid->cbSize != sizeof(EffectID))
+ return false;
+
+ eidw->cbSize = sizeof(EffectIDW);
+
+ strncpy_s(eidw->dbSettingsGroup, eid->dbSettingsGroup, _TRUNCATE);
+ strncpy_s(eidw->setting, eid->setting, _TRUNCATE);
+ 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);
+ return true;
+}
+
+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);
+}
+
+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)
+{
+ GetDefaultFontSetting(lf, colour);
+
+ char idstr[256];
+ if (flags & FIDF_APPENDNAME)
+ mir_snprintf(idstr, "%sName", prefix);
+ else
+ strncpy_s(idstr, prefix, _TRUNCATE);
+
+ int retval = 0;
+ ptrT tszGroup(db_get_tsa(NULL, settings_group, idstr));
+ if (tszGroup != NULL)
+ _tcsncpy_s(lf->lfFaceName, tszGroup, _TRUNCATE);
+ else
+ retval = 1;
+
+ if (colour) {
+ mir_snprintf(idstr, "%sCol", prefix);
+ *colour = db_get_dw(NULL, settings_group, idstr, *colour);
+ }
+
+ mir_snprintf(idstr, "%sSize", prefix);
+ lf->lfHeight = (char)db_get_b(NULL, settings_group, idstr, lf->lfHeight);
+
+ mir_snprintf(idstr, "%sSty", prefix);
+ BYTE style = (BYTE)db_get_b(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, "%sSet", prefix);
+ lf->lfCharSet = db_get_b(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
+ HFONT hFont = CreateFontIndirect(lf);
+ HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
+
+ TEXTMETRIC tm;
+ GetTextMetrics(hdc, &tm);
+ lf->lfHeight = -(lf->lfHeight - tm.tmInternalLeading);
+
+ SelectObject(hdc, hOldFont);
+ DeleteObject(hFont);
+ }
+
+ ReleaseDC(0, hdc);
+ }
+
+ return retval;
+}
+
+int CreateFromFontSettings(FontSettingsT *fs, LOGFONT *lf)
+{
+ GetDefaultFontSetting(lf, 0);
+
+ _tcsncpy_s(lf->lfFaceName, fs->szFace, _TRUNCATE);
+
+ 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(FontIDW *font_id, FontSettingsT *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 = 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;
+ _tcsncpy_s(fontsettings->szFace, lf.lfFaceName, _TRUNCATE);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// RegisterFont service
+
+static int sttRegisterFontWorker(FontIDW *font_id, int hLangpack)
+{
+ if (font_id->cbSize != sizeof(FontIDW) && font_id->cbSize != FontIDW_OLDSIZE)
+ return -1;
+
+ for (int i = 0; i < font_id_list.getCount(); i++) {
+ FontInternal& F = font_id_list[i];
+ if (!mir_tstrcmp(F.group, font_id->group) && !mir_tstrcmp(F.name, font_id->name) && !(F.flags & FIDF_ALLOWREREGISTER))
+ return 1;
+ }
+
+ char idstr[256];
+ mir_snprintf(idstr, "%sFlags", font_id->prefix);
+ db_set_dw(0, font_id->dbSettingsGroup, idstr, font_id->flags);
+
+ FontInternal* newItem = new FontInternal;
+ memset(newItem, 0, sizeof(FontInternal));
+ memcpy(newItem, font_id, font_id->cbSize);
+ newItem->hLangpack = hLangpack;
+
+ if (!mir_tstrcmp(newItem->deffontsettings.szFace, _T("MS Shell Dlg"))) {
+ LOGFONT lf;
+ SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, FALSE);
+ mir_tstrncpy(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;
+}
+
+INT_PTR RegisterFontW(WPARAM wParam, LPARAM lParam)
+{
+ return sttRegisterFontWorker((FontIDW*)wParam, (int)lParam);
+}
+
+INT_PTR RegisterFont(WPARAM wParam, LPARAM lParam)
+{
+ FontIDW temp;
+ if (!ConvertFontID((FontID*)wParam, &temp)) return -1;
+ return sttRegisterFontWorker(&temp, (int)lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// GetFont service
+
+static INT_PTR sttGetFontWorker(FontIDW *font_id, LOGFONT *lf)
+{
+ COLORREF colour;
+
+ for (int i = 0; i < font_id_list.getCount(); i++) {
+ FontInternal& 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 = F.deffontsettings.colour;
+ }
+
+ return colour;
+ }
+ }
+
+ GetDefaultFontSetting(lf, &colour);
+ return colour;
+}
+
+INT_PTR GetFontW(WPARAM wParam, LPARAM lParam)
+{
+ return sttGetFontWorker((FontIDW*)wParam, (LOGFONT*)lParam);
+}
+
+INT_PTR GetFont(WPARAM wParam, LPARAM lParam)
+{
+ FontIDW temp;
+ if (!ConvertFontID((FontID*)wParam, &temp))
+ return -1;
+
+ LOGFONT lftemp;
+ int ret = sttGetFontWorker(&temp, &lftemp);
+ ConvertLOGFONT(&lftemp, (LOGFONTA*)lParam);
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KillModuleFonts(int hLangpack)
+{
+ for (int i = font_id_list.getCount() - 1; i >= 0; i--)
+ if (font_id_list[i].hLangpack == hLangpack)
+ font_id_list.remove(i);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// RegisterColour service
+
+void UpdateColourSettings(ColourIDW *colour_id, COLORREF *colour)
+{
+ *colour = (COLORREF)db_get_dw(NULL, colour_id->dbSettingsGroup, colour_id->setting, colour_id->defcolour);
+}
+
+static INT_PTR sttRegisterColourWorker(ColourIDW *colour_id, int hLangpack)
+{
+ if (colour_id->cbSize != sizeof(ColourIDW))
+ return -1;
+
+ for (int i = 0; i < colour_id_list.getCount(); i++) {
+ ColourInternal& C = colour_id_list[i];
+ if (!mir_tstrcmp(C.group, colour_id->group) && !mir_tstrcmp(C.name, colour_id->name))
+ return 1;
+ }
+
+ ColourInternal* newItem = new ColourInternal;
+ memset(newItem, 0, sizeof(ColourInternal));
+ memcpy(newItem, colour_id, sizeof(ColourIDW));
+ newItem->hLangpack = hLangpack;
+ UpdateColourSettings(colour_id, &newItem->value);
+ colour_id_list.insert(newItem);
+ return 0;
+}
+
+INT_PTR RegisterColourW(WPARAM wParam, LPARAM lParam)
+{
+ return sttRegisterColourWorker((ColourIDW*)wParam, (int)lParam);
+}
+
+INT_PTR RegisterColour(WPARAM wParam, LPARAM lParam)
+{
+ ColourIDW temp;
+ if (!ConvertColourID((ColourID*)wParam, &temp)) return -1;
+ return sttRegisterColourWorker(&temp, (int)lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// GetColour service
+
+static INT_PTR sttGetColourWorker(ColourIDW *colour_id)
+{
+ for (int i = 0; i < colour_id_list.getCount(); i++) {
+ ColourInternal& C = colour_id_list[i];
+ if (!mir_tstrcmp(C.group, colour_id->group) && !mir_tstrcmp(C.name, colour_id->name))
+ return db_get_dw(NULL, C.dbSettingsGroup, C.setting, C.defcolour);
+ }
+
+ return -1;
+}
+
+INT_PTR GetColourW(WPARAM wParam, LPARAM)
+{
+ return sttGetColourWorker((ColourIDW*)wParam);
+}
+
+INT_PTR GetColour(WPARAM wParam, LPARAM)
+{
+ ColourIDW temp;
+ if (!ConvertColourID((ColourID*)wParam, &temp)) return -1;
+ return sttGetColourWorker(&temp);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KillModuleColours(int hLangpack)
+{
+ for (int i = colour_id_list.getCount() - 1; i >= 0; i--)
+ if (colour_id_list[i].hLangpack == hLangpack)
+ colour_id_list.remove(i);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Effects
+
+void UpdateEffectSettings(EffectIDW *effect_id, FONTEFFECT *effectsettings)
+{
+ char str[256];
+ mir_snprintf(str, "%sEffect", effect_id->setting);
+ effectsettings->effectIndex = db_get_b(NULL, effect_id->dbSettingsGroup, str, effect_id->defeffect.effectIndex);
+
+ mir_snprintf(str, "%sEffectCol1", effect_id->setting);
+ effectsettings->baseColour = db_get_dw(NULL, effect_id->dbSettingsGroup, str, effect_id->defeffect.baseColour);
+
+ mir_snprintf(str, "%sEffectCol2", effect_id->setting);
+ effectsettings->secondaryColour = db_get_dw(NULL, effect_id->dbSettingsGroup, str, effect_id->defeffect.secondaryColour);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// RegisterEffect service
+
+static INT_PTR sttRegisterEffectWorker(EffectIDW *effect_id, int hLangpack)
+{
+ if (effect_id->cbSize != sizeof(EffectIDW))
+ return -1;
+
+ for (int i = 0; i < effect_id_list.getCount(); i++) {
+ EffectInternal& E = effect_id_list[i];
+ if (!mir_tstrcmp(E.group, effect_id->group) && !mir_tstrcmp(E.name, effect_id->name))
+ return 1;
+ }
+
+ EffectInternal* newItem = new EffectInternal;
+ memset(newItem, 0, sizeof(EffectInternal));
+ memcpy(newItem, effect_id, sizeof(EffectIDW));
+ newItem->hLangpack = hLangpack;
+ UpdateEffectSettings(effect_id, &newItem->value);
+ effect_id_list.insert(newItem);
+ return 0;
+}
+
+INT_PTR RegisterEffectW(WPARAM wParam, LPARAM lParam)
+{
+ return sttRegisterEffectWorker((EffectIDW*)wParam, (int)lParam);
+}
+
+INT_PTR RegisterEffect(WPARAM wParam, LPARAM lParam)
+{
+ EffectIDW temp;
+ if (!ConvertEffectID((EffectID*)wParam, &temp)) return -1;
+ return sttRegisterEffectWorker(&temp, (int)lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// GetEffect service
+
+static INT_PTR sttGetEffectWorker(EffectIDW *effect_id, FONTEFFECT *effect)
+{
+ for (int i = 0; i < effect_id_list.getCount(); i++) {
+ EffectInternal& E = effect_id_list[i];
+ if (!_tcsncmp(E.name, effect_id->name, SIZEOF(E.name)) && !_tcsncmp(E.group, effect_id->group, SIZEOF(E.group))) {
+ FONTEFFECT temp;
+ UpdateEffectSettings(effect_id, &temp);
+
+ effect->effectIndex = temp.effectIndex;
+ effect->baseColour = temp.baseColour;
+ effect->secondaryColour = temp.secondaryColour;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+INT_PTR GetEffectW(WPARAM wParam, LPARAM lParam)
+{
+ return sttGetEffectWorker((EffectIDW*)wParam, (FONTEFFECT*)lParam);
+}
+
+INT_PTR GetEffect(WPARAM wParam, LPARAM lParam)
+{
+ EffectIDW temp;
+ if (!ConvertEffectID((EffectID*)wParam, &temp)) return -1;
+ return sttGetEffectWorker(&temp, (FONTEFFECT*)lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KillModuleEffects(int hLangpack)
+{
+ for (int i = effect_id_list.getCount() - 1; i >= 0; i--)
+ if (effect_id_list[i].hLangpack == hLangpack)
+ effect_id_list.remove(i);
+}
diff --git a/src/mir_app/src/skin.h b/src/mir_app/src/skin.h new file mode 100644 index 0000000000..d0e6eae726 --- /dev/null +++ b/src/mir_app/src/skin.h @@ -0,0 +1,75 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#define DBMODULENAME "SkinHotKeys"
+
+#define WM_HOTKEYUNREGISTERED (WM_USER+721)
+
+typedef enum { HKT_GLOBAL, HKT_LOCAL, HKT_MANUAL, HKT_COUNT } THotkeyType;
+
+struct THotkeyBoxData
+{
+ BYTE shift;
+ BYTE key;
+};
+
+struct THotkeyItem
+{
+ THotkeyType type;
+ char *pszService, *pszName; // pszName is valid _only_ for "root" hotkeys
+ TCHAR *ptszSection, *ptszDescription;
+ LPARAM lParam;
+ WORD DefHotkey, Hotkey;
+ bool Enabled;
+ int hLangpack;
+ 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
+
+ __inline TCHAR* getSection() const { return TranslateTH(hLangpack, ptszSection); }
+ __inline TCHAR* getDescr() const { return TranslateTH(hLangpack, ptszDescription); }
+};
+
+extern LIST<THotkeyItem> hotkeys;
+extern HWND g_hwndHkOptions, g_hwndHotkeyHost;
+extern DWORD g_pid, g_hkid;
+extern HANDLE hEvChanged;
+
+int HotkeyOptionsInit(WPARAM, LPARAM);
+
+void FreeHotkey(THotkeyItem *item);
+void RegisterHotkeys();
+void UnregisterHotkeys();
+
+void HotkeyEditCreate(HWND hwnd);
+void HotkeyEditDestroy(HWND hwnd);
diff --git a/src/mir_app/src/skin2opts.cpp b/src/mir_app/src/skin2opts.cpp new file mode 100644 index 0000000000..c5645c5008 --- /dev/null +++ b/src/mir_app/src/skin2opts.cpp @@ -0,0 +1,1048 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#include "IcoLib.h"
+
+struct TreeItem
+{
+ char *paramName;
+ DWORD value;
+};
+
+struct IcoLibOptsData
+{
+ HWND hwndIndex;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+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;
+
+ mir_tstrncpy(file, path, SIZEOF(file));
+ comma = _tcsrchr(file, ',');
+ if (!comma)
+ n = 0;
+ else {
+ n = _ttoi(comma + 1);
+ *comma = 0;
+ }
+ PathToAbsoluteT(file, fileFull);
+ hIcon = NULL;
+
+ //SHOULD BE REPLACED WITH GOOD ENOUGH FUNCTION
+ _ExtractIconEx(fileFull, n, cxIcon, cyIcon, &hIcon, LR_COLOR);
+ return hIcon;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib_ReleaseIcon
+// lParam: pszIconName or NULL
+// wParam: HICON or NULL
+
+MIR_APP_DLL(int) IcoLib_ReleaseIcon(HICON hIcon, char* szIconName, bool big)
+{
+ mir_cslock lck(csIconList);
+
+ IcolibItem *item = NULL;
+ 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;
+ }
+ }
+
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IconItem_GetIcon_Preview
+
+HICON IconItem_GetIcon_Preview(IcolibItem* 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 GUI service routines
+
+static void __fastcall MySetCursor(TCHAR* nCursor)
+{
+ SetCursor(LoadCursor(NULL, nCursor));
+}
+
+static void LoadSectionIcons(TCHAR *filename, SectionItem* sectionActive)
+{
+ TCHAR path[MAX_PATH];
+ mir_sntprintf(path, SIZEOF(path), _T("%s,"), filename);
+ size_t suffIndx = mir_tstrlen(path);
+
+ mir_cslock lck(csIconList);
+
+ for (int indx = 0; indx < iconList.getCount(); indx++) {
+ IcolibItem *item = iconList[indx];
+
+ if (item->default_file && item->section == sectionActive) {
+ _itot(item->default_indx, path + suffIndx, 10);
+ HICON hIcon = ExtractIconFromPath(path, item->cx, item->cy);
+ if (!hIcon)
+ continue;
+
+ SAFE_FREE((void**)&item->temp_file);
+ SafeDestroyIcon(&item->temp_icon);
+
+ item->temp_file = mir_tstrdup(path);
+ item->temp_icon = hIcon;
+ item->temp_reset = FALSE;
+ }
+ }
+}
+
+void LoadSubIcons(HWND htv, TCHAR *filename, HTREEITEM hItem)
+{
+ TVITEM tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ TreeView_GetItem(htv, &tvi);
+
+ TreeItem *treeItem = (TreeItem *)tvi.lParam;
+ SectionItem* 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)
+{
+ IcolibItem *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 };
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ TreeView_GetItem(htv, &tvi);
+
+ TreeItem *treeItem = (TreeItem *)tvi.lParam;
+ if (SECTIONPARAM_FLAGS(treeItem->value) & SECTIONPARAM_HAVEPAGE) {
+ mir_cslock lck(csIconList);
+
+ for (int indx = 0; indx < iconList.getCount(); indx++)
+ if (iconList[indx]->section == sectionList[SECTIONPARAM_INDEX(treeItem->value)])
+ UndoChanges(indx, cmd);
+ }
+
+ 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, OUF_NEWWINDOW, (LPARAM)"http://miranda-ng.org/");
+}
+
+static int OpenPopupMenu(HWND hwndDlg)
+{
+ HMENU hMenu, hPopup;
+ POINT pt;
+ int cmd;
+
+ GetCursorPos(&pt);
+ hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_ICOLIB_CONTEXT));
+ hPopup = GetSubMenu(hMenu, 0);
+ TranslateMenu(hPopup);
+ 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;
+
+ mir_tstrcpy(filter, TranslateT("Icon sets"));
+ if (bAll)
+ mir_tstrcat(filter, _T(" (*.dll;*.icl;*.exe;*.ico)"));
+ else
+ mir_tstrcat(filter, _T(" (*.dll)"));
+
+ pfilter = filter + mir_tstrlen(filter) + 1;
+ if (bAll)
+ mir_tstrcpy(pfilter, _T("*.DLL;*.ICL;*.EXE;*.ICO"));
+ else
+ mir_tstrcpy(pfilter, _T("*.DLL"));
+
+ pfilter += mir_tstrlen(pfilter) + 1;
+ mir_tstrcpy(pfilter, TranslateT("All files"));
+ mir_tstrcat(pfilter, _T(" (*)"));
+ pfilter += mir_tstrlen(pfilter) + 1;
+ mir_tstrcpy(pfilter, _T("*"));
+ pfilter += mir_tstrlen(pfilter) + 1;
+ *pfilter = '\0';
+
+ ofn.lpstrFilter = filter;
+ ofn.lpstrDefExt = _T("dll");
+ mir_tstrncpy(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;
+
+ mir_cslock lck(csIconList); // Destroy unused icons
+ for (int indx = 0; indx < iconList.getCount(); indx++) {
+ IcolibItem *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);
+ }
+ }
+}
+
+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 = SIZEOF(str);
+
+ while (tvi.hItem) {
+ TreeView_GetItem(hwndTree, &tvi);
+
+ if (!mir_tstrcmp(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));
+ }
+
+ SHAutoComplete(GetDlgItem(hwndDlg, IDC_ICONSET), 1);
+
+ SetDlgItemText(hwndDlg, IDC_ICONSET, _T("icons.dll"));
+ return TRUE;
+
+ case DM_REBUILDICONSPREVIEW:
+ {
+ MySetCursor(IDC_WAIT);
+ ListView_DeleteAllItems(hPreview);
+ HIMAGELIST hIml = ListView_GetImageList(hPreview, LVSIL_NORMAL);
+ ImageList_RemoveAll(hIml);
+
+ TCHAR filename[MAX_PATH], caption[64];
+ 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;
+ }
+
+ LVITEM lvi;
+ lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
+ lvi.iSubItem = 0;
+ lvi.iItem = 0;
+ int count = (int)_ExtractIconEx(filename, -1, 16, 16, NULL, LR_DEFAULTCOLOR);
+ for (int i = 0; i < count; lvi.iItem++, i++) {
+ mir_sntprintf(caption, SIZEOF(caption), _T("%d"), i + 1);
+ lvi.pszText = caption;
+
+ HICON hIcon = NULL;
+ if (_ExtractIconEx(filename, i, 16, 16, &hIcon, LR_DEFAULTCOLOR) == 1) {
+ 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], *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));
+ PathToRelativeT(fullPath, filename);
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = dragItem; lvi.iSubItem = 0;
+ ListView_GetItem(hPreview, &lvi);
+ mir_sntprintf(path, SIZEOF(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_SIZE: // make the dlg resizeable
+ if (!IsIconic(hwndDlg)) {
+ UTILRESIZEDIALOG urd = { 0 };
+ urd.cbSize = sizeof(urd);
+ urd.hInstance = g_hInst;
+ 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;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ EnableWindow(GetDlgItem(hwndParent, IDC_IMPORT), TRUE);
+ break;
+
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// IcoLib options window procedure
+
+static int CALLBACK DoSortIconsFunc(LPARAM lParam1, LPARAM lParam2, LPARAM)
+{
+ return mir_tstrcmpi(iconList[lParam1]->getDescr(), iconList[lParam2]->getDescr());
+}
+
+static int CALLBACK DoSortIconsFuncByOrder(LPARAM lParam1, LPARAM lParam2, LPARAM)
+{
+ return iconList[lParam1]->orderID - iconList[lParam2]->orderID;
+}
+
+static void SaveCollapseState(HWND hwndTree)
+{
+ HTREEITEM hti = TreeView_GetRoot(hwndTree);
+ while (hti != NULL) {
+ TVITEM tvi;
+ 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)
+ db_set_b(NULL, "SkinIconsUI", treeItem->paramName, TVIS_EXPANDED);
+ else
+ db_set_b(NULL, "SkinIconsUI", treeItem->paramName, 0);
+ }
+
+ HTREEITEM 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
+ //
+ {
+ mir_cslock lck(csIconList);
+
+ for (int indx = 0; indx < iconList.getCount(); indx++) {
+ iconList[indx]->temp_file = NULL;
+ iconList[indx]->temp_icon = NULL;
+ iconList[indx]->temp_reset = FALSE;
+ }
+ bNeedRebuild = FALSE;
+ }
+
+ //
+ // 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;
+ mir_tstrcpy(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;
+ tvis.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_STATE;
+ tvis.item.pszText = pItemName;
+ tvis.item.lParam = (LPARAM)treeItem;
+
+ tvis.item.state = tvis.item.stateMask = db_get_b(NULL, "SkinIconsUI", treeItem->paramName, TVIS_EXPANDED);
+ hItem = TreeView_InsertItem(hwndTree, &tvis);
+ }
+ else {
+ TVITEM tvi = { 0 };
+ tvi.hItem = hItem;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ TreeView_GetItem(hwndTree, &tvi);
+ TreeItem *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:
+ {
+ SectionItem* sectionActive = (SectionItem*)lParam;
+ EnableWindow(hPreview, sectionActive != NULL);
+
+ ListView_DeleteAllItems(hPreview);
+ HIMAGELIST hIml = ListView_GetImageList(hPreview, LVSIL_NORMAL);
+ ImageList_RemoveAll(hIml);
+
+ if (sectionActive == NULL)
+ break;
+
+ LVITEM lvi = { 0 };
+ lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
+ {
+ mir_cslock lck(csIconList);
+
+ for (int indx = 0; indx < iconList.getCount(); indx++) {
+ IcolibItem *item = iconList[indx];
+ if (item->section == sectionActive) {
+ lvi.pszText = item->getDescr();
+ HICON 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);
+ }
+ }
+ }
+
+ 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);
+ {
+ mir_cslock lck(csIconList);
+ hIcon = iconList[lvi.lParam]->temp_icon;
+ if (!hIcon)
+ hIcon = IconItem_GetIcon_Preview(iconList[lvi.lParam]);
+ }
+
+ 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:
+ {
+ LVITEM lvi = { 0 };
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = wParam;
+ ListView_GetItem(hPreview, &lvi);
+ {
+ mir_cslock lck(csIconList);
+ IcolibItem *item = iconList[lvi.lParam];
+
+ SAFE_FREE((void**)&item->temp_file);
+ SafeDestroyIcon(&item->temp_icon);
+
+ TCHAR *path = (TCHAR*)lParam;
+ item->temp_file = mir_tstrdup(path);
+ item->temp_icon = (HICON)ExtractIconFromPath(path, item->cx, item->cy);
+ item->temp_reset = FALSE;
+ }
+ DoOptionsChanged(hwndDlg);
+ }
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_IMPORT) {
+ dat->hwndIndex = CreateDialogParam(g_hInst, 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];
+
+ PathToRelativeT(file, 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:
+ {
+ mir_cslock lck(csIconList);
+
+ for (int indx = 0; indx < iconList.getCount(); indx++) {
+ IcolibItem *item = iconList[indx];
+ if (item->temp_reset) {
+ db_unset(NULL, "SkinIcons", item->name);
+ if (item->source_small != item->default_icon) {
+ IconSourceItem_Release(&item->source_small);
+ }
+ }
+ else if (item->temp_file) {
+ db_set_ts(NULL, "SkinIcons", item->name, item->temp_file);
+ IconSourceItem_Release(&item->source_small);
+ SafeDestroyIcon(&item->temp_icon);
+ }
+ }
+ }
+
+ DoIconsChanged(hwndDlg);
+ return TRUE;
+ }
+ break;
+
+ case IDC_PREVIEW:
+ if (((LPNMHDR)lParam)->code == LVN_GETINFOTIP) {
+ IcolibItem *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_s(pInfoTip->pszText, pInfoTip->cchTextMax, item->temp_file, _TRUNCATE);
+ else if (item->default_file)
+ mir_sntprintf(pInfoTip->pszText, pInfoTip->cchTextMax, _T("%s, %d"), item->default_file, item->default_indx);
+ }
+ }
+ if (bNeedRebuild) {
+ bNeedRebuild = FALSE;
+ SendMessage(hwndDlg, DM_REBUILD_CTREE, 0, 0);
+ }
+ break;
+
+ case IDC_CATEGORYLIST:
+ switch (((NMHDR*)lParam)->code) {
+ case TVN_SELCHANGED:
+ {
+ 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_DELETEITEM:
+ TreeItem *treeItem = (TreeItem *)(((LPNMTREEVIEW)lParam)->itemOld.lParam);
+ if (treeItem) {
+ mir_free(treeItem->paramName);
+ mir_free(treeItem);
+ }
+ break;
+ }
+
+ if (bNeedRebuild) {
+ {
+ mir_cslock lck(csIconList);
+ bNeedRebuild = FALSE;
+ }
+ SendMessage(hwndDlg, DM_REBUILD_CTREE, 0, 0);
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ SaveCollapseState(GetDlgItem(hwndDlg, IDC_CATEGORYLIST));
+ DestroyWindow(dat->hwndIndex);
+ {
+ mir_cslock lck(csIconList);
+ for (int indx = 0; indx < iconList.getCount(); indx++) {
+ IcolibItem *item = iconList[indx];
+
+ SAFE_FREE((void**)&item->temp_file);
+ SafeDestroyIcon(&item->temp_icon);
+ }
+ }
+
+ SAFE_FREE((void**)&dat);
+ break;
+ }
+
+ return FALSE;
+}
+
+int SkinOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.hInstance = g_hInst;
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.position = -180000000;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICOLIB);
+ odp.pszTitle = LPGEN("Icons");
+ odp.pfnDlgProc = DlgProcIcoLibOpts;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/skinicons.cpp b/src/mir_app/src/skinicons.cpp new file mode 100644 index 0000000000..93173a8f5b --- /dev/null +++ b/src/mir_app/src/skinicons.cpp @@ -0,0 +1,506 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+#include <io.h>
+
+struct StandardIconDescription
+{
+ int id;
+ LPCSTR description;
+ int resource_id;
+ int pf2;
+ LPCSTR section;
+ HANDLE hIcolib;
+};
+
+static struct StandardIconDescription mainIcons[] =
+{
+ { SKINICON_OTHER_MIRANDA, LPGEN("Miranda NG"), -IDI_MIRANDA }, // 0
+ { SKINICON_EVENT_MESSAGE, LPGEN("Message"), -IDI_RECVMSG }, // 1
+ { SKINICON_EVENT_URL, LPGEN("URL"), -IDI_URL }, // 2
+ { SKINICON_EVENT_FILE, LPGEN("File"), -IDI_FILE }, // 3
+ { SKINICON_OTHER_USERONLINE, LPGEN("User online"), -IDI_USERONLINE }, // 4
+ { SKINICON_OTHER_GROUPOPEN, LPGEN("Group (open)"), -IDI_GROUPOPEN }, // 5
+ { SKINICON_OTHER_GROUPSHUT, LPGEN("Group (closed)"), -IDI_GROUPSHUT }, // 6
+ { SKINICON_OTHER_CONNECTING, LPGEN("Connecting"), -IDI_LOAD }, // 7
+ { SKINICON_OTHER_ADDCONTACT, LPGEN("Add contact"), -IDI_ADDCONTACT }, // 8
+ { SKINICON_OTHER_USERDETAILS, LPGEN("User details"), -IDI_USERDETAILS }, // 9
+ { SKINICON_OTHER_HISTORY, LPGEN("History"), -IDI_HISTORY }, // 10
+ { SKINICON_OTHER_DOWNARROW, LPGEN("Down arrow"), -IDI_DOWNARROW }, // 11
+ { SKINICON_OTHER_FINDUSER, LPGEN("Find user"), -IDI_FINDUSER }, // 12
+ { SKINICON_OTHER_OPTIONS, LPGEN("Options"), -IDI_OPTIONS }, // 13
+ { SKINICON_OTHER_SENDEMAIL, LPGEN("Send e-mail"), -IDI_SENDEMAIL }, // 14
+ { SKINICON_OTHER_DELETE, LPGEN("Delete"), -IDI_DELETE }, // 15
+ { SKINICON_OTHER_RENAME, LPGEN("Rename"), -IDI_RENAME }, // 16
+ { SKINICON_OTHER_SMS, LPGEN("SMS"), -IDI_SMS }, // 17
+ { SKINICON_OTHER_SEARCHALL, LPGEN("Search all"), -IDI_SEARCHALL }, // 18
+ { SKINICON_OTHER_TICK, LPGEN("Tick"), -IDI_TICK }, // 19
+ { SKINICON_OTHER_NOTICK, LPGEN("No tick"), -IDI_NOTICK }, // 20
+ { SKINICON_OTHER_HELP, LPGEN("Help"), -IDI_HELP }, // 21
+ { SKINICON_OTHER_MIRANDAWEB, LPGEN("Miranda website"), -IDI_MIRANDAWEBSITE }, // 22
+ { SKINICON_OTHER_TYPING, LPGEN("Typing"), -IDI_TYPING }, // 23
+ { SKINICON_OTHER_SMALLDOT, LPGEN("Small dot"), -IDI_SMALLDOT }, // 24
+ { SKINICON_OTHER_FILLEDBLOB, LPGEN("Filled blob"), -IDI_FILLEDBLOB }, // 25
+ { SKINICON_OTHER_EMPTYBLOB, LPGEN("Empty blob"), -IDI_EMPTYBLOB }, // 26
+ { SKINICON_OTHER_UNICODE, LPGEN("Unicode plugin"), -IDI_UNICODE }, // 27
+ { SKINICON_OTHER_ANSI, LPGEN("ANSI plugin"), -IDI_ANSI }, // 28
+ { SKINICON_OTHER_LOADED, LPGEN("Running plugin"), -IDI_LOADED }, // 29
+ { SKINICON_OTHER_NOTLOADED, LPGEN("Unloaded plugin"), -IDI_NOTLOADED }, // 30
+ { SKINICON_OTHER_UNDO, LPGEN("Undo"), -IDI_UNDO }, // 31
+ { SKINICON_OTHER_WINDOW, LPGEN("Window"), -IDI_WINDOW }, // 32
+ { SKINICON_OTHER_WINDOWS, LPGEN("System"), -IDI_WINDOWS }, // 33
+ { SKINICON_OTHER_ACCMGR, LPGEN("Accounts"), -IDI_ACCMGR }, // 34
+ { SKINICON_OTHER_SHOWHIDE, LPGEN("Show/Hide"), -IDI_SHOWHIDE }, // 35
+ { SKINICON_OTHER_EXIT, LPGEN("Exit"), -IDI_EXIT }, // 36
+ { SKINICON_OTHER_MAINMENU, LPGEN("Main menu"), -IDI_MAINMENU }, // 37
+ { SKINICON_OTHER_STATUS, LPGEN("Status"), -IDI_ONLINE }, // 38
+ { SKINICON_CHAT_JOIN, LPGEN("Join chat"), -IDI_JOINCHAT }, // 39
+ { SKINICON_CHAT_LEAVE, LPGEN("Leave chat"), -IDI_LEAVECHAT }, // 40
+ { SKINICON_OTHER_GROUP, LPGEN("Move to group"), -IDI_MOVETOGROUP }, // 41
+ { SKINICON_OTHER_ON, LPGEN("On"), -IDI_ON }, // 42
+ { SKINICON_OTHER_OFF, LPGEN("Off"), -IDI_OFF }, // 43
+ { SKINICON_OTHER_LOADEDGRAY, LPGEN("Running core plugin"), -IDI_LOADED_GRAY }, // 44
+ { SKINICON_OTHER_NOTLOADEDGRAY, LPGEN("Non-loadable plugin"), -IDI_NOTLOADED_GRAY }, // 45
+ { SKINICON_OTHER_FRAME, LPGEN("Frames"), -IDI_FRAME }, // 46
+ { SKINICON_AUTH_ADD, LPGEN("Add to list"), -IDI_AUTH_ADD }, // 47
+ { SKINICON_AUTH_REQUEST, LPGEN("Request authorization"), -IDI_AUTH_REQUEST }, // 48
+ { SKINICON_AUTH_GRANT, LPGEN("Grant authorization"), -IDI_AUTH_GRANT }, // 49
+ { SKINICON_AUTH_REVOKE, LPGEN("Revoke authorization"), -IDI_AUTH_REVOKE }, // 50
+ { SKINICON_FATAL, LPGEN("Fatal error"), -IDI_MFATAL },
+ { SKINICON_ERROR, LPGEN("Error"), -IDI_MERROR },
+ { SKINICON_WARNING, LPGEN("Warning"), -IDI_MWARNING },
+ { SKINICON_INFORMATION, LPGEN("Information"), -IDI_MINFO },
+
+ { SKINICON_OTHER_VISIBLE_ALL, LPGEN("Always visible"), -IDI_ALWAYSVIS, 0, LPGEN("Contact list") },
+ { SKINICON_OTHER_INVISIBLE_ALL, LPGEN("Always invisible"), -IDI_NEVERVIS, 0, LPGEN("Contact list") },
+ { SKINICON_OTHER_STATUS_LOCKED, LPGEN("Locked status"), -IDI_STATUS_LOCKED, 0, LPGEN("Status icons") },
+};
+
+static 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 }
+};
+
+const char mainIconsFmt[] = "core_main_";
+const char statusIconsFmt[] = "core_status_";
+const char protoIconsFmt[] = LPGEN("%s icons");
+
+#define PROTOCOLS_PREFIX LPGEN("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
+ if (g_hInst != hInstance)
+ hResIcon = bShared ? LoadSmallIcon(g_hInst, lpIconName) : LoadSmallIconShared(g_hInst, lpIconName);
+ }
+ return hResIcon;
+}
+
+int ImageList_AddIcon_NotShared(HIMAGELIST hIml, LPCTSTR szResource)
+{
+ HICON hTempIcon = LoadIconEx(g_hInst, 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);
+ IcoLib_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);
+ IcoLib_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);
+ IcoLib_ReleaseIcon(hIcon, 0);
+ return res;
+}
+
+MIR_APP_DLL(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));
+}
+
+MIR_APP_DLL(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));
+}
+
+MIR_APP_DLL(void) Window_FreeIcon_IcoLib(HWND hWnd)
+{
+ IcoLib_ReleaseIcon((HICON)SendMessage(hWnd, WM_SETICON, ICON_BIG, 0), NULL);
+ IcoLib_ReleaseIcon((HICON)SendMessage(hWnd, WM_SETICON, ICON_SMALL, 0), NULL);
+}
+
+MIR_APP_DLL(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, TRUE, 0);
+ SendMessage(hWnd, BUTTONADDTOOLTIP, (WPARAM)tooltip, 0);
+}
+
+MIR_APP_DLL(void) Button_FreeIcon_IcoLib(HWND hwndDlg, int itemId)
+{
+ HICON hIcon = (HICON)SendDlgItemMessage(hwndDlg, itemId, BM_SETIMAGE, IMAGE_ICON, 0);
+ IcoLib_ReleaseIcon(hIcon, 0);
+}
+
+//
+// wParam = szProto
+// lParam = status
+//
+MIR_APP_DLL(HICON) LoadSkinProtoIcon(const char *szProto, int status, bool big)
+{
+ char iconName[MAX_PATH];
+ INT_PTR caps2;
+ if (szProto == NULL)
+ caps2 = -1;
+ else if ((caps2 = CallProtoServiceInt(NULL,szProto, PS_GETCAPS, PFLAGNUM_2, 0)) == CALLSERVICE_NOTFOUND)
+ caps2 = 0;
+
+ if (IsStatusConnecting(status)) {
+ mir_snprintf(iconName, SIZEOF(iconName), "%s%d", mainIconsFmt, 7);
+ return IcoLib_GetIcon(iconName, big);
+ }
+
+ int statusIndx = -1;
+ for (int 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) {
+ // format: core_status_%proto%statusindex
+ mir_snprintf(iconName, SIZEOF(iconName), "%s%s%d", statusIconsFmt, szProto, statusIndx);
+
+ HICON 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 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;
+ GetModuleFileName(NULL, szPath, SIZEOF(szPath));
+
+ //
+ // Queried protocol isn't in list, adding
+ //
+ TCHAR tszSection[MAX_PATH];
+ mir_sntprintf(tszSection, SIZEOF(tszSection), _T(PROTOCOLS_PREFIX)_T("/%s"), pa->tszAccountName);
+
+ SKINICONDESC sid = { 0 };
+ sid.section.t = tszSection;
+ sid.flags = SIDF_ALL_TCHAR;
+
+ str = _tcsrchr(szPath, '\\');
+ if (str != NULL)
+ *str = 0;
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\Icons\\proto_%S.dll"), szPath, pa->szProtoName);
+ if (GetFileAttributes(szFullPath) != INVALID_FILE_ATTRIBUTES)
+ sid.defaultFile.t = szFullPath;
+ else {
+ mir_sntprintf(szFullPath, SIZEOF(szFullPath), _T("%s\\Plugins\\%S.dll"), szPath, szProto);
+ if (int(ExtractIconEx(szFullPath, statusIcons[statusIndx].resource_id, NULL, &hIcon, 1)) > 0) {
+ DestroyIcon(hIcon);
+ sid.defaultFile.t = szFullPath;
+ hIcon = NULL;
+ }
+
+ if (sid.defaultFile.a == NULL) {
+ if (str != NULL)
+ *str = '\\';
+ sid.defaultFile.t = szPath;
+ } }
+
+ //
+ // Add global icons to list
+ //
+
+ int lowidx, highidx;
+ if (caps2 == 0)
+ lowidx = statusIndx, highidx = statusIndx+1;
+ else
+ lowidx = 0, highidx = SIZEOF(statusIcons);
+
+ for (int 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.description.t = cli.pfnGetStatusModeDescription(statusIcons[i].id, 0);
+ sid.iDefaultIndex = statusIcons[i].resource_id;
+ IcoLib_AddNewIcon(0, &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;
+}
+
+MIR_APP_DLL(HANDLE) GetSkinIconHandle(int idx)
+{
+ for (int i=0; i < SIZEOF(mainIcons); i++)
+ if (idx == mainIcons[i].id)
+ return mainIcons[i].hIcolib;
+
+ return NULL;
+}
+
+char* GetSkinIconName(int idx)
+{
+ static char szIconName[100];
+
+ for (int i = 0; i < SIZEOF(mainIcons); i++) {
+ if (idx != mainIcons[i].id)
+ continue;
+
+ mir_snprintf(szIconName, SIZEOF(szIconName), "%s%d", mainIconsFmt, i);
+ return szIconName;
+ }
+ return NULL;
+}
+
+MIR_APP_DLL(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 + mir_strlen(moduleName);
+ char *pi = iconName + mir_strlen(iconName);
+
+ for (int i = 0; i < SIZEOF(statusIcons); i++) {
+ _itoa(statusIcons[i].id, pm, 10);
+
+ DBVARIANT dbv;
+ if (!db_get_ts(NULL, "Icons", moduleName, &dbv)) {
+ _itoa(i, pi, 10);
+
+ db_set_ts(NULL, "SkinIcons", iconName, dbv.ptszVal);
+ db_free(&dbv);
+
+ db_unset(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);
+ case 3: return (INT_PTR)GetSkinIconName(wParam);
+ }
+
+ 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)
+{
+ int i, j = 0;
+ char iconName[MAX_PATH], moduleName[MAX_PATH];
+ DBVARIANT dbv;
+
+ //
+ // Perform "1st-time running import"
+
+ for (i = 0; i < SIZEOF(mainIcons); i++) {
+ _itoa(mainIcons[i].id, moduleName, 10);
+ if (db_get_ts(NULL, "Icons", moduleName, &dbv))
+ break;
+
+ mir_snprintf(iconName, SIZEOF(iconName), "%s%d", mainIconsFmt, i);
+
+ db_set_ts(NULL, "SkinIcons", iconName, dbv.ptszVal);
+ db_free(&dbv);
+
+ db_unset(NULL, "Icons", moduleName);
+ }
+
+ for (;;) {
+ // get the next protocol name
+ moduleName[0] = 'p';
+ moduleName[1] = 0;
+ _itoa(j++, moduleName + 1, 100);
+ if (db_get_ts(NULL, "Icons", moduleName, &dbv))
+ break;
+
+ db_unset(NULL, "Icons", moduleName);
+
+ // make old skinicons' prefix
+ mir_snprintf(moduleName, SIZEOF(moduleName), "%S", dbv.ptszVal);
+ // make IcoLib's prefix
+ mir_snprintf(iconName, SIZEOF(iconName), "%s%S", statusIconsFmt, dbv.ptszVal);
+
+ convertOneProtocol(moduleName, iconName);
+ db_free(&dbv);
+ }
+ moduleName[0] = 0;
+ strncpy_s(iconName, "core_status_" GLOBAL_PROTO_NAME, _TRUNCATE);
+ convertOneProtocol(moduleName, iconName);
+
+ CreateServiceFunction(MS_SKIN_LOADICON, sttLoadSkinIcon);
+ CreateServiceFunction(MS_SKIN_LOADPROTOICON, sttLoadSkinProtoIcon);
+ CreateServiceFunction(MS_SKIN_LOADPROTOICONBIG, sttLoadSkinProtoIconBig);
+
+ TCHAR modulePath[MAX_PATH];
+ GetModuleFileName(g_hInst, modulePath, SIZEOF(modulePath));
+
+ SKINICONDESC sid = { 0 };
+ sid.defaultFile.t = 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.section.a = mainIcons[i].section == NULL ? LPGEN("Main icons") : (char*)mainIcons[i].section;
+ sid.description.a = (char*)mainIcons[i].description;
+ sid.iDefaultIndex = mainIcons[i].resource_id;
+ mainIcons[i].hIcolib = IcoLib_AddNewIcon(0, &sid);
+ }
+ //
+ // Add global icons to list
+ //
+ sid.section.a = PROTOCOLS_PREFIX "/" LPGEN("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.description.a = (char*)statusIcons[i].description;
+ sid.iDefaultIndex = statusIcons[i].resource_id;
+ statusIcons[i].hIcolib = IcoLib_AddNewIcon(0, &sid);
+ }
+ return 0;
+}
diff --git a/src/mir_app/src/sounds.cpp b/src/mir_app/src/sounds.cpp new file mode 100644 index 0000000000..712a39441f --- /dev/null +++ b/src/mir_app/src/sounds.cpp @@ -0,0 +1,473 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#pragma comment(lib, "winmm.lib")
+
+struct SoundItem
+{
+ char* name;
+ TCHAR* ptszSection;
+ TCHAR* ptszDescription;
+ TCHAR* ptszTempFile;
+ int hLangpack;
+
+ __inline TCHAR* getSection() const { return TranslateTH(hLangpack, ptszSection); }
+ __inline TCHAR* getDescr() const { return TranslateTH(hLangpack, ptszDescription); }
+
+ __inline void clear(void)
+ {
+ mir_free(name);
+ mir_free(ptszSection);
+ mir_free(ptszDescription);
+ mir_free(ptszTempFile);
+ }
+};
+
+static int CompareSounds(const SoundItem* p1, const SoundItem* p2)
+{
+ return mir_strcmp(p1->name, p2->name);
+}
+
+static OBJLIST<SoundItem> arSounds(10, CompareSounds);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void KillModuleSounds(int hLangpack)
+{
+ for (int i = arSounds.getCount()-1; i >= 0; i--) {
+ SoundItem& p = arSounds[i];
+ if (p.hLangpack == hLangpack) {
+ p.clear();
+ arSounds.remove(i);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static BOOL bModuleInitialized = FALSE;
+static HANDLE hPlayEvent = NULL;
+
+static INT_PTR ServiceSkinAddNewSound(WPARAM wParam, LPARAM lParam)
+{
+ SKINSOUNDDESCEX *ssd = (SKINSOUNDDESCEX*)lParam;
+ if (ssd->cbSize != sizeof(SKINSOUNDDESCEX) || ssd->pszName == NULL || ssd->pszDescription == NULL)
+ return 1;
+
+ SoundItem* item = new SoundItem; // due to OBJLIST
+ item->name = mir_strdup(ssd->pszName);
+ item->ptszTempFile = NULL;
+ item->hLangpack = (int)wParam;
+ arSounds.insert(item);
+
+ TCHAR* ptszDefaultFile;
+ if (ssd->dwFlags & SSDF_UNICODE) {
+ item->ptszDescription = mir_tstrdup(ssd->ptszDescription);
+ item->ptszSection = mir_tstrdup((ssd->pszSection != NULL) ? ssd->ptszSection : _T("Other"));
+ ptszDefaultFile = mir_tstrdup(ssd->ptszDefaultFile);
+ }
+ else {
+ item->ptszDescription = mir_a2t(ssd->pszDescription);
+ item->ptszSection = mir_a2t((ssd->pszSection != NULL) ? ssd->pszSection : "Other");
+ ptszDefaultFile = mir_a2t(ssd->pszDefaultFile);
+ }
+
+ if (ptszDefaultFile) {
+ DBVARIANT dbv;
+ if (db_get_s(NULL, "SkinSounds", item->name, &dbv))
+ db_set_ts(NULL, "SkinSounds", item->name, ptszDefaultFile);
+ else
+ db_free(&dbv);
+ mir_free(ptszDefaultFile);
+ }
+
+ return 0;
+}
+
+static int SkinPlaySoundDefault(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR* pszFile = (TCHAR*) lParam;
+ if (pszFile && (db_get_b(NULL, "Skin", "UseSound", 0) || (int)wParam == 1))
+ PlaySound(pszFile, NULL, SND_ASYNC | SND_FILENAME | SND_NOSTOP);
+
+ return 0;
+}
+
+static INT_PTR ServiceSkinPlaySoundFile(WPARAM, LPARAM lParam)
+{
+ TCHAR *ptszFileName = (TCHAR*)lParam;
+ if (ptszFileName == NULL)
+ return 1;
+
+ TCHAR tszFull[MAX_PATH];
+ PathToAbsoluteT(ptszFileName, tszFull);
+ NotifyEventHooks(hPlayEvent, 0, (LPARAM)tszFull);
+ return 0;
+}
+
+static INT_PTR ServiceSkinPlaySound(WPARAM, LPARAM lParam)
+{
+ char* pszSoundName = (char*)lParam;
+ if (pszSoundName == NULL)
+ return 1;
+
+ SoundItem tmp = { pszSoundName };
+ int idx = arSounds.getIndex( &tmp );
+ if (idx == -1)
+ return 1;
+
+ if ( db_get_b(NULL, "SkinSoundsOff", pszSoundName, 0))
+ return 1;
+
+ DBVARIANT dbv;
+ if ( db_get_ts(NULL, "SkinSounds", pszSoundName, &dbv) == 0) {
+ ServiceSkinPlaySoundFile(0, (LPARAM)dbv.ptszVal);
+ db_free(&dbv);
+ return 0;
+ }
+ return 1;
+}
+
+#define DM_REBUILD_STREE (WM_USER+1)
+#define DM_HIDEPANE (WM_USER+2)
+#define DM_SHOWPANE (WM_USER+3)
+#define DM_CHECKENABLED (WM_USER+4)
+
+INT_PTR CALLBACK DlgProcSoundOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static HWND hwndTree = NULL;
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ hwndTree = GetDlgItem(hwndDlg, IDC_SOUNDTREE);
+ SetWindowLongPtr(hwndTree, GWL_STYLE, GetWindowLongPtr(hwndTree, GWL_STYLE)|TVS_NOHSCROLL|TVS_CHECKBOXES);
+ SendMessage(hwndDlg, DM_HIDEPANE, 0, 0);
+ SendMessage(hwndDlg, DM_REBUILD_STREE, 0, 0);
+ TreeView_SetItemState(hwndTree, 0, TVIS_SELECTED, TVIS_SELECTED);
+ CheckDlgButton(hwndDlg, IDC_ENABLESOUNDS, db_get_b(NULL, "Skin", "UseSound", 0) ? BST_CHECKED : BST_UNCHECKED);
+ SendMessage(hwndDlg, DM_CHECKENABLED, 0, 0);
+ return TRUE;
+
+ case DM_REBUILD_STREE:
+ TreeView_SelectItem(hwndTree, NULL);
+ ShowWindow(hwndTree, SW_HIDE);
+ TreeView_DeleteAllItems(hwndTree);
+ {
+ TVINSERTSTRUCT tvis;
+ 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 (int i=0; i < arSounds.getCount(); i++) {
+ tvis.item.stateMask = TVIS_EXPANDED;
+ tvis.item.state = TVIS_EXPANDED;
+ tvis.hParent = FindNamedTreeItemAtRoot(hwndTree, arSounds[i].getSection());
+ if (tvis.hParent == NULL) {
+ tvis.item.lParam = -1;
+ tvis.item.pszText = arSounds[i].getSection();
+ tvis.hParent = tvis.item.hItem = TreeView_InsertItem(hwndTree, &tvis);
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(0);
+ TreeView_SetItem(hwndTree, &tvis.item);
+ }
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(!db_get_b(NULL, "SkinSoundsOff", arSounds[i].name, 0)?2:1);
+ tvis.item.lParam = i;
+ tvis.item.pszText = arSounds[i].getDescr();
+ TreeView_InsertItem(hwndTree, &tvis);
+ } }
+ {
+ TVITEM tvi;
+ 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)
+ TreeView_SetItemState(hwndTree, tvi.hItem, INDEXTOSTATEIMAGEMASK(0), TVIS_STATEIMAGEMASK);
+
+ tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem);
+ } }
+
+ ShowWindow(hwndTree, SW_SHOW);
+ break;
+
+ case DM_HIDEPANE:
+ ShowWindow( GetDlgItem(hwndDlg, IDC_SGROUP), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_NAME), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_NAMEVAL), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_SLOC), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_LOCATION), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_CHANGE), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_PREVIEW), SW_HIDE);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_GETMORE), SW_HIDE);
+ break;
+
+ case DM_SHOWPANE:
+ ShowWindow( GetDlgItem(hwndDlg, IDC_SGROUP), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_NAME), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_NAMEVAL), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_SLOC), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_LOCATION), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_CHANGE), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_PREVIEW), SW_SHOW);
+ ShowWindow( GetDlgItem(hwndDlg, IDC_GETMORE), SW_SHOW);
+ break;
+
+ case DM_CHECKENABLED:
+ EnableWindow( GetDlgItem(hwndDlg, IDC_SOUNDTREE), IsDlgButtonChecked(hwndDlg, IDC_ENABLESOUNDS));
+ if (BST_UNCHECKED == IsDlgButtonChecked(hwndDlg, IDC_ENABLESOUNDS))
+ SendMessage(hwndDlg, DM_HIDEPANE, 0, 0);
+ else if (TreeView_GetSelection(hwndTree) && TreeView_GetParent(hwndTree, TreeView_GetSelection(hwndTree)))
+ SendMessage(hwndDlg, DM_SHOWPANE, 0, 0);
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_ENABLESOUNDS)
+ SendMessage(hwndDlg, DM_CHECKENABLED, 0, 0);
+
+ if (LOWORD(wParam) == IDC_PREVIEW) {
+ HTREEITEM hti = TreeView_GetSelection(hwndTree);
+ if (hti == NULL)
+ break;
+
+ TVITEM tvi = { 0 };
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT;
+ tvi.hItem = hti;
+ if (TreeView_GetItem(hwndTree, &tvi) == FALSE)
+ break;
+ if (tvi.lParam == -1)
+ break;
+
+ if (arSounds[tvi.lParam].ptszTempFile)
+ NotifyEventHooks(hPlayEvent, 1, (LPARAM)arSounds[tvi.lParam].ptszTempFile);
+ else {
+ DBVARIANT dbv;
+ if (!db_get_ts(NULL, "SkinSounds", arSounds[tvi.lParam].name, &dbv)) {
+ TCHAR szPathFull[MAX_PATH];
+ PathToAbsoluteT(dbv.ptszVal, szPathFull);
+ NotifyEventHooks(hPlayEvent, 1, (LPARAM)szPathFull);
+ db_free(&dbv);
+ }
+ }
+ break;
+ }
+
+ if (LOWORD(wParam) == IDC_CHANGE) {
+ HTREEITEM hti = TreeView_GetSelection(hwndTree);
+ if (hti == NULL)
+ break;
+
+ TVITEM tvi = { 0 };
+ tvi.mask = TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM|TVIF_TEXT;
+ tvi.hItem = hti;
+ if (TreeView_GetItem(hwndTree, &tvi) == FALSE)
+ break;
+ if (tvi.lParam == -1)
+ break;
+
+ SoundItem& snd = arSounds[tvi.lParam];
+
+ TCHAR str[MAX_PATH], strFull[MAX_PATH], strdir[MAX_PATH], filter[MAX_PATH];
+ if (snd.ptszTempFile)
+ _tcsncpy_s(strFull, snd.ptszTempFile, _TRUNCATE);
+ else {
+ if (db_get_b(NULL, "SkinSoundsOff", snd.name, 0) == 0) {
+ DBVARIANT dbv;
+ if (db_get_ts(NULL, "SkinSounds", snd.name, &dbv) == 0) {
+ PathToAbsoluteT(dbv.ptszVal, strdir);
+ db_free(&dbv);
+ } } }
+
+ _tcsncpy_s(strFull, (snd.ptszTempFile ? snd.ptszTempFile : _T("")), _TRUNCATE);
+ PathToAbsoluteT(strFull, strdir);
+
+ OPENFILENAME ofn;
+ memset(&ofn, 0, sizeof(ofn));
+ if (GetModuleHandle(_T("bass_interface.dll")))
+ mir_sntprintf(filter, SIZEOF(filter), _T("%s (*.wav, *.mp3, *.ogg)%c*.wav;*.mp3;*.ogg%c%s (*)%c*%c"), TranslateT("Sound files"), 0, 0, TranslateT("All files"), 0, 0);
+ else
+ mir_sntprintf(filter, SIZEOF(filter), _T("%s (*.wav)%c*.wav%c%s (*)%c*%c"), TranslateT("WAV files"), 0, 0, TranslateT("All files"), 0, 0);
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ ofn.hwndOwner = GetParent(hwndDlg);
+ ofn.hInstance = NULL;
+ ofn.lpstrFilter = filter;
+
+ TCHAR* slash = _tcsrchr(strdir, '\\');
+ if (slash) {
+ *slash = 0;
+ ofn.lpstrInitialDir = strdir;
+ }
+
+ str[0] = 0;
+ ofn.lpstrFile = str;
+ ofn.Flags = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY|OFN_EXPLORER|OFN_LONGNAMES|OFN_NOCHANGEDIR;
+ ofn.nMaxFile = SIZEOF(str);
+ ofn.nMaxFileTitle = MAX_PATH;
+ ofn.lpstrDefExt = _T("wav");
+ if (!GetOpenFileName(&ofn))
+ break;
+
+ PathToRelativeT(str, strFull);
+ snd.ptszTempFile = mir_tstrdup(strFull);
+ SetDlgItemText(hwndDlg, IDC_LOCATION, strFull);
+ }
+ if (LOWORD(wParam) == IDC_GETMORE) {
+ CallService(MS_UTILS_OPENURL, OUF_NEWWINDOW, (LPARAM)"http://miranda-ng.org/addons/category/14");
+ break;
+ }
+ if (LOWORD(wParam) == IDC_LOCATION)
+ break;
+
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+
+ case WM_NOTIFY:
+ switch(((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ if (((LPNMHDR)lParam)->code == PSN_APPLY) {
+ db_set_b(NULL, "Skin", "UseSound", (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ENABLESOUNDS));
+
+ for (int i=0; i < arSounds.getCount(); i++)
+ if (arSounds[i].ptszTempFile)
+ db_set_ts(NULL, "SkinSounds", arSounds[i].name, arSounds[i].ptszTempFile);
+
+ 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))
+ db_unset(NULL, "SkinSoundsOff", arSounds[tvic.lParam].name);
+ else
+ db_set_b(NULL, "SkinSoundsOff", arSounds[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_SELCHANGED:
+ {
+ NMTREEVIEW *pnmtv = (NMTREEVIEW*)lParam;
+ TVITEM tvi = pnmtv->itemNew;
+
+ if (tvi.lParam == -1)
+ SendMessage(hwndDlg, DM_HIDEPANE, 0, 0);
+ else {
+ TCHAR buf[256];
+ mir_sntprintf(buf, _T("%s: %s"), arSounds[tvi.lParam].getSection(), arSounds[tvi.lParam].getDescr());
+ SetDlgItemText(hwndDlg, IDC_NAMEVAL, buf);
+ if (arSounds[tvi.lParam].ptszTempFile)
+ SetDlgItemText(hwndDlg, IDC_LOCATION, arSounds[tvi.lParam].ptszTempFile);
+ else {
+ DBVARIANT dbv;
+ if (!db_get_ts(NULL, "SkinSounds", arSounds[tvi.lParam].name, &dbv)) {
+ SetDlgItemText(hwndDlg, IDC_LOCATION, dbv.ptszVal);
+ db_free(&dbv);
+ }
+ else SetDlgItemText(hwndDlg, IDC_LOCATION, TranslateT("<not specified>"));
+ }
+ 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 | TVHT_ONITEMSTATEICON))
+ if (TreeView_GetParent(hwndTree, hti.hItem) != NULL)
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ break;
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ ImageList_Destroy(TreeView_GetImageList(hwndTree, TVSIL_STATE));
+ break;
+ }
+ return FALSE;
+}
+
+static int SkinOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.position = -200000000;
+ odp.hInstance = g_hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_SOUND);
+ odp.pszTitle = LPGEN("Sounds");
+ odp.pfnDlgProc = DlgProcSoundOpts;
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+static int SkinSystemModulesLoaded(WPARAM, LPARAM)
+{
+ HookEvent(ME_OPT_INITIALISE, SkinOptionsInit);
+ return 0;
+}
+
+int LoadSkinSounds(void)
+{
+ bModuleInitialized = TRUE;
+
+ CreateServiceFunction("Skin/Sounds/AddNew", ServiceSkinAddNewSound);
+ CreateServiceFunction(MS_SKIN_PLAYSOUND, ServiceSkinPlaySound);
+ CreateServiceFunction(MS_SKIN_PLAYSOUNDFILE, ServiceSkinPlaySoundFile);
+ HookEvent(ME_SYSTEM_MODULESLOADED, SkinSystemModulesLoaded);
+ hPlayEvent = CreateHookableEvent(ME_SKIN_PLAYINGSOUND);
+ SetHookDefaultForHookableEvent(hPlayEvent, SkinPlaySoundDefault);
+ return 0;
+}
+
+void UnloadSkinSounds(void)
+{
+ for (int i=0; i < arSounds.getCount(); i++)
+ arSounds[i].clear();
+}
diff --git a/src/mir_app/src/srmm_statusicon.cpp b/src/mir_app/src/srmm_statusicon.cpp new file mode 100644 index 0000000000..87916cebb0 --- /dev/null +++ b/src/mir_app/src/srmm_statusicon.cpp @@ -0,0 +1,221 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG 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 "stdafx.h"
+
+struct StatusIconChild : public MZeroedObject
+{
+ ~StatusIconChild()
+ {
+ SafeDestroyIcon(hIcon);
+ SafeDestroyIcon(hIconDisabled);
+ mir_free(tszTooltip);
+ }
+
+ MCONTACT hContact;
+ HICON hIcon, hIconDisabled;
+ int flags;
+ TCHAR *tszTooltip;
+
+ void SafeDestroyIcon(HICON hIcon)
+ {
+ if (hIcon == NULL)
+ return;
+
+ if (!IcoLib_IsManaged(hIcon))
+ ::DestroyIcon(hIcon);
+ }
+};
+
+struct StatusIconMain : public MZeroedObject
+{
+ StatusIconMain() :
+ arChildren(3, NumericKeySortT)
+ {}
+
+ ~StatusIconMain()
+ {
+ mir_free(sid.szModule);
+ mir_free(sid.szTooltip);
+ }
+
+ StatusIconData sid;
+
+ int hLangpack;
+ OBJLIST<StatusIconChild> arChildren;
+};
+
+static int CompareIcons(const StatusIconMain *p1, const StatusIconMain *p2)
+{
+ int res = mir_strcmp(p1->sid.szModule, p2->sid.szModule);
+ if (res)
+ return res;
+
+ return p1->sid.dwId - p2->sid.dwId;
+}
+
+static OBJLIST<StatusIconMain> arIcons(3, CompareIcons);
+
+static HANDLE hHookIconsChanged;
+
+INT_PTR ModifyStatusIcon(WPARAM hContact, LPARAM lParam)
+{
+ StatusIconData *sid = (StatusIconData *)lParam;
+ if (sid == NULL || sid->cbSize != sizeof(StatusIconData))
+ return 1;
+
+ StatusIconMain *p = arIcons.find((StatusIconMain*)sid);
+ if (p == NULL)
+ return 1;
+
+ if (hContact == NULL) {
+ mir_free(p->sid.szModule);
+ mir_free(p->sid.szTooltip);
+ memcpy(&p->sid, sid, sizeof(p->sid));
+ p->sid.szModule = mir_strdup(sid->szModule);
+ p->sid.tszTooltip = (sid->flags & MBF_UNICODE) ? mir_u2t(sid->wszTooltip) : mir_a2t(sid->szTooltip);
+
+ NotifyEventHooks(hHookIconsChanged, NULL, (LPARAM)p);
+ return 0;
+ }
+
+ StatusIconChild *pc = p->arChildren.find((StatusIconChild*)&hContact);
+ if (pc == NULL) {
+ pc = new StatusIconChild();
+ pc->hContact = hContact;
+ p->arChildren.insert(pc);
+ }
+ else pc->SafeDestroyIcon(pc->hIcon);
+
+ pc->flags = sid->flags;
+ pc->hIcon = sid->hIcon;
+ pc->hIconDisabled = sid->hIconDisabled;
+
+ mir_free(pc->tszTooltip);
+ pc->tszTooltip = (sid->flags & MBF_UNICODE) ? mir_u2t(sid->wszTooltip) : mir_a2t(sid->szTooltip);
+
+ NotifyEventHooks(hHookIconsChanged, hContact, (LPARAM)p);
+ return 0;
+}
+
+static INT_PTR AddStatusIcon(WPARAM wParam, LPARAM lParam)
+{
+ StatusIconData *sid = (StatusIconData *)lParam;
+ if (sid == NULL || sid->cbSize != sizeof(StatusIconData))
+ return 1;
+
+ StatusIconMain *p = arIcons.find((StatusIconMain*)sid);
+ if (p != NULL)
+ return ModifyStatusIcon(0, lParam);
+
+ p = new StatusIconMain;
+ memcpy(&p->sid, sid, sizeof(p->sid));
+ p->hLangpack = (int)wParam;
+ p->sid.szModule = mir_strdup(sid->szModule);
+ if (sid->flags & MBF_UNICODE)
+ p->sid.tszTooltip = mir_u2t(sid->wszTooltip);
+ else
+ p->sid.tszTooltip = mir_a2t(sid->szTooltip);
+ arIcons.insert(p);
+
+ NotifyEventHooks(hHookIconsChanged, NULL, (LPARAM)p);
+ return 0;
+}
+
+static INT_PTR RemoveStatusIcon(WPARAM, LPARAM lParam)
+{
+ StatusIconData *sid = (StatusIconData *)lParam;
+ if (sid == NULL || sid->cbSize != sizeof(StatusIconData))
+ return 1;
+
+ StatusIconMain *p = arIcons.find((StatusIconMain*)sid);
+ if (p == NULL)
+ return 1;
+
+ arIcons.remove(p);
+ return 0;
+}
+
+static INT_PTR GetNthIcon(WPARAM wParam, LPARAM lParam)
+{
+ static StatusIconData res;
+
+ for (int i=arIcons.getCount()-1, nVis = 0; i >= 0; i--) {
+ StatusIconMain &p = arIcons[i];
+
+ StatusIconChild *pc = p.arChildren.find((StatusIconChild*)&wParam);
+ if (pc) {
+ if (pc->flags & MBF_HIDDEN)
+ continue;
+ }
+ else if (p.sid.flags & MBF_HIDDEN)
+ continue;
+
+ if (nVis == (int)lParam) {
+ memcpy(&res, &p, sizeof(res));
+ if (pc) {
+ if (pc->hIcon) res.hIcon = pc->hIcon;
+ if (pc->hIconDisabled)
+ res.hIconDisabled = pc->hIconDisabled;
+ else if (pc->hIcon)
+ res.hIconDisabled = pc->hIcon;
+ if (pc->tszTooltip) res.tszTooltip = pc->tszTooltip;
+ res.flags = pc->flags;
+ }
+ res.tszTooltip = TranslateTH(p.hLangpack, res.tszTooltip);
+ return (INT_PTR)&res;
+ }
+ nVis++;
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KillModuleSrmmIcons(int hLangpack)
+{
+ for (int i=arIcons.getCount()-1; i >= 0; i--) {
+ StatusIconMain &p = arIcons[i];
+ if (p.hLangpack == hLangpack)
+ arIcons.remove(i);
+ }
+}
+
+int LoadSrmmModule()
+{
+ CreateServiceFunction("MessageAPI/AddIcon", AddStatusIcon);
+ CreateServiceFunction(MS_MSG_REMOVEICON, RemoveStatusIcon);
+ CreateServiceFunction(MS_MSG_MODIFYICON, ModifyStatusIcon);
+ CreateServiceFunction("MessageAPI/GetNthIcon", GetNthIcon);
+
+ hHookIconsChanged = CreateHookableEvent(ME_MSG_ICONSCHANGED);
+ return 0;
+}
+
+void UnloadSrmmModule()
+{
+ arIcons.destroy();
+ NotifyEventHooks(hHookIconsChanged, NULL, NULL);
+ DestroyHookableEvent(hHookIconsChanged);
+}
diff --git a/src/mir_app/src/stdafx.cxx b/src/mir_app/src/stdafx.cxx new file mode 100644 index 0000000000..5d3cae4e8e --- /dev/null +++ b/src/mir_app/src/stdafx.cxx @@ -0,0 +1,19 @@ +/*
+
+Copyright (C) 2012-15 Miranda NG team (http://miranda-ng.org)
+
+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 version 2
+of the License.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
\ No newline at end of file diff --git a/src/mir_app/src/stdafx.h b/src/mir_app/src/stdafx.h new file mode 100644 index 0000000000..f4ffff15c9 --- /dev/null +++ b/src/mir_app/src/stdafx.h @@ -0,0 +1,105 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+*/
+
+#define _ALPHA_BASE_ 1 // defined for CVS builds
+#define _ALPHA_FUSE_ 1 // defined for fuse powered core
+
+#define INCL_WINSOCK_API_TYPEDEFS 1
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <windows.h>
+#include <windowsx.h>
+#include <shlobj.h>
+#include <uxtheme.h>
+#include <commctrl.h>
+#include <vssym32.h>
+#include <Shlwapi.h>
+#include <Richedit.h>
+
+#ifdef _DEBUG
+#include <crtdbg.h>
+#endif
+
+#include <assert.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <stddef.h>
+#include <process.h>
+#include <io.h>
+#include <limits.h>
+#include <string.h>
+#include <locale.h>
+#include <direct.h>
+
+#include <win2k.h>
+
+#include <m_system.h>
+#include <m_system_cpp.h>
+#include <m_string.h>
+#include <newpluginapi.h>
+#include <m_database.h>
+#include <m_db_int.h>
+#include <m_clc.h>
+#include <m_clui.h>
+#include <m_crypto.h>
+#include <m_langpack.h>
+#include <m_clist.h>
+#include <m_clistint.h>
+#include <m_avatars.h>
+#include <m_button.h>
+#include <m_protosvc.h>
+#include <m_protomod.h>
+#include <m_protocols.h>
+#include <m_protoint.h>
+#include <m_options.h>
+#include <m_skin.h>
+#include <m_contacts.h>
+#include <m_message.h>
+#include <m_userinfo.h>
+#include <m_addcontact.h>
+#include <m_findadd.h>
+#include <m_file.h>
+#include <m_awaymsg.h>
+#include <m_ignore.h>
+#include <m_icolib.h>
+#include <m_modernopt.h>
+#include <m_timezones.h>
+#include <m_extraicons.h>
+#include <m_xstatus.h>
+#include <m_metacontacts.h>
+#include <m_gui.h>
+
+#include "miranda.h"
+
+#include <m_ssl.h>
+#include <m_netlib.h>
+#include <m_xml.h>
+
+typedef struct GlobalLogSettingsBase GlobalLogSettings;
+#include <m_chat_int.h>
+
+#include "resource.h"
\ No newline at end of file diff --git a/src/mir_app/src/timeutils.cpp b/src/mir_app/src/timeutils.cpp new file mode 100644 index 0000000000..57afeff219 --- /dev/null +++ b/src/mir_app/src/timeutils.cpp @@ -0,0 +1,103 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+
+implements services to handle location - based timezones, instead of
+simple UTC offsets.
+*/
+
+#include "stdafx.h"
+
+// KB167296
+void UnixTimeToFileTime(mir_time ts, LPFILETIME pft)
+{
+ unsigned __int64 ll = UInt32x32To64(ts, 10000000) + 116444736000000000i64;
+ pft->dwLowDateTime = (DWORD)ll;
+ pft->dwHighDateTime = ll >> 32;
+}
+
+mir_time FileTimeToUnixTime(LPFILETIME pft)
+{
+ unsigned __int64 ll = (unsigned __int64)pft->dwHighDateTime << 32 | pft->dwLowDateTime;
+ ll -= 116444736000000000i64;
+ return (mir_time)(ll / 10000000);
+}
+
+void FormatTime(const SYSTEMTIME *st, const TCHAR *szFormat, TCHAR *szDest, int cbDest)
+{
+ if (szDest == NULL || cbDest == 0) return;
+
+ CMString tszTemp;
+
+ for (const TCHAR* pFormat = szFormat; *pFormat; ++pFormat) {
+ DWORD fmt = 0;
+ bool date = false, 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:
+ tszTemp.AppendChar(*pFormat);
+ continue;
+ }
+
+ TCHAR dateTimeStr[64];
+ if (iso)
+ tszTemp.AppendFormat(_T("%d-%02d-%02dT%02d:%02d:%02dZ"), st->wYear, st->wMonth, st->wDay, st->wHour, st->wMinute, st->wSecond);
+ else if (date) {
+ GetDateFormat(LOCALE_USER_DEFAULT, fmt, st, NULL, dateTimeStr, SIZEOF(dateTimeStr));
+ tszTemp.Append(dateTimeStr);
+ }
+ else {
+ GetTimeFormat(LOCALE_USER_DEFAULT, fmt, st, NULL, dateTimeStr, SIZEOF(dateTimeStr));
+ tszTemp.Append(dateTimeStr);
+ }
+ }
+
+ _tcsncpy_s(szDest, cbDest, tszTemp, _TRUNCATE);
+}
diff --git a/src/mir_app/src/timezones.cpp b/src/mir_app/src/timezones.cpp new file mode 100644 index 0000000000..d95fef6169 --- /dev/null +++ b/src/mir_app/src/timezones.cpp @@ -0,0 +1,557 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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.
+
+implements services to handle location - based timezones, instead of
+simple UTC offsets.
+*/
+
+#include "stdafx.h"
+
+TIME_API tmi;
+
+typedef DWORD (WINAPI *pfnGetDynamicTimeZoneInformation_t)(DYNAMIC_TIME_ZONE_INFORMATION *pdtzi);
+static pfnGetDynamicTimeZoneInformation_t pfnGetDynamicTimeZoneInformation;
+
+struct REG_TZI_FORMAT
+{
+ LONG Bias;
+ LONG StandardBias;
+ LONG DaylightBias;
+ SYSTEMTIME StandardDate;
+ SYSTEMTIME DaylightDate;
+};
+
+#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; }
+};
+
+struct TZ_INT_INFO
+{
+ DWORD timestamp; // last time updated
+ MIM_TIMEZONE myTZ; // set to my own timezone
+};
+
+static TZ_INT_INFO myInfo;
+
+static OBJLIST<MIM_TIMEZONE> g_timezones(55, NumericKeySortT);
+static LIST<MIM_TIMEZONE> g_timezonesBias(55, MIM_TIMEZONE::compareBias);
+
+void FormatTime(const SYSTEMTIME *st, const TCHAR *szFormat, TCHAR *szDest, int cbDest);
+void UnixTimeToFileTime(mir_time ts, LPFILETIME pft);
+mir_time FileTimeToUnixTime(LPFILETIME pft);
+
+#define fnSystemTimeToTzSpecificLocalTime SystemTimeToTzSpecificLocalTime
+
+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 LPCTSTR timeapiGetTzDescription(LPCTSTR TZname)
+{
+ for (int i = 0; i < g_timezonesBias.getCount(); i++) {
+ MIM_TIMEZONE *tz = g_timezonesBias[i];
+
+ if (!mir_tstrcmp(tz->tszName, TZname))
+ return tz->szDisplay;
+ }
+ return _T("");
+}
+
+static void CalcTsOffset(MIM_TIMEZONE *tz)
+{
+ SYSTEMTIME st, stl;
+ GetSystemTime(&st);
+
+ FILETIME ft;
+ SystemTimeToFileTime(&st, &ft);
+ mir_time ts1 = FileTimeToUnixTime(&ft);
+
+ if (!fnSystemTimeToTzSpecificLocalTime(&tz->tzi, &st, &stl))
+ return;
+
+ SystemTimeToFileTime(&stl, &ft);
+ mir_time 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 (mir_tstrcmp(myInfo.myTZ.tszName, tszName) == 0)
+ return (dwFlags & TZF_DIFONLY) ? NULL : &myInfo.myTZ;
+
+ MIM_TIMEZONE tzsearch;
+ tzsearch.hash = mir_hashstrT(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(MCONTACT hContact, LPCSTR szModule, DWORD dwFlags)
+{
+ if (hContact == NULL && szModule == NULL)
+ return (dwFlags & (TZF_DIFONLY | TZF_KNOWNONLY)) ? NULL : &myInfo.myTZ;
+
+ if (szModule == NULL) szModule = "UserInfo";
+
+ DBVARIANT dbv;
+ if (!db_get_ts(hContact, szModule, "TzName", &dbv)) {
+ HANDLE res = timeapiGetInfoByName(dbv.ptszVal, dwFlags);
+ db_free(&dbv);
+ if (res) return res;
+ }
+
+ signed char timezone = (signed char)db_get_b(hContact, szModule, "Timezone", -1);
+ if (timezone == -1) {
+ char *szProto = GetContactProto(hContact);
+ if (!db_get_ts(hContact, szProto, "TzName", &dbv)) {
+ HANDLE res = timeapiGetInfoByName(dbv.ptszVal, dwFlags);
+ db_free(&dbv);
+ if (res) return res;
+ }
+ timezone = (signed char)db_get_b(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(MCONTACT hContact, LPCSTR szModule, HANDLE hTZ)
+{
+ if (szModule == NULL) szModule = "UserInfo";
+
+ MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ;
+ if (tz) {
+ db_set_ts(hContact, szModule, "TzName", tz->tszName);
+ db_set_b(hContact, szModule, "Timezone", (char)((tz->tzi.Bias + tz->tzi.StandardBias) / 30));
+ }
+ else {
+ db_unset(hContact, szModule, "TzName");
+ db_unset(hContact, szModule, "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, mir_time 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;
+
+ if (tz == NULL)
+ tz = &myInfo.myTZ;
+
+ FILETIME ft;
+ 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 mir_time timeapiTimeStampToTimeZoneTimeStamp(HANDLE hTZ, mir_time ts)
+{
+ MIM_TIMEZONE *tz = (MIM_TIMEZONE*)hTZ;
+ if (tz == NULL)
+ tz = &myInfo.myTZ;
+
+ if (tz == UTC_TIME_HANDLE)
+ return ts;
+
+ if (tz->offset == INT_MIN)
+ CalcTsOffset(tz);
+
+ return ts + tz->offset;
+}
+
+struct ListMessages
+{
+ UINT addStr, getSel, setSel, getData, setData;
+};
+
+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 (hWnd == NULL)
+ return NULL;
+
+ if (!(dwFlags & (TZF_PLF_CB | TZF_PLF_LB))) {
+ TCHAR tszClassName[128];
+ GetClassName(hWnd, tszClassName, SIZEOF(tszClassName));
+ if (!mir_tstrcmpi(tszClassName, _T("COMBOBOX")))
+ dwFlags |= TZF_PLF_CB;
+ else if (!mir_tstrcmpi(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(MCONTACT hContact, LPCSTR szModule, HWND hWnd, DWORD dwFlags)
+{
+ const ListMessages *lstMsg = GetListMessages(hWnd, dwFlags);
+ if (lstMsg == NULL)
+ return -1;
+
+ if (szModule == NULL) szModule = "UserInfo";
+
+ int iSelection = 0;
+ ptrT tszName(db_get_tsa(hContact, szModule, "TzName"));
+ if (tszName != NULL) {
+ unsigned hash = mir_hashstrT(tszName);
+ for (int i = 0; i < g_timezonesBias.getCount(); i++) {
+ if (hash == g_timezonesBias[i]->hash) {
+ iSelection = i + 1;
+ break;
+ }
+ }
+ }
+ else {
+ signed char cBias = db_get_b(hContact, szModule, "Timezone", -100);
+ if (cBias != -100) {
+ int iBias = cBias * 30;
+ for (int i = 0; i < g_timezonesBias.getCount(); i++) {
+ if (iBias == g_timezonesBias[i]->tzi.Bias) {
+ iSelection = i + 1;
+ break;
+ }
+ }
+ }
+ }
+
+ SendMessage(hWnd, lstMsg->setSel, iSelection, 0);
+ return iSelection;
+}
+
+static int timeapiPrepareList(MCONTACT hContact, LPCSTR szModule, HWND hWnd, DWORD dwFlags)
+{
+ const ListMessages *lstMsg = GetListMessages(hWnd, dwFlags);
+ if (lstMsg == NULL)
+ return 0;
+
+ SendMessage(hWnd, lstMsg->addStr, 0, (LPARAM)TranslateT("<unspecified>"));
+
+ for (int i = 0; i < g_timezonesBias.getCount(); i++) {
+ MIM_TIMEZONE *tz = g_timezonesBias[i];
+
+ SendMessage(hWnd, lstMsg->addStr, 0, (LPARAM)tz->szDisplay);
+ SendMessage(hWnd, lstMsg->setData, i + 1, (LPARAM)tz);
+ }
+
+ return timeapiSelectListItem(hContact, szModule, hWnd, dwFlags);
+}
+
+static void timeapiStoreListResult(MCONTACT hContact, LPCSTR szModule, HWND hWnd, DWORD dwFlags)
+{
+ if (szModule == NULL) szModule = "UserInfo";
+
+ const ListMessages *lstMsg = GetListMessages(hWnd, dwFlags);
+ if (lstMsg) {
+ 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, szModule, tz);
+ }
+ else timeapiSetInfoByContact(hContact, szModule, 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;
+ tmi->getTzDescription = timeapiGetTzDescription;
+
+ return TRUE;
+}
+
+static INT_PTR TimestampToLocal(WPARAM wParam, LPARAM)
+{
+ return timeapiTimeStampToTimeZoneTimeStamp(NULL, (mir_time)wParam);
+}
+
+static INT_PTR TimestampToStringT(WPARAM wParam, LPARAM lParam)
+{
+ DBTIMETOSTRINGT *tts = (DBTIMETOSTRINGT*)lParam;
+ if (tts != NULL)
+ timeapiPrintTimeStamp(NULL, (mir_time)wParam, tts->szFormat, tts->szDest, tts->cbDest, 0);
+ return 0;
+}
+
+static INT_PTR TimestampToStringA(WPARAM wParam, LPARAM lParam)
+{
+ DBTIMETOSTRING *tts = (DBTIMETOSTRING*)lParam;
+ if (tts != NULL) {
+ TCHAR *szDest = (TCHAR*)alloca(tts->cbDest*sizeof(TCHAR));
+ timeapiPrintTimeStamp(NULL, (mir_time)wParam, _A2T(tts->szFormat), szDest, tts->cbDest, 0);
+ WideCharToMultiByte(CP_ACP, 0, szDest, -1, tts->szDest, tts->cbDest, NULL, NULL);
+ }
+ return 0;
+}
+
+void GetLocalizedString(HKEY hSubKey, const TCHAR *szName, wchar_t *szBuf, DWORD cbLen)
+{
+ DWORD dwLength = cbLen * sizeof(wchar_t);
+ RegQueryValueEx(hSubKey, szName, NULL, NULL, (unsigned char *)szBuf, &dwLength);
+ szBuf[min(dwLength / sizeof(TCHAR), cbLen - 1)] = 0;
+}
+
+extern "C" 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);
+ _tcsncpy_s(myInfo.myTZ.tszName, myTzKey, _TRUNCATE);
+ 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 (!mir_wstrcmp(tz.tzi.StandardName, myInfo.myTZ.tzi.StandardName) || !mir_wstrcmp(tz.tzi.DaylightName, myInfo.myTZ.tzi.DaylightName)) {
+ _tcsncpy_s(myInfo.myTZ.tszName, tz.tszName, _TRUNCATE);
+ found = true;
+ }
+ }
+ }
+}
+
+void InitTimeZones(void)
+{
+ REG_TZI_FORMAT tzi;
+ HKEY hKey;
+
+ const TCHAR *tszKey = _T("SOFTWARE\\Microsoft\\Windows NT\\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 (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;
+
+ mir_tstrcpy(tz->tszName, tszName);
+ tz->hash = mir_hashstrT(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);
+
+ CreateServiceFunction(MS_DB_TIME_TIMESTAMPTOSTRING, TimestampToStringA);
+
+ tmi.cbSize = sizeof(tmi);
+ GetTimeApi(0, (LPARAM)&tmi);
+}
diff --git a/src/mir_app/src/usedIcons.cpp b/src/mir_app/src/usedIcons.cpp new file mode 100644 index 0000000000..5592e01f82 --- /dev/null +++ b/src/mir_app/src/usedIcons.cpp @@ -0,0 +1,94 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+struct Icon
+{
+ char *name;
+ int refCount;
+ HANDLE hImage;
+
+ Icon(const char *icolibName) :
+ name( mir_strdup(icolibName)), refCount(0), hImage(INVALID_HANDLE_VALUE)
+ {
+ }
+
+ ~Icon()
+ { mir_free(name);
+ }
+};
+
+static int SortFunc(const Icon *p1, const Icon *p2)
+{
+ return mir_strcmp(p1->name, p2->name);
+}
+
+static OBJLIST<Icon> usedIcons(50, SortFunc);
+
+static Icon* FindIcon(const char *icolibName)
+{
+ Icon *icon = usedIcons.find((Icon*)&icolibName);
+ if (icon == NULL)
+ usedIcons.insert(icon = new Icon(icolibName));
+
+ if (icon->hImage == INVALID_HANDLE_VALUE) {
+ HICON hIcon = Skin_GetIcon(icon->name);
+ if (hIcon != NULL) {
+ icon->hImage = ExtraIcon_Add(hIcon);
+ Skin_ReleaseIcon(hIcon);
+ }
+ }
+
+ return icon;
+}
+
+HANDLE GetIcon(const char *icolibName)
+{
+ return FindIcon(icolibName)->hImage;
+}
+
+HANDLE AddIcon(const char *icolibName)
+{
+ Icon *icon = FindIcon(icolibName);
+ icon->refCount++;
+ return icon->hImage;
+}
+
+void RemoveIcon(const char *icolibName)
+{
+ Icon *icon = usedIcons.find((Icon*)&icolibName);
+ if (icon != NULL)
+ icon->refCount--;
+}
+
+void ResetIcons()
+{
+ for (int i = usedIcons.getCount()-1; i >= 0; i--) {
+ Icon &p = usedIcons[i];
+ if (p.refCount <= 0)
+ usedIcons.remove(i);
+ else
+ p.hImage = INVALID_HANDLE_VALUE;
+ }
+}
diff --git a/src/mir_app/src/usedIcons.h b/src/mir_app/src/usedIcons.h new file mode 100644 index 0000000000..b15da88108 --- /dev/null +++ b/src/mir_app/src/usedIcons.h @@ -0,0 +1,34 @@ +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-15 Miranda NG project
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#ifndef __USEDICONS_H__
+#define __USEDICONS_H__
+
+HANDLE GetIcon(LPCSTR icolibName);
+
+HANDLE AddIcon(LPCSTR icolibName);
+HANDLE AddIcon(HANDLE hIcolib);
+
+void RemoveIcon(LPCSTR icolibName);
+void ResetIcons();
+
+
+#endif // __USEDICONS_H__
diff --git a/src/mir_app/src/utils.cpp b/src/mir_app/src/utils.cpp new file mode 100644 index 0000000000..9ac79dd185 --- /dev/null +++ b/src/mir_app/src/utils.cpp @@ -0,0 +1,496 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (Ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+#define MS_SYSTEM_GET_MD5I "Miranda/System/GetMD5I"
+
+INT_PTR ResizeDialog(WPARAM wParam, LPARAM lParam);
+
+int InitOpenUrl(void);
+int InitWindowList(void);
+int InitPathUtils(void);
+int InitHyperlink(void);
+int InitColourPicker(void);
+void InitXmlApi(void);
+
+void InitTimeZones(void);
+
+int InitCrypt(void);
+void UninitCrypt(void);
+
+INT_PTR __cdecl svcEnterString(WPARAM, LPARAM lParam);
+
+static BOOL bModuleInitialized = FALSE;
+
+static CountryListEntry countries[] = {
+ {0, LPGEN("Unspecified"), ""},
+ {9999, LPGEN("Other"), ""},
+ {0xFFFF, LPGEN("Unknown"), ""},
+ {93, LPGEN("Afghanistan"), "AF"},
+ {358, LPGEN("Aland Islands"), "AX"},
+ {355, LPGEN("Albania"), "AL"},
+ {213, LPGEN("Algeria"), "DZ"},
+ {1684, LPGEN("American Samoa"), "AS"},
+ {376, LPGEN("Andorra"), "AD"},
+ {244, LPGEN("Angola"), "AO"},
+ {1264, LPGEN("Anguilla"), "AI"},
+ {0xFFFE, LPGEN("Antarctica"), "AQ"},
+ {1268, LPGEN("Antigua and Barbuda"), "AG"},
+ {54, LPGEN("Argentina"), "AR"},
+ {374, LPGEN("Armenia"), "AM"},
+ {297, LPGEN("Aruba"), "AW"},
+ {61, LPGEN("Australia"), "AU"},
+ {43, LPGEN("Austria"), "AT"},
+ {994, LPGEN("Azerbaijan"), "AZ"},
+ {1242, LPGEN("Bahamas"), "BS"},
+ {973, LPGEN("Bahrain"), "BH"},
+ {880, LPGEN("Bangladesh"), "BD"},
+ {1246, LPGEN("Barbados"), "BB"},
+ {375, LPGEN("Belarus"), "BY"},
+ {32, LPGEN("Belgium"), "BE"},
+ {501, LPGEN("Belize"), "BZ"},
+ {229, LPGEN("Benin"), "BJ"},
+ {1441, LPGEN("Bermuda"), "BM"},
+ {975, LPGEN("Bhutan"), "BT"},
+ {591, LPGEN("Bolivia"), "BO"},
+ {5997, LPGEN("Bonaire, Sint Eustatius and Saba"), "BQ"},
+ {387, LPGEN("Bosnia and Herzegovina"), "BA"},
+ {267, LPGEN("Botswana"), "BW"},
+ {55, LPGEN("Bouvet Island"), "BV"},
+ {55, LPGEN("Brazil"), "BR"},
+ {246, LPGEN("British Indian Ocean Territory"), "IO"},
+ {673, LPGEN("Brunei"), "BN"},
+ {359, LPGEN("Bulgaria"), "BG"},
+ {226, LPGEN("Burkina Faso"), "BF"},
+ {257, LPGEN("Burundi"), "BI"},
+ {855, LPGEN("Cambodia"), "KH"},
+ {237, LPGEN("Cameroon"), "CM"},
+ {1, LPGEN("Canada"), "CA"},
+ {238, LPGEN("Cape Verde"), "CV"},
+ {1345, LPGEN("Cayman Islands"), "KY"},
+ {236, LPGEN("Central African Republic"), "CF"},
+ {235, LPGEN("Chad"), "TD"},
+ {56, LPGEN("Chile"), "CL"},
+ {86, LPGEN("China"), "CN"},
+ {61, LPGEN("Christmas Island"), "CX"},
+ {61, LPGEN("Cocos (Keeling) Islands"), "CC"},
+ {57, LPGEN("Colombia"), "CO"},
+ {269, LPGEN("Comoros"), "KM"},
+ {242, LPGEN("Congo, Republic of the"), "CG"},
+ {243, LPGEN("Congo, Democratic Republic of the"), "CD"},
+ {682, LPGEN("Cook Islands"), "CK"},
+ {506, LPGEN("Costa Rica"), "CR"},
+ {225, LPGEN("Cote d'Ivoire"), "CI"},
+ {385, LPGEN("Croatia"), "HR"},
+ {53, LPGEN("Cuba"), "CU"},
+ {5999, LPGEN("Curacao"), "CW"},
+ {357, LPGEN("Cyprus"), "CY"},
+ {420, LPGEN("Czech Republic"), "CZ"},
+ {45, LPGEN("Denmark"), "DK"},
+ {253, LPGEN("Djibouti"), "DJ"},
+ {1767, LPGEN("Dominica"), "DM"},
+ {1809, LPGEN("Dominican Republic"), "DO"},
+ {670, LPGEN("East Timor"), "TL"},
+ {593, LPGEN("Ecuador"), "EC"},
+ {20, LPGEN("Egypt"), "EG"},
+ {503, LPGEN("El Salvador"), "SV"},
+ {240, LPGEN("Equatorial Guinea"), "GQ"},
+ {291, LPGEN("Eritrea"), "ER"},
+ {372, LPGEN("Estonia"), "EE"},
+ {251, LPGEN("Ethiopia"), "ET"},
+ {500, LPGEN("Falkland Islands (Malvinas)"), "FK"},
+ {298, LPGEN("Faroe Islands"), "FO"},
+ {679, LPGEN("Fiji"), "FJ"},
+ {358, LPGEN("Finland"), "FI"},
+ {33, LPGEN("France"), "FR"},
+ {594, LPGEN("French Guiana"), "GF"},
+ {689, LPGEN("French Polynesia"), "PF"},
+ {0xFFFE, LPGEN("French Southern and Antarctic Lands"), "TF"},
+ {241, LPGEN("Gabon"), "GA"},
+ {220, LPGEN("Gambia"), "GM"},
+ {995, LPGEN("Georgia"), "GE"},
+ {49, LPGEN("Germany"), "DE"},
+ {233, LPGEN("Ghana"), "GH"},
+ {350, LPGEN("Gibraltar"), "GI"},
+ {30, LPGEN("Greece"), "GR"},
+ {299, LPGEN("Greenland"), "GL"},
+ {1473, LPGEN("Grenada"), "GD"},
+ {590, LPGEN("Guadeloupe"), "GP"},
+ {1671, LPGEN("Guam"), "GU"},
+ {502, LPGEN("Guatemala"), "GT"},
+ {44, LPGEN("Guernsey"), "GG"},
+ {224, LPGEN("Guinea"), "GN"},
+ {245, LPGEN("Guinea-Bissau"), "GW"},
+ {592, LPGEN("Guyana"), "GY"},
+ {509, LPGEN("Haiti"), "HT"},
+ {0xFFFE, LPGEN("Heard Island and McDonald Islands"), "HM"},
+ {504, LPGEN("Honduras"), "HN"},
+ {852, LPGEN("Hong Kong"), "HK"},
+ {36, LPGEN("Hungary"), "HU"},
+ {354, LPGEN("Iceland"), "IS"},
+ {91, LPGEN("India"), "IN"},
+ {62, LPGEN("Indonesia"), "ID"},
+ {98, LPGEN("Iran"), "IR"},
+ {964, LPGEN("Iraq"), "IQ"},
+ {353, LPGEN("Ireland"), "IE"},
+ {44, LPGEN("Isle of Man"), "IM"},
+ {972, LPGEN("Israel"), "IL"},
+ {39, LPGEN("Italy"), "IT"},
+ {1876, LPGEN("Jamaica"), "JM"},
+ {81, LPGEN("Japan"), "JP"},
+ {44, LPGEN("Jersey"), "JE"},
+ {962, LPGEN("Jordan"), "JO"},
+ {76, LPGEN("Kazakhstan"), "KZ"},
+ {254, LPGEN("Kenya"), "KE"},
+ {686, LPGEN("Kiribati"), "KI"},
+ {850, LPGEN("North Korea"), "KP"},
+ {82, LPGEN("South Korea"), "KR"},
+ {965, LPGEN("Kuwait"), "KW"},
+ {996, LPGEN("Kyrgyzstan"), "KG"},
+ {856, LPGEN("Laos"), "LA"},
+ {371, LPGEN("Latvia"), "LV"},
+ {961, LPGEN("Lebanon"), "LB"},
+ {266, LPGEN("Lesotho"), "LS"},
+ {231, LPGEN("Liberia"), "LR"},
+ {218, LPGEN("Libya"), "LY"},
+ {423, LPGEN("Liechtenstein"), "LI"},
+ {370, LPGEN("Lithuania"), "LT"},
+ {352, LPGEN("Luxembourg"), "LU"},
+ {853, LPGEN("Macau"), "MO"},
+ {389, LPGEN("Macedonia"), "MK"},
+ {261, LPGEN("Madagascar"), "MG"},
+ {265, LPGEN("Malawi"), "MW"},
+ {60, LPGEN("Malaysia"), "MY"},
+ {960, LPGEN("Maldives"), "MV"},
+ {223, LPGEN("Mali"), "ML"},
+ {356, LPGEN("Malta"), "MT"},
+ {692, LPGEN("Marshall Islands"), "MH"},
+ {596, LPGEN("Martinique"), "MQ"},
+ {222, LPGEN("Mauritania"), "MR"},
+ {230, LPGEN("Mauritius"), "MU"},
+ {262, LPGEN("Mayotte"), "YT"},
+ {52, LPGEN("Mexico"), "MX"},
+ {691, LPGEN("Micronesia, Federated States of"), "FM"},
+ {373, LPGEN("Moldova"), "MD"},
+ {377, LPGEN("Monaco"), "MC"},
+ {976, LPGEN("Mongolia"), "MN"},
+ {382, LPGEN("Montenegro"), "ME"},
+ {1664, LPGEN("Montserrat"), "MS"},
+ {212, LPGEN("Morocco"), "MA"},
+ {258, LPGEN("Mozambique"), "MZ"},
+ {95, LPGEN("Myanmar"), "MM"},
+ {264, LPGEN("Namibia"), "NA"},
+ {674, LPGEN("Nauru"), "NR"},
+ {977, LPGEN("Nepal"), "NP"},
+ {31, LPGEN("Netherlands"), "NL"},
+ {687, LPGEN("New Caledonia"), "NC"},
+ {64, LPGEN("New Zealand"), "NZ"},
+ {505, LPGEN("Nicaragua"), "NI"},
+ {227, LPGEN("Niger"), "NE"},
+ {234, LPGEN("Nigeria"), "NG"},
+ {683, LPGEN("Niue"), "NU"},
+ {672, LPGEN("Norfolk Island"), "NF"},
+ {1670, LPGEN("Northern Mariana Islands"), "MP"},
+ {47, LPGEN("Norway"), "NO"},
+ {968, LPGEN("Oman"), "OM"},
+ {92, LPGEN("Pakistan"), "PK"},
+ {680, LPGEN("Palau"), "PW"},
+ {970, LPGEN("Palestinian Territories"), "PS"},
+ {507, LPGEN("Panama"), "PA"},
+ {675, LPGEN("Papua New Guinea"), "PG"},
+ {595, LPGEN("Paraguay"), "PY"},
+ {51, LPGEN("Peru"), "PE"},
+ {63, LPGEN("Philippines"), "PH"},
+ {64, LPGEN("Pitcairn Islands"), "PN"},
+ {48, LPGEN("Poland"), "PL"},
+ {351, LPGEN("Portugal"), "PT"},
+ {1787, LPGEN("Puerto Rico"), "PR"},
+ {974, LPGEN("Qatar"), "QA"},
+ {262, LPGEN("Reunion"), "RE"},
+ {40, LPGEN("Romania"), "RO"},
+ {7, LPGEN("Russia"), "RU"},
+ {250, LPGEN("Rwanda"), "RW"},
+ {590, LPGEN("Saint Barthelemy"), "BL"},
+ {290, LPGEN("Saint Helena, Ascension and Tristan da Cunha"), "SH"},
+ {1869, LPGEN("Saint Kitts and Nevis"), "KN"},
+ {1758, LPGEN("Saint Lucia"), "LC"},
+ {590, LPGEN("Saint Martin (French part)"), "MF"},
+ {508, LPGEN("Saint Pierre and Miquelon"), "PM"},
+ {1784, LPGEN("Saint Vincent and the Grenadines"), "VC"},
+ {685, LPGEN("Samoa"), "WS"},
+ {378, LPGEN("San Marino"), "SM"},
+ {239, LPGEN("Sao Tome and Principe"), "ST"},
+ {966, LPGEN("Saudi Arabia"), "SA"},
+ {221, LPGEN("Senegal"), "SN"},
+ {381, LPGEN("Serbia"), "RS"},
+ {248, LPGEN("Seychelles"), "SC"},
+ {232, LPGEN("Sierra Leone"), "SL"},
+ {65, LPGEN("Singapore"), "SG"},
+ {1721, LPGEN("Sint Maarten (Dutch part)"), "SX"},
+ {421, LPGEN("Slovakia"), "SK"},
+ {386, LPGEN("Slovenia"), "SI"},
+ {677, LPGEN("Solomon Islands"), "SB"},
+ {252, LPGEN("Somalia"), "SO"},
+ {27, LPGEN("South Africa"), "ZA"},
+ {500, LPGEN("South Georgia and the South Sandwich Islands"), "GS"},
+ {211, LPGEN("South Sudan"), "SS"},
+ {34, LPGEN("Spain"), "ES"},
+ {94, LPGEN("Sri Lanka"), "LK"},
+ {249, LPGEN("Sudan"), "SD"},
+ {597, LPGEN("Suriname"), "SR"},
+ {4779, LPGEN("Svalbard and Jan Mayen"), "SJ"},
+ {268, LPGEN("Swaziland"), "SZ"},
+ {46, LPGEN("Sweden"), "SE"},
+ {41, LPGEN("Switzerland"), "CH"},
+ {963, LPGEN("Syria"), "SY"},
+ {886, LPGEN("Taiwan"), "TW"},
+ {992, LPGEN("Tajikistan"), "TJ"},
+ {255, LPGEN("Tanzania"), "TZ"},
+ {66, LPGEN("Thailand"), "TH"},
+ {228, LPGEN("Togo"), "TG"},
+ {690, LPGEN("Tokelau"), "TK"},
+ {676, LPGEN("Tonga"), "TO"},
+ {1868, LPGEN("Trinidad and Tobago"), "TT"},
+ {216, LPGEN("Tunisia"), "TN"},
+ {90, LPGEN("Turkey"), "TR"},
+ {993, LPGEN("Turkmenistan"), "TM"},
+ {1649, LPGEN("Turks and Caicos Islands"), "TC"},
+ {688, LPGEN("Tuvalu"), "TV"},
+ {256, LPGEN("Uganda"), "UG"},
+ {380, LPGEN("Ukraine"), "UA"},
+ {971, LPGEN("United Arab Emirates"), "AE"},
+ {44, LPGEN("United Kingdom"), "GB"},
+ {1, LPGEN("United States"), "US"},
+ {699, LPGEN("United States Minor Outlying Islands"), "UM"},
+ {598, LPGEN("Uruguay"), "UY"},
+ {998, LPGEN("Uzbekistan"), "UZ"},
+ {678, LPGEN("Vanuatu"), "VU"},
+ {379, LPGEN("Vatican City"), "VA"},
+ {58, LPGEN("Venezuela"), "VE"},
+ {84, LPGEN("Vietnam"), "VN"},
+ {1284, LPGEN("Virgin Islands (British)"), "VG"},
+ {1340, LPGEN("Virgin Islands (United States)"), "VI"},
+ {681, LPGEN("Wallis and Futuna"), "WF"},
+ {5289, LPGEN("Western Sahara"), "EH"},
+ {967, LPGEN("Yemen"), "YE"},
+ {260, LPGEN("Zambia"), "ZM"},
+ {263, LPGEN("Zimbabwe"), "ZW"}
+};
+
+static INT_PTR GetCountryByNumber(WPARAM wParam, LPARAM)
+{
+ for (int i = 0; i < SIZEOF(countries); i++)
+ if ((int)wParam == countries[i].id)
+ return (INT_PTR)countries[i].szName;
+
+ return NULL;
+}
+
+static INT_PTR GetCountryByISOCode(WPARAM wParam, LPARAM)
+{
+ for (int i = 0; i < SIZEOF(countries); i++)
+ if ( mir_strcmpi((char*)wParam, countries[i].ISOcode) == 0)
+ return (INT_PTR)countries[i].szName;
+
+ return NULL;
+}
+
+static INT_PTR GetCountryList(WPARAM wParam, LPARAM lParam)
+{
+ *(int*)wParam = SIZEOF(countries);
+ *(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);
+ db_set_dw(swp->hContact, swp->szModule, szSettingName, wp.rcNormalPosition.left);
+ mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sy", swp->szNamePrefix);
+ db_set_dw(swp->hContact, swp->szModule, szSettingName, wp.rcNormalPosition.top);
+ mir_snprintf(szSettingName, SIZEOF(szSettingName), "%swidth", swp->szNamePrefix);
+ db_set_dw(swp->hContact, swp->szModule, szSettingName, wp.rcNormalPosition.right-wp.rcNormalPosition.left);
+ mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sheight", swp->szNamePrefix);
+ db_set_dw(swp->hContact, swp->szModule, szSettingName, wp.rcNormalPosition.bottom-wp.rcNormalPosition.top);
+ return 0;
+}
+
+static INT_PTR svcAssertInsideScreen(WPARAM wParam, LPARAM)
+{
+ LPRECT rc = (LPRECT)wParam;
+ if (rc == NULL)
+ return -1;
+
+ return AssertInsideScreen(*rc);
+}
+
+int AssertInsideScreen(RECT &rc)
+{
+ RECT rcScreen;
+ SystemParametersInfo(SPI_GETWORKAREA, 0, &rcScreen, FALSE);
+ if (MonitorFromRect(&rc, MONITOR_DEFAULTTONULL))
+ return 0;
+
+ MONITORINFO mi = {0};
+ HMONITOR hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
+ mi.cbSize = sizeof(mi);
+ if (GetMonitorInfo(hMonitor, &mi))
+ rcScreen = mi.rcWork;
+
+ 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 = db_get_dw(swp->hContact, swp->szModule, szSettingName, -1);
+ mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sy", swp->szNamePrefix);
+ y = (int)db_get_dw(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+db_get_dw(swp->hContact, swp->szModule, szSettingName, -1);
+ mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sheight", swp->szNamePrefix);
+ wp.rcNormalPosition.bottom = wp.rcNormalPosition.top+db_get_dw(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(wp.rcNormalPosition);
+
+ SetWindowPlacement(swp->hwnd, &wp);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR RestartMiranda(WPARAM wParam, LPARAM)
+{
+ TCHAR mirandaPath[MAX_PATH], cmdLine[MAX_PATH];
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si = {0};
+ si.cb = sizeof(si);
+ GetModuleFileName(NULL, mirandaPath, SIZEOF(mirandaPath));
+ if (wParam) {
+ VARST profilename( _T("%miranda_profilename%"));
+ mir_sntprintf(cmdLine, SIZEOF(cmdLine), _T("\"%s\" /restart:%d /profile=%s"), mirandaPath, GetCurrentProcessId(), (TCHAR*)profilename);
+ }
+ else mir_sntprintf(cmdLine, SIZEOF(cmdLine), _T("\"%s\" /restart:%d"), mirandaPath, GetCurrentProcessId());
+
+ CallService("CloseAction", 0, 0);
+ 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;
+ }
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int LoadUtilsModule(void)
+{
+ bModuleInitialized = TRUE;
+
+ CreateServiceFunction(MS_UTILS_RESIZEDIALOG, ResizeDialog);
+ CreateServiceFunction(MS_UTILS_SAVEWINDOWPOSITION, SaveWindowPosition);
+ CreateServiceFunction(MS_UTILS_RESTOREWINDOWPOSITION, RestoreWindowPosition);
+ CreateServiceFunction(MS_UTILS_ASSERTINSIDESCREEN, svcAssertInsideScreen);
+ CreateServiceFunction(MS_UTILS_GETCOUNTRYBYNUMBER, GetCountryByNumber);
+ CreateServiceFunction(MS_UTILS_GETCOUNTRYBYISOCODE, GetCountryByISOCode);
+ CreateServiceFunction(MS_UTILS_GETCOUNTRYLIST, GetCountryList);
+ CreateServiceFunction(MS_UTILS_GETRANDOM, GenerateRandom);
+ CreateServiceFunction(MS_UTILS_ENTERSTRING, svcEnterString);
+ CreateServiceFunction(MS_SYSTEM_RESTART, RestartMiranda);
+
+ InitOpenUrl();
+ InitWindowList();
+ InitHyperlink();
+ InitPathUtils();
+ InitColourPicker();
+ InitXmlApi();
+ InitTimeZones();
+ InitCrypt();
+ return 0;
+}
+
+void UnloadUtilsModule(void)
+{
+ if (!bModuleInitialized)
+ return;
+
+ UninitCrypt();
+}
diff --git a/src/mir_app/src/visibility.cpp b/src/mir_app/src/visibility.cpp new file mode 100644 index 0000000000..5c5f85f84e --- /dev/null +++ b/src/mir_app/src/visibility.cpp @@ -0,0 +1,289 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+static void SetListGroupIcons(HWND hwndList, HANDLE hFirstItem, HANDLE hParentItem, int *groupChildCount)
+{
+ int iconOn[2] = {1, 1};
+ int childCount[2] = {0, 0};
+
+ int typeOfFirst = SendMessage(hwndList, CLM_GETITEMTYPE, (WPARAM)hFirstItem, 0);
+ //check groups
+ HANDLE hItem;
+ if (typeOfFirst == CLCIT_GROUP)
+ hItem = hFirstItem;
+ else
+ hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXTGROUP, (LPARAM)hFirstItem);
+
+ while (hItem) {
+ HANDLE hChildItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
+ if (hChildItem) SetListGroupIcons(hwndList, hChildItem, hItem, childCount);
+ for (int 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 (int i=0; i < SIZEOF(iconOn); i++) {
+ int iImage = SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, i);
+ if (iconOn[i] && iImage == 0) iconOn[i] = 0;
+ if (iImage != EMPTY_EXTRA_ICON)
+ childCount[i]++;
+ }
+ hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXTCONTACT, (LPARAM)hItem);
+ }
+ //set icons
+ for (int i=0; i < SIZEOF(iconOn); i++) {
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hParentItem, MAKELPARAM(i, childCount[i]?(iconOn[i]?i+1:0):EMPTY_EXTRA_ICON));
+ if (groupChildCount) groupChildCount[i]+=childCount[i];
+ }
+}
+
+static void SetAllChildIcons(HWND hwndList, HANDLE hFirstItem, int iColumn, int iImage)
+{
+ int typeOfFirst = SendMessage(hwndList, CLM_GETITEMTYPE, (WPARAM)hFirstItem, 0);
+ //check groups
+ HANDLE hItem;
+ if (typeOfFirst == CLCIT_GROUP)
+ hItem = hFirstItem;
+ else
+ hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXTGROUP, (LPARAM)hFirstItem);
+ while (hItem) {
+ HANDLE 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) {
+ int iOldIcon = SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, iColumn);
+ if (iOldIcon != EMPTY_EXTRA_ICON && 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)
+{
+ SetWindowLongPtr(hwndList, GWL_STYLE, GetWindowLongPtr(hwndList, GWL_STYLE)|CLS_SHOWHIDDEN);
+}
+
+static void SetAllContactIcons(HWND hwndList)
+{
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, hContact, 0);
+ if (hItem == NULL)
+ continue;
+
+ DWORD flags;
+ WORD status;
+ char *szProto = GetContactProto(hContact);
+ if (szProto == NULL) {
+ flags = 0;
+ status = 0;
+ }
+ else {
+ flags = CallProtoServiceInt(NULL,szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ status = db_get_w(hContact, szProto, "ApparentMode", 0);
+ }
+
+ if (flags & PF1_INVISLIST)
+ if (SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(0, 0)) == EMPTY_EXTRA_ICON)
+ 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)) == EMPTY_EXTRA_ICON)
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(1, status == ID_STATUS_OFFLINE ? 2 : 0));
+ }
+}
+
+static INT_PTR CALLBACK DlgProcVisibilityOpts(HWND hwndDlg, UINT msg, WPARAM, LPARAM lParam)
+{
+ static HICON hVisibleIcon, hInvisibleIcon;
+ static HANDLE hItemAll;
+
+ HIMAGELIST hIml;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ hIml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 3, 3);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_SMALLDOT);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_VISIBLE_ALL);
+ ImageList_AddIcon_IconLibLoaded(hIml, SKINICON_OTHER_INVISIBLE_ALL);
+ 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 = { 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:
+ // Make sure we have an extra column
+ NMCLISTCONTROL *nm = (NMCLISTCONTROL*)lParam;
+ if (nm->iColumn == -1)
+ break;
+
+ // Find clicked item
+ DWORD hitFlags;
+ HANDLE hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_HITTEST, (WPARAM)&hitFlags, MAKELPARAM(nm->pt.x, nm->pt.y));
+ 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)
+ int 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...)
+ int 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)) != EMPTY_EXTRA_ICON)
+ 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;
+
+ case 0:
+ if (((LPNMHDR)lParam)->code == PSN_APPLY) {
+ for (MCONTACT hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) {
+ HANDLE hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_LIST, CLM_FINDCONTACT, hContact, 0);
+ if (hItem == NULL)
+ continue;
+
+ int set = 0;
+ for (int i = 0; i < 2; i++) {
+ int 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);
+ }
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ DestroyIcon(hVisibleIcon);
+ DestroyIcon(hInvisibleIcon);
+
+ 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.position = 850000000;
+ odp.hInstance = g_hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_VISIBILITY);
+ odp.pszTitle = LPGEN("Visibility");
+ odp.pszGroup = LPGEN("Contacts");
+ odp.pfnDlgProc = DlgProcVisibilityOpts;
+ odp.flags = ODPF_BOLDGROUPS;
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+int LoadVisibilityModule(void)
+{
+ HookEvent(ME_OPT_INITIALISE, VisibilityOptInitialise);
+ return 0;
+}
diff --git a/src/mir_app/src/windowlist.cpp b/src/mir_app/src/windowlist.cpp new file mode 100644 index 0000000000..cea13f4e6e --- /dev/null +++ b/src/mir_app/src/windowlist.cpp @@ -0,0 +1,111 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.h"
+
+struct TWindowListItem
+{
+ TWindowListItem(MCONTACT _contact, HWND _wnd) :
+ hContact(_contact),
+ hWnd(_wnd)
+ {}
+
+ MCONTACT hContact;
+ HWND hWnd;
+};
+
+typedef OBJLIST<TWindowListItem> TWindowList;
+
+static INT_PTR AllocWindowList(WPARAM, LPARAM)
+{
+ return (INT_PTR)new TWindowList(10, NumericKeySortT);
+}
+
+static INT_PTR DestroyWindowList(WPARAM wParam, LPARAM)
+{
+ delete (TWindowList*)wParam;
+ return 0;
+}
+
+static INT_PTR AddToWindowList(WPARAM, LPARAM lParam)
+{
+ WINDOWLISTENTRY *pEntry = (WINDOWLISTENTRY*)lParam;
+ TWindowList *pList = (TWindowList*)pEntry->hList;
+ if (pList != NULL)
+ pList->insert(new TWindowListItem(pEntry->hContact, pEntry->hwnd));
+ return 0;
+}
+
+static INT_PTR RemoveFromWindowList(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam == 0) return 1;
+ TWindowList &pList = *(TWindowList*)wParam;
+ for (int i = 0; i < pList.getCount(); i++) {
+ if (pList[i].hWnd == (HWND)lParam) {
+ pList.remove(i);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static INT_PTR FindInWindowList(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam == 0) return NULL;
+ TWindowList &pList = *(TWindowList*)wParam;
+ TWindowListItem *p = pList.find((TWindowListItem*)&lParam);
+ return (p == NULL) ? NULL : (INT_PTR)p->hWnd;
+}
+
+static INT_PTR BroadcastToWindowList(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam == 0 || lParam == 0) return NULL;
+ TWindowList &pList = *(TWindowList*)wParam;
+ MSG *msg = (MSG*)lParam;
+ for (int i = pList.getCount()-1; i >= 0; i--)
+ SendMessage(pList[i].hWnd, msg->message, msg->wParam, msg->lParam);
+ return 0;
+}
+
+static INT_PTR BroadcastToWindowListAsync(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam == 0 || lParam == 0) return NULL;
+ TWindowList &pList = *(TWindowList*)wParam;
+ MSG *msg = (MSG*)lParam;
+ for (int i = pList.getCount()-1; i >= 0; i--)
+ PostMessage(pList[i].hWnd, msg->message, msg->wParam, msg->lParam);
+ return 0;
+}
+
+int InitWindowList(void)
+{
+ CreateServiceFunction(MS_UTILS_ALLOCWINDOWLIST, AllocWindowList);
+ CreateServiceFunction(MS_UTILS_DESTROYWINDOWLIST, DestroyWindowList);
+ 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;
+}
diff --git a/src/mir_app/src/xmlApi.cpp b/src/mir_app/src/xmlApi.cpp new file mode 100644 index 0000000000..cd0c347987 --- /dev/null +++ b/src/mir_app/src/xmlApi.cpp @@ -0,0 +1,467 @@ +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org),
+Copyright (c) 2000-12 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 "stdafx.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 xmlapiParseFile(LPCTSTR str, int* datalen, LPCTSTR tag)
+{
+ if (str == NULL) return NULL;
+
+ XMLResults res;
+ XMLNode result = XMLNode::parseFile(str, tag, &res);
+
+ if (datalen != NULL)
+ datalen[0] += res.nChars;
+
+ return (res.error == eXMLErrorNone || (tag != NULL && res.error == eXMLErrorMissingEndTag)) ? result.detach() : NULL;
+}
+
+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 XMLError xmlapiToFile(HXML _n, LPCTSTR filename, int withformatting)
+{
+ return XMLNode(_n).writeToFile(filename, NULL, withformatting);
+}
+
+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->parseFile = xmlapiParseFile;
+ xi->toFile = xmlapiToFile;
+
+ 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/mir_app/src/xmlParser.cpp b/src/mir_app/src/xmlParser.cpp new file mode 100644 index 0000000000..be360ea40f --- /dev/null +++ b/src/mir_app/src/xmlParser.cpp @@ -0,0 +1,3076 @@ +/** +**************************************************************************** +* <P> 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. </P> +* +* @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: +* <a><b>some text</b><b>other text </a> +* as an error. Otherwise, this tring will be equivalent to: +* <a><b>some text</b><b>other text</b></a> +* +* NOTE: +* +* If you add "#define APPROXIMATE_PARSING" on the first line of this file +* the parser will see the following XML-stream: +* <data name = "n1"> +* <data name = "n2"> +* <data name = "n3" /> +* as equivalent to the following XML-stream: +* <data name = "n1" /> +* <data name = "n2" /> +* <data name = "n3" /> +* 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): +* <data name = "n1"> +* <data name = "n2"> +* <data name = "n3" /> +* </data> +* </data> +* +* 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 +* <a href = "http://www.Business-Insight.com">Business-Insight</a> +* All rights reserved. +* See the file "AFPL-license.txt" about the licensing terms +* +**************************************************************************** +*/ + +#include "stdafx.h" +#include "xmlParser.h" + +#include <memory.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +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 = 0, 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 "<!DOCTYPE" declaration must be the second in the list. +// The "<!--" declaration must be the third in the list. +// All ClearTag Strings must start with the '<' character. +typedef struct { XMLCSTR lpszOpen; int openTagLen; XMLCSTR lpszClose;} ALLXMLClearTag; +static ALLXMLClearTag XMLClearTags[] = +{ + { _CXML("<![CDATA["), 9, _CXML("]]>") }, + { _CXML("<!DOCTYPE"), 9, _CXML(">") }, + { _CXML("<!--") , 4, _CXML("-->") }, + { _CXML("<PRE>") , 5, _CXML("</PRE>") }, + // { _CXML("<Script>") , 8, _CXML("</Script>")}, + { 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<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; i<len; i++) if (s[i] <= (unsigned short)255) stats++; + if (stats>len/2) return TRUE; + + // Check for UNICODE NULL chars + for (i=0; i<len; i++) if (!s[i]) return TRUE; + + return FALSE; +} +#else +char myIsTextWideChar(const void *b, int l) { return (char)IsTextUnicode((CONST LPVOID)b, l, NULL); } +#endif +#endif + +#ifdef _XMLWINDOWS +// for Microsoft Visual Studio 6.0 and Microsoft Visual Studio .NET and Borland C++ Builder 6.0 +#ifdef _XMLWIDECHAR +wchar_t *myMultiByteToWideChar(const char *s, XMLNode::XMLCharEncoding ce) +{ + int i; + if (ce == XMLNode::char_encoding_UTF8) i = (int)MultiByteToWideChar(CP_UTF8, 0 , s, -1, NULL, 0); + else i = (int)MultiByteToWideChar(CP_ACP , MB_PRECOMPOSED, s, -1, NULL, 0); + if (i<0) return NULL; + wchar_t *d = (wchar_t *)malloc((i+1)*sizeof(XMLCHAR)); + if (ce == XMLNode::char_encoding_UTF8) i = (int)MultiByteToWideChar(CP_UTF8, 0 , s, -1, d, i); + else i = (int)MultiByteToWideChar(CP_ACP , MB_PRECOMPOSED, s, -1, d, i); + d[i] = 0; + return d; +} +static inline FILE *xfopen(XMLCSTR filename, XMLCSTR mode) { return _wfopen(filename, mode); } +static inline size_t xstrlen(XMLCSTR c) { return mir_wstrlen(c); } +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return _wcsnicmp(c1, c2, l);} +static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncmp(c1, c2, l);} +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return _wcsicmp(c1, c2); } +static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)wcsstr(c1, c2); } +static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)mir_wstrcpy(c1, c2); } +#else +char *myWideCharToMultiByte(const wchar_t *s) +{ + UINT codePage = CP_ACP; if (characterEncoding == XMLNode::char_encoding_UTF8) codePage = CP_UTF8; + int i = (int)WideCharToMultiByte(codePage, // code page + 0, // performance and mapping flags + s, // wide-character string + -1, // number of chars in string + NULL, // buffer for new string + 0, // size of buffer + NULL, // default for unmappable chars + NULL // set when default char used + ); + if (i<0) return NULL; + char *d = (char*)malloc(i+1); + WideCharToMultiByte(codePage, // code page + 0, // performance and mapping flags + s, // wide-character string + -1, // number of chars in string + d, // buffer for new string + i, // size of buffer + NULL, // default for unmappable chars + NULL // set when default char used + ); + d[i] = 0; + return d; +} +static inline FILE *xfopen(XMLCSTR filename, XMLCSTR mode) { return fopen(filename, mode); } +static inline size_t xstrlen(XMLCSTR c) { return mir_strlen(c); } +#ifdef __BORLANDC__ +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return strnicmp(c1, c2, l);} +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return mir_strcmpi(c1, c2); } +#else +static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return _strnicmp(c1, c2, l);} +static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return _stricmp(c1, c2); } +#endif +static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncmp(c1, c2, l);} +static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)strstr(c1, c2); } +static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)mir_strcpy(c1, c2); } +#endif +#else +// for gcc and CC +#ifdef XML_NO_WIDE_CHAR +char *myWideCharToMultiByte(const wchar_t *s) { return NULL; } +#else +char *myWideCharToMultiByte(const wchar_t *s) +{ + const wchar_t *ss = s; + int i = (int)wcsrtombs(NULL, &ss, 0, NULL); + if (i<0) return NULL; + char *d = (char *)malloc(i+1); + wcsrtombs(d, &s, i, NULL); + d[i] = 0; + return d; +} +#endif +#ifdef _XMLWIDECHAR +wchar_t *myMultiByteToWideChar(const char *s, XMLNode::XMLCharEncoding ce) +{ + const char *ss = s; + int i = (int)mbsrtowcs(NULL, &ss, 0, NULL); + if (i<0) return NULL; + wchar_t *d = (wchar_t *)malloc((i+1)*sizeof(wchar_t)); + mbsrtowcs(d, &s, i, NULL); + d[i] = 0; + return d; +} +int xstrlen(XMLCSTR c) { return mir_wstrlen(c); } +#ifdef sun +// for CC +#include <widec.h> +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 <wctype.h> +// 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)mir_wstrcpy(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 mir_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)mir_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 <widec.h> +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"; } + mir_snprintf(message, SIZEOF(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, /* "</" */ + eTokenCloseTag, /* ">" */ + eTokenEquals, /* " = " */ + eTokenDeclaration, /* "<?" */ + eTokenShortHandClose, /* "/>" */ + 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 ( fputws(L"<?xml version = \"1.0\" encoding = \"utf-16\"?>\n", f) == EOF) + { + 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, "<?xml version = \"1.0\" encoding = \"%s\"?>\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 '</', + // or an xml declaration '<?' + chTemp = pXML->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 (;i<n;i++) + if ((o[i]&3) == (int)t) o[i]-=4; + // We should normally do: + // d->pOrder = (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<n)&&((o[pos]&3) != (int)xtype)) pos++; + if (pos == n) { *_pos = nc; o[n] = (int)((nc<<2)+xtype); return p; } + + o[i] = o[pos]; + for (i = pos+1;i <= n;i++) if ((o[i]&3) == (int)xtype) o[i]+=4; + + *_pos = pos = o[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 (!mir_tstrcmp(p+1, d->lpszNS) || (d->pParent && !mir_tstrcmp(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 <!DOCTYPE need a better treatment so lets handle it + if (pClear.lpszOpen == XMLClearTags[1].lpszOpen) + { + XMLCSTR pCh = lpXML; + while (*pCh) + { + if (*pCh == _CXML('<')) { pClear.lpszClose = docTypeEnd; lpszTemp = xstrstr(lpXML, docTypeEnd); break; } + else if (*pCh == _CXML('>')) { 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 + size_t n = d->nChild+d->nText+d->nClear-1; + int *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]); + size_t n2 = xstrlen(lpt)+1; + d->pText[i] = (XMLSTR)realloc((void*)d->pText[i], (n+n2)*sizeof(XMLCHAR)); + if (!d->pText[i]) { + free(lpt); + 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 '<?' + case eTokenTagStart: + case eTokenDeclaration: + + // Cache whether this new element is a declaration or not + nDeclaration = (xtype == eTokenDeclaration); + + // If we have node text then add this to the element + if (maybeAddTxT(pXML, token.pStr)) return FALSE; + + // Find the name of the tag + token = GetNextToken(pXML, &cbToken, &xtype); + + // Return an error if we couldn't obtain the next token or + // it wasnt text + if (xtype != eTokenText) + { + pXML->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: /* '</' */ + case eTokenEquals: /* '=' */ + case eTokenDeclaration: /* '<?' */ + case eTokenClear: + pXML->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 '<?' then we need + // to remove extra closing '?' if it exists + pXML->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 </' */ + case eTokenDeclaration: /* 'Attribute <?' */ + case eTokenClear: + pXML->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 '<?' then we need + // to remove extra closing '?' if it exists + if (d->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 eTokenCloseTag: /* 'Attr = >' */ + case eTokenShortHandClose: /* "Attr = />" */ + case eTokenEquals: /* 'Attr = = ' */ + case eTokenDeclaration: /* 'Attr = <?' */ + case eTokenClear: + pXML->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.nIndex<nUpto) + { + ch = getNextChar(&xml); + if (ch != _CXML('\n')) pResults->nColumn++; + 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; + int i, j; + + assert(pEntry); + + nElementI = pEntry->nChild+pEntry->nText+pEntry->nClear; + if ((nFormat>=0)&&(nElementI == 1)&&(pEntry->nText == 1)&&(!pEntry->isDeclaration)) nFormat = -2; + +#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) + { + // "<elementname " + if (lpszMarker) + { + if (cb) charmemset(lpszMarker, INDENTCHAR, cb); + nResult = cb; + lpszMarker[nResult++] = _CXML('<'); + if (pEntry->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; i<pEntry->nAttribute; 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; i<nElementI; i++) + { + j = pEntry->pOrder[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 - "<elementname>blah blah blah</elementname>" + if (nElementI) + { + // "</elementname>\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 - + // "<elementname/>" + // "/>\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; i<dd->nChild; i++) + { + pc = dd->pChild+i; + pc->d->pParent = NULL; + pc->d->ref_count--; + pc->emptyTheNode(force); + } + myFree(dd->pChild); + for (i=0; i<dd->nText; i++) free((void*)dd->pText[i]); + myFree(dd->pText); + for (i=0; i<dd->nClear; i++) free((void*)dd->pClear[i].lpszValue); + myFree(dd->pClear); + for (i=0; i<dd->nAttribute; 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<l; i++) if (lpszValue == p[i]) return i; + return -1; +} + +void XMLNode::deleteText(int i) +{ + if ((!d) || (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; i<l; i++) if (lpszValue == p[i].lpszValue) return i; + return -1; +} + +void XMLNode::deleteClear(XMLCSTR lpszValue) { deleteClear(indexClear(lpszValue)); } +void XMLNode::deleteClear(XMLClear *a) { if (a) deleteClear(a->lpszValue); } + +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; i<n; i++) + { + if (xstricmp(pc->d->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 (; i<n; i++) + { + if (!xstricmp(pc->d->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; i<n; i++) + if (par->pChild[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 (; i<n; i++) + { + if (xstricmp(pAttr->lpszName, 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; i<n; i++) + { + if (xstricmp(pAttr->lpszName, 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 (i<d->nAttribute) + { + 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(); + size_t length = 1; + for (int 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 (int 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<l) + switch (XML_utf8ByteTable[b[i]]) + { + case 4: i++; if ((i<l)&&(b[i]& 0xC0) != 0x80) { bestGuess = char_encoding_legacy; i = l; } // 10bbbbbb ? + case 3: i++; if ((i<l)&&(b[i]& 0xC0) != 0x80) { bestGuess = char_encoding_legacy; i = l; } // 10bbbbbb ? + case 2: i++; if ((i<l)&&(b[i]& 0xC0) != 0x80) { bestGuess = char_encoding_legacy; i = l; } // 10bbbbbb ? + case 1: i++; break; + case 0: i = l; + } + if (!useXMLEncodingAttribute) return bestGuess; + // if encoding is specified and different from utf-8 than it's non-utf8 + // otherwise it's utf-8 + char bb[201]; + l = mmin(l, 200); + memcpy(bb, buf, l); // copy buf into bb to be able to do "bb[l] = 0" + bb[l] = 0; + b = (unsigned char*)strstr(bb, "encoding"); + if (!b) return bestGuess; + b+=8; while XML_isSPACECHAR(*b) b++; if (*b != '=') return bestGuess; + b++; while XML_isSPACECHAR(*b) b++; if ((*b != '\'')&&(*b != '"')) return bestGuess; + b++; while XML_isSPACECHAR(*b) b++; + + if ((xstrnicmp((char*)b, "utf-8", 5) == 0) || + (xstrnicmp((char*)b, "utf8", 4) == 0)) + { + if (bestGuess == char_encoding_legacy) return char_encoding_error; + return char_encoding_UTF8; + } + + if ((xstrnicmp((char*)b, "shiftjis", 8) == 0) || + (xstrnicmp((char*)b, "shift-jis", 9) == 0) || + (xstrnicmp((char*)b, "sjis", 4) == 0)) return char_encoding_ShiftJIS; + + if (xstrnicmp((char*)b, "GB2312", 6) == 0) return char_encoding_GB2312; + if (xstrnicmp((char*)b, "Big5", 4) == 0) return char_encoding_Big5; + if (xstrnicmp((char*)b, "GBK", 3) == 0) return char_encoding_GBK; + + return char_encoding_legacy; +#endif +} +#undef XML_isSPACECHAR + +////////////////////////////////////////////////////////// +// Here starts the base64 conversion functions. // +////////////////////////////////////////////////////////// + +static const char base64Fillchar = _CXML('='); // used to mark partial words at the end + +// this lookup table defines the base64 encoding +XMLCSTR base64EncodeTable = _CXML("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + +// Decode Table gives the index of any valid base64 character in the Base64 table] +// 96: '=' - 97: space char - 98: illegal char - 99: end of string +const unsigned char base64DecodeTable[] = { + 99, 98, 98, 98, 98, 98, 98, 98, 98, 97, 97, 98, 98, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, //00 -29 + 98, 98, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 62, 98, 98, 98, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 98, 98, //30 -59 + 98, 96, 98, 98, 98, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, //60 -89 + 25, 98, 98, 98, 98, 98, 98, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, //90 -119 + 49, 50, 51, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, //120 -149 + 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, //150 -179 + 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, //180 -209 + 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, //210 -239 + 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98 //240 -255 +}; + +XMLParserBase64Tool::~XMLParserBase64Tool() { freeBuffer(); } + +void XMLParserBase64Tool::freeBuffer() { if (buf) free(buf); buf = NULL; buflen = 0; } + +int XMLParserBase64Tool::encodeLength(int inlen, char formatted) +{ + unsigned int i = ((inlen-1)/3*4+4+1); + if (formatted) i+=inlen/54; + return i; +} + +XMLSTR XMLParserBase64Tool::encode(unsigned char *inbuf, unsigned int inlen, char formatted) +{ + int i = encodeLength(inlen, formatted), k = 17, eLen = inlen/3, j; + alloc(i*sizeof(XMLCHAR)); + XMLSTR curr = (XMLSTR)buf; + for (i=0;i<eLen;i++) + { + // Copy next three bytes into lower 24 bits of int, paying attention to sign. + j = (inbuf[0]<<16)|(inbuf[1]<<8)|inbuf[2]; inbuf+=3; + // Encode the int into four chars + *(curr++) = base64EncodeTable[ j>>18 ]; + *(curr++) = base64EncodeTable[(j>>12)&0x3f]; + *(curr++) = base64EncodeTable[(j>> 6)&0x3f]; + *(curr++) = base64EncodeTable[(j)&0x3f]; + if (formatted) { if (!k) { *(curr++) = _CXML('\n'); k = 18; } k--; } + } + eLen = inlen-eLen*3; // 0 - 2. + if (eLen == 1) + { + *(curr++) = base64EncodeTable[ inbuf[0]>>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) { + if (outlen) + *outlen = 0; + return (unsigned char*)""; // XXX: check me! + } + + 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/mir_app/src/xmlParser.h b/src/mir_app/src/xmlParser.h new file mode 100644 index 0000000000..aa9bf91a88 --- /dev/null +++ b/src/mir_app/src/xmlParser.h @@ -0,0 +1,715 @@ +/****************************************************************************/
+/*! \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
+ * <a href = "http://www.Business-Insight.com">Business-Insight</a>
+ * All rights reserved.
+ * See the file <a href = "../../AFPL-license.txt">AFPL-license.txt</a> about the licensing terms
+ *
+ * \section tutorial First Tutorial
+ * You can follow a simple <a href = "../../xmlParser.html">Tutorial</a> to know the basics...
+ *
+ * \section usage General usage: How to include the XMLParser library inside your project.
+ *
+ * The library is composed of two files: <a href = "../../xmlParser.cpp">xmlParser.cpp</a> and
+ * <a href = "../../xmlParser.h">xmlParser.h</a>. 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
+ * <a href = "../../xmlParser.h">xmlParser.h</a>. 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
+ * <a href = "../../xmlParser.html">xmlParser.html</a>
+ *
+ * Some additional small examples are also inside the file <a href = "../../xmlTest.cpp">xmlTest.cpp</a>
+ * (for the "char*" version of the library) and inside the file
+ * <a href = "../../xmlTestUnicode.cpp">xmlTestUnicode.cpp</a> (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
+ * <a href = "../../xmlParser.cpp">xmlParser.cpp</a> and
+ * <a href = "../../xmlParser.h">xmlParser.h</a> files into the project).
+ *
+ * The file <a href = "../../XMLNodeAutoexp.txt">XMLNodeAutoexp.txt</a> 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 <stdlib.h>
+
+// 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
+
+#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 <wchar.h> or <tchar.h> libraries anymore to compile)
+//#define XML_NO_WIDE_CHAR
+
+#ifdef XML_NO_WIDE_CHAR
+#undef _XMLWINDOWS
+#undef _XMLWIDECHAR
+#endif
+
+#ifdef _XMLWINDOWS
+#include <tchar.h>
+#else
+#define XMLDLLENTRY
+#ifndef XML_NO_WIDE_CHAR
+#include <wchar.h> // 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 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:
+ * <ul>
+ * <li> XMLNode::parseString </li>
+ * <li> XMLNode::parseFile </li>
+ * <li> XMLNode::openFileHelper </li>
+ * <li> XMLNode::createXMLTopNode (or XMLNode::createXMLTopNode_WOSD)</li>
+ * </ul> */
+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). <br>
+ *
+ * REMARK: 0 <= pos < nChild()+nText()+nClear() <br>
+ */
+
+ /** @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 "<![CDATA["
+ * @param lpszClose 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 "mir_strcmp" function is used to find the right attribute)
+ void deleteAttribute(XMLAttribute *anAttribute); ///< Delete the attribute with the name "anAttribute->lpszName" (the "mir_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("<a>foo<!-- hello -->bar<!DOCTYPE world >chu</a>", "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) --> "<!-- hello -->"
+ * x.getClear(1) --> "<!DOCTYPE world >"
+ * \endcode
+ * If removeCommentsInMiddleOfText = 1, then we will have:
+ * \code
+ * x.getText(0) -> "foobar"
+ * x.getText(1) -> "chu"
+ * x.getClear(0) --> "<!DOCTYPE world >"
+ * \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 - '<?xml ?>'
+ 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();///<call this function when you have finished using this object to release memory used by the internal buffer.
+
+ XMLSTR toXML(XMLCSTR source);///< returns a pointer to an internal buffer that contains a XML-encoded string based on the "source" parameter.
+
+ /** The "toXMLUnSafe" function is deprecated because there is a possibility of
+ * "destination-buffer-overflow". It converts the string
+ * "source" to the string "dest". */
+ static XMLSTR toXMLUnSafe(XMLSTR dest, XMLCSTR source); ///< deprecated: use "toXML" instead
+ static int lengthXMLString(XMLCSTR source); ///< deprecated: use "toXML" instead
+
+private:
+ XMLSTR buf;
+ int buflen;
+} ToXMLStringTool;
+/** @} */
+
+/** @defgroup XMLParserBase64Tool Helper class to include binary data inside XML strings using "Base64 encoding".
+* @ingroup XMLParserGeneral
+* @{ */
+/// Helper class to include binary data inside XML strings using "Base64 encoding".
+/** The "XMLParserBase64Tool" class allows you to include any binary data (images, sounds, ...)
+* into an XML document using "Base64 encoding". This class is completely
+* separated from the rest of the xmlParser library and can be removed without any problem.
+* To include some binary data into an XML file, you must convert the binary data into
+* standard text (using "encode"). To retrieve the original binary data from the
+* b64-encoded text included inside the XML file, use "decode". Alternatively, these
+* functions can also be used to "encrypt/decrypt" some critical data contained inside
+* the XML (it's not a strong encryption at all, but sometimes it can be useful). */
+typedef struct XMLDLLENTRY XMLParserBase64Tool
+{
+public:
+ XMLParserBase64Tool(): buf(NULL), buflen(0) {}
+ ~XMLParserBase64Tool();
+ void freeBuffer();///< Call this function when you have finished using this object to release memory used by the internal buffer.
+
+ /**
+ * @param formatted If "formatted" = true, some space will be reserved for a carriage-return every 72 chars. */
+ static int encodeLength(int inBufLen, char formatted = 0); ///< return the length of the base64 string that encodes a data buffer of size inBufLen bytes.
+
+ /**
+ * The "base64Encode" function returns a string containing the base64 encoding of "inByteLen" bytes
+ * from "inByteBuf". If "formatted" parameter is true, then there will be a carriage-return every 72 chars.
+ * The string will be free'd when the XMLParserBase64Tool object is deleted.
+ * All returned strings are sharing the same memory space. */
+ XMLSTR encode(unsigned char *inByteBuf, unsigned int inByteLen, char formatted = 0); ///< returns a pointer to an internal buffer containing the base64 string containing the binary data encoded from "inByteBuf"
+
+ /// returns the number of bytes which will be decoded from "inString".
+ static unsigned int decodeSize(XMLCSTR inString, XMLError *xe = NULL);
+
+ /**
+ * The "decode" function returns a pointer to a buffer containing the binary data decoded from "inString"
+ * The output buffer will be free'd when the XMLParserBase64Tool object is deleted.
+ * All output buffer are sharing the same memory space.
+ * @param inString If "instring" is malformed, NULL will be returned */
+ unsigned char* decode(XMLCSTR inString, int *outByteLen = NULL, XMLError *xe = NULL); ///< returns a pointer to an internal buffer containing the binary data decoded from "inString"
+
+ /**
+ * decodes data from "inString" to "outByteBuf". You need to provide the size (in byte) of "outByteBuf"
+ * in "inMaxByteOutBuflen". If "outByteBuf" is not large enough or if data is malformed, then "FALSE"
+ * will be returned; otherwise "TRUE". */
+ static unsigned char decode(XMLCSTR inString, unsigned char *outByteBuf, int inMaxByteOutBuflen, XMLError *xe = NULL); ///< deprecated.
+
+private:
+ void *buf;
+ int buflen;
+ void alloc(int newsize);
+}XMLParserBase64Tool;
+/** @} */
+
+#undef XMLDLLENTRY
+
+#endif
|