/* Chat module plugin for Miranda IM Copyright (C) 2003 Jörgen Persson Copyright 2003-2009 Miranda ICQ/IM project, 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 "../commonheaders.h" #ifndef EM_GETSCROLLPOS #define EM_GETSCROLLPOS (WM_USER+221) #endif // The code for streaming the text is to a large extent copied from // the srmm module and then modified to fit the chat module. extern FONTINFO aFonts[OPTIONS_FONTCOUNT]; static PBYTE pLogIconBmpBits[14]; static const char *logIconNames[14] = { "chat_log_action", "chat_log_addstatus", "chat_log_highlight", "chat_log_info", "chat_log_join", "chat_log_kick", "chat_log_message_in", "chat_log_message_out", "chat_log_nick", "chat_log_notice", "chat_log_part", "chat_log_quit", "chat_log_removestatus", "chat_log_topic" }; static int logIconBmpSize[ SIZEOF(pLogIconBmpBits) ]; 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 7; else return 6; case GC_EVENT_JOIN: return 4; case GC_EVENT_PART: return 10; case GC_EVENT_QUIT: return 11; case GC_EVENT_NICK: return 8; case GC_EVENT_KICK: return 5; case GC_EVENT_NOTICE: return 9; case GC_EVENT_TOPIC: return 13; case GC_EVENT_INFORMATION:return 3; case GC_EVENT_ADDSTATUS: return 1; case GC_EVENT_REMOVESTATUS: return 12; case GC_EVENT_ACTION: return 0; } return 0; } static char *Log_SetStyle(int style, int fontindex) { static char szStyle[128]; mir_snprintf(szStyle, SIZEOF(szStyle), "\\f%u\\cf%u\\ul0\\highlight0\\b%d\\i%d\\fs%u", style, style+1, aFonts[fontindex].lf.lfWeight >= FW_BOLD ? 1 : 0, aFonts[fontindex].lf.lfItalic, 2 * abs(aFonts[fontindex].lf.lfHeight) * 74 / g_dat.logPixelSY); return szStyle; } static int Log_AppendRTF(LOGSTREAMDATA* streamData, BOOL simpleMode, char **buffer, int *cbBufferEnd, int *cbBufferAlloced, const TCHAR *fmt, ...) { va_list va; int lineLen, textCharsCount=0; TCHAR* line = (TCHAR*)alloca( 8001*sizeof(TCHAR)); char* d; va_start(va, fmt); lineLen = _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[0] += (lineLen + 1024 - lineLen % 1024); *buffer = (char*) mir_realloc(*buffer, *cbBufferAlloced); } d = *buffer + *cbBufferEnd; for (; *line; line++, textCharsCount++) { if (*line == '\r' && line[1] == '\n') { CopyMemory(d, "\\par ", 5); line++; d += 5; } else if (*line == '\n') { CopyMemory(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.StripFormat || 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, SIZEOF(szTemp), ( c == 'c' ) ? "\\cf%u " : "\\highlight%u ", col); } break; case 'C': case 'F': if ( !g_Settings.StripFormat && !streamData->bStripFormat) { int j = streamData->lin->bIsHighlighted ? 16 : EventToIndex(streamData->lin); if ( *line == 'C' ) mir_snprintf(szTemp, SIZEOF(szTemp), "\\cf%u ", j+1); else mir_snprintf(szTemp, SIZEOF(szTemp), "\\highlight0 "); } break; case 'b': case 'u': case 'i': if ( !streamData->bStripFormat ) { mir_snprintf(szTemp, SIZEOF(szTemp), (*line == 'u') ? "\\%cl " : "\\%c ", *line ); } break; case 'B': case 'U': case 'I': if ( !streamData->bStripFormat ) { mir_snprintf( szTemp, SIZEOF(szTemp), (*line == 'U') ? "\\%cl0 " : "\\%c0 ", *line ); CharLowerA( szTemp ); } break; case 'r': if ( !streamData->bStripFormat ) { int index = EventToIndex(streamData->lin); mir_snprintf(szTemp, SIZEOF(szTemp), "%s ", Log_SetStyle(index, index)); } break; } if ( szTemp[0] ) { int iLen = lstrlenA(szTemp); memcpy( d, szTemp, iLen ); d += iLen; } } else if (*line == '\t' && !streamData->bStripFormat) { CopyMemory(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 int Log_AppendIEView(LOGSTREAMDATA* streamData, BOOL simpleMode, TCHAR **buffer, int *cbBufferEnd, int *cbBufferAlloced, const TCHAR *fmt, ...) { va_list va; int lineLen, textCharsCount=0; TCHAR* line = (TCHAR*)alloca( 8001 * sizeof(TCHAR)); TCHAR* d; MODULEINFO *mi = MM_FindModule(streamData->si->pszModule); va_start(va, fmt); lineLen = _vsntprintf( line, 8000, fmt, va); if (lineLen < 0) return 0; line[lineLen] = 0; va_end(va); lineLen = lineLen*9 + 8; if (*cbBufferEnd + lineLen > *cbBufferAlloced) { cbBufferAlloced[0] += (lineLen + 1024 - lineLen % 1024); *buffer = (TCHAR *) mir_realloc(*buffer, *cbBufferAlloced * sizeof(TCHAR)); } d = *buffer + *cbBufferEnd; for (; *line; line++, textCharsCount++) { if (*line == '%' && !simpleMode ) { TCHAR szTemp[200]; szTemp[0] = '\0'; switch ( *++line ) { case '\0': case '%': *d++ = '%'; break; case 'c': case 'f': if (!g_Settings.StripFormat && !streamData->bStripFormat) { 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'; col = _ttoi(szTemp3); mir_sntprintf(szTemp, SIZEOF(szTemp), _T("%%%c#%02X%02X%02X"), c, GetRValue(mi->crColors[col]), GetGValue(mi->crColors[col]), GetBValue(mi->crColors[col])); } } line += 2; break; case 'C': case 'F': if ( !g_Settings.StripFormat && !streamData->bStripFormat) { mir_sntprintf(szTemp, SIZEOF(szTemp), _T("%%%c"), *line ); } break; case 'b': case 'u': case 'i': case 'B': case 'U': case 'I': case 'r': if ( !streamData->bStripFormat ) { mir_sntprintf(szTemp, SIZEOF(szTemp), _T("%%%c"), *line ); } break; } if ( szTemp[0] ) { size_t iLen = _tcslen(szTemp); memcpy( d, szTemp, iLen * sizeof(TCHAR)); d += iLen; } } else if (*line == '%') { *d++ = '%'; *d++ = (char) *line; } else { *d++ = (char) *line; } } *d = '\0'; *cbBufferEnd = (int) (d - *buffer); return textCharsCount; } static void AddEventTextToBuffer(char **buffer, int *bufferEnd, int *bufferAlloced, LOGSTREAMDATA *streamData) { if (streamData->lin->ptszText) Log_AppendRTF(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T(": %s"), streamData->lin->ptszText); } static void AddEventToBuffer(char **buffer, int *bufferEnd, int *bufferAlloced, LOGSTREAMDATA *streamData, TCHAR *pszNick) { if ( streamData && streamData->lin ) { switch ( streamData->lin->iType ) { case GC_EVENT_MESSAGE: if ( streamData->lin->ptszText ) { TCHAR *ptszTemp = NULL; TCHAR *ptszText = streamData->lin->ptszText; if (streamData->si->windowData.codePage != CP_ACP) { char *aText = t2acp(streamData->lin->ptszText, CP_ACP); ptszText = ptszTemp = a2tcp(aText, streamData->si->windowData.codePage); mir_free(aText); } Log_AppendRTF( streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T("%s"), ptszText ); mir_free(ptszTemp); } break; case GC_EVENT_ACTION: if ( pszNick && streamData->lin->ptszText) { Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, _T("%s "), pszNick); 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, TRUE, 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); AddEventTextToBuffer(buffer, bufferEnd, bufferAlloced, streamData); break; case GC_EVENT_QUIT: if (pszNick) Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has disconnected"), pszNick); AddEventTextToBuffer(buffer, bufferEnd, bufferAlloced, streamData); 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 (pszNick && streamData->lin->ptszStatus) Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s kicked %s"), streamData->lin->ptszStatus, pszNick); AddEventTextToBuffer(buffer, bufferEnd, bufferAlloced, streamData); break; case GC_EVENT_NOTICE: if (pszNick && streamData->lin->ptszText) { Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("Notice from %s"), pszNick ); AddEventTextToBuffer(buffer, bufferEnd, bufferAlloced, streamData); } 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 (pszNick) Log_AppendRTF(streamData, TRUE, buffer, bufferEnd, bufferAlloced, streamData->lin->ptszUserInfo ? TranslateT(" (set by %s on %s)"): TranslateT(" (set by %s)"), pszNick, 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 (pszNick && 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, pszNick); break; case GC_EVENT_REMOVESTATUS: if (pszNick && 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, pszNick); break; } } } static void AddEventTextToBufferIEView(TCHAR **buffer, int *bufferEnd, int *bufferAlloced, LOGSTREAMDATA *streamData) { if (streamData->lin->ptszText) Log_AppendIEView(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T(": %s"), streamData->lin->ptszText); } static void AddEventToBufferIEView(TCHAR **buffer, int *bufferEnd, int *bufferAlloced, LOGSTREAMDATA *streamData, TCHAR *pszNick) { if ( streamData && streamData->lin ) { switch ( streamData->lin->iType ) { case GC_EVENT_MESSAGE: if ( streamData->lin->ptszText ) { TCHAR *ptszTemp = NULL; TCHAR *ptszText = streamData->lin->ptszText; if (streamData->si->windowData.codePage != CP_ACP) { char *aText = t2acp(streamData->lin->ptszText, CP_ACP); ptszText = ptszTemp = a2tcp(aText, streamData->si->windowData.codePage); mir_free(aText); } Log_AppendIEView( streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T("%s"), ptszText ); mir_free(ptszTemp); } break; case GC_EVENT_ACTION: if ( pszNick && streamData->lin->ptszText) { Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, _T("%s "), streamData->lin->ptszNick); Log_AppendIEView(streamData, FALSE, buffer, bufferEnd, bufferAlloced, _T("%s"), streamData->lin->ptszText); } break; case GC_EVENT_JOIN: if (pszNick) { if (!streamData->lin->bIsMe) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has joined"), pszNick); else Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("You have joined %s"), streamData->si->ptszName); } break; case GC_EVENT_PART: if (pszNick) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has left"), pszNick); AddEventTextToBufferIEView(buffer, bufferEnd, bufferAlloced, streamData); break; case GC_EVENT_QUIT: if (pszNick) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s has disconnected"), pszNick); AddEventTextToBufferIEView(buffer, bufferEnd, bufferAlloced, streamData); break; case GC_EVENT_NICK: if (pszNick && streamData->lin->ptszText) { if (!streamData->lin->bIsMe) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s is now known as %s"), pszNick, streamData->lin->ptszText); else Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("You are now known as %s"), streamData->lin->ptszText); } break; case GC_EVENT_KICK: if (pszNick && streamData->lin->ptszStatus) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s kicked %s"), streamData->lin->ptszStatus, streamData->lin->ptszNick); AddEventTextToBufferIEView(buffer, bufferEnd, bufferAlloced, streamData); break; case GC_EVENT_NOTICE: if (pszNick && streamData->lin->ptszText) { Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("Notice from %s"), pszNick ); AddEventTextToBufferIEView(buffer, bufferEnd, bufferAlloced, streamData); } break; case GC_EVENT_TOPIC: if (streamData->lin->ptszText) Log_AppendIEView(streamData, FALSE, buffer, bufferEnd, bufferAlloced, TranslateT("The topic is \'%s%s\'"), streamData->lin->ptszText, _T("%r")); if (pszNick) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, streamData->lin->ptszUserInfo ? TranslateT(" (set by %s on %s)"): TranslateT(" (set by %s)"), pszNick, streamData->lin->ptszUserInfo); break; case GC_EVENT_INFORMATION: if (streamData->lin->ptszText) Log_AppendIEView(streamData, FALSE, buffer, bufferEnd, bufferAlloced, (streamData->lin->bIsMe) ? _T("--> %s") : _T("%s"), streamData->lin->ptszText); break; case GC_EVENT_ADDSTATUS: if (pszNick && streamData->lin->ptszText && streamData->lin->ptszStatus) Log_AppendIEView(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 (pszNick && streamData->lin->ptszText && streamData->lin->ptszStatus) Log_AppendIEView(streamData, TRUE, buffer, bufferEnd, bufferAlloced, TranslateT("%s disables \'%s\' status for %s"), streamData->lin->ptszText , streamData->lin->ptszStatus, streamData->lin->ptszNick); break; } } } static void LogEventIEView(LOGSTREAMDATA *streamData, TCHAR *ptszNick) { TCHAR *buffer = NULL; int bufferEnd = 0; int bufferAlloced = 0; IEVIEWEVENTDATA ied = { 0 }; IEVIEWEVENT event = { sizeof(event) }; event.hwnd = streamData->si->windowData.hwndLog; event.hContact = streamData->si->windowData.hContact; event.codepage = streamData->si->windowData.codePage; event.pszProto = streamData->si->pszModule; event.iType = IEE_LOG_MEM_EVENTS; event.eventData = &ied; event.count = 1; AddEventToBufferIEView(&buffer, &bufferEnd, &bufferAlloced, streamData, ptszNick); ied.ptszNick = ptszNick; ied.ptszText = buffer; ied.time = streamData->lin->time; ied.bIsMe = streamData->lin->bIsMe; switch ( streamData->lin->iType ) { case GC_EVENT_MESSAGE: ied.iType = IEED_GC_EVENT_MESSAGE; ied.dwData = IEEDD_GC_SHOW_NICK; break; case GC_EVENT_ACTION: ied.iType = IEED_GC_EVENT_ACTION; break; case GC_EVENT_JOIN: ied.iType = IEED_GC_EVENT_JOIN; break; case GC_EVENT_PART: ied.iType = IEED_GC_EVENT_PART; break; case GC_EVENT_QUIT: ied.iType = IEED_GC_EVENT_QUIT; break; case GC_EVENT_NICK: ied.iType = IEED_GC_EVENT_NICK; break; case GC_EVENT_KICK: ied.iType = IEED_GC_EVENT_KICK; break; case GC_EVENT_NOTICE: ied.iType = IEED_GC_EVENT_NOTICE; break; case GC_EVENT_TOPIC: ied.iType = IEED_GC_EVENT_TOPIC; break; case GC_EVENT_INFORMATION: ied.iType = IEED_GC_EVENT_INFORMATION; break; case GC_EVENT_ADDSTATUS: ied.iType = IEED_GC_EVENT_ADDSTATUS; break; case GC_EVENT_REMOVESTATUS: ied.iType = IEED_GC_EVENT_REMOVESTATUS; break; } ied.dwData |= g_Settings.ShowTime ? IEEDD_GC_SHOW_TIME : 0; ied.dwData |= IEEDD_GC_SHOW_ICON; ied.dwFlags = IEEDF_UNICODE_TEXT | IEEDF_UNICODE_NICK | IEEDF_UNICODE_TEXT2; ied.next = NULL; /* ied.color = event->color; ied.fontSize = event->iFontSize; ied.fontStyle = event->dwFlags; ied.fontName = getFontName(event->iFont); */ CallService(MS_IEVIEW_EVENT, 0, (LPARAM)&event); mir_free(buffer); /* iew.cbSize = sizeof(IEVIEWWINDOW); iew.iType = IEW_SCROLLBOTTOM; iew.hwnd = hWndLog; CallService(MS_IEVIEW_WINDOW, 0, (LPARAM)&iew); */ } TCHAR* MakeTimeStamp( TCHAR* pszStamp, time_t time) { static TCHAR szTime[30]; if ( !_tcsftime(szTime, SIZEOF(szTime)-1, pszStamp, localtime(&time))) _tcsncpy(szTime, TranslateT(""), SIZEOF(szTime)); return szTime; } static char* Log_CreateRTF(LOGSTREAMDATA *streamData, BOOL ieviewMode) { char *buffer, *header; int bufferAlloced, bufferEnd, i; LOGINFO * lin = streamData->lin; // guesstimate amount of memory for the RTF bufferEnd = 0; bufferAlloced = streamData->bRedraw ? 1024 * (streamData->si->iEventCount+2) : 2048; buffer = (char*) mir_alloc(bufferAlloced); buffer[0] = '\0'; // ### RTF HEADER header = streamData->si->pszHeader; if (header) AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, header); // ### RTF BODY (one iteration per event that should be streamed in) while ( lin ) { // filter if (streamData->si->iType != GCW_CHATROOM || !streamData->si->bFilterEnabled || (streamData->si->iLogFilterFlags&lin->iType) != 0) { TCHAR szTemp[512], szTemp2[512]; TCHAR* pszNick = NULL; streamData->lin = lin; if ( lin->ptszNick ) { if ( g_Settings.LogLimitNames && lstrlen( lin->ptszNick ) > 20 ) { lstrcpyn( szTemp2, lin->ptszNick, 20 ); lstrcpyn( szTemp2+20, _T("..."), 4); } else lstrcpyn( szTemp2, lin->ptszNick, 511 ); if ( lin->ptszUserInfo && lin->iType != GC_EVENT_TOPIC) mir_sntprintf( szTemp, SIZEOF(szTemp), _T("%s (%s)"), szTemp2, lin->ptszUserInfo ); else mir_sntprintf( szTemp, SIZEOF(szTemp), _T("%s"), szTemp2 ); pszNick = szTemp; } if (streamData->si->windowData.hwndLog != NULL) { LogEventIEView(streamData, pszNick); } { // create new line, and set font and color if (!streamData->isFirst) { AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\par"); } AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s ", Log_SetStyle(0, 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) ? 2 : EventToIcon(lin); AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\fs1 "); while (bufferAlloced - bufferEnd < logIconBmpSize[0]) bufferAlloced += 4096; buffer = (char*) mir_realloc(buffer, bufferAlloced); CopyMemory(buffer + bufferEnd, pLogIconBmpBits[iIndex], logIconBmpSize[iIndex]); bufferEnd += logIconBmpSize[iIndex]; AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " "); } if (g_Settings.TimeStampEventColour) { // colored timestamps static char szStyle[256]; int iii; if (lin->ptszNick && lin->iType == GC_EVENT_MESSAGE) { 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, aFonts[0].lf.lfWeight >= FW_BOLD ? 1 : 0, aFonts[0].lf.lfItalic, 2 * abs(aFonts[0].lf.lfHeight) * 74 / g_dat.logPixelSY); AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s ", szStyle); } else { iii = lin->bIsHighlighted?16:EventToIndex(lin); mir_snprintf(szStyle, SIZEOF(szStyle), "\\f0\\cf%u\\ul0\\highlight0\\b%d\\i%d\\fs%u", iii+1, aFonts[0].lf.lfWeight >= FW_BOLD ? 1 : 0, aFonts[0].lf.lfItalic, 2 * abs(aFonts[0].lf.lfHeight) * 74 / g_dat.logPixelSY); AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s ", szStyle); } } else AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s ", Log_SetStyle(0, 0 )); // insert a TAB if necessary to put the timestamp in the right position // if (g_Settings.dwIconFlags) // AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\tab "); //insert timestamp if (g_Settings.ShowTime) { TCHAR szTimeStamp[30], szOldTimeStamp[30]; lstrcpyn( szTimeStamp, MakeTimeStamp(g_Settings.pszTimeStamp, lin->time), 30); lstrcpyn( szOldTimeStamp, MakeTimeStamp(g_Settings.pszTimeStamp, streamData->si->LastTime), 30); if ( !g_Settings.ShowTimeIfChanged || streamData->si->LastTime == 0 || lstrcmp(szTimeStamp, szOldTimeStamp )) { streamData->si->LastTime = lin->time; Log_AppendRTF( streamData, TRUE, &buffer, &bufferEnd, &bufferAlloced, _T("%s"), szTimeStamp ); } AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\tab "); } // Insert the nick if (pszNick && lin->iType == GC_EVENT_MESSAGE) { TCHAR pszTemp[300], *p1; AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s ", Log_SetStyle(lin->bIsMe ? 2 : 1, lin->bIsMe ? 2 : 1)); lstrcpyn(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, pszNick); AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " "); } // Insert the message i = lin->bIsHighlighted?16:EventToIndex(lin); AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s ", Log_SetStyle(i, i)); AddEventToBuffer(&buffer, &bufferEnd, &bufferAlloced, streamData, pszNick); } streamData->isFirst = FALSE; } lin = lin->prev; } // ### RTF END AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}"); return buffer; } static DWORD CALLBACK Log_StreamCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb) { LOGSTREAMDATA *lstrdat = (LOGSTREAMDATA *) dwCookie; if (lstrdat) { // create the RTF if (lstrdat->buffer == NULL) { lstrdat->bufferOffset = 0; lstrdat->buffer = Log_CreateRTF(lstrdat, lstrdat->si->windowData.hwndLog != NULL); lstrdat->bufferLen = lstrlenA(lstrdat->buffer); } // give the RTF to the RE control *pcb = min(cb, lstrdat->bufferLen - lstrdat->bufferOffset); CopyMemory(pbBuff, lstrdat->buffer + lstrdat->bufferOffset, *pcb); lstrdat->bufferOffset += *pcb; // free stuff if the streaming operation is complete if (lstrdat->bufferOffset == lstrdat->bufferLen) { mir_free(lstrdat->buffer); lstrdat->buffer = NULL; } } return 0; } void Log_StreamInEvent(HWND hwndDlg, LOGINFO* lin, SESSION_INFO *si, BOOL bRedraw) { CHARRANGE oldsel, sel, newsel; POINT point ={0}; SCROLLINFO scroll; WPARAM wp; HWND hwndRich; if (hwndDlg == 0 || lin == 0 || si == 0) return; hwndRich = GetDlgItem(hwndDlg, IDC_CHAT_LOG); LOGSTREAMDATA streamData = { 0 }; streamData.hwnd = hwndRich; streamData.si = si; streamData.lin = lin; streamData.bStripFormat = FALSE; streamData.isFirst = bRedraw ? 1 : (GetRichTextLength(hwndRich, CP_ACP, FALSE) == 0); if (bRedraw || si->iType != GCW_CHATROOM || !si->bFilterEnabled || (si->iLogFilterFlags&lin->iType) != 0) { BOOL bFlag = FALSE; EDITSTREAM stream = { 0 }; stream.pfnCallback = Log_StreamCallback; stream.dwCookie = (DWORD_PTR) & streamData; scroll.cbSize= sizeof(SCROLLINFO); scroll.fMask= SIF_RANGE | SIF_POS|SIF_PAGE; GetScrollInfo(hwndRich, SB_VERT, &scroll); SendMessage(hwndRich, EM_GETSCROLLPOS, 0, (LPARAM)&point); // do not scroll to bottom if there is a selection SendMessage(hwndRich, EM_EXGETSEL, 0, (LPARAM)&oldsel); if (oldsel.cpMax != oldsel.cpMin) SendMessage(hwndRich, WM_SETREDRAW, FALSE, 0); //set the insertion point at the bottom sel.cpMin = sel.cpMax = GetRichTextLength(hwndRich, CP_ACP, FALSE); SendMessage(hwndRich, EM_EXSETSEL, 0, (LPARAM)& sel); SendMessage(hwndRich, EM_EXGETSEL, 0, (LPARAM)& sel); // fix for the indent... must be a M$ bug if (sel.cpMax == 0) bRedraw = TRUE; // should the event(s) be appended to the current log wp = bRedraw?SF_RTF:SFF_SELECTION|SF_RTF; //get the number of pixels per logical inch if (bRedraw) { SendMessage(hwndRich, WM_SETREDRAW, FALSE, 0); bFlag = TRUE; // SetCursor(LoadCursor(NULL, IDC_CHAT_ARROW)); } // stream in the event(s) streamData.lin = lin; streamData.bRedraw = bRedraw; SendMessage(hwndRich, EM_STREAMIN, wp, (LPARAM)& stream); // do smileys if (g_dat.smileyAddInstalled && (bRedraw || (lin->ptszText && lin->iType != GC_EVENT_JOIN && lin->iType != GC_EVENT_NICK && lin->iType != GC_EVENT_ADDSTATUS && lin->iType != GC_EVENT_REMOVESTATUS ))) { newsel.cpMax = -1; newsel.cpMin = sel.cpMin; if (newsel.cpMin < 0) newsel.cpMin = 0; SMADD_RICHEDIT3 sm = { sizeof(sm) }; sm.hwndRichEditControl = hwndRich; sm.Protocolname = si->pszModule; sm.rangeToReplace = bRedraw?NULL:&newsel; sm.flags = 0; sm.disableRedraw = TRUE; sm.hContact = si->windowData.hContact; CallService(MS_SMILEYADD_REPLACESMILEYS, 0, (LPARAM)&sm); } // scroll log to bottom if the log was previously scrolled to bottom, else restore old position if (bRedraw || (UINT)scroll.nPos >= (UINT)scroll.nMax-scroll.nPage-5 || scroll.nMax-scroll.nMin-scroll.nPage < 50) { SendMessage(GetParent(hwndRich), GC_SCROLLTOBOTTOM, 0, 0); } else SendMessage(hwndRich, EM_SETSCROLLPOS, 0, (LPARAM)&point); // do we need to restore the selection if (oldsel.cpMax != oldsel.cpMin) { SendMessage(hwndRich, EM_EXSETSEL, 0, (LPARAM)& oldsel); SendMessage(hwndRich, WM_SETREDRAW, TRUE, 0); InvalidateRect(hwndRich, NULL, TRUE); } // need to invalidate the window if (bFlag) { sel.cpMin = sel.cpMax = GetRichTextLength(hwndRich, CP_ACP, FALSE); SendMessage(hwndRich, EM_EXSETSEL, 0, (LPARAM)& sel); SendMessage(hwndRich, WM_SETREDRAW, TRUE, 0); InvalidateRect(hwndRich, NULL, TRUE); } } } char * Log_CreateRtfHeader(MODULEINFO * mi, SESSION_INFO *si) { char *buffer; int bufferAlloced, bufferEnd, i = 0; int charset = 0; BOOL forceCharset = FALSE; // guesstimate amount of memory for the RTF header bufferEnd = 0; bufferAlloced = 4096; buffer = (char*) mir_realloc(si->pszHeader, bufferAlloced); buffer[0] = '\0'; // ### RTF HEADER // font table AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "{\\rtf1\\ansi\\deff0{\\fonttbl"); for (i = 0; i < OPTIONS_FONTCOUNT; i++) AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "{\\f%u\\fnil\\fcharset%u%S;}", i, (!forceCharset) ? aFonts[i].lf.lfCharSet : charset, aFonts[i].lf.lfFaceName); // colour table AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}{\\colortbl ;"); for (i = 0; i < OPTIONS_FONTCOUNT; i++) AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\red%u\\green%u\\blue%u;", GetRValue(aFonts[i].color), GetGValue(aFonts[i].color), GetBValue(aFonts[i].color)); for(i = 0; i < mi->nColorCount; i++) AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\red%u\\green%u\\blue%u;", GetRValue(mi->crColors[i]), GetGValue(mi->crColors[i]), GetBValue(mi->crColors[i])); // new paragraph AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}\\pard"); // set tabs and indents { int iIndent = 0; if (g_Settings.dwIconFlags) { iIndent += (14*1440)/g_dat.logPixelSX; AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\tx%u", iIndent); } if (g_Settings.ShowTime) { int iSize = (g_Settings.LogTextIndent*1440)/g_dat.logPixelSX; AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\tx%u", iIndent + iSize ); if (g_Settings.LogIndentEnabled) iIndent += iSize; } AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\fi-%u\\li%u", iIndent, iIndent); } return buffer; } #define RTFPICTHEADERMAXSIZE 78 void LoadMsgLogBitmaps(void) { HBRUSH hBkgBrush = CreateSolidBrush(db_get_dw(NULL, "Chat", "ColorLogBG", GetSysColor(COLOR_WINDOW))); BITMAPINFOHEADER bih = { 0 }; bih.biSize = sizeof(bih); bih.biBitCount = 24; bih.biCompression = BI_RGB; bih.biHeight = 10; //GetSystemMetrics(SM_CYSMICON); bih.biPlanes = 1; bih.biWidth = 10; //GetSystemMetrics(SM_CXSMICON); 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++) { HICON hIcon = GetCachedIcon(logIconNames[i]); pLogIconBmpBits[i] = (PBYTE) mir_alloc(RTFPICTHEADERMAXSIZE + (bih.biSize + widthBytes * bih.biHeight) * 2); int rtfHeaderSize = sprintf((char*)pLogIconBmpBits[i], "{\\pict\\dibitmap0\\wbmbitspixel%u\\wbmplanes1\\wbmwidthbytes%u\\picw%u\\pich%u ", bih.biBitCount, widthBytes, (unsigned int)bih.biWidth, (unsigned int)bih.biHeight); 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); int n; for (n = 0; n < sizeof(BITMAPINFOHEADER); n++) sprintf((char*)pLogIconBmpBits[i] + rtfHeaderSize + n * 2, "%02X", ((PBYTE) & bih)[n]); for (n = 0; n < widthBytes * bih.biHeight; n += 4) sprintf((char*)pLogIconBmpBits[i] + rtfHeaderSize + (bih.biSize + n) * 2, "%02X%02X%02X%02X", pBmpBits[n], pBmpBits[n + 1], pBmpBits[n + 2], pBmpBits[n + 3]); logIconBmpSize[i] = rtfHeaderSize + (bih.biSize + widthBytes * bih.biHeight) * 2 + 1; pLogIconBmpBits[i][logIconBmpSize[i] - 1] = '}'; } mir_free(pBmpBits); DeleteDC(hdcMem); DeleteObject(hBmp); ReleaseDC(NULL, hdc); DeleteObject(hBkgBrush); } void FreeMsgLogBitmaps(void) { for (int i = 0; i < SIZEOF(pLogIconBmpBits); i++) mir_free(pLogIconBmpBits[i]); }