/* Miranda SmileyAdd Plugin Copyright (C) 2005 - 2011 Boris Krasnovskiy All Rights Reserved Copyright (C) 2003 - 2004 Rein-Peter de Boer 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 . */ #include "stdafx.h" #define SB_MYMOVE 20 ///////////////////////////////////////////////////////////////////////////////////////// // SmileyToolwindowType class SmileyToolWindowType { unsigned m_NumberOfVerticalButtons = 0; unsigned m_NumberOfHorizontalButtons = 0; SIZE m_BitmapWidth = { 0, 0 }; SIZE m_ButtonSize = { 0, 0 }; unsigned m_ButtonSpace = 1; unsigned m_NumberOfButtons = 0; int m_WindowSizeY; HWND m_hwndDialog; HWND m_hToolTip; HWND m_hWndTarget; SmileyPackType *m_pSmileyPack; int m_CurrentHotTrack; int m_CurrMouseTrack; int m_XPosition; int m_YPosition; int m_Direction; UINT m_TargetMessage; WPARAM m_TargetWParam; int rowSel = -1; bool m_Choosing; AnimatedPack *m_AniPack = nullptr; void InitDialog(LPARAM lParam); void PaintWindow(void); void InsertSmiley(void); void MouseMove(int x, int y); void KeyUp(WPARAM wParam, LPARAM lParam); void SmileySel(int but); void ScrollV(int action, int dist = 0); int GetRowSize(void) const { return m_ButtonSize.cy + m_ButtonSpace; } void CreateSmileyBitmap(HDC hdc); void CreateSmileyWinDim(void); RECT CalculateButtonToCoordinates(int buttonPosition, int scroll); int CalculateCoordinatesToButton(POINT pt, int scroll); public: SmileyToolWindowType(HWND hWnd); LRESULT DialogProcedure(UINT msg, WPARAM wParam, LPARAM lParam); }; static LRESULT CALLBACK DlgProcSmileyToolWindow(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { SmileyToolWindowType *pOD = (SmileyToolWindowType*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (pOD == nullptr) { pOD = new SmileyToolWindowType(hwndDlg); SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)pOD); } LRESULT Result = pOD->DialogProcedure(msg, wParam, lParam); if (msg == WM_NCDESTROY) { delete pOD; Result = FALSE; } return Result; } SmileyToolWindowType::SmileyToolWindowType(HWND hWnd) : m_hwndDialog(hWnd) { } LRESULT SmileyToolWindowType::DialogProcedure(UINT msg, WPARAM wParam, LPARAM lParam) { LRESULT Result = FALSE; switch (msg) { case WM_ACTIVATE: if (wParam == WA_INACTIVE) DestroyWindow(m_hwndDialog); break; case WM_PAINT: PaintWindow(); break; case WM_TIMER: if (m_AniPack) m_AniPack->ProcessTimerTick(m_hwndDialog); break; case WM_DESTROY: KillTimer(m_hwndDialog, 1); if (m_AniPack) delete m_AniPack; m_AniPack = nullptr; DestroyWindow(m_hToolTip); PostQuitMessage(0); if (m_Choosing) SetFocus(m_hWndTarget); break; case WM_KEYUP: KeyUp(wParam, lParam); break; case WM_CREATE: InitDialog(lParam); break; case WM_VSCROLL: ScrollV(LOWORD(wParam)); break; case WM_MOUSEMOVE: MouseMove(LOWORD(lParam), HIWORD(lParam)); break; case WM_LBUTTONUP: SmileySel(m_CurrMouseTrack); InsertSmiley(); break; case WM_RBUTTONUP: SmileySel(m_CurrMouseTrack); break; case WM_MOUSEWHEEL: ScrollV(SB_MYMOVE, ((short)HIWORD(wParam)) / -120); MouseMove(LOWORD(lParam), HIWORD(lParam)); break; default: Result = DefWindowProc(m_hwndDialog, msg, wParam, lParam); break; } return Result; } void SmileyToolWindowType::InsertSmiley(void) { if (m_CurrentHotTrack >= 0 && m_hWndTarget != nullptr) { SmileyType *sml = m_pSmileyPack->GetSmiley(m_CurrentHotTrack); CMStringW insertText; if (opt.SurroundSmileyWithSpaces) insertText = ' '; insertText += sml->GetInsertText(); if (opt.SurroundSmileyWithSpaces) insertText += ' '; SendMessage(m_hWndTarget, m_TargetMessage, m_TargetWParam, (LPARAM)insertText.c_str()); m_Choosing = true; DestroyWindow(m_hwndDialog); } else if (m_hWndTarget == nullptr) DestroyWindow(m_hwndDialog); } void SmileyToolWindowType::SmileySel(int but) { if (but != m_CurrentHotTrack) { SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_POS; si.nPos = 0; GetScrollInfo(m_hwndDialog, SB_VERT, &si); HDC hdc = GetDC(m_hwndDialog); if (m_CurrentHotTrack >= 0) { // erase old rectangle selection and tooltip RECT rect = CalculateButtonToCoordinates(m_CurrentHotTrack, si.nPos); DrawFocusRect(hdc, &rect); m_CurrentHotTrack = -1; SendMessage(m_hToolTip, TTM_ACTIVATE, FALSE, 0); } // when selection is outside the window, then scroll int dist = 0; // scrolling distance if (but < si.nPos * (opt.HorizontalSorting ? (int)m_NumberOfHorizontalButtons : 1)) dist = but / (opt.HorizontalSorting ? (int)m_NumberOfHorizontalButtons : 1) - si.nPos; int numVert = m_WindowSizeY / (m_ButtonSize.cy + m_ButtonSpace); if (but >= (si.nPos + numVert) * (opt.HorizontalSorting ? (int)m_NumberOfHorizontalButtons : 1)) dist = but / (opt.HorizontalSorting ? m_NumberOfHorizontalButtons : 1) - numVert - si.nPos + 1; m_CurrentHotTrack = but; if (m_CurrentHotTrack >= 0) { RECT rect = CalculateButtonToCoordinates(m_CurrentHotTrack, si.nPos); DrawFocusRect(hdc, &rect); if (m_AniPack) m_AniPack->SetSel(rect); } if (dist != 0) ScrollV(SB_MYMOVE, dist); ReleaseDC(m_hwndDialog, hdc); } } void SmileyToolWindowType::ScrollV(int action, int dist) { SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_ALL; GetScrollInfo(m_hwndDialog, SB_VERT, &si); // Save the position for comparison later on int yPos = si.nPos; switch (action) { // user clicked the HOME keyboard key case SB_TOP: si.nPos = si.nMin; break; // user clicked the END keyboard key case SB_BOTTOM: si.nPos = si.nMax; break; // user clicked the top arrow case SB_LINEUP: si.nPos -= 1; break; // user clicked the bottom arrow case SB_LINEDOWN: si.nPos += 1; break; // user clicked the scroll bar shaft above the scroll box case SB_PAGEUP: si.nPos -= si.nPage; break; // user clicked the scroll bar shaft below the scroll box case SB_PAGEDOWN: si.nPos += si.nPage; break; // user dragged the scroll box case SB_THUMBTRACK: si.nPos = si.nTrackPos; break; // user dragged the scroll box case SB_MYMOVE: si.nPos += dist; break; } // Set the position and then retrieve it. Due to adjustments // by Windows it may not be the same as the value set. si.fMask = SIF_POS; SetScrollInfo(m_hwndDialog, SB_VERT, &si, TRUE); GetScrollInfo(m_hwndDialog, SB_VERT, &si); // If the position has changed, scroll window and update it if (si.nPos != yPos) { if (m_AniPack) m_AniPack->SetOffset(si.nPos*GetRowSize()); ScrollWindowEx(m_hwndDialog, 0, (yPos - si.nPos) * GetRowSize(), nullptr, nullptr, nullptr, nullptr, SW_INVALIDATE); UpdateWindow(m_hwndDialog); } } void SmileyToolWindowType::MouseMove(int xposition, int yposition) { if (m_CurrentHotTrack == -2) return; //prevent focussing when not drawn yet! SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_POS; si.nPos = 0; GetScrollInfo(m_hwndDialog, SB_VERT, &si); POINT pt = { xposition, yposition }; int but = CalculateCoordinatesToButton(pt, si.nPos); if (but<0) SendMessage(m_hToolTip, TTM_ACTIVATE, FALSE, 0); else if (m_CurrMouseTrack != but) { TOOLINFO ti = {}; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; ti.hwnd = m_hwndDialog; ti.uId = (UINT_PTR)m_hwndDialog; const CMStringW &toolText = m_pSmileyPack->GetSmiley(but)->GetToolText(); ti.lpszText = const_cast(toolText.c_str()); SendMessage(m_hToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); SendMessage(m_hToolTip, TTM_ACTIVATE, TRUE, 0); } m_CurrMouseTrack = but; } void SmileyToolWindowType::KeyUp(WPARAM wParam, LPARAM lParam) { int colSel = -1, numKey = -1; int but = m_CurrentHotTrack; switch (wParam) { case VK_END: but = m_NumberOfButtons - 1; break; case VK_HOME: but = 0; break; case VK_LEFT: but -= (opt.HorizontalSorting ? 1 : m_NumberOfVerticalButtons) * LOWORD(lParam); break; case VK_UP: but -= (opt.HorizontalSorting ? m_NumberOfHorizontalButtons : 1) * LOWORD(lParam); break; case VK_RIGHT: but += (opt.HorizontalSorting ? 1 : m_NumberOfVerticalButtons) * LOWORD(lParam); break; case VK_DOWN: but += (opt.HorizontalSorting ? m_NumberOfHorizontalButtons : 1) * LOWORD(lParam); break; case VK_PRIOR: but -= m_WindowSizeY / (m_ButtonSize.cy + m_ButtonSpace) * (opt.HorizontalSorting ? m_NumberOfHorizontalButtons : 1); break; case VK_NEXT: but += m_WindowSizeY / (m_ButtonSize.cy + m_ButtonSpace) * (opt.HorizontalSorting ? m_NumberOfHorizontalButtons : 1); break; case VK_SPACE: case VK_RETURN: if (but != -1) InsertSmiley(); return; case VK_ESCAPE: DestroyWindow(m_hwndDialog); return; case VK_NUMPAD1: case VK_NUMPAD2: case VK_NUMPAD3: case VK_NUMPAD4: case VK_NUMPAD5: case VK_NUMPAD6: case VK_NUMPAD7: case VK_NUMPAD8: case VK_NUMPAD9: if ((GetKeyState(VK_NUMLOCK) & 1) != 0) numKey = (int)wParam - VK_NUMPAD1; else { rowSel = -1; return; } break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': numKey = (int)wParam - '1'; break; default: rowSel = -1; return; } if (numKey != -1) { if (rowSel == -1) { rowSel = numKey; but = (opt.HorizontalSorting ? m_NumberOfHorizontalButtons : 1) * rowSel; } else { colSel = numKey; if (opt.HorizontalSorting) but = colSel + m_NumberOfHorizontalButtons * rowSel; else but = rowSel + m_NumberOfVerticalButtons * colSel; } } if (but < 0) but = 0; if (but >= (int)m_NumberOfButtons) but = m_NumberOfButtons - 1; SmileySel(but); if (colSel != -1) InsertSmiley(); } void SmileyToolWindowType::InitDialog(LPARAM lParam) { LPCREATESTRUCT createStruct = (LPCREATESTRUCT)lParam; SmileyToolWindowParam *stwp = (SmileyToolWindowParam*)createStruct->lpCreateParams; m_pSmileyPack = stwp->pSmileyPack; m_XPosition = stwp->xPosition; m_YPosition = stwp->yPosition; m_hWndTarget = stwp->hWndTarget; m_TargetMessage = stwp->targetMessage; m_TargetWParam = stwp->targetWParam; m_Direction = stwp->direction; m_CurrentHotTrack = -2; m_CurrMouseTrack = -1; m_Choosing = false; CreateSmileyWinDim(); int width = m_BitmapWidth.cx; int height = m_BitmapWidth.cy; const int colsz = GetRowSize(); const int heightn = m_WindowSizeY; SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nMax = height / colsz - 1; si.nPage = heightn / colsz; SetScrollInfo(m_hwndDialog, SB_VERT, &si, TRUE); if (GetWindowLongPtr(m_hwndDialog, GWL_STYLE) & WS_VSCROLL) width += GetSystemMetrics(SM_CXVSCROLL); RECT rc = { 0, 0, width, heightn }; AdjustWindowRectEx(&rc, GetWindowLongPtr(m_hwndDialog, GWL_STYLE), FALSE, GetWindowLongPtr(m_hwndDialog, GWL_EXSTYLE)); width = rc.right - rc.left; height = rc.bottom - rc.top; switch (m_Direction) { case 1: m_XPosition -= width; break; case 2: m_XPosition -= width; m_YPosition -= height; break; case 3: m_YPosition -= height; break; } // Get screen dimentions int xoScreen = GetSystemMetrics(SM_XVIRTUALSCREEN); int yoScreen = GetSystemMetrics(SM_YVIRTUALSCREEN); int xScreen = GetSystemMetrics(SM_CXVIRTUALSCREEN); int yScreen = GetSystemMetrics(SM_CYVIRTUALSCREEN); if (xScreen == 0) xScreen = GetSystemMetrics(SM_CXSCREEN); if (yScreen == 0) yScreen = GetSystemMetrics(SM_CYSCREEN); xScreen += xoScreen; yScreen += yoScreen; // Prevent window from opening off-screen if (m_YPosition + height > yScreen) m_YPosition = yScreen - height; if (m_XPosition + width > xScreen) m_XPosition = xScreen - width; if (m_YPosition < yoScreen) m_YPosition = yoScreen; if (m_XPosition < xoScreen) m_XPosition = xoScreen; // Move window to desired location SetWindowPos(m_hwndDialog, nullptr, m_XPosition, m_YPosition, width, height, SWP_NOZORDER); m_AniPack = new AnimatedPack(m_hwndDialog, height, m_ButtonSize, opt.SelWndBkgClr); int i = 0; for (auto &it : m_pSmileyPack->GetSmileyList()) if (!it->IsHidden()) m_AniPack->Add(it, CalculateButtonToCoordinates(i++, 0), opt.IEViewStyle); m_AniPack->SetOffset(0); if (opt.AnimateSel) SetTimer(m_hwndDialog, 1, 100, nullptr); //add tooltips m_hToolTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, L"", TTS_NOPREFIX | WS_POPUP, 0, 0, 0, 0, m_hwndDialog, nullptr, g_plugin.getInst(), nullptr); TOOLINFO ti = {}; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; ti.hwnd = m_hwndDialog; ti.uId = (UINT_PTR)m_hwndDialog; ti.lpszText = TranslateT("d'Oh!"); SendMessage(m_hToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); } void SmileyToolWindowType::PaintWindow(void) { SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_POS; si.nPos = 0; GetScrollInfo(m_hwndDialog, SB_VERT, &si); PAINTSTRUCT ps; HDC hdc = BeginPaint(m_hwndDialog, &ps); HBITMAP hBmp = CreateCompatibleBitmap(hdc, m_BitmapWidth.cx, m_BitmapWidth.cy); HDC hdcMem = CreateCompatibleDC(hdc); HANDLE hOld = SelectObject(hdcMem, hBmp); CreateSmileyBitmap(hdcMem); if (m_AniPack) m_AniPack->Draw(hdcMem); BitBlt(hdc, 0, 0, m_BitmapWidth.cx, m_WindowSizeY, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, hOld); DeleteObject(hBmp); DeleteDC(hdcMem); if (m_CurrentHotTrack == -2) m_CurrentHotTrack = -1; EndPaint(m_hwndDialog, &ps); } void SmileyToolWindowType::CreateSmileyWinDim(void) { m_NumberOfButtons = m_pSmileyPack->VisibleSmileyCount(); if (m_NumberOfButtons == 0) return; // Find largest smiley if (m_pSmileyPack->selec.x == 0 || m_pSmileyPack->selec.y == 0) { if (opt.ScaleAllSmileys) { m_pSmileyPack->GetSmiley(0)->GetSize(m_ButtonSize); ++m_ButtonSize.cx; ++m_ButtonSize.cy; } else { m_ButtonSize.cx = 0; m_ButtonSize.cy = 0; auto &sml = m_pSmileyPack->GetSmileyList(); for (unsigned i = 0; i < m_NumberOfButtons; i++) { SIZE smsz; sml[i].GetSize(smsz); if (m_ButtonSize.cx < smsz.cx) m_ButtonSize.cx = smsz.cx; if (m_ButtonSize.cy < smsz.cy) m_ButtonSize.cy = smsz.cy; } } } else m_ButtonSize = *(SIZE*)&m_pSmileyPack->selec; if (m_pSmileyPack->win.x == 0 || m_pSmileyPack->win.y == 0) { if (opt.IEViewStyle) { // All integer square root unsigned i; for (i = 1; i*i < m_NumberOfButtons; i++); m_NumberOfHorizontalButtons = min(i, 350 / (m_ButtonSize.cx + m_ButtonSpace)); m_NumberOfVerticalButtons = m_NumberOfButtons / m_NumberOfHorizontalButtons + (m_NumberOfButtons % m_NumberOfHorizontalButtons != 0); } else { const int nh = min(10u, GetSystemMetrics(SM_CXSCREEN) / ((m_ButtonSize.cx + m_ButtonSpace) * 2)); m_NumberOfVerticalButtons = m_NumberOfButtons / nh + (m_NumberOfButtons % nh != 0); if (m_NumberOfVerticalButtons < 5) m_NumberOfVerticalButtons = 5; m_NumberOfHorizontalButtons = m_NumberOfButtons / m_NumberOfVerticalButtons + (m_NumberOfButtons % m_NumberOfVerticalButtons != 0); } } else { m_NumberOfHorizontalButtons = m_pSmileyPack->win.x; m_NumberOfVerticalButtons = m_NumberOfButtons / m_NumberOfHorizontalButtons + (m_NumberOfButtons % m_NumberOfHorizontalButtons != 0); } m_BitmapWidth.cx = m_NumberOfHorizontalButtons * (m_ButtonSize.cx + m_ButtonSpace) + m_ButtonSpace; m_BitmapWidth.cy = m_NumberOfVerticalButtons * (m_ButtonSize.cy + m_ButtonSpace) + m_ButtonSpace; const int colsz = m_ButtonSize.cy + m_ButtonSpace; int wndsz = min((int)m_BitmapWidth.cy, GetSystemMetrics(SM_CYSCREEN) / 2); if (opt.IEViewStyle) wndsz = min(wndsz, 400); if (m_pSmileyPack->win.x != 0 && m_pSmileyPack->win.y != 0) wndsz = min(wndsz, m_pSmileyPack->win.y * (m_ButtonSize.cy + (int)m_ButtonSpace) + (int)m_ButtonSpace); m_WindowSizeY = wndsz - (wndsz % colsz) + m_ButtonSpace; } void SmileyToolWindowType::CreateSmileyBitmap(HDC hdc) { const RECT rc = { 0, 0, m_BitmapWidth.cx, m_WindowSizeY }; SetBkColor(hdc, opt.SelWndBkgClr); const HBRUSH hBkgBrush = CreateSolidBrush(opt.SelWndBkgClr); FillRect(hdc, &rc, hBkgBrush); DeleteObject(hBkgBrush); if (opt.IEViewStyle) { HPEN hpen = CreatePen(PS_DOT, 1, 0); HGDIOBJ hOldPen = SelectObject(hdc, hpen); POINT pts[2] = { {0, 0}, {m_BitmapWidth.cx, 0} }; for (unsigned i = 0; i <= m_NumberOfVerticalButtons; i++) { pts[0].y = pts[1].y = i * (m_ButtonSize.cy + m_ButtonSpace); if (pts[0].y > m_WindowSizeY) break; Polyline(hdc, pts, 2); } pts[0].y = 0; pts[1].y = m_BitmapWidth.cy; for (unsigned j = 0; j <= m_NumberOfHorizontalButtons; j++) { pts[0].x = pts[1].x = j * (m_ButtonSize.cx + m_ButtonSpace); Polyline(hdc, pts, 2); } SelectObject(hdc, hOldPen); DeleteObject(hpen); } } RECT SmileyToolWindowType::CalculateButtonToCoordinates(int buttonPosition, int scroll) { int row, rowpos; if (opt.HorizontalSorting) { row = buttonPosition / m_NumberOfHorizontalButtons; rowpos = buttonPosition % m_NumberOfHorizontalButtons; } else { row = buttonPosition % m_NumberOfVerticalButtons; rowpos = buttonPosition / m_NumberOfVerticalButtons; } RECT pt; pt.left = rowpos * (m_ButtonSize.cx + m_ButtonSpace) + m_ButtonSpace; pt.top = (row - scroll) * (m_ButtonSize.cy + m_ButtonSpace) + m_ButtonSpace; pt.right = pt.left + m_ButtonSize.cx; pt.bottom = pt.top + m_ButtonSize.cy; return pt; } int SmileyToolWindowType::CalculateCoordinatesToButton(POINT pt, int scroll) { const int rowpos = (pt.x - m_ButtonSpace) / (m_ButtonSize.cx + m_ButtonSpace); const int row = (pt.y - m_ButtonSpace) / (m_ButtonSize.cy + m_ButtonSpace) + scroll; int pos; if (opt.HorizontalSorting) pos = m_NumberOfHorizontalButtons * row + rowpos; else pos = m_NumberOfVerticalButtons * rowpos + row; if (pos >= (int)m_NumberOfButtons) pos = -1; return pos; } void __cdecl SmileyToolThread(SmileyToolWindowParam *stwp) { Thread_SetName("SmileyAdd: SmileyToolThread"); if (stwp->pSmileyPack && stwp->pSmileyPack->VisibleSmileyCount()) { WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_SAVEBITS; wndclass.lpfnWndProc = DlgProcSmileyToolWindow; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 4; wndclass.hInstance = g_plugin.getInst(); wndclass.hIcon = nullptr; wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush(opt.SelWndBkgClr); wndclass.lpszMenuName = nullptr; wndclass.lpszClassName = L"SmileyTool"; wndclass.hIconSm = nullptr; RegisterClassEx(&wndclass); CreateWindowEx(WS_EX_TOPMOST | WS_EX_NOPARENTNOTIFY, L"SmileyTool", nullptr, WS_BORDER | WS_POPUP | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, stwp->hWndParent, nullptr, g_plugin.getInst(), stwp); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } UnregisterClass(L"SmileyTool", g_plugin.getInst()); } delete stwp; }