/*
Copyright (C) 2012-24 Miranda NG team (https://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 .
*/
#include "stdafx.h"
#define ACF_TYPE_NUMBER 0x00 // Parameter is number
#define ACF_TYPE_STRING 0x01 // Parameter is ANSI String
#define ACF_TYPE_UNICODE 0x02 // Parameter is Unicode string
#define ACF_TYPE_STRUCT 0x03 // Parameter is (result is in) structure
#define ACF_TYPE_PARAM 0x08 // Parameter is Call parameter
#define ACF_TYPE_CURRENT 0x09 // Parameter is ignored, used current user handle from current message window
#define ACF_TYPE_RESULT 0x0A // Parameter is previous action result
#define ACF_TYPE_MASK 0x0F // parameter/result type mask
ColumnItem::ColumnItem(const wchar_t *pwszTitle, int _width, int _setting_type) :
title(mir_wstrdup(pwszTitle)),
width(_width),
setting_type(_setting_type)
{
bEnabled = true;
}
ColumnItem::ColumnItem(const ColumnItem &src)
{
memcpy(this, &src, sizeof(ColumnItem));
title = mir_wstrdup(title);
switch (setting_type) {
case QST_SETTING:
module = mir_strdup(module);
setting = mir_strdup(setting);
break;
case QST_SCRIPT:
script = mir_wstrdup(script);
break;
case QST_SERVICE:
svc.service = mir_strdup(svc.service);
switch (svc.wFlags) {
case ACF_TYPE_NUMBER:
case ACF_TYPE_STRING:
case ACF_TYPE_UNICODE:
svc.wParam = (WPARAM)mir_wstrdup((wchar_t *)svc.wParam);
break;
case ACF_TYPE_STRUCT:
svc.wParam = (WPARAM)mir_strdup((char *)svc.wParam);
break;
}
switch (svc.lFlags) {
case ACF_TYPE_NUMBER:
case ACF_TYPE_STRING:
case ACF_TYPE_UNICODE:
svc.lParam = (WPARAM)mir_wstrdup((wchar_t *)svc.lParam);
break;
case ACF_TYPE_STRUCT:
svc.lParam = (WPARAM)mir_strdup((char *)svc.lParam);
break;
}
break;
}
}
ColumnItem::~ColumnItem()
{
mir_free(title);
switch (setting_type) {
case QST_SETTING:
mir_free(module);
mir_free(setting);
break;
case QST_SCRIPT:
mir_free(script);
break;
case QST_SERVICE:
mir_free(svc.service);
switch (svc.wFlags) {
case ACF_TYPE_NUMBER:
case ACF_TYPE_STRING:
case ACF_TYPE_UNICODE:
mir_free((wchar_t *)svc.wParam);
break;
case ACF_TYPE_STRUCT:
mir_free((char *)svc.wParam);
break;
}
switch (svc.lFlags) {
case ACF_TYPE_NUMBER:
case ACF_TYPE_STRING:
case ACF_TYPE_UNICODE:
mir_free((wchar_t *)svc.lParam);
break;
case ACF_TYPE_STRUCT:
mir_free((char *)svc.lParam);
break;
}
break;
}
}
int ColumnItem::HasImage(const wchar_t *pwsztext) const
{
if (isClient && (g_plugin.m_flags & QSO_CLIENTICONS) && pwsztext)
return LVIF_IMAGE;
if (isGender || isXstatus || isAccount)
return LVIF_IMAGE;
return 0;
}
void ColumnItem::SetSpecialColumns()
{
if (setting_type == QST_SETTING) {
if (datatype == QSTS_STRING && !mir_strcmp(module, "CList") && !mir_strcmp(setting, "Group"))
isGroup = true;
else if (datatype == QSTS_STRING && !mir_strcmp(module, "Tab_SRMsg") && !mir_strcmp(setting, "containerW"))
isContainer = true;
else if (datatype == QSTS_BYTE && !mir_strcmpi(setting, "XStatusId"))
isXstatus = true;
else if (datatype == QSTS_STRING && !mir_strcmp(setting, "MirVer") && g_bFingerInstalled)
isClient = true;
}
else if (setting_type == QST_CONTACTINFO && cnftype == CNF_GENDER)
isGender = true;
else if (setting_type == QST_OTHER && cnftype == QSTO_ACCOUNT)
isAccount = true;
}
/////////////////////////////////////////////////////////////////////////////////////////
// column functions
int ListViewToColumn(int col)
{
for (auto &it : g_plugin.m_columns) {
if (!it->bEnabled)
continue;
if (col-- <= 0)
return g_plugin.m_columns.indexOf(&it);
}
return -1;
}
int ColumnToListView(int col)
{
int res = -1;
for (auto &it : g_plugin.m_columns) {
if (it->bEnabled)
res++;
if (col-- <= 0)
break;
}
return res;
}
void LoadDefaultColumns(OBJLIST &dst)
{
dst.destroy();
auto *pNew = new ColumnItem(TranslateT("Account"), 82, QST_OTHER);
pNew->other = QSTO_ACCOUNT;
dst.insert(pNew);
dst.insert(new ContactIntoColumn(TranslateT("Gender"), 20, CNF_GENDER));
pNew = new ContactIntoColumn(TranslateT("UserID"), 80, CNF_UNIQUEID);
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ContactIntoColumn(TranslateT("Nickname"), 76, QST_OTHER);
pNew->bFilter = true;
pNew->other = QSTO_DISPLAYNAME;
pNew = new ContactIntoColumn(TranslateT("First name"), 68, CNF_FIRSTNAME);
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ContactIntoColumn(TranslateT("Last name"), 66, CNF_LASTNAME);
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Group"), 80, QST_SETTING);
pNew->datatype = QSTS_STRING;
pNew->module = mir_strdup("CList");
pNew->setting = mir_strdup("Group");
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Container"), 80, QST_SETTING);
pNew->datatype = QSTS_STRING;
pNew->module = mir_strdup("Tab_SRMsg");
pNew->setting = mir_strdup("containerW");
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ContactIntoColumn(TranslateT("Email"), 116, CNF_EMAIL);
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Client ID"), 60, QST_SETTING);
pNew->datatype = QSTS_STRING;
pNew->setting = mir_strdup("MirVer");
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Last seen"), 116, QST_OTHER);
pNew->other = QSTO_LASTSEEN;
pNew->dwFlags = 0;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Last event"), 100, QST_OTHER);
pNew->other = QSTO_LASTEVENT;
pNew->dwFlags = 0;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Online since"), 100, QST_SETTING);
pNew->datatype = QSTS_TIMESTAMP;
pNew->setting = mir_strdup("LogonTS");
pNew->bFilter = true;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Metacontact"), 50, QST_OTHER);
pNew->other = QSTO_METACONTACT;
pNew->dwFlags = 0;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Event count"), 50, QST_OTHER);
pNew->other = QSTO_EVENTCOUNT;
pNew->dwFlags = 0;
dst.insert(pNew);
pNew = new ColumnItem(TranslateT("Contact add time"), 80, QST_SETTING);
pNew->datatype = QSTS_TIMESTAMP;
pNew->module = mir_strdup("UserInfo");
pNew->setting = mir_strdup("ContactAddTime");
dst.insert(pNew);
}
/////////////////////////////////////////////////////////////////////////////////////////
// window options
void CMPlugin::LoadOptWnd()
{
m_rect.bottom = getDword(so_mbottom);
m_rect.right = getDword(so_mright);
m_rect.left = getDword(so_mleft);
m_rect.top = getDword(so_mtop);
m_flags = getDword(so_flags, QSO_SORTBYSTATUS + QSO_DRAWGRID + QSO_CLIENTICONS + QSO_COLORIZE + QSO_SORTASC);
m_sortOrder = getDword(so_columnsort);
}
void CMPlugin::SaveOptWnd()
{
setDword(so_mbottom, m_rect.bottom);
setDword(so_mright, m_rect.right);
setDword(so_mleft, m_rect.left);
setDword(so_mtop, m_rect.top);
setDword(so_flags, m_flags);
setDword(so_columnsort, m_sortOrder);
}
/////////////////////////////////////////////////////////////////////////////////////////
// load options
int CMPlugin::LoadColumns(OBJLIST &dst)
{
m_flags = getDword(so_flags, QSO_SORTBYSTATUS + QSO_DRAWGRID + QSO_CLIENTICONS + QSO_COLORIZE + QSO_SORTASC);
int numCols = getWord(so_numcolumns);
for (int i = 0; i < numCols; i++) {
auto *pNew = new ColumnItem(nullptr);
LoadColumn(i, *pNew);
dst.insert(pNew);
}
return numCols;
}
void CMPlugin::LoadColumn(int n, ColumnItem &col)
{
char buf[127];
int offset = mir_snprintf(buf, "%s%d_", so_item, n);
strcpy(buf + offset, so_title); col.title = getWStringA(buf);
strcpy(buf + offset, so_setting_type); col.setting_type = getWord(buf);
strcpy(buf + offset, so_flags); col.dwFlags = getWord(buf);
strcpy(buf + offset, so_width); col.width = getWord(buf);
switch (col.setting_type) {
case QST_SETTING:
strcpy(buf + offset, so_datatype); col.datatype = getWord(buf);
strcpy(buf + offset, so_module); col.module = getStringA(buf);
strcpy(buf + offset, so_setting); col.setting = getStringA(buf);
break;
case QST_SCRIPT:
strcpy(buf + offset, so_script); col.script = getWStringA(buf);
break;
case QST_CONTACTINFO:
strcpy(buf + offset, so_cnftype); col.cnftype = getWord(buf);
break;
case QST_SERVICE:
offset = mir_snprintf(buf, "%s%d/service/", so_item, n);
strcpy(buf + offset, so_service); col.svc.service = getStringA(buf);
strcpy(buf + offset, so_restype); col.svc.flags = getDword(buf);
if (!mir_strcmp(col.svc.service, "Proto/GetContactBaseAccount")) {
col.setting_type = QST_OTHER;
col.other = QSTO_ACCOUNT;
col.isAccount = true;
break;
}
strcpy(buf + offset, so_wparam);
LoadParamValue(buf, col.svc.wFlags, col.svc.wParam);
strcpy(buf + offset, so_lparam);
LoadParamValue(buf, col.svc.lFlags, col.svc.lParam);
break;
case QST_OTHER:
strcpy(buf + offset, so_other); col.other = getWord(buf);
break;
}
}
void CMPlugin::LoadParamValue(char *buf, uint32_t &dwFlags, LPARAM &dwWalue)
{
char *pEnd = buf + strlen(buf);
strcpy(pEnd, "flags"); dwFlags = getDword(buf);
strcpy(pEnd, "value");
switch (dwFlags) {
case ACF_TYPE_NUMBER:
case ACF_TYPE_STRING:
case ACF_TYPE_UNICODE:
dwWalue = LPARAM(getWStringA(buf));
break;
case ACF_TYPE_STRUCT:
dwWalue = LPARAM(getStringA(buf));
break;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// save options
static int ListSettings(const char *szSetting, void *param)
{
if (!memcmp(szSetting, so_item, 4)) {
auto *pList = (LIST*)param;
pList->insert(mir_strdup(szSetting));
}
return 0;
}
void CMPlugin::SaveOptions()
{
// remove old settings
LIST settings(30);
db_enum_settings(0, ListSettings, MODULENAME, &settings);
for (auto &it : settings) {
delSetting(it);
mir_free(it);
}
// write new settings
setDword(so_flags, m_flags);
setWord(so_numcolumns, m_columns.getCount());
int i = 0;
for (auto &it : m_columns)
SaveColumn(i++, *it);
}
void CMPlugin::SaveColumn(int n, const ColumnItem &col)
{
char buf[127];
int offset = mir_snprintf(buf, "%s%d_", so_item, n);
strcpy(buf + offset, so_title); setWString(buf, col.title);
strcpy(buf + offset, so_setting_type); setWord(buf, col.setting_type);
strcpy(buf + offset, so_flags); setWord(buf, col.dwFlags);
strcpy(buf + offset, so_width); setWord(buf, col.width);
switch (col.setting_type) {
case QST_SETTING:
strcpy(buf + offset, so_datatype); setWord(buf, col.datatype);
strcpy(buf + offset, so_module); setString(buf, col.module);
strcpy(buf + offset, so_setting); setString(buf, col.setting);
break;
case QST_SCRIPT:
strcpy(buf + offset, so_script); setWString(buf, col.script);
break;
case QST_CONTACTINFO:
strcpy(buf + offset, so_cnftype); setWord(buf, col.cnftype);
break;
case QST_SERVICE:
offset = mir_snprintf(buf, "%s%d/service/", so_item, n);
strcpy(buf + offset, so_service); setString(buf, col.svc.service);
strcpy(buf + offset, so_restype); setDword(buf, col.svc.flags);
strcpy(buf + offset, so_wparam);
SaveParamValue(buf, col.svc.wFlags, col.svc.wParam);
strcpy(buf + offset, so_lparam);
SaveParamValue(buf, col.svc.lFlags, col.svc.lParam);
break;
case QST_OTHER:
strcpy(buf + offset, so_other); setWord(buf, col.other);
break;
}
}
void CMPlugin::SaveParamValue(char *buf, uint32_t flags, LPARAM value)
{
char *pEnd = buf + strlen(buf);
strcpy(pEnd, "flags"); setDword(buf, flags);
strcpy(pEnd, "value");
switch (flags) {
case ACF_TYPE_NUMBER:
case ACF_TYPE_STRING:
case ACF_TYPE_UNICODE:
setWString(buf, (wchar_t *)value);
break;
case ACF_TYPE_STRUCT:
setString(buf, (char *)value);
break;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
const wchar_t* cnf2str(int cnf)
{
switch (cnf) {
case CNF_FIRSTNAME: return TranslateT("First name");
case CNF_LASTNAME: return TranslateT("Last name");
case CNF_NICK: return TranslateT("Nick");
case CNF_CUSTOMNICK: return TranslateT("Custom nick");
case CNF_EMAIL: return TranslateT("Email");
case CNF_CITY: return TranslateT("City");
case CNF_STATE: return TranslateT("State");
case CNF_COUNTRY: return TranslateT("Country");
case CNF_PHONE: return TranslateT("Phone");
case CNF_HOMEPAGE: return TranslateT("Homepage");
case CNF_ABOUT: return TranslateT("About");
case CNF_GENDER: return TranslateT("Gender");
case CNF_AGE: return TranslateT("Age");
case CNF_FIRSTLAST: return TranslateT("First name/Last name");
case CNF_UNIQUEID: return TranslateT("Unique ID");
case CNF_FAX: return TranslateT("Fax");
case CNF_CELLULAR: return TranslateT("Cellular");
case CNF_TIMEZONE: return TranslateT("Time zone");
case CNF_MYNOTES: return TranslateT("My notes");
case CNF_BIRTHDAY: return TranslateT("Birth day");
case CNF_BIRTHMONTH: return TranslateT("Birth month");
case CNF_BIRTHYEAR: return TranslateT("Birth year");
case CNF_STREET: return TranslateT("Street");
case CNF_ZIP: return TranslateT("ZIP code");
case CNF_LANGUAGE1: return TranslateT("Language #1");
case CNF_LANGUAGE2: return TranslateT("Language #2");
case CNF_LANGUAGE3: return TranslateT("Language #3");
case CNF_CONAME: return TranslateT("Company name");
case CNF_CODEPT: return TranslateT("Company department");
case CNF_COPOSITION: return TranslateT("Company position");
case CNF_COSTREET: return TranslateT("Company street");
case CNF_COCITY: return TranslateT("Company city");
case CNF_COSTATE: return TranslateT("Company state");
case CNF_COZIP: return TranslateT("Company ZIP");
case CNF_COCOUNTRY: return TranslateT("Company country");
case CNF_COHOMEPAGE: return TranslateT("Company homepage");
case CNF_DISPLAYUID: return TranslateT("Display ID");
}
return nullptr;
}
/////////////////////////////////////////////////////////////////////////////////////////
// formatters
wchar_t* BuildLastSeenTime(uint32_t ts)
{
int year = ts / (60 * 24 * 31 * 356);
if (year == 0)
return nullptr;
year += 1980; ts = ts % (60 * 24 * 31 * 356);
int month = ts / (60 * 24 * 31); ts = ts % (60 * 24 * 31);
int day = ts / (60 * 24); ts = ts % (60 * 24);
int hours = ts / 60;
int mins = ts % 60;
return CMStringW(FORMAT, L"%02d.%02d.%04d - %02d:%02d", year, month, day, hours, mins).Detach();
}
uint32_t BuildLastSeenTimeInt(MCONTACT hContact, const char *szModule)
{
int year = db_get_w(hContact, szModule, "Year");
if (year == 0)
return 0;
int day = db_get_w(hContact, szModule, "Day");
int month = db_get_w(hContact, szModule, "Month");
int hours = db_get_w(hContact, szModule, "Hours");
int minutes = db_get_w(hContact, szModule, "Minutes");
return ((((year - 1980) * 356 + month) * 31 + day) * 24 + hours) * 60 + minutes;
}
wchar_t* TimeToStrW(uint32_t timestamp)
{
wchar_t buf[63];
TimeZone_ToStringW(timestamp, L"d - t", buf, _countof(buf));
return mir_wstrdup(buf);
}
/////////////////////////////////////////////////////////////////////////////////////////
void SnapToScreen(RECT &rc)
{
int left = GetSystemMetrics(SM_XVIRTUALSCREEN);
int top = GetSystemMetrics(SM_YVIRTUALSCREEN);
int right = GetSystemMetrics(SM_CXVIRTUALSCREEN) + left;
int bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN) + top;
if (rc.right > right)
rc.right = right;
if (rc.bottom > bottom)
rc.bottom = bottom;
if (rc.left < left)
rc.left = left;
if (rc.top < top)
rc.top = top;
}