diff options
author | berkeviktor@aol.com <berkeviktor@aol.com> | 2011-07-27 09:07:09 +0200 |
---|---|---|
committer | berkeviktor@aol.com <berkeviktor@aol.com> | 2011-07-27 09:07:09 +0200 |
commit | a11d1857f1f242199ea330e1679cf55e2771395b (patch) | |
tree | c57bdc180418073e25724d93d87491c3fa9c2e45 /plugins/xtray/utility.cpp | |
parent | f39d21328603673ee83c55a5d6d55b276cc8bdb8 (diff) | |
parent | 69ba67b2549b6c6a46abd19879b67e1545c9ae3b (diff) |
Merge with default
Diffstat (limited to 'plugins/xtray/utility.cpp')
-rw-r--r-- | plugins/xtray/utility.cpp | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/plugins/xtray/utility.cpp b/plugins/xtray/utility.cpp new file mode 100644 index 00000000..4faed90b --- /dev/null +++ b/plugins/xtray/utility.cpp @@ -0,0 +1,606 @@ +/* X-Tray + * Copyright (C) 2005 Michael Hotaling <Mike.Hotaling@SinisterDevelopments.com> + * + * X-Tray 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. + * + * X-Tray 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 X-Tray; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <windows.h> +#include <stdio.h> +#include <commctrl.h> +#include <tchar.h> +#include "utility.h" +#include "plugin.h" +#include "xtray.h" +#include "callbacks.h" +#include "resource.h" + +TCHAR BACKUP_INI_FILE[] = _T(".\\plugins\\config\\xtray.conf"); +struct HOTKEY g_hHotKey; + +/* we need to convert ALT and SHIFT modifiers +// from <winuser.h> +#define MOD_ALT 0x0001 +#define MOD_CONTROL 0x0002 +#define MOD_SHIFT 0x0004 +// from <commctrl.h> +#define HOTKEYF_SHIFT 0x01 +#define HOTKEYF_CONTROL 0x02 +#define HOTKEYF_ALT 0x04 +*/ + +WORD HotkeyfToMod(WORD modifiers) +{ + WORD newmods = 0; + + if (modifiers & HOTKEYF_SHIFT) + newmods |= MOD_SHIFT; + + if (modifiers & HOTKEYF_CONTROL) + newmods |= MOD_CONTROL; + + if (modifiers & HOTKEYF_ALT) + newmods |= MOD_ALT; + + return newmods; +} + +WORD ModToHotkeyf(WORD modifiers) +{ + WORD newmods = 0; + + if (modifiers & MOD_SHIFT) + newmods |= HOTKEYF_SHIFT; + + if (modifiers & MOD_CONTROL) + newmods |= HOTKEYF_CONTROL; + + if (modifiers & MOD_ALT) + newmods |= HOTKEYF_ALT; + + return newmods; +} + +void SavePrefs(int iDlg) +{ + /**************************************************************************************************/ + /********** allocate space for our string, and then set it to the currently logged on user ********/ + /**************************************************************************************************/ + DWORD dBuff = 257; + TCHAR szUser[257]; + + GetUserName(szUser, &dBuff); + + /**************************************************************************************************/ + /*********************** Test if the file exists, If it doesn't, Create It ************************/ + /**************************************************************************************************/ + TCHAR XTRAY_INI_FILE[1024]; + +#ifdef UNICODE + char temp[1024]; + TCHAR TEMP_INI_FILE[1024]; + + _snprintf(temp, 1024, "%s\\xtray.conf", xchat_get_info(ph, "xchatdir")); + ConvertString(temp, TEMP_INI_FILE, 1024); + + // ok this one is really ugly + // it checks to see if the file exists in two locations + // X-Chat default config dir, if that fails it trys xchat\plugins\config + // if neither one exists it tries to create it in + // X-Chat default config dir, if that fails it trys xchat\plugins\config + // In either case it writes \xFF\xFE to the file ( on creation ) + // so that we can save unicode away messages WritePrivateProfile doesn't + // do this for us, though I think it really should + + if(FileExists(TEMP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, TEMP_INI_FILE); + } + else + { + if(FileExists(BACKUP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, BACKUP_INI_FILE); + } + else + { + HANDLE xTemp; + DWORD dwBytesTemp; + + if(xTemp = CreateFile(TEMP_INI_FILE, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)) + { + if(GetLastError() != ERROR_ALREADY_EXISTS) + { + WriteFile(xTemp, _T("\xFF\xFE"), 4, &dwBytesTemp, NULL); + } + CloseHandle(xTemp); + } + + if(FileExists(TEMP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, TEMP_INI_FILE); + } + else + { + HANDLE xBackup; + DWORD dwBytesBackup; + + if(xBackup = CreateFile(TEMP_INI_FILE, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)) + { + if(GetLastError() != ERROR_ALREADY_EXISTS) + { + WriteFile(xBackup, _T("\xFF\xFE"), 4, &dwBytesBackup, NULL); + } + + CloseHandle(xBackup); + } + + if(FileExists(BACKUP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, BACKUP_INI_FILE); + } + } + } + } + +#else + _tcscpy(XTRAY_INI_FILE, BACKUP_INI_FILE); +#endif + + WritePrivateProfileInt(szUser, _T("SETTINGS"), g_dwPrefs, XTRAY_INI_FILE); + WritePrivateProfileInt(szUser, _T("AOT"), g_iTime, XTRAY_INI_FILE); + WritePrivateProfileInt(szUser, _T("KEY"), g_hHotKey.key, XTRAY_INI_FILE); + WritePrivateProfileInt(szUser, _T("MOD"), g_hHotKey.mod, XTRAY_INI_FILE); + WritePrivateProfileString(szUser, _T("AWAY"), g_szAway, XTRAY_INI_FILE); +} + +void LoadPrefs() +{ + /**************************************************************************************************/ + /*********************** Our Settings Section *****************************************************/ + /**************************************************************************************************/ + DWORD dBuff = 257; + TCHAR szUser[257]; + + GetUserName(szUser, &dBuff); + + /**************************************************************************************************/ + /*********************** Test if the file exists, If it doesn't, Create It ************************/ + /**************************************************************************************************/ + TCHAR XTRAY_INI_FILE[1024]; + +#ifdef UNICODE + char temp[1024]; + TCHAR TEMP_INI_FILE[1024]; + + _snprintf(temp, 1024, "%s\\xtray.conf", xchat_get_info(ph, "xchatdir")); + ConvertString(temp, TEMP_INI_FILE, 1024); + + if(FileExists(TEMP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, TEMP_INI_FILE); + } + else + { + if(FileExists(BACKUP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, BACKUP_INI_FILE); + } + else + { + HANDLE xTemp; + DWORD dwBytesTemp; + + if(xTemp = CreateFile(TEMP_INI_FILE, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)) + { + if(GetLastError() != ERROR_ALREADY_EXISTS) + { + WriteFile(xTemp, _T("\xFF\xFE"), 4, &dwBytesTemp, NULL); + } + + CloseHandle(xTemp); + } + + if(FileExists(TEMP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, TEMP_INI_FILE); + } + else + { + HANDLE xBackup; + DWORD dwBytesBackup; + + if(xBackup = CreateFile(TEMP_INI_FILE, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)) + { + if(GetLastError() != ERROR_ALREADY_EXISTS) + { + WriteFile(xBackup, _T("\xFF\xFE"), 4, &dwBytesBackup, NULL); + } + + CloseHandle(xBackup); + } + + if(FileExists(BACKUP_INI_FILE)) + { + _tcscpy(XTRAY_INI_FILE, BACKUP_INI_FILE); + } + } + } + } + +#else + _tcscpy(XTRAY_INI_FILE, BACKUP_INI_FILE); +#endif + + /**************************************************************************************************/ + /*************************** Get the value for each of our preferances ****************************/ + /**************************************************************************************************/ + g_dwPrefs = GetPrivateProfileInt(szUser, _T("SETTINGS"), 0, XTRAY_INI_FILE); + + // backwards compatability + // also allows us to set defaults if its a new installation + // disable topic change, channel message and server notice by default + if(g_dwPrefs == 0) + { + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("HILIGHT"), 1, XTRAY_INI_FILE)<<1); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("INVITE"), 1, XTRAY_INI_FILE)<<2); + /* g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("TOPIC"), 1, XTRAY_INI_FILE)<<3); */ + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("BANNED"), 1, XTRAY_INI_FILE)<<4); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("KICKED"), 1, XTRAY_INI_FILE)<<5); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("CTCP"), 1, XTRAY_INI_FILE)<<6); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("PMSG"), 1, XTRAY_INI_FILE)<<7); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("KILLED"), 1, XTRAY_INI_FILE)<<8); + /* g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("NOTICE"), 1, XTRAY_INI_FILE)<<9); */ + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("DISCONNECT"), 1, XTRAY_INI_FILE)<<10); + + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("AOM"), 0, XTRAY_INI_FILE)<<11); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("TOT"), 1, XTRAY_INI_FILE)<<12); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("AMAE"), 1, XTRAY_INI_FILE)<<13); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("OSBWM"), 0, XTRAY_INI_FILE)<<14); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("UWIOB"), 0, XTRAY_INI_FILE)<<15); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("KAOI"), 0, XTRAY_INI_FILE)<<16); + g_dwPrefs |= (GetPrivateProfileInt(szUser, _T("MIOC"), 0, XTRAY_INI_FILE)<<17); + } + + g_hHotKey.key = GetPrivateProfileInt(szUser, _T("KEY"), 84, XTRAY_INI_FILE); + g_hHotKey.mod = GetPrivateProfileInt(szUser, _T("MOD"), (MOD_CONTROL | MOD_SHIFT), XTRAY_INI_FILE); + g_iTime = GetPrivateProfileInt(szUser, _T("AOT"), 5, XTRAY_INI_FILE); + + GetPrivateProfileString(szUser, _T("AWAY"), _T(""), g_szAway, 1024, XTRAY_INI_FILE); + + /**************************************************************************************************/ + /******************************** Register our hotkey with windows ********************************/ + /**************************************************************************************************/ + if(g_dwPrefs & (1<<PREF_UWIOB)) + { + RegisterHotKey(g_hHotkeyWnd, 1, g_hHotKey.mod, g_hHotKey.key); + } +} + +void CheckPrefs(HWND hwnd, int iDlg) +{ + /**************************************************************************************************/ + /**************** save the preferances based on the checkmarks/options ****************************/ + /**************************************************************************************************/ + switch(iDlg) + { + case IDD_EVENTS: + { + SetOption(hwnd, CHAN_HILIGHT, CHAN_HILIGHT); + SetOption(hwnd, CHAN_INVITE, CHAN_INVITE); + SetOption(hwnd, CHAN_TOPIC_CHANGE, CHAN_TOPIC_CHANGE); + SetOption(hwnd, CHAN_BANNED, CHAN_BANNED); + SetOption(hwnd, CHAN_KICKED, CHAN_KICKED); + SetOption(hwnd, CTCP_GENERIC, CTCP_GENERIC); + SetOption(hwnd, PMSG_RECEIVE, PMSG_RECEIVE); + SetOption(hwnd, SERV_KILLED, SERV_KILLED); + SetOption(hwnd, SERV_NOTICE, SERV_NOTICE); + SetOption(hwnd, SERV_DISCONNECT, SERV_DISCONNECT); + SetOption(hwnd, CHAN_MESSAGE, CHAN_MESSAGE); + } + break; + case IDD_ALERTS: + { + SetOption(hwnd, PREF_AMAE, PREF_AMAE); + SetOption(hwnd, PREF_OSBWM, PREF_OSBWM); + SetOption(hwnd, PREF_UWIOB, PREF_UWIOB); + SetOption(hwnd, PREF_KAOI, PREF_KAOI); + SetOption(hwnd, PREF_BLINK, PREF_BLINK); + + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + TCHAR tTime[512]; + + GetWindowText(GetDlgItem(hwnd, IDC_ALERT_TIME), tTime, 511); + + g_iTime = _tstoi(tTime); + + /**************************************************************************/ + /**************** Get our Hotkey and save it **********/ + /**************** then remove the old hotkey and add the new one **********/ + /**************************************************************************/ + DWORD hHotkey; + hHotkey = SendDlgItemMessage(hwnd, IDC_ALERT_HOTKEY, HKM_GETHOTKEY, 0, 0); + + g_hHotKey.key = LOBYTE(hHotkey); + g_hHotKey.mod = HotkeyfToMod(HIBYTE(hHotkey)); + + if(IsDlgButtonChecked(hwnd, PREF_UWIOB) == BST_CHECKED) + { + UnregisterHotKey(g_hHotkeyWnd, 1); + RegisterHotKey(g_hHotkeyWnd, 1, g_hHotKey.mod, g_hHotKey.key); + } + else + { + UnregisterHotKey(g_hHotkeyWnd, 1); + } + + /*************************************************************************/ + /*********** Get and save the away msg and alert time ********************/ + /*************************************************************************/ + } + break; + case IDD_SETTINGS: + { + SetOption(hwnd, PREF_AOM, PREF_AOM); + SetOption(hwnd, PREF_TOT, PREF_TOT); + SetOption(hwnd, PREF_MIOC, PREF_MIOC); + SetOption(hwnd, PREF_DNSIT, PREF_DNSIT); + + GetDlgItemText(hwnd, IDC_AWAY_MSG, g_szAway, 511); + + if(g_dwPrefs & (1<<PREF_DNSIT)) + { + DWORD dwStyle; + dwStyle = GetWindowLong(g_hXchatWnd, GWL_STYLE); + dwStyle |= (1<<WS_CHILD); + SetWindowLongPtr(g_hXchatWnd, GWL_STYLE, (LONG_PTR)dwStyle); + SetWindowLongPtr(g_hXchatWnd, GWL_HWNDPARENT, (LONG_PTR)g_hHotkeyWnd); + } + else + { + DWORD dwStyle; + dwStyle = GetWindowLong(g_hXchatWnd, GWL_STYLE); + dwStyle &= ~(1<<WS_CHILD); + SetWindowLongPtr(g_hXchatWnd, GWL_STYLE, (LONG_PTR)dwStyle); + SetWindowLongPtr(g_hXchatWnd, GWL_HWNDPARENT, NULL); + } + } + break; + } +} + +void SetDialog(HWND hwnd, int iDlg) +{ + switch(iDlg) + { + case IDD_EVENTS: + { + SetCheck(hwnd, CHAN_HILIGHT, CHAN_HILIGHT); + SetCheck(hwnd, CHAN_INVITE, CHAN_INVITE); + SetCheck(hwnd, CHAN_TOPIC_CHANGE, CHAN_TOPIC_CHANGE); + SetCheck(hwnd, CHAN_BANNED, CHAN_BANNED); + SetCheck(hwnd, CHAN_KICKED, CHAN_KICKED); + SetCheck(hwnd, CTCP_GENERIC, CTCP_GENERIC); + SetCheck(hwnd, PMSG_RECEIVE, PMSG_RECEIVE); + SetCheck(hwnd, SERV_KILLED, SERV_KILLED); + SetCheck(hwnd, SERV_NOTICE, SERV_NOTICE); + SetCheck(hwnd, SERV_DISCONNECT, SERV_DISCONNECT); + SetCheck(hwnd, CHAN_MESSAGE, CHAN_MESSAGE); + } + break; + case IDD_SETTINGS: + { + SetCheck(hwnd, PREF_TOT, PREF_TOT); + SetCheck(hwnd, PREF_MIOC, PREF_MIOC); + SetCheck(hwnd, PREF_AOM, PREF_AOM); + SetCheck(hwnd, PREF_DNSIT, PREF_DNSIT); + + SetDlgItemText(hwnd, IDC_AWAY_MSG, g_szAway); + } + break; + case IDD_ALERTS: + { + + SetCheck(hwnd, PREF_BLINK, PREF_BLINK); + SetCheck(hwnd, PREF_OSBWM, PREF_OSBWM); + SetCheck(hwnd, PREF_UWIOB, PREF_UWIOB); + SetCheck(hwnd, PREF_KAOI, PREF_KAOI); + + /**********************************************************/ + /**********************************************************/ + /**********************************************************/ + if(SetCheck(hwnd, PREF_AMAE, PREF_AMAE) == false) + { + SetToggle(hwnd, PREF_OSBWM, PREF_AMAE, TRUE); + SetToggle(hwnd, PREF_UWIOB, PREF_AMAE, TRUE); + SetToggle(hwnd, PREF_KAOI, PREF_AMAE, TRUE); + SetToggle(hwnd, IDC_ALERT_TIME, PREF_AMAE, TRUE); + SetToggle(hwnd, IDC_ALERT_TIME_TEXT, PREF_AMAE, TRUE); + SetToggle(hwnd, IDC_ALERT_HOTKEY, PREF_AMAE, TRUE); + SetToggle(hwnd, IDC_ALERT_HOTKEY_TEXT, PREF_AMAE, TRUE); + } + else + { + + SetToggle(hwnd, IDC_ALERT_HOTKEY, PREF_UWIOB, TRUE); + SetToggle(hwnd, IDC_ALERT_HOTKEY_TEXT, PREF_UWIOB, TRUE); + SetToggle(hwnd, IDC_ALERT_TIME, PREF_KAOI, FALSE); + SetToggle(hwnd, IDC_ALERT_TIME_TEXT, PREF_KAOI, FALSE); + } + + /**********************************************************/ + /**********************************************************/ + /**********************************************************/ + TCHAR tTime[255]; + SendDlgItemMessage(hwnd, IDC_ALERT_TIME, WM_SETTEXT, 0, (LPARAM)_itot(g_iTime, tTime, 10)); + SendDlgItemMessage(hwnd, IDC_ALERT_HOTKEY, HKM_SETHOTKEY, MAKEWORD(g_hHotKey.key, ModToHotkeyf(g_hHotKey.mod)), 0); + } + break; + } +} + +int SetCheck(HWND hDialog, unsigned int uiCheckBox, unsigned int uiPref) +{ + if((g_dwPrefs & (1<<uiPref))) + { + CheckDlgButton(hDialog, uiCheckBox, BST_CHECKED); + return 1; + } + else + { + CheckDlgButton(hDialog, uiCheckBox, BST_UNCHECKED); + return 0; + } + + return 0; +} + +int SetToggle(HWND hDialog, unsigned int uiCheckBox, unsigned int uiTestbox, bool offeqoff) +{ + /**************************************************************************************************/ + /*********************** if(true) then if option is off turn toggle off ***************************/ + /*********************** if(false) then if option is off turn toggle on ***************************/ + /**************************************************************************************************/ + if(offeqoff) + { + if(IsDlgButtonChecked(hDialog, uiTestbox) == BST_CHECKED) + { + EnableWindow(GetDlgItem(hDialog, uiCheckBox), TRUE); + return 1; + } + else + { + EnableWindow(GetDlgItem(hDialog, uiCheckBox), FALSE); + return 0; + } + } + else + { + if(IsDlgButtonChecked(hDialog, uiTestbox) == BST_CHECKED) + { + EnableWindow(GetDlgItem(hDialog, uiCheckBox), FALSE); + return 1; + } + else + { + EnableWindow(GetDlgItem(hDialog, uiCheckBox), TRUE); + return 0; + } + } + + return 0; +} + +int SetOption(HWND hDialog, unsigned int uiCheckBox, unsigned int uiPref) +{ + if(IsDlgButtonChecked(hDialog, uiCheckBox) == BST_CHECKED) + { + g_dwPrefs |= (1<<uiPref); + } + else + { + g_dwPrefs &= ~(1<<uiPref); + } + + return (g_dwPrefs & (1<<uiPref)); +} + +int WritePrivateProfileIntA(char *szSection, char *szItem, int iData, char *szPath) +{ + char szData[33]; + itoa(iData, szData, 10); + return WritePrivateProfileStringA(szSection, szItem, szData, szPath); +} + +int WritePrivateProfileIntW(wchar_t *wszSection, wchar_t *wszItem, int iData, wchar_t *wszPath) +{ + wchar_t wszData[33]; + _itow(iData, wszData, 10); + return WritePrivateProfileStringW(wszSection, wszItem, wszData, wszPath); +} + +// For cleanup ( Closing windows and the such ) +void HoldClose() +{ + HANDLE hcThread; + DWORD dwThreadID; + hcThread = CreateThread(NULL, 0, HoldCloseThread, 0, 0, &dwThreadID); +} + +DWORD WINAPI HoldCloseThread(LPVOID lpParam) +{ + Sleep(1000); + PostMessage(g_hXchatWnd, WM_CLOSE, 0, 0); + return 0; +} + +bool FileExists(TCHAR *file) +{ + HANDLE hTemp = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + int nRet = GetLastError(); + CloseHandle(hTemp); + + if(nRet == 0) + { + return true; + } + else + { + return false; + } + + return false; +} + +void ConvertString(const char *in, wchar_t *out, int size) +{ + MultiByteToWideChar(CP_UTF8, 0, in, -1, out, size); +} + +void ConvertString(const wchar_t *in, char *out, int size) +{ + WideCharToMultiByte(CP_UTF8, 0, in, (size - 1), out, size, NULL, NULL); +} + +void ConvertString(const char *in, char *out, int size) +{ + strncpy(out, in, size); +} + +void ErrorDebug(LPTSTR lpszFunction) +{ + TCHAR szBuf[80]; + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + _stprintf(szBuf, + _T("%s failed with error %d: %s"), + lpszFunction, dw, lpMsgBuf); + + MessageBox(NULL, szBuf, _T("Error"), MB_OK); + + LocalFree(lpMsgBuf); +} + |