Advertisement
adgiczone

window

Apr 25th, 2019
241
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 15.18 KB | None | 0 0
  1. //
  2. // Log Window Test App - by Napalm
  3. //
  4. // You may use all or any part of the following code as long as you agree
  5. // to the Creative Commons Attribution 2.0 UK: England & Wales license.
  6. // [url="http://creativecommons.org/licenses/by/2.0/uk/"]http://creativecommo...nses/by/2.0/uk/[/url]
  7. //
  8. // You must have the up to date headers to compile this.. if you can't
  9. // compile it install the PSDK/Windows SDK and try again.
  10. //
  11. //
  12.  
  13. #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
  14. #pragma comment(lib, "comctl32.lib")
  15. #pragma comment(lib, "shlwapi.lib")
  16. #pragma comment(lib, "msimg32.lib")
  17.  
  18. #define _WIN32_WINNT            0x0501
  19. #include <windows.h>
  20. #include <commctrl.h>
  21. #include <shlwapi.h>
  22.  
  23. #define LOG_LINE_LIMIT          25
  24.  
  25. // Child Window/Control IDs
  26. #define IDC_TXTENTRY            100
  27. #define IDC_TXTLOG              101
  28. #define IDC_BTNADDENTRY         102
  29. #define IDC_CHKPINTOBOTTOM      103
  30. #define IDC_CHKLIMITBUFFER      104
  31.  
  32. // Globals
  33. HINSTANCE g_hInst;
  34. HFONT g_hfText;
  35.  
  36.  
  37.  
  38. // I created this to change the default properties of how edit controls behave
  39. // when accessed by the dialog manager.
  40. LRESULT CALLBACK SubclassEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  41. {
  42.     WNDPROC wpOld = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_USERDATA);
  43.     LRESULT lrResult = 0;
  44.  
  45.     if (wpOld){
  46.         switch (uMsg)
  47.         {
  48.             // Draw fancy gradient background in log edit control
  49.         case WM_ERASEBKGND:
  50.         {
  51.             RECT rc;
  52.             if (GetClientRect(hWnd, &rc)){
  53.                 INT nW = (rc.right - rc.left);
  54.                 INT nH = (rc.bottom - rc.top);
  55.                 TRIVERTEX triVertex[5] = {
  56.                     { 0, nH, 0xFF00, 0xFF00, 0xFF00, 0x0000 },
  57.                     { nW, nH / 2, 0xFF00, 0xFF00, 0xFF00, 0x0000 },
  58.                     { nW, nH, 0xBB00, 0xDD00, 0xF700, 0x0000 },
  59.                 };
  60.                 GRADIENT_TRIANGLE triMesh = { 0, 1, 2 };
  61.                 HDC hdc = (HDC)wParam;
  62.                 INT ndc = SaveDC(hdc);
  63.                 SetBkColor(hdc, RGB(255, 255, 255));
  64.                 ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
  65.                 GradientFill(hdc, triVertex, 3, &triMesh, 1, GRADIENT_FILL_TRIANGLE);
  66.                 RestoreDC(hdc, ndc);
  67.                 return 0;
  68.             }
  69.         }
  70.         break;
  71.  
  72.         // Last message to a window so we de-subclass ourselves.
  73.         case WM_NCDESTROY:
  74.             SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)wpOld);
  75.             SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
  76.             break;
  77.  
  78.             // Sent by IsDialogMessage() API to determine what keys the control wants.
  79.             // We use this to forward the tab key so it selects the next control.
  80.         case WM_GETDLGCODE:
  81.             lrResult = CallWindowProc(wpOld, hWnd, uMsg, wParam, lParam);
  82.             lrResult &= ~(DLGC_HASSETSEL | DLGC_WANTTAB);
  83.             if (lParam && ((LPMSG)lParam)->message == WM_KEYDOWN &&
  84.                 ((LPMSG)lParam)->wParam == VK_TAB)
  85.                 lrResult &= ~DLGC_WANTMESSAGE;
  86.             return lrResult;
  87.         }
  88.  
  89.         // Call the original window procedure.
  90.         return CallWindowProc(wpOld, hWnd, uMsg, wParam, lParam);
  91.     }
  92.  
  93.     // Crap couldn't find the original window procedure... use default.
  94.     return DefWindowProc(hWnd, uMsg, wParam, lParam);
  95. }
  96.  
  97. LRESULT CALLBACK MainWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  98. {
  99.     // We should not have used a static.. we should really attach this value to the window
  100.     // using either GWL_USERDATA or a allocated structure with a a pointer stored in GWL_USERDATA.
  101.     static HWND s_hWndLastFocus;
  102.  
  103.     switch (uMsg)
  104.     {
  105.         // Initialize our window and create our child controls.
  106.     case WM_CREATE:
  107.     {
  108.         HWND hWndChild;
  109.         TCHAR szBuffer[MAX_PATH];
  110.  
  111.         // TEXT("This text will be appended to the box below.")
  112.         // Create the 'entry box' single-line edit control.
  113.         hWndChild = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, NULL,
  114.             ES_AUTOHSCROLL | WS_CHILD | WS_TABSTOP | WS_VISIBLE,
  115.             0, 0, 0, 0, hWnd, (HMENU)IDC_TXTENTRY, g_hInst, NULL);
  116.         if (!hWndChild) return -1;
  117.         // Subclass the edit control.
  118.         SetWindowLongPtr(hWndChild, GWLP_USERDATA, GetWindowLongPtr(hWndChild, GWLP_WNDPROC));
  119.         SetWindowLongPtr(hWndChild, GWLP_WNDPROC, (LONG_PTR)SubclassEditProc);
  120.         // Set the edit controls properties.
  121.         SendMessage(hWndChild, WM_SETFONT, (WPARAM)g_hfText, FALSE);
  122.         SendMessage(hWndChild, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, 0);
  123.         SendMessage(hWndChild, EM_SETCUEBANNER, 0, (LPARAM)TEXT("Log Entry Text"));
  124.  
  125.         // Create the 'add entry' button.
  126.         hWndChild = CreateWindowEx(0, WC_BUTTON, TEXT("&Add Entry"),
  127.             BS_PUSHBUTTON | BS_TEXT |
  128.             WS_CHILD | WS_TABSTOP | WS_VISIBLE,
  129.             0, 0, 0, 0, hWnd, (HMENU)IDC_BTNADDENTRY, g_hInst, NULL);
  130.         if (!hWndChild) return -1;
  131.         // Set the button controls properties.
  132.         SendMessage(hWndChild, WM_SETFONT, (WPARAM)g_hfText, FALSE);
  133.  
  134.         // Create first options check-box.
  135.         hWndChild = CreateWindowEx(0, WC_BUTTON, TEXT("Pin scroll to bottom."),
  136.             BS_CHECKBOX | BS_AUTOCHECKBOX | BS_TEXT | BS_VCENTER |
  137.             WS_CHILD | WS_TABSTOP | WS_VISIBLE,
  138.             0, 0, 0, 0, hWnd, (HMENU)IDC_CHKPINTOBOTTOM, g_hInst, NULL);
  139.         if (!hWndChild) return -1;
  140.         // Set the button controls properties.
  141.         SendMessage(hWndChild, WM_SETFONT, (WPARAM)g_hfText, FALSE);
  142.         SendMessage(hWndChild, BM_SETCHECK, BST_CHECKED, 0);
  143.  
  144.         // Create second options check-box.
  145.         wsprintf(szBuffer, TEXT("Limit log to %u lines."), LOG_LINE_LIMIT);
  146.         hWndChild = CreateWindowEx(0, WC_BUTTON, szBuffer,
  147.             BS_CHECKBOX | BS_AUTOCHECKBOX | BS_TEXT | BS_VCENTER |
  148.             WS_CHILD | WS_TABSTOP | WS_VISIBLE,
  149.             0, 0, 0, 0, hWnd, (HMENU)IDC_CHKLIMITBUFFER, g_hInst, NULL);
  150.         if (!hWndChild) return -1;
  151.         // Set the button controls properties.
  152.         SendMessage(hWndChild, WM_SETFONT, (WPARAM)g_hfText, FALSE);
  153.         SendMessage(hWndChild, BM_SETCHECK, BST_CHECKED, 0);
  154.  
  155.         // Create 'log window' multi-line edit control.
  156.         hWndChild = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, NULL,
  157.             ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL | ES_NOHIDESEL |
  158.             WS_VSCROLL | WS_CHILD | WS_TABSTOP | WS_VISIBLE,
  159.             0, 0, 0, 0, hWnd, (HMENU)IDC_TXTLOG, g_hInst, NULL);
  160.         if (!hWndChild) return -1;
  161.         // Subclass the edit control.
  162.         SetWindowLongPtr(hWndChild, GWLP_USERDATA, GetWindowLongPtr(hWndChild, GWLP_WNDPROC));
  163.         SetWindowLongPtr(hWndChild, GWLP_WNDPROC, (LONG_PTR)SubclassEditProc);
  164.         // Set the edit controls properties.
  165.         SendMessage(hWndChild, WM_SETFONT, (WPARAM)g_hfText, FALSE);
  166.         SendMessage(hWndChild, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, 0);
  167.  
  168.         SetFocus(hWndChild);
  169.         s_hWndLastFocus = NULL;
  170.     }
  171.     return 0;
  172.  
  173.     // We item this message with WA_INACTIVE set when our window is no-longer
  174.     // the foreground window so we want to save which of our controls has the focus
  175.     // so that when the user returns the right control gets the keyboard input.
  176.     case WM_ACTIVATE:
  177.         if (LOWORD(wParam) == WA_INACTIVE)
  178.             s_hWndLastFocus = GetFocus();
  179.         return 0;
  180.  
  181.         // We get this message when our window receives the user focus. We then
  182.         // move that focus to the previously used child window.
  183.     case WM_SETFOCUS:
  184.         if (s_hWndLastFocus)
  185.             SetFocus(s_hWndLastFocus);
  186.         return 0;
  187.  
  188.         // We accept this message so we can set a minimum window size. This only sets the users
  189.         // tracking size. The window itself can always be resized smaller programmatically unless
  190.         // you restrict it in WM_WINDOWPOSCHANGING/WM_WINDOWPOSCHANGED.
  191.     case WM_GETMINMAXINFO:
  192.     {
  193.         LPMINMAXINFO lpInfo = (LPMINMAXINFO)lParam;
  194.         if (lpInfo)
  195.             lpInfo->ptMinTrackSize.x = 350, lpInfo->ptMinTrackSize.y = 280;
  196.     }
  197.     return 0;
  198.  
  199.     // These next two messages are better to use rather than WM_MOVE/WM_SIZE.
  200.     // Remember WM_MOVE/WM_SIZE are from 16bit windows. In 32bit windows the window
  201.     // manager only sends these two messages and the DefWindowProc() handler actually
  202.     // accepts them and converts them to WM_MOVE/WM_SIZE.
  203.     //
  204.     // We accept this so we can scale our controls to the client size.
  205.     case WM_WINDOWPOSCHANGING:
  206.     case WM_WINDOWPOSCHANGED:
  207.     {
  208.         HDWP hDWP;
  209.         RECT rc;
  210.  
  211.         // Create a deferred window handle.
  212.         if (hDWP = BeginDeferWindowPos(5)){ // Deferring 5 child controls
  213.             GetClientRect(hWnd, &rc);
  214.  
  215.             // Defer each window move/size until end and do them all at once.
  216.             hDWP = DeferWindowPos(hDWP, GetDlgItem(hWnd, IDC_TXTENTRY), NULL,
  217.                 10, 10, rc.right - 130, 25,
  218.                 SWP_NOZORDER | SWP_NOREDRAW);
  219.  
  220.             hDWP = DeferWindowPos(hDWP, GetDlgItem(hWnd, IDC_BTNADDENTRY), NULL,
  221.                 rc.right - 110, 10, 100, 25,
  222.                 SWP_NOZORDER | SWP_NOREDRAW);
  223.  
  224.             hDWP = DeferWindowPos(hDWP, GetDlgItem(hWnd, IDC_CHKPINTOBOTTOM), NULL,
  225.                 10, 35, (rc.right / 2) - 15, 35,
  226.                 SWP_NOZORDER | SWP_NOREDRAW);
  227.  
  228.             hDWP = DeferWindowPos(hDWP, GetDlgItem(hWnd, IDC_CHKLIMITBUFFER), NULL,
  229.                 (rc.right / 2) + 5, 35, (rc.right / 2) - 15, 35,
  230.                 SWP_NOZORDER | SWP_NOREDRAW);
  231.  
  232.             hDWP = DeferWindowPos(hDWP, GetDlgItem(hWnd, IDC_TXTLOG), NULL,
  233.                 10, 70, rc.right - 20, rc.bottom - 80,
  234.                 SWP_NOZORDER | SWP_NOREDRAW);
  235.  
  236.             // Resize all windows under the deferred window handled at the same time.
  237.             EndDeferWindowPos(hDWP);
  238.  
  239.             // We told DeferWindowPos not to redraw the controls so we can redraw
  240.             // them here all at once.
  241.             RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN |
  242.                 RDW_ERASE | RDW_NOFRAME | RDW_UPDATENOW);
  243.         }
  244.     }
  245.     return 0;
  246.  
  247.     // Handle the notifications of button presses.
  248.     case WM_COMMAND:
  249.         // If it was a button press and came from our button.
  250.         if (wParam == MAKELONG(IDC_BTNADDENTRY, BN_CLICKED)){
  251.  
  252.             UINT uChkPinToBottom, uChkLimitLogLength;
  253.             INT nSelStart, nSelEnd;
  254.             SCROLLINFO siLogVert;
  255.             HWND hWndChild;
  256.             LPTSTR lpBuffer;
  257.             INT cchTextLen;
  258.  
  259.             // Allocate a buffer for our entry text.
  260.             // The +2 is for an extra space we append and null terminator.
  261.             hWndChild = GetDlgItem(hWnd, IDC_TXTENTRY);
  262.             cchTextLen = GetWindowTextLength(hWndChild);
  263.             lpBuffer = (LPTSTR)HeapAlloc(GetProcessHeap(),
  264.                 HEAP_ZERO_MEMORY, (cchTextLen + 2) * sizeof(TCHAR));
  265.             if (lpBuffer == NULL){
  266.                 // Fuck.. what happened???
  267.                 MessageBeep(MB_ICONERROR);
  268.                 return 0;
  269.             }
  270.  
  271.             // Read our entry text.
  272.             if (GetWindowText(hWndChild, lpBuffer, cchTextLen + 1)){
  273.                 StrCat(lpBuffer, TEXT(" "));
  274.  
  275.                 // Get the check-box states so we can change our logic depending on them.
  276.                 uChkPinToBottom = (DWORD)SendDlgItemMessage(hWnd,
  277.                     IDC_CHKPINTOBOTTOM, BM_GETCHECK, 0, 0);
  278.                 uChkLimitLogLength = (DWORD)SendDlgItemMessage(hWnd,
  279.                     IDC_CHKLIMITBUFFER, BM_GETCHECK, 0, 0);
  280.  
  281.                 // Get our edit log window handle.
  282.                 hWndChild = GetDlgItem(hWnd, IDC_TXTLOG);
  283.  
  284.                 // Tell edit control not to update the screen.
  285.                 SendMessage(hWndChild, WM_SETREDRAW, FALSE, 0);
  286.  
  287.                 // Save our current selection.
  288.                 nSelStart = nSelEnd = 0;
  289.                 SendMessage(hWndChild, EM_GETSEL, (WPARAM)&nSelStart, (LPARAM)&nSelEnd);
  290.  
  291.                 // Save our current scroll info.
  292.                 ZeroMemory(&siLogVert, sizeof(SCROLLINFO));
  293.                 siLogVert.cbSize = sizeof(SCROLLINFO);
  294.                 siLogVert.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
  295.                 GetScrollInfo(hWndChild, SB_VERT, &siLogVert);
  296.  
  297.                 // Limit log to LOG_LINE_LIMIT lines.
  298.                 if (uChkLimitLogLength == BST_CHECKED){
  299.                     // Test if more than LOG_LINE_LIMIT.
  300.                     INT nLines = (INT)SendMessage(hWndChild, EM_GETLINECOUNT, 0, 0);
  301.                     if (nLines > LOG_LINE_LIMIT){
  302.                         // Replace content to remove with nothing.
  303.                         INT nRemove = (DWORD)SendMessage(hWndChild, EM_LINEINDEX,
  304.                             (WPARAM)(nLines - LOG_LINE_LIMIT), 0);
  305.                         SendMessage(hWndChild, EM_SETSEL, 0, nRemove);
  306.                         SendMessage(hWndChild, EM_REPLACESEL, FALSE, (LPARAM)"");
  307.                         // Update old selection indexes.
  308.                         nSelStart = max(nSelStart - nRemove, 0);
  309.                         nSelEnd = max(nSelEnd - nRemove, 0);
  310.                     }
  311.                 }
  312.  
  313.                 // Update the log window by appending text to it.
  314.                 cchTextLen = GetWindowTextLength(hWndChild);
  315.                 SendMessage(hWndChild, EM_SETSEL, cchTextLen, cchTextLen);
  316.                 SendMessage(hWndChild, EM_REPLACESEL, FALSE, (LPARAM)lpBuffer);
  317.  
  318.                 // Update Pin-To-Bottom behavior.
  319.                 if (uChkPinToBottom == BST_CHECKED){
  320.                     // Only Pin-To-Bottom when the user is in the bottom page.
  321.                     UINT uScrollLines = 1;
  322.                     SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &uScrollLines, 0);
  323.                     if (siLogVert.nPos > (INT)(siLogVert.nMax -
  324.                         siLogVert.nPage - uScrollLines))
  325.                         SendMessage(hWndChild, WM_VSCROLL, SB_BOTTOM, 0);
  326.                 }
  327.                 else{
  328.                     // Restore scroll position if not pinned.
  329.                     SendMessage(hWndChild, WM_VSCROLL, MAKELONG(siLogVert.nPos,
  330.                         SB_THUMBPOSITION), 0);
  331.                 }
  332.  
  333.                 // Restore old text selection.
  334.                 SendMessage(hWndChild, EM_SETSEL, nSelStart, nSelEnd);
  335.  
  336.                 // Update the state of the edit control on the screen.
  337.                 SendMessage(hWndChild, WM_SETREDRAW, TRUE, 0);
  338.                 UpdateWindow(hWndChild);
  339.  
  340.             }
  341.             else{
  342.                 // No text in the entry box?
  343.                 MessageBeep(MB_ICONWARNING);
  344.             }
  345.  
  346.             // Free temporary entry box allocation.
  347.             HeapFree(GetProcessHeap(), 0, lpBuffer);
  348.             return 0;
  349.         }
  350.         break;
  351.  
  352.         // Sent by all edit controls that are not disabled.
  353.     case WM_CTLCOLOREDIT:
  354.         // Test to see if the request is from our log edit control.
  355.         if ((HWND)lParam == GetDlgItem(hWnd, IDC_TXTLOG)){
  356.             // Set the edit control painting of the background to transparent.
  357.             SetBkMode((HDC)wParam, TRANSPARENT);
  358.             SetTextColor((HDC)wParam, RGB(0x2B, 0x4C, 0x67));
  359.             return (LRESULT)GetStockObject(HOLLOW_BRUSH);
  360.         }
  361.         break;
  362.  
  363.     case WM_DESTROY:
  364.         // We post a WM_QUIT when our window is destroyed so we break the main message loop.
  365.         PostQuitMessage(0);
  366.         break;
  367.     }
  368.  
  369.     // Not a message we wanted? No problem hand it over to the Default Window Procedure.
  370.     return DefWindowProc(hWnd, uMsg, wParam, lParam);
  371. }
  372.  
  373.  
  374. // Program Entry Point
  375. int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, INT nShowCmd)
  376. {
  377.  
  378.     OSVERSIONINFO lpVer;
  379.     WNDCLASSEX wcex;
  380.     DWORD dwExStyle;
  381.     HDC hdcScreen;
  382.     HWND hWnd;
  383.     MSG msg;
  384.  
  385.     g_hInst = hInst;
  386.  
  387.     // Link in comctl32.dll
  388.     InitCommonControls();
  389.  
  390.     ZeroMemory(&msg, sizeof(MSG));
  391.     ZeroMemory(&wcex, sizeof(WNDCLASSEX));
  392.  
  393.     // Register our Main Window class.
  394.     wcex.cbSize = sizeof(WNDCLASSEX);
  395.     wcex.hInstance = hInst;
  396.     wcex.lpszClassName = TEXT("MainWindow");
  397.     wcex.lpfnWndProc = MainWindowProc;
  398.     wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  399.     wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  400.     wcex.hIconSm = wcex.hIcon;
  401.     wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
  402.     if (!RegisterClassEx(&wcex))
  403.         return 1;
  404.  
  405.     // Create a font we can later use on our controls.
  406.     hdcScreen = GetDC(HWND_DESKTOP);
  407.     g_hfText = CreateFont(-MulDiv(11, GetDeviceCaps(hdcScreen, LOGPIXELSY), 72), // 11pt
  408.         0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS,
  409.         CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, TEXT("Tahoma"));
  410.     ReleaseDC(HWND_DESKTOP, hdcScreen);
  411.  
  412.     // Default main window ex-style.
  413.     dwExStyle = WS_EX_APPWINDOW;
  414.  
  415.     // If we are using XP or above lets 'double-buffer' the window to reduce the
  416.     // flicker to the edit controls when drawing (notepad needs this).
  417.     lpVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  418.     //if (GetVersionEx(&lpVer) && (lpVer.dwMajorVersion > 5 ||
  419.     //  (lpVer.dwMajorVersion == 5 && lpVer.dwMinorVersion == 1)))
  420.     //  dwExStyle |= WS_EX_COMPOSITED;
  421.  
  422.     // Create an instance of the Main Window.
  423.     hWnd = CreateWindowEx(dwExStyle, wcex.lpszClassName, TEXT("Log Window Test App v2 - by Napalm"),
  424.         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 450, 330,
  425.         HWND_DESKTOP, NULL, hInst, NULL);
  426.  
  427.     if (hWnd){
  428.         // Show the main window and enter the message loop.
  429.         ShowWindow(hWnd, nShowCmd);
  430.         UpdateWindow(hWnd);
  431.         while (GetMessage(&msg, NULL, 0, 0))
  432.         {
  433.             // If the message was not wanted by the Dialog Manager dispatch it like normal.
  434.             if (!IsDialogMessage(hWnd, &msg)){
  435.                 TranslateMessage(&msg);
  436.                 DispatchMessage(&msg);
  437.             }
  438.         }
  439.     }
  440.  
  441.     // Free up our resources and return.
  442.     DeleteObject(g_hfText);
  443.     return (int)msg.wParam;
  444. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement