static TCHAR* getDBSetting(MCONTACT hContact, char* module, char* setting, TCHAR* defaultValue) { DBVARIANT dbv; if (db_get_s(hContact, module, setting, &dbv, 0)) return defaultValue; TCHAR *var = NULL; switch (dbv.type) { case DBVT_BYTE: var = itot(dbv.bVal); break; case DBVT_WORD: var = itot(dbv.wVal); break; case DBVT_DWORD: var = itot(dbv.dVal); break; case DBVT_ASCIIZ: var = mir_a2t(dbv.pszVal); break; case DBVT_WCHAR: var = mir_wstrdup(dbv.pwszVal); break; case DBVT_UTF8: Utf8Decode(dbv.pszVal, &var); break; } db_free(&dbv); return var; }
/** Write out a line of text, escaping special characters. */ static void writeEscaped(struct ADrawTag *ctx, const char *string) { FILE *f = getSvgFile(ctx); while(*string != '\0') { unsigned int code, bytes; switch(*string) { case '<': fprintf(f, "<"); break; case '>': fprintf(f, ">"); break; case '"': fprintf(f, """); break; case '&': fprintf(f, "&"); break; default: if(Utf8Decode(string, &code, &bytes)) { fprintf(f, "&#x%x;", code); string += bytes - 1; } else { fputc(*string, f); } break; } string++; } }
char* utf8_iso_8859_2(char *buf){ static int tab[256]; // static int itab=iso_8859_2_unicode((int*)tab); ... avoid warning static bool init_tab = true; if (init_tab) { iso_8859_2_unicode((int*)tab); init_tab = false; } unsigned int u; char *p,*q; p=q=buf; while (*p){ p+=Utf8Decode((int&)u,(unsigned char*)p); if (u>0x80){ int i; for (i=0x80;i<256;i++) if (tab[i]==(int)u){*q=i;break;} if (i==256)*q=u; }else *q=u; q++; } *q=0; return buf; }
/** * Delete a character from a textbuffer, either with 'Delete' or 'Backspace' * The character is delete from the position the caret is at * @param keycode Type of deletion, either WKC_BACKSPACE or WKC_DELETE * @return Return true on successful change of Textbuf, or false otherwise */ bool Textbuf::DeleteChar(uint16 keycode) { bool word = (keycode & WKC_CTRL) != 0; keycode &= ~WKC_SPECIAL_KEYS; if (keycode != WKC_BACKSPACE && keycode != WKC_DELETE) return false; bool backspace = keycode == WKC_BACKSPACE; if (!CanDelChar(backspace)) return false; char *s = this->buf + this->caretpos; uint16 len = 0; if (word) { /* Delete a complete word. */ if (backspace) { /* Delete whitespace and word in front of the caret. */ len = this->caretpos - (uint16)this->char_iter->Prev(StringIterator::ITER_WORD); s -= len; } else { /* Delete word and following whitespace following the caret. */ len = (uint16)this->char_iter->Next(StringIterator::ITER_WORD) - this->caretpos; } /* Update character count. */ for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) { this->chars--; } } else { /* Delete a single character. */ if (backspace) { /* Delete the last code point in front of the caret. */ s = Utf8PrevChar(s); WChar c; len = (uint16)Utf8Decode(&c, s); this->chars--; } else { /* Delete the complete character following the caret. */ len = (uint16)this->char_iter->Next(StringIterator::ITER_CHARACTER) - this->caretpos; /* Update character count. */ for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) { this->chars--; } } } /* Move the remaining characters over the marker */ memmove(s, s + len, this->bytes - (s - this->buf) - len); this->bytes -= len; if (backspace) this->caretpos -= len; this->UpdateStringIter(); this->UpdateWidth(); this->UpdateCaretPosition(); this->UpdateMarkedText(); return true; }
char* utf8_ascii(char *buf){ unsigned int u; char *p,*q; p=q=buf; while (*p){ p+=Utf8Decode((int&)u,(unsigned char*)p); *q++=u; } *q=0; return buf; }
/** * Scans the string for valid characters and if it finds invalid ones, * replaces them with a question mark '?' (if not ignored) * @param str the string to validate * @param last the last valid character of str * @param settings the settings for the string validation. */ void str_validate(char *str, const char *last, StringValidationSettings settings) { /* Assume the ABSOLUTE WORST to be in str as it comes from the outside. */ char *dst = str; while (str <= last && *str != '\0') { size_t len = Utf8EncodedCharLen(*str); /* If the character is unknown, i.e. encoded length is 0 * we assume worst case for the length check. * The length check is needed to prevent Utf8Decode to read * over the terminating '\0' if that happens to be placed * within the encoding of an UTF8 character. */ if ((len == 0 && str + 4 > last) || str + len > last) break; WChar c; len = Utf8Decode(&c, str); /* It's possible to encode the string termination character * into a multiple bytes. This prevents those termination * characters to be skipped */ if (c == '\0') break; if ((IsPrintable(c) && (c < SCC_SPRITE_START || c > SCC_SPRITE_END)) || ((settings & SVS_ALLOW_CONTROL_CODE) != 0 && c == SCC_ENCODED)) { /* Copy the character back. Even if dst is current the same as str * (i.e. no characters have been changed) this is quicker than * moving the pointers ahead by len */ do { *dst++ = *str++; } while (--len != 0); } else if ((settings & SVS_ALLOW_NEWLINE) != 0 && c == '\n') { *dst++ = *str++; } else { if ((settings & SVS_ALLOW_NEWLINE) != 0 && c == '\r' && str[1] == '\n') { str += len; continue; } /* Replace the undesirable character with a question mark */ str += len; if ((settings & SVS_REPLACE_WITH_QUESTION_MARK) != 0) *dst++ = '?'; /* In case of these two special cases assume that they really * mean SETX/SETXY and also "eat" the parameter. If this was * not the case the string was broken to begin with and this * would not break much more. */ if (c == SCC_SETX) { str++; } else if (c == SCC_SETXY) { str += 2; } } } *dst = '\0'; }
/** Scans the string for colour codes and strips them */ void str_strip_colours(char *str) { char *dst = str; WChar c; size_t len; for (len = Utf8Decode(&c, str); c != '\0'; len = Utf8Decode(&c, str)) { if (c < SCC_BLUE || c > SCC_BLACK) { /* Copy the character back. Even if dst is current the same as str * (i.e. no characters have been changed) this is quicker than * moving the pointers ahead by len */ do { *dst++ = *str++; } while (--len != 0); } else { /* Just skip (strip) the colour codes */ str += len; } } *dst = '\0'; }
/** * Create a new layouter. * @param str The string to create the layout for. * @param maxw The maximum width. * @param colour The colour of the font. * @param fontsize The size of font to use. */ Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) : string(str) { FontState state(colour, fontsize); WChar c = 0; do { /* Scan string for end of line */ const char *lineend = str; for (;;) { size_t len = Utf8Decode(&c, lineend); if (c == '\0' || c == '\n') break; lineend += len; } LineCacheItem& line = GetCachedParagraphLayout(str, lineend - str, state); if (line.layout != NULL) { /* Line is in cache */ str = lineend + 1; state = line.state_after; line.layout->Reflow(); } else { /* Line is new, layout it */ #ifdef WITH_ICU_LAYOUT FontState old_state = state; const char *old_str = str; GetLayouter<ICUParagraphLayout>(line, str, state); if (line.layout == NULL) { static bool warned = false; if (!warned) { DEBUG(misc, 0, "ICU layouter bailed on the font. Falling back to the fallback layouter"); warned = true; } state = old_state; str = old_str; GetLayouter<FallbackParagraphLayout>(line, str, state); } #else GetLayouter<FallbackParagraphLayout>(line, str, state); #endif } /* Copy all lines into a local cache so we can reuse them later on more easily. */ const ParagraphLayouter::Line *l; while ((l = line.layout->NextLine(maxw)) != NULL) { *this->Append() = l; } } while (c != '\0'); }
/** * Scan the string for old values of SCC_ENCODED and fix it to * it's new, static value. * @param str the string to scan * @param last the last valid character of str */ void str_fix_scc_encoded(char *str, const char *last) { while (str <= last && *str != '\0') { size_t len = Utf8EncodedCharLen(*str); if ((len == 0 && str + 4 > last) || str + len > last) break; WChar c; len = Utf8Decode(&c, str); if (c == '\0') break; if (c == 0xE028 || c == 0xE02A) { c = SCC_ENCODED; } str += Utf8Encode(str, c); } *str = '\0'; }
static INT_PTR DbEventGetStringT( WPARAM wParam, LPARAM lParam ) { DBEVENTINFO* dbei = ( DBEVENTINFO* )wParam; char* string = ( char* )lParam; #if defined( _UNICODE ) if ( dbei->flags & DBEF_UTF ) return ( INT_PTR )Utf8DecodeUcs2( string ); return ( INT_PTR )mir_a2t( string ); #else char* res = mir_strdup( string ); if ( dbei->flags & DBEF_UTF ) Utf8Decode( res, NULL ); return ( INT_PTR )res; #endif }
static WChar _io_file_lexfeed_UTF8(SQUserPointer file) { char buffer[5]; /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */ if (((SQFile *)file)->Read(buffer, sizeof(buffer[0]), 1) != 1) return 0; uint len = Utf8EncodedCharLen(buffer[0]); if (len == 0) return -1; /* Read the remaining bits. */ if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0; /* Convert the character, and when definitely invalid, bail out as well. */ WChar c; if (Utf8Decode(&c, buffer) != len) return -1; return c; }
/** * Get the position of a character in the layout. * @param ch Character to get the position of. * @return Upper left corner of the character relative to the start of the string. * @note Will only work right for single-line strings. */ Point Layouter::GetCharPosition(const char *ch) const { /* Find the code point index which corresponds to the char * pointer into our UTF-8 source string. */ size_t index = 0; const char *str = this->string; while (str < ch) { WChar c; size_t len = Utf8Decode(&c, str); if (c == '\0' || c == '\n') break; str += len; index += (*this->Begin())->GetInternalCharLength(c); } if (str == ch) { /* Valid character. */ const ParagraphLayouter::Line *line = *this->Begin(); /* Pointer to the end-of-string/line marker? Return total line width. */ if (*ch == '\0' || *ch == '\n') { Point p = { line->GetWidth(), 0 }; return p; } /* Scan all runs until we've found our code point index. */ for (int run_index = 0; run_index < line->CountRuns(); run_index++) { const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index); for (int i = 0; i < run->GetGlyphCount(); i++) { /* Matching glyph? Return position. */ if ((size_t)run->GetGlyphToCharMap()[i] == index) { Point p = { (int)run->GetPositions()[i * 2], (int)run->GetPositions()[i * 2 + 1] }; return p; } } } } Point p = { 0, 0 }; return p; }
/** * Checks whether the given string is valid, i.e. contains only * valid (printable) characters and is properly terminated. * @param str The string to validate. * @param last The last character of the string, i.e. the string * must be terminated here or earlier. */ bool StrValid(const char *str, const char *last) { /* Assume the ABSOLUTE WORST to be in str as it comes from the outside. */ while (str <= last && *str != '\0') { size_t len = Utf8EncodedCharLen(*str); /* Encoded length is 0 if the character isn't known. * The length check is needed to prevent Utf8Decode to read * over the terminating '\0' if that happens to be placed * within the encoding of an UTF8 character. */ if (len == 0 || str + len > last) return false; WChar c; len = Utf8Decode(&c, str); if (!IsPrintable(c) || (c >= SCC_SPRITE_START && c <= SCC_SPRITE_END)) { return false; } str += len; } return *str == '\0'; }
static void SetValue(HWND hwndDlg, int idCtrl, HANDLE hContact, char *szModule, char *szSetting, int special) { char str[80], *pstr = NULL; TCHAR* ptstr = NULL; char* szProto = GetContactProto(hContact); bool proto_service = szProto && (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_INFOSETTINGSVC); DBVARIANT dbv = { DBVT_DELETED }; int unspecified; if (szModule == NULL) unspecified = 1; else if (proto_service) unspecified = Proto_GetContactInfoSetting(hContact, szProto, szModule, szSetting, &dbv, 0); else unspecified = db_get_s(hContact, szModule, szSetting, &dbv, 0); if ( !unspecified) { switch(dbv.type) { case DBVT_BYTE: if (special == SVS_GENDER) { if (dbv.cVal == 'M') ptstr = TranslateT("Male"); else if (dbv.cVal == 'F') ptstr = TranslateT("Female"); else unspecified = 1; } else if (special == SVS_MONTH) { if (dbv.bVal>0 && dbv.bVal <= 12) { pstr = str; GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1-1+dbv.bVal, str, SIZEOF(str)); } else unspecified = 1; } else if (special == SVS_TIMEZONE) { if (dbv.cVal == -100) unspecified = 1; else { pstr = str; mir_snprintf(str, SIZEOF(str), dbv.cVal?"UTC%+d:%02d":"UTC", -dbv.cVal/2, (dbv.cVal&1)*30); } } else { unspecified = (special == SVS_ZEROISUNSPEC && dbv.bVal == 0); pstr = _itoa(special == SVS_SIGNED?dbv.cVal:dbv.bVal, str, 10); } break; case DBVT_WORD: if (special == SVS_COUNTRY) { WORD wSave = dbv.wVal; if (wSave == (WORD)-1) { char szSettingName[100]; mir_snprintf(szSettingName, SIZEOF(szSettingName), "%sName", szSetting); if ( !db_get_ts(hContact, szModule, szSettingName, &dbv)) { ptstr = dbv.ptszVal; unspecified = false; break; } } pstr = Translate((char*)CallService(MS_UTILS_GETCOUNTRYBYNUMBER, wSave, 0)); unspecified = pstr == NULL; } else { unspecified = (special == SVS_ZEROISUNSPEC && dbv.wVal == 0); pstr = _itoa(special == SVS_SIGNED?dbv.sVal:dbv.wVal, str, 10); } break; case DBVT_DWORD: unspecified = (special == SVS_ZEROISUNSPEC && dbv.dVal == 0); if (special == SVS_IP) { struct in_addr ia; ia.S_un.S_addr = htonl(dbv.dVal); pstr = inet_ntoa(ia); if (dbv.dVal == 0) unspecified = 1; } else pstr = _itoa(special == SVS_SIGNED?dbv.lVal:dbv.dVal, str, 10); break; case DBVT_ASCIIZ: unspecified = (special == SVS_ZEROISUNSPEC && dbv.pszVal[0] == '\0'); pstr = dbv.pszVal; break; case DBVT_UTF8: unspecified = (special == SVS_ZEROISUNSPEC && dbv.pszVal[0] == '\0'); if ( !unspecified) { WCHAR* wszStr; Utf8Decode(dbv.pszVal, &wszStr); SetDlgItemTextW(hwndDlg, idCtrl, TranslateTS(wszStr)); mir_free(wszStr); goto LBL_Exit; } pstr = dbv.pszVal; Utf8Decode(dbv.pszVal, NULL); break; default: pstr = str; lstrcpyA(str, "???"); break; } } if (unspecified) SetDlgItemText(hwndDlg, idCtrl, TranslateT("<not specified>")); else if (ptstr != NULL) SetDlgItemText(hwndDlg, idCtrl, ptstr); else SetDlgItemTextA(hwndDlg, idCtrl, pstr); LBL_Exit: EnableWindow(GetDlgItem(hwndDlg, idCtrl), !unspecified); if (proto_service) Proto_FreeInfoVariant(&dbv); else db_free(&dbv); }
static INT_PTR DbEventGetText(WPARAM wParam, LPARAM lParam) { DBEVENTGETTEXT* egt = (DBEVENTGETTEXT*)lParam; BOOL bIsDenyUnicode = (egt->datatype & DBVTF_DENYUNICODE); DBEVENTINFO* dbei = egt->dbei; DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )DbEventTypeGet( ( WPARAM )dbei->szModule, ( LPARAM )dbei->eventType ); if ( et && ServiceExists( et->textService )) return CallService( et->textService, wParam, lParam ); if ( !dbei->pBlob ) return 0; if ( dbei->eventType == EVENTTYPE_FILE ) { char* filename = ((char *)dbei->pBlob) + sizeof(DWORD); char* descr = filename + lstrlenA( filename ) + 1; char* str = (*descr == 0) ? filename : descr; switch ( egt->datatype ) { case DBVT_WCHAR: return ( INT_PTR )(( dbei->flags & DBEF_UTF ) ? Utf8DecodeT( str ) : mir_a2t( str )); case DBVT_ASCIIZ: return ( INT_PTR )(( dbei->flags & DBEF_UTF ) ? Utf8Decode( mir_strdup( str ), NULL ) : mir_strdup( str )); } return 0; } // temporary fix for bug with event types conflict between jabber chat states notifications // and srmm's status changes, must be commented out in future releases if ( dbei->eventType == 25368 && dbei->cbBlob == 1 && dbei->pBlob[0] == 1 ) return 0; egt->datatype &= ~DBVTF_DENYUNICODE; if ( egt->datatype == DBVT_WCHAR ) { WCHAR* msg = NULL; if ( dbei->flags & DBEF_UTF ) { char* str = (char*)alloca(dbei->cbBlob + 1); if (str == NULL) return NULL; memcpy(str, dbei->pBlob, dbei->cbBlob); str[dbei->cbBlob] = 0; Utf8DecodeCP( str, egt->codepage, &msg ); } else { size_t msglen = strlen(( char* )dbei->pBlob) + 1, msglenW = 0; if ( msglen != dbei->cbBlob ) { size_t i, count = (( dbei->cbBlob - msglen ) / sizeof( WCHAR )); WCHAR* p = ( WCHAR* )&dbei->pBlob[ msglen ]; for ( i=0; i < count; i++ ) { if ( p[i] == 0 ) { msglenW = i; break; } } } if ( msglenW > 0 && msglenW < msglen && !bIsDenyUnicode ) msg = mir_wstrdup(( WCHAR* )&dbei->pBlob[ msglen ] ); else { msg = ( WCHAR* )mir_alloc( sizeof(WCHAR) * msglen ); MultiByteToWideChar( egt->codepage, 0, (char *) dbei->pBlob, -1, msg, (int)msglen ); } } return ( INT_PTR )msg; } else if ( egt->datatype == DBVT_ASCIIZ ) { char* msg = mir_strdup(( char* )dbei->pBlob ); if (dbei->flags & DBEF_UTF) Utf8DecodeCP( msg, egt->codepage, NULL ); return ( INT_PTR )msg; } return 0; }
/** * Translate TTDPatch string codes into something OpenTTD can handle (better). * @param grfid The (NewGRF) ID associated with this string * @param language_id The (NewGRF) language ID associated with this string. * @param allow_newlines Whether newlines are allowed in the string or not. * @param str The string to translate. * @param [out] olen The length of the final string. * @param byte80 The control code to use as replacement for the 0x80-value. * @return The translated string. */ char *TranslateTTDPatchCodes(uint32 grfid, uint8 language_id, bool allow_newlines, const char *str, int *olen, StringControlCode byte80) { char *tmp = MallocT<char>(strlen(str) * 10 + 1); // Allocate space to allow for expansion char *d = tmp; bool unicode = false; WChar c; size_t len = Utf8Decode(&c, str); /* Helper variable for a possible (string) mapping. */ UnmappedChoiceList *mapping = NULL; if (c == NFO_UTF8_IDENTIFIER) { unicode = true; str += len; } for (;;) { if (unicode && Utf8EncodedCharLen(*str) != 0) { c = Utf8Consume(&str); /* 'Magic' range of control codes. */ if (GB(c, 8, 8) == 0xE0) { c = GB(c, 0, 8); } else if (c >= 0x20) { if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; d += Utf8Encode(d, c); continue; } } else { c = (byte)*str++; } if (c == '\0') break; switch (c) { case 0x01: if (str[0] == '\0') goto string_end; d += Utf8Encode(d, ' '); str++; break; case 0x0A: break; case 0x0D: if (allow_newlines) { *d++ = 0x0A; } else { grfmsg(1, "Detected newline in string that does not allow one"); } break; case 0x0E: d += Utf8Encode(d, SCC_TINYFONT); break; case 0x0F: d += Utf8Encode(d, SCC_BIGFONT); break; case 0x1F: if (str[0] == '\0' || str[1] == '\0') goto string_end; d += Utf8Encode(d, ' '); str += 2; break; case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_SIGNED + c - 0x7B); break; case 0x80: d += Utf8Encode(d, byte80); break; case 0x81: { if (str[0] == '\0' || str[1] == '\0') goto string_end; StringID string; string = ((uint8)*str++); string |= ((uint8)*str++) << 8; d += Utf8Encode(d, SCC_NEWGRF_STRINL); d += Utf8Encode(d, MapGRFStringID(grfid, string)); break; } case 0x82: case 0x83: case 0x84: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_DATE_LONG + c - 0x82); break; case 0x85: d += Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD); break; case 0x86: d += Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break; case 0x87: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_VOLUME_LONG); break; case 0x88: d += Utf8Encode(d, SCC_BLUE); break; case 0x89: d += Utf8Encode(d, SCC_SILVER); break; case 0x8A: d += Utf8Encode(d, SCC_GOLD); break; case 0x8B: d += Utf8Encode(d, SCC_RED); break; case 0x8C: d += Utf8Encode(d, SCC_PURPLE); break; case 0x8D: d += Utf8Encode(d, SCC_LTBROWN); break; case 0x8E: d += Utf8Encode(d, SCC_ORANGE); break; case 0x8F: d += Utf8Encode(d, SCC_GREEN); break; case 0x90: d += Utf8Encode(d, SCC_YELLOW); break; case 0x91: d += Utf8Encode(d, SCC_DKGREEN); break; case 0x92: d += Utf8Encode(d, SCC_CREAM); break; case 0x93: d += Utf8Encode(d, SCC_BROWN); break; case 0x94: d += Utf8Encode(d, SCC_WHITE); break; case 0x95: d += Utf8Encode(d, SCC_LTBLUE); break; case 0x96: d += Utf8Encode(d, SCC_GRAY); break; case 0x97: d += Utf8Encode(d, SCC_DKBLUE); break; case 0x98: d += Utf8Encode(d, SCC_BLACK); break; case 0x9A: { int code = *str++; switch (code) { case 0x00: goto string_end; case 0x01: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break; /* 0x02: ignore next colour byte is not supported. It works on the final * string and as such hooks into the string drawing routine. At that * point many things already happened, such as splitting up of strings * when drawn over multiple lines or right-to-left translations, which * make the behaviour peculiar, e.g. only happening at specific width * of windows. Or we need to add another pass over the string to just * support this. As such it is not implemented in OpenTTD. */ case 0x03: { if (str[0] == '\0' || str[1] == '\0') goto string_end; uint16 tmp = ((uint8)*str++); tmp |= ((uint8)*str++) << 8; d += Utf8Encode(d, SCC_NEWGRF_PUSH_WORD); d += Utf8Encode(d, tmp); break; } case 0x04: if (str[0] == '\0') goto string_end; d += Utf8Encode(d, SCC_NEWGRF_UNPRINT); d += Utf8Encode(d, *str++); break; case 0x06: d += Utf8Encode(d, SCC_NEWGRF_PRINT_BYTE_HEX); break; case 0x07: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_HEX); break; case 0x08: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_HEX); break; /* 0x09, 0x0A are TTDPatch internal use only string codes. */ case 0x0B: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_HEX); break; case 0x0C: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_STATION_NAME); break; case 0x0D: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG); break; case 0x0E: case 0x0F: { if (str[0] == '\0') goto string_end; const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id); int index = *str++; int mapped = lm != NULL ? lm->GetMapping(index, code == 0x0E) : -1; if (mapped >= 0) { d += Utf8Encode(d, code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE); d += Utf8Encode(d, code == 0x0E ? mapped : mapped + 1); } break; } case 0x10: case 0x11: if (str[0] == '\0') goto string_end; if (mapping == NULL) { if (code == 0x10) str++; // Skip the index grfmsg(1, "choice list %s marker found when not expected", code == 0x10 ? "next" : "default"); break; } else { /* Terminate the previous string. */ *d = '\0'; int index = (code == 0x10 ? *str++ : 0); if (mapping->strings.Contains(index)) { grfmsg(1, "duplicate choice list string, ignoring"); d++; } else { d = mapping->strings[index] = MallocT<char>(strlen(str) * 10 + 1); } } break; case 0x12: if (mapping == NULL) { grfmsg(1, "choice list end marker found when not expected"); } else { /* Terminate the previous string. */ *d = '\0'; /* Now we can start flushing everything and clean everything up. */ d = mapping->Flush(LanguageMap::GetLanguageMap(grfid, language_id)); delete mapping; mapping = NULL; } break; case 0x13: case 0x14: case 0x15: if (str[0] == '\0') goto string_end; if (mapping != NULL) { grfmsg(1, "choice lists can't be stacked, it's going to get messy now..."); if (code != 0x14) str++; } else { static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST }; mapping = new UnmappedChoiceList(mp[code - 0x13], d, code == 0x14 ? 0 : *str++); } break; case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD_DATE_LONG + code - 0x16); break; default: grfmsg(1, "missing handler for extended format code"); break; } break; } case 0x9E: d += Utf8Encode(d, 0x20AC); break; // Euro case 0x9F: d += Utf8Encode(d, 0x0178); break; // Y with diaeresis case 0xA0: d += Utf8Encode(d, SCC_UP_ARROW); break; case 0xAA: d += Utf8Encode(d, SCC_DOWN_ARROW); break; case 0xAC: d += Utf8Encode(d, SCC_CHECKMARK); break; case 0xAD: d += Utf8Encode(d, SCC_CROSS); break; case 0xAF: d += Utf8Encode(d, SCC_RIGHT_ARROW); break; case 0xB4: d += Utf8Encode(d, SCC_TRAIN); break; case 0xB5: d += Utf8Encode(d, SCC_LORRY); break; case 0xB6: d += Utf8Encode(d, SCC_BUS); break; case 0xB7: d += Utf8Encode(d, SCC_PLANE); break; case 0xB8: d += Utf8Encode(d, SCC_SHIP); break; case 0xB9: d += Utf8Encode(d, SCC_SUPERSCRIPT_M1); break; case 0xBC: d += Utf8Encode(d, SCC_SMALL_UP_ARROW); break; case 0xBD: d += Utf8Encode(d, SCC_SMALL_DOWN_ARROW); break; default: /* Validate any unhandled character */ if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; d += Utf8Encode(d, c); break; } } string_end: if (mapping != NULL) { grfmsg(1, "choice list was incomplete, the whole list is ignored"); delete mapping; } *d = '\0'; if (olen != NULL) *olen = d - tmp + 1; tmp = ReallocT(tmp, d - tmp + 1); return tmp; }
char *TranslateTTDPatchCodes(uint32 grfid, const char *str) { char *tmp = MallocT<char>(strlen(str) * 10 + 1); // Allocate space to allow for expansion char *d = tmp; bool unicode = false; WChar c; size_t len = Utf8Decode(&c, str); if (c == 0x00DE) { /* The thorn ('รพ') indicates a unicode string to TTDPatch */ unicode = true; str += len; } for (;;) { if (unicode && Utf8EncodedCharLen(*str) != 0) { c = Utf8Consume(&str); /* 'Magic' range of control codes. */ if (GB(c, 8, 8) == 0xE0) { c = GB(c, 0, 8); } else if (c >= 0x20) { if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; d += Utf8Encode(d, c); continue; } } else { c = (byte)*str++; } if (c == 0) break; switch (c) { case 0x01: d += Utf8Encode(d, SCC_SETX); *d++ = *str++; break; case 0x0A: break; case 0x0D: *d++ = 0x0A; break; case 0x0E: d += Utf8Encode(d, SCC_TINYFONT); break; case 0x0F: d += Utf8Encode(d, SCC_BIGFONT); break; case 0x1F: d += Utf8Encode(d, SCC_SETXY); *d++ = *str++; *d++ = *str++; break; case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: case 0x80: d += Utf8Encode(d, SCC_NEWGRF_PRINT_DWORD + c - 0x7B); break; case 0x81: { StringID string; string = ((uint8)*str++); string |= ((uint8)*str++) << 8; d += Utf8Encode(d, SCC_STRING_ID); d += Utf8Encode(d, MapGRFStringID(grfid, string)); break; } case 0x82: case 0x83: case 0x84: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_SPEED + c - 0x82); break; case 0x85: d += Utf8Encode(d, SCC_NEWGRF_DISCARD_WORD); break; case 0x86: d += Utf8Encode(d, SCC_NEWGRF_ROTATE_TOP_4_WORDS); break; case 0x87: d += Utf8Encode(d, SCC_NEWGRF_PRINT_WORD_LITRES); break; case 0x88: d += Utf8Encode(d, SCC_BLUE); break; case 0x89: d += Utf8Encode(d, SCC_SILVER); break; case 0x8A: d += Utf8Encode(d, SCC_GOLD); break; case 0x8B: d += Utf8Encode(d, SCC_RED); break; case 0x8C: d += Utf8Encode(d, SCC_PURPLE); break; case 0x8D: d += Utf8Encode(d, SCC_LTBROWN); break; case 0x8E: d += Utf8Encode(d, SCC_ORANGE); break; case 0x8F: d += Utf8Encode(d, SCC_GREEN); break; case 0x90: d += Utf8Encode(d, SCC_YELLOW); break; case 0x91: d += Utf8Encode(d, SCC_DKGREEN); break; case 0x92: d += Utf8Encode(d, SCC_CREAM); break; case 0x93: d += Utf8Encode(d, SCC_BROWN); break; case 0x94: d += Utf8Encode(d, SCC_WHITE); break; case 0x95: d += Utf8Encode(d, SCC_LTBLUE); break; case 0x96: d += Utf8Encode(d, SCC_GRAY); break; case 0x97: d += Utf8Encode(d, SCC_DKBLUE); break; case 0x98: d += Utf8Encode(d, SCC_BLACK); break; case 0x9A: switch (*str++) { case 0: // FALL THROUGH case 1: d += Utf8Encode(d, SCC_NEWGRF_PRINT_QWORD_CURRENCY); break; case 3: { uint16 tmp = ((uint8)*str++); tmp |= ((uint8)*str++) << 8; d += Utf8Encode(d, SCC_NEWGRF_PUSH_WORD); d += Utf8Encode(d, tmp); } break; case 4: d += Utf8Encode(d, SCC_NEWGRF_UNPRINT); d += Utf8Encode(d, *str++); break; case 6: d += Utf8Encode(d, SCC_NEWGRF_PRINT_HEX_BYTE); break; case 7: d += Utf8Encode(d, SCC_NEWGRF_PRINT_HEX_WORD); break; case 8: d += Utf8Encode(d, SCC_NEWGRF_PRINT_HEX_DWORD); break; case 0x0B: d += Utf8Encode(d, SCC_NEWGRF_PRINT_HEX_QWORD); break; default: grfmsg(1, "missing handler for extended format code"); break; } break; case 0x9E: d += Utf8Encode(d, 0x20AC); break; // Euro case 0x9F: d += Utf8Encode(d, 0x0178); break; // Y with diaeresis case 0xA0: d += Utf8Encode(d, SCC_UPARROW); break; case 0xAA: d += Utf8Encode(d, SCC_DOWNARROW); break; case 0xAC: d += Utf8Encode(d, SCC_CHECKMARK); break; case 0xAD: d += Utf8Encode(d, SCC_CROSS); break; case 0xAF: d += Utf8Encode(d, SCC_RIGHTARROW); break; case 0xB4: d += Utf8Encode(d, SCC_TRAIN); break; case 0xB5: d += Utf8Encode(d, SCC_LORRY); break; case 0xB6: d += Utf8Encode(d, SCC_BUS); break; case 0xB7: d += Utf8Encode(d, SCC_PLANE); break; case 0xB8: d += Utf8Encode(d, SCC_SHIP); break; case 0xB9: d += Utf8Encode(d, SCC_SUPERSCRIPT_M1); break; case 0xBC: d += Utf8Encode(d, SCC_SMALLUPARROW); break; case 0xBD: d += Utf8Encode(d, SCC_SMALLDOWNARROW); break; default: /* Validate any unhandled character */ if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; d += Utf8Encode(d, c); break; } } *d = '\0'; tmp = ReallocT(tmp, strlen(tmp) + 1); return tmp; }
LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { ClcGroup *group; ClcContact *contact; DWORD hitFlags; int hit; ClcData *dat = (struct ClcData *) GetWindowLongPtr(hwnd, 0); if (msg >= CLM_FIRST && msg < CLM_LAST) return cli.pfnProcessExternalMessages(hwnd, dat, msg, wParam, lParam); switch (msg) { case WM_CREATE: WindowList_Add(hClcWindowList, hwnd, NULL); cli.pfnRegisterFileDropping(hwnd); if (dat == NULL) { dat = (struct ClcData *) mir_calloc(sizeof(struct ClcData)); SetWindowLongPtr(hwnd, 0, (LONG_PTR)dat); } { for (int i = 0; i <= FONTID_MAX; i++) dat->fontInfo[i].changed = 1; } dat->selection = -1; dat->iconXSpace = 20; dat->checkboxSize = 13; dat->dragAutoScrollHeight = 30; dat->iDragItem = -1; dat->iInsertionMark = -1; dat->insertionMarkHitHeight = 5; dat->iHotTrack = -1; dat->infoTipTimeout = db_get_w(NULL, "CLC", "InfoTipHoverTime", 750); dat->extraColumnSpacing = 20; dat->list.cl.increment = 30; dat->needsResort = 1; cli.pfnLoadClcOptions(hwnd, dat, TRUE); if (!IsWindowVisible(hwnd)) SetTimer(hwnd, TIMERID_REBUILDAFTER, 10, NULL); else { cli.pfnRebuildEntireList(hwnd, dat); NMCLISTCONTROL nm; nm.hdr.code = CLN_LISTREBUILT; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); } break; case INTM_SCROLLBARCHANGED: if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CONTACTLIST) { if (dat->noVScrollbar) ShowScrollBar(hwnd, SB_VERT, FALSE); else cli.pfnRecalcScrollBar(hwnd, dat); } break; case INTM_RELOADOPTIONS: cli.pfnLoadClcOptions(hwnd, dat, FALSE); cli.pfnSaveStateAndRebuildList(hwnd, dat); break; case WM_THEMECHANGED: cli.pfnInvalidateRect(hwnd, NULL, FALSE); break; case WM_SIZE: cli.pfnEndRename(hwnd, dat, 1); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); cli.pfnRecalcScrollBar(hwnd, dat); { // creating imagelist containing blue line for highlight RECT rc; GetClientRect(hwnd, &rc); if (rc.right == 0) break; rc.bottom = dat->rowHeight; HDC hdc = GetDC(hwnd); int depth = GetDeviceCaps(hdc, BITSPIXEL); if (depth < 16) depth = 16; HBITMAP hBmp = CreateBitmap(rc.right, rc.bottom, 1, depth, NULL); HBITMAP hBmpMask = CreateBitmap(rc.right, rc.bottom, 1, 1, NULL); HDC hdcMem = CreateCompatibleDC(hdc); HBITMAP hoBmp = (HBITMAP)SelectObject(hdcMem, hBmp); HBRUSH hBrush = CreateSolidBrush(dat->useWindowsColours ? GetSysColor(COLOR_HIGHLIGHT) : dat->selBkColour); FillRect(hdcMem, &rc, hBrush); DeleteObject(hBrush); HBITMAP hoMaskBmp = (HBITMAP)SelectObject(hdcMem, hBmpMask); FillRect(hdcMem, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); SelectObject(hdcMem, hoMaskBmp); SelectObject(hdcMem, hoBmp); DeleteDC(hdcMem); ReleaseDC(hwnd, hdc); if (dat->himlHighlight) ImageList_Destroy(dat->himlHighlight); dat->himlHighlight = ImageList_Create(rc.right, rc.bottom, ILC_COLOR32 | ILC_MASK, 1, 1); ImageList_Add(dat->himlHighlight, hBmp, hBmpMask); DeleteObject(hBmpMask); DeleteObject(hBmp); } break; case WM_SYSCOLORCHANGE: SendMessage(hwnd, WM_SIZE, 0, 0); break; case WM_GETDLGCODE: if (lParam) { MSG *msg = (MSG *)lParam; if (msg->message == WM_KEYDOWN) { if (msg->wParam == VK_TAB) return 0; if (msg->wParam == VK_ESCAPE && dat->hwndRenameEdit == NULL && dat->szQuickSearch[0] == 0) return 0; } if (msg->message == WM_CHAR) { if (msg->wParam == '\t') return 0; if (msg->wParam == 27 && dat->hwndRenameEdit == NULL && dat->szQuickSearch[0] == 0) return 0; } } return DLGC_WANTMESSAGE; case WM_KILLFOCUS: KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); case WM_SETFOCUS: case WM_ENABLE: cli.pfnInvalidateRect(hwnd, NULL, FALSE); break; case WM_GETFONT: return (LRESULT)dat->fontInfo[FONTID_CONTACTS].hFont; case INTM_GROUPSCHANGED: { DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *)lParam; if (dbcws->value.type == DBVT_ASCIIZ || dbcws->value.type == DBVT_UTF8) { int groupId = atoi(dbcws->szSetting) + 1; TCHAR szFullName[512]; int i, eq; //check name of group and ignore message if just being expanded/collapsed if (cli.pfnFindItem(hwnd, dat, groupId | HCONTACT_ISGROUP, &contact, &group, NULL)) { mir_tstrcpy(szFullName, contact->szText); while (group->parent) { for (i = 0; i < group->parent->cl.count; i++) if (group->parent->cl.items[i]->group == group) break; if (i == group->parent->cl.count) { szFullName[0] = '\0'; break; } group = group->parent; size_t nameLen = mir_tstrlen(group->cl.items[i]->szText); if (mir_tstrlen(szFullName) + 1 + nameLen > _countof(szFullName)) { szFullName[0] = '\0'; break; } memmove(szFullName + 1 + nameLen, szFullName, sizeof(TCHAR)*(mir_tstrlen(szFullName) + 1)); memcpy(szFullName, group->cl.items[i]->szText, sizeof(TCHAR)*nameLen); szFullName[nameLen] = '\\'; } if (dbcws->value.type == DBVT_ASCIIZ) { WCHAR* wszGrpName = mir_a2u(dbcws->value.pszVal + 1); eq = !mir_tstrcmp(szFullName, wszGrpName); mir_free(wszGrpName); } else { char* szGrpName = NEWSTR_ALLOCA(dbcws->value.pszVal + 1); WCHAR* wszGrpName; Utf8Decode(szGrpName, &wszGrpName); eq = !mir_tstrcmp(szFullName, wszGrpName); mir_free(wszGrpName); } if (eq && (contact->group->hideOffline != 0) == ((dbcws->value.pszVal[0] & GROUPF_HIDEOFFLINE) != 0)) break; //only expanded has changed: no action reqd } } cli.pfnSaveStateAndRebuildList(hwnd, dat); } break; case INTM_NAMEORDERCHANGED: cli.pfnInitAutoRebuild(hwnd); break; case INTM_CONTACTADDED: cli.pfnAddContactToTree(hwnd, dat, wParam, 1, 1); cli.pfnNotifyNewContact(hwnd, wParam); SortClcByTimer(hwnd); break; case INTM_CONTACTDELETED: cli.pfnDeleteItemFromTree(hwnd, wParam); SortClcByTimer(hwnd); break; case INTM_HIDDENCHANGED: { DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *)lParam; if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN) break; if (dbcws->value.type == DBVT_DELETED || dbcws->value.bVal == 0) { if (cli.pfnFindItem(hwnd, dat, wParam, NULL, NULL, NULL)) break; cli.pfnAddContactToTree(hwnd, dat, wParam, 1, 1); cli.pfnNotifyNewContact(hwnd, wParam); } else cli.pfnDeleteItemFromTree(hwnd, wParam); dat->needsResort = 1; SortClcByTimer(hwnd); } break; case INTM_GROUPCHANGED: { WORD iExtraImage[EXTRA_ICON_COUNT]; BYTE flags = 0; if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) memset(iExtraImage, 0xFF, sizeof(iExtraImage)); else { memcpy(iExtraImage, contact->iExtraImage, sizeof(iExtraImage)); flags = contact->flags; } cli.pfnDeleteItemFromTree(hwnd, wParam); if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN || !db_get_b(wParam, "CList", "Hidden", 0)) { NMCLISTCONTROL nm; cli.pfnAddContactToTree(hwnd, dat, wParam, 1, 1); if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) { memcpy(contact->iExtraImage, iExtraImage, sizeof(iExtraImage)); if (flags & CONTACTF_CHECKED) contact->flags |= CONTACTF_CHECKED; } nm.hdr.code = CLN_CONTACTMOVED; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; nm.hItem = (HANDLE)wParam; SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); dat->needsResort = 1; } } SetTimer(hwnd, TIMERID_REBUILDAFTER, 1, NULL); break; case INTM_ICONCHANGED: { int recalcScrollBar = 0, shouldShow; WORD status; MCONTACT hSelItem = NULL; ClcContact *selcontact = NULL; char *szProto = GetContactProto(wParam); if (szProto == NULL) status = ID_STATUS_OFFLINE; else status = db_get_w(wParam, szProto, "Status", ID_STATUS_OFFLINE); // this means an offline msg is flashing, so the contact should be shown DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE); shouldShow = (style & CLS_SHOWHIDDEN || !db_get_b(wParam, "CList", "Hidden", 0)) && (!cli.pfnIsHiddenMode(dat, status) || CallService(MS_CLIST_GETCONTACTICON, wParam, 0) != lParam); contact = NULL; group = NULL; if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, &group, NULL)) { if (shouldShow && CallService(MS_DB_CONTACT_IS, wParam, 0)) { if (dat->selection >= 0 && cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) != -1) hSelItem = (MCONTACT)cli.pfnContactToHItem(selcontact); cli.pfnAddContactToTree(hwnd, dat, wParam, (style & CLS_CONTACTLIST) == 0, 0); recalcScrollBar = 1; cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL); if (contact) { contact->iImage = (WORD)lParam; cli.pfnNotifyNewContact(hwnd, wParam); dat->needsResort = 1; } } } else { // item in list already if (contact->iImage == (WORD)lParam) break; if (!shouldShow && !(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { if (dat->selection >= 0 && cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) != -1) hSelItem = (MCONTACT)cli.pfnContactToHItem(selcontact); cli.pfnRemoveItemFromGroup(hwnd, group, contact, (style & CLS_CONTACTLIST) == 0); recalcScrollBar = 1; } else { contact->iImage = (WORD)lParam; if (!cli.pfnIsHiddenMode(dat, status)) contact->flags |= CONTACTF_ONLINE; else contact->flags &= ~CONTACTF_ONLINE; } dat->needsResort = 1; } if (hSelItem) { ClcGroup *selgroup; if (cli.pfnFindItem(hwnd, dat, hSelItem, &selcontact, &selgroup, NULL)) dat->selection = cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf((SortedList*)&selgroup->cl, selcontact)); else dat->selection = -1; } SortClcByTimer(hwnd); } break; case INTM_NAMECHANGED: if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) break; mir_tstrncpy(contact->szText, cli.pfnGetContactDisplayName(wParam, 0), _countof(contact->szText)); dat->needsResort = 1; SortClcByTimer(hwnd); break; case INTM_PROTOCHANGED: if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) break; contact->proto = GetContactProto(wParam); cli.pfnInvalidateDisplayNameCacheEntry(wParam); mir_tstrncpy(contact->szText, cli.pfnGetContactDisplayName(wParam, 0), _countof(contact->szText)); SortClcByTimer(hwnd); break; case INTM_NOTONLISTCHANGED: if (!cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) break; if (contact->type == CLCIT_CONTACT) { DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *)lParam; if (dbcws->value.type == DBVT_DELETED || dbcws->value.bVal == 0) contact->flags &= ~CONTACTF_NOTONLIST; else contact->flags |= CONTACTF_NOTONLIST; cli.pfnInvalidateRect(hwnd, NULL, FALSE); } break; case INTM_INVALIDATE: cli.pfnInvalidateRect(hwnd, NULL, FALSE); break; case INTM_APPARENTMODECHANGED: if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) { char *szProto = GetContactProto(wParam); if (szProto == NULL) break; WORD apparentMode = db_get_w(wParam, szProto, "ApparentMode", 0); contact->flags &= ~(CONTACTF_INVISTO | CONTACTF_VISTO); if (apparentMode == ID_STATUS_OFFLINE) contact->flags |= CONTACTF_INVISTO; else if (apparentMode == ID_STATUS_ONLINE) contact->flags |= CONTACTF_VISTO; else if (apparentMode) contact->flags |= CONTACTF_VISTO | CONTACTF_INVISTO; cli.pfnInvalidateRect(hwnd, NULL, FALSE); } break; case INTM_SETINFOTIPHOVERTIME: dat->infoTipTimeout = wParam; break; case INTM_IDLECHANGED: if (cli.pfnFindItem(hwnd, dat, wParam, &contact, NULL, NULL)) { char *szProto = GetContactProto(wParam); if (szProto == NULL) break; contact->flags &= ~CONTACTF_IDLE; if (db_get_dw(wParam, szProto, "IdleTS", 0)) contact->flags |= CONTACTF_IDLE; cli.pfnInvalidateRect(hwnd, NULL, FALSE); } break; case WM_PRINTCLIENT: cli.pfnPaintClc(hwnd, dat, (HDC)wParam, NULL); break; case WM_NCPAINT: if (wParam != 1) { POINT ptTopLeft = { 0, 0 }; HRGN hClientRgn; ClientToScreen(hwnd, &ptTopLeft); hClientRgn = CreateRectRgn(0, 0, 1, 1); CombineRgn(hClientRgn, (HRGN)wParam, NULL, RGN_COPY); OffsetRgn(hClientRgn, -ptTopLeft.x, -ptTopLeft.y); InvalidateRgn(hwnd, hClientRgn, FALSE); DeleteObject(hClientRgn); UpdateWindow(hwnd); } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); /* we get so many cli.pfnInvalidateRect()'s that there is no point painting, Windows in theory shouldn't queue up WM_PAINTs in this case but it does so we'll just ignore them */ if (IsWindowVisible(hwnd)) cli.pfnPaintClc(hwnd, dat, hdc, &ps.rcPaint); EndPaint(hwnd, &ps); } break; case WM_VSCROLL: cli.pfnEndRename(hwnd, dat, 1); cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); { int desty = dat->yScroll, noSmooth = 0; RECT clRect; GetClientRect(hwnd, &clRect); switch (LOWORD(wParam)) { case SB_LINEUP: desty -= dat->rowHeight; break; case SB_LINEDOWN: desty += dat->rowHeight; break; case SB_PAGEUP: desty -= clRect.bottom - dat->rowHeight; break; case SB_PAGEDOWN: desty += clRect.bottom - dat->rowHeight; break; case SB_BOTTOM: desty = 0x7FFFFFFF; break; case SB_TOP: desty = 0; break; case SB_THUMBTRACK: desty = HIWORD(wParam); noSmooth = 1; break; //noone has more than 4000 contacts, right? default: return 0; } cli.pfnScrollTo(hwnd, dat, desty, noSmooth); } break; case WM_MOUSEWHEEL: cli.pfnEndRename(hwnd, dat, 1); cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); { UINT scrollLines; if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, FALSE)) scrollLines = 3; cli.pfnScrollTo(hwnd, dat, dat->yScroll - (short)HIWORD(wParam) * dat->rowHeight * (signed)scrollLines / WHEEL_DELTA, 0); } return 0; case WM_KEYDOWN: { int selMoved = 0; int changeGroupExpand = 0; int pageSize; cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); if (CallService(MS_CLIST_MENUPROCESSHOTKEY, wParam, MPCF_CONTACTMENU)) break; { RECT clRect; GetClientRect(hwnd, &clRect); pageSize = clRect.bottom / dat->rowHeight; } switch (wParam) { case VK_DOWN: dat->selection++; selMoved = 1; break; case VK_UP: dat->selection--; selMoved = 1; break; case VK_PRIOR: dat->selection -= pageSize; selMoved = 1; break; case VK_NEXT: dat->selection += pageSize; selMoved = 1; break; case VK_HOME: dat->selection = 0; selMoved = 1; break; case VK_END: dat->selection = cli.pfnGetGroupContentsCount(&dat->list, 1) - 1; selMoved = 1; break; case VK_LEFT: changeGroupExpand = 1; break; case VK_RIGHT: changeGroupExpand = 2; break; case VK_RETURN: cli.pfnDoSelectionDefaultAction(hwnd, dat); dat->szQuickSearch[0] = 0; if (dat->filterSearch) cli.pfnSaveStateAndRebuildList(hwnd, dat); return 0; case VK_F2: cli.pfnBeginRenameSelection(hwnd, dat); return 0; case VK_DELETE: cli.pfnDeleteFromContactList(hwnd, dat); return 0; default: { NMKEY nmkey; nmkey.hdr.hwndFrom = hwnd; nmkey.hdr.idFrom = GetDlgCtrlID(hwnd); nmkey.hdr.code = NM_KEYDOWN; nmkey.nVKey = wParam; nmkey.uFlags = HIWORD(lParam); if (SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nmkey)) return 0; } } if (changeGroupExpand) { if (!dat->filterSearch) dat->szQuickSearch[0] = 0; hit = cli.pfnGetRowByIndex(dat, dat->selection, &contact, &group); if (hit != -1) { if (changeGroupExpand == 1 && contact->type == CLCIT_CONTACT) { if (group == &dat->list) return 0; dat->selection = cli.pfnGetRowsPriorTo(&dat->list, group, -1); selMoved = 1; } else { if (contact->type == CLCIT_GROUP) cli.pfnSetGroupExpand(hwnd, dat, contact->group, changeGroupExpand == 2); return 0; } } else return 0; } if (selMoved) { if (!dat->filterSearch) dat->szQuickSearch[0] = 0; if (dat->selection >= cli.pfnGetGroupContentsCount(&dat->list, 1)) dat->selection = cli.pfnGetGroupContentsCount(&dat->list, 1) - 1; if (dat->selection < 0) dat->selection = 0; cli.pfnInvalidateRect(hwnd, NULL, FALSE); cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); UpdateWindow(hwnd); return 0; } } break; case WM_CHAR: cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); if (wParam == 27) //escape dat->szQuickSearch[0] = 0; else if (wParam == '\b' && dat->szQuickSearch[0]) dat->szQuickSearch[mir_tstrlen(dat->szQuickSearch) - 1] = '\0'; else if (wParam < ' ') break; else if (wParam == ' ' && dat->szQuickSearch[0] == '\0' && GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CHECKBOXES) { NMCLISTCONTROL nm; if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1) break; if (contact->type != CLCIT_CONTACT) break; contact->flags ^= CONTACTF_CHECKED; if (contact->type == CLCIT_GROUP) cli.pfnSetGroupChildCheckboxes(contact->group, contact->flags & CONTACTF_CHECKED); cli.pfnRecalculateGroupCheckboxes(hwnd, dat); cli.pfnInvalidateRect(hwnd, NULL, FALSE); nm.hdr.code = CLN_CHECKCHANGED; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); } else { TCHAR szNew[2]; szNew[0] = (TCHAR)wParam; szNew[1] = '\0'; if (mir_tstrlen(dat->szQuickSearch) >= _countof(dat->szQuickSearch) - 1) { MessageBeep(MB_OK); break; } mir_tstrcat(dat->szQuickSearch, szNew); } if (dat->filterSearch) cli.pfnSaveStateAndRebuildList(hwnd, dat); if (dat->szQuickSearch[0]) { int index; index = cli.pfnFindRowByText(hwnd, dat, dat->szQuickSearch, 1); if (index != -1) dat->selection = index; else { MessageBeep(MB_OK); dat->szQuickSearch[mir_tstrlen(dat->szQuickSearch) - 1] = '\0'; cli.pfnSaveStateAndRebuildList(hwnd, dat); } cli.pfnInvalidateRect(hwnd, NULL, FALSE); cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); } else cli.pfnInvalidateRect(hwnd, NULL, FALSE); break; case WM_SYSKEYDOWN: cli.pfnEndRename(hwnd, dat, 1); cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); dat->iHotTrack = -1; cli.pfnInvalidateRect(hwnd, NULL, FALSE); ReleaseCapture(); if (wParam == VK_F10 && GetKeyState(VK_SHIFT) & 0x8000) break; SendMessage(GetParent(hwnd), msg, wParam, lParam); return 0; case WM_TIMER: switch (wParam) { case TIMERID_RENAME: cli.pfnBeginRenameSelection(hwnd, dat); break; case TIMERID_DRAGAUTOSCROLL: cli.pfnScrollTo(hwnd, dat, dat->yScroll + dat->dragAutoScrolling * dat->rowHeight * 2, 0); break; case TIMERID_INFOTIP: { CLCINFOTIP it; RECT clRect; POINT ptClientOffset = { 0 }; KillTimer(hwnd, wParam); GetCursorPos(&it.ptCursor); ScreenToClient(hwnd, &it.ptCursor); if (it.ptCursor.x != dat->ptInfoTip.x || it.ptCursor.y != dat->ptInfoTip.y) break; GetClientRect(hwnd, &clRect); it.rcItem.left = 0; it.rcItem.right = clRect.right; hit = cli.pfnHitTest(hwnd, dat, it.ptCursor.x, it.ptCursor.y, &contact, NULL, NULL); if (hit == -1) break; if (contact->type != CLCIT_GROUP && contact->type != CLCIT_CONTACT) break; ClientToScreen(hwnd, &it.ptCursor); ClientToScreen(hwnd, &ptClientOffset); it.isTreeFocused = GetFocus() == hwnd; it.rcItem.top = cli.pfnGetRowTopY(dat, hit) - dat->yScroll; it.rcItem.bottom = it.rcItem.top + cli.pfnGetRowHeight(dat, hit); OffsetRect(&it.rcItem, ptClientOffset.x, ptClientOffset.y); it.isGroup = contact->type == CLCIT_GROUP; it.hItem = (contact->type == CLCIT_GROUP) ? (HANDLE)contact->groupId : (HANDLE)contact->hContact; it.cbSize = sizeof(it); dat->hInfoTipItem = cli.pfnContactToHItem(contact); NotifyEventHooks(hShowInfoTipEvent, 0, (LPARAM)& it); break; } case TIMERID_REBUILDAFTER: KillTimer(hwnd, TIMERID_REBUILDAFTER); cli.pfnInvalidateRect(hwnd, NULL, FALSE); cli.pfnSaveStateAndRebuildList(hwnd, dat); break; case TIMERID_DELAYEDRESORTCLC: KillTimer(hwnd, TIMERID_DELAYEDRESORTCLC); cli.pfnInvalidateRect(hwnd, NULL, FALSE); cli.pfnSortCLC(hwnd, dat, 1); cli.pfnRecalcScrollBar(hwnd, dat); break; } break; case WM_MBUTTONDOWN: case WM_LBUTTONDOWN: if (GetFocus() != hwnd) SetFocus(hwnd); cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_INFOTIP); KillTimer(hwnd, TIMERID_RENAME); cli.pfnEndRename(hwnd, dat, 1); dat->ptDragStart.x = (short)LOWORD(lParam); dat->ptDragStart.y = (short)HIWORD(lParam); if (!dat->filterSearch) dat->szQuickSearch[0] = 0; hit = cli.pfnHitTest(hwnd, dat, (short)LOWORD(lParam), (short)HIWORD(lParam), &contact, &group, &hitFlags); if (hit != -1) { if (hit == dat->selection && hitFlags & CLCHT_ONITEMLABEL && dat->exStyle & CLS_EX_EDITLABELS) { SetCapture(hwnd); dat->iDragItem = dat->selection; dat->dragStage = DRAGSTAGE_NOTMOVED | DRAGSTAGEF_MAYBERENAME; dat->dragAutoScrolling = 0; break; } } if (hit != -1 && contact->type == CLCIT_GROUP) { if (hitFlags & CLCHT_ONITEMICON) { ClcGroup *selgroup; ClcContact *selcontact; dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, &selgroup); cli.pfnSetGroupExpand(hwnd, dat, contact->group, -1); if (dat->selection != -1) { dat->selection = cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf((SortedList*)&selgroup->cl, selcontact)); if (dat->selection == -1) dat->selection = cli.pfnGetRowsPriorTo(&dat->list, contact->group, -1); } cli.pfnInvalidateRect(hwnd, NULL, FALSE); UpdateWindow(hwnd); break; } } if (hit != -1 && hitFlags & CLCHT_ONITEMCHECK) { NMCLISTCONTROL nm; contact->flags ^= CONTACTF_CHECKED; if (contact->type == CLCIT_GROUP) cli.pfnSetGroupChildCheckboxes(contact->group, contact->flags & CONTACTF_CHECKED); cli.pfnRecalculateGroupCheckboxes(hwnd, dat); cli.pfnInvalidateRect(hwnd, NULL, FALSE); nm.hdr.code = CLN_CHECKCHANGED; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&nm); } if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL | CLCHT_ONITEMCHECK))) { NMCLISTCONTROL nm; nm.hdr.code = NM_CLICK; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; if (hit == -1) nm.hItem = NULL; else nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); nm.iColumn = hitFlags & CLCHT_ONITEMEXTRA ? HIBYTE(HIWORD(hitFlags)) : -1; nm.pt = dat->ptDragStart; SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); } if (hitFlags & (CLCHT_ONITEMCHECK | CLCHT_ONITEMEXTRA)) break; dat->selection = hit; cli.pfnInvalidateRect(hwnd, NULL, FALSE); if (dat->selection != -1) cli.pfnEnsureVisible(hwnd, dat, hit, 0); UpdateWindow(hwnd); if (dat->selection != -1 && (contact->type == CLCIT_CONTACT || contact->type == CLCIT_GROUP) && !(hitFlags & (CLCHT_ONITEMEXTRA | CLCHT_ONITEMCHECK))) { SetCapture(hwnd); dat->iDragItem = dat->selection; dat->dragStage = DRAGSTAGE_NOTMOVED; dat->dragAutoScrolling = 0; } break; case WM_MOUSEMOVE: if (dat->iDragItem == -1) { int iOldHotTrack = dat->iHotTrack; if (dat->hwndRenameEdit != NULL) break; if (GetKeyState(VK_MENU) & 0x8000 || GetKeyState(VK_F10) & 0x8000) break; dat->iHotTrack = cli.pfnHitTest(hwnd, dat, (short)LOWORD(lParam), (short)HIWORD(lParam), NULL, NULL, NULL); if (iOldHotTrack != dat->iHotTrack) { if (iOldHotTrack == -1) SetCapture(hwnd); else if (dat->iHotTrack == -1) ReleaseCapture(); if (dat->exStyle & CLS_EX_TRACKSELECT) { cli.pfnInvalidateItem(hwnd, dat, iOldHotTrack); cli.pfnInvalidateItem(hwnd, dat, dat->iHotTrack); } cli.pfnHideInfoTip(hwnd, dat); } KillTimer(hwnd, TIMERID_INFOTIP); if (wParam == 0 && dat->hInfoTipItem == NULL) { dat->ptInfoTip.x = (short)LOWORD(lParam); dat->ptInfoTip.y = (short)HIWORD(lParam); SetTimer(hwnd, TIMERID_INFOTIP, dat->infoTipTimeout, NULL); } break; } if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_NOTMOVED && !(dat->exStyle & CLS_EX_DISABLEDRAGDROP)) { if (abs((short)LOWORD(lParam) - dat->ptDragStart.x) >= GetSystemMetrics(SM_CXDRAG) || abs((short)HIWORD(lParam) - dat->ptDragStart.y) >= GetSystemMetrics(SM_CYDRAG)) dat->dragStage = (dat->dragStage & ~DRAGSTAGEM_STAGE) | DRAGSTAGE_ACTIVE; } if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_ACTIVE) { HCURSOR hNewCursor; RECT clRect; POINT pt; int target; GetClientRect(hwnd, &clRect); pt.x = (short)LOWORD(lParam); pt.y = (short)HIWORD(lParam); hNewCursor = LoadCursor(NULL, IDC_NO); cli.pfnInvalidateRect(hwnd, NULL, FALSE); if (dat->dragAutoScrolling) { KillTimer(hwnd, TIMERID_DRAGAUTOSCROLL); dat->dragAutoScrolling = 0; } target = cli.pfnGetDropTargetInformation(hwnd, dat, pt); if (dat->dragStage & DRAGSTAGEF_OUTSIDE && target != DROPTARGET_OUTSIDE) { cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); NMCLISTCONTROL nm; nm.hdr.code = CLN_DRAGSTOP; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); dat->dragStage &= ~DRAGSTAGEF_OUTSIDE; } switch (target) { case DROPTARGET_ONSELF: case DROPTARGET_ONCONTACT: break; case DROPTARGET_ONGROUP: hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)); break; case DROPTARGET_INSERTION: hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)); break; case DROPTARGET_OUTSIDE: { NMCLISTCONTROL nm; if (pt.x >= 0 && pt.x < clRect.right && ((pt.y < 0 && pt.y > -dat->dragAutoScrollHeight) || (pt.y >= clRect.bottom && pt.y < clRect.bottom + dat->dragAutoScrollHeight))) { if (!dat->dragAutoScrolling) { if (pt.y < 0) dat->dragAutoScrolling = -1; else dat->dragAutoScrolling = 1; SetTimer(hwnd, TIMERID_DRAGAUTOSCROLL, dat->scrollTime, NULL); } SendMessage(hwnd, WM_TIMER, TIMERID_DRAGAUTOSCROLL, 0); } dat->dragStage |= DRAGSTAGEF_OUTSIDE; cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); nm.hdr.code = CLN_DRAGGING; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); nm.pt = pt; if (SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm)) return 0; } break; default: cli.pfnGetRowByIndex(dat, dat->iDragItem, NULL, &group); if (group->parent) hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)); break; } SetCursor(hNewCursor); } break; case WM_LBUTTONUP: if (dat->iDragItem == -1) break; SetCursor((HCURSOR)GetClassLongPtr(hwnd, GCLP_HCURSOR)); if (dat->exStyle & CLS_EX_TRACKSELECT) { dat->iHotTrack = cli.pfnHitTest(hwnd, dat, (short)LOWORD(lParam), (short)HIWORD(lParam), NULL, NULL, NULL); if (dat->iHotTrack == -1) ReleaseCapture(); } else ReleaseCapture(); KillTimer(hwnd, TIMERID_DRAGAUTOSCROLL); if (dat->dragStage == (DRAGSTAGE_NOTMOVED | DRAGSTAGEF_MAYBERENAME)) SetTimer(hwnd, TIMERID_RENAME, GetDoubleClickTime(), NULL); else if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_ACTIVE) { POINT pt = { LOWORD(lParam), HIWORD(lParam) }; int target = cli.pfnGetDropTargetInformation(hwnd, dat, pt); switch (target) { case DROPTARGET_ONSELF: case DROPTARGET_ONCONTACT: break; case DROPTARGET_ONGROUP: { ClcContact *contactn, *contacto; cli.pfnGetRowByIndex(dat, dat->selection, &contactn, NULL); cli.pfnGetRowByIndex(dat, dat->iDragItem, &contacto, NULL); if (contacto->type == CLCIT_CONTACT) //dropee is a contact CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contacto->hContact, contactn->groupId); else if (contacto->type == CLCIT_GROUP) { //dropee is a group TCHAR szNewName[120]; mir_sntprintf(szNewName, _countof(szNewName), _T("%s\\%s"), cli.pfnGetGroupName(contactn->groupId, NULL), contacto->szText); cli.pfnRenameGroup(contacto->groupId, szNewName); } } break; case DROPTARGET_INSERTION: cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); { ClcContact *destcontact; ClcGroup *destgroup; if (cli.pfnGetRowByIndex(dat, dat->iInsertionMark, &destcontact, &destgroup) == -1 || destgroup != contact->group->parent) CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, 0); else { if (destcontact->type == CLCIT_GROUP) destgroup = destcontact->group; CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, destgroup->groupId); } } break; case DROPTARGET_OUTSIDE: cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL); { NMCLISTCONTROL nm; nm.hdr.code = CLN_DROPPED; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.flags = 0; nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags); nm.pt = pt; SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); } break; default: cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, &group); if (!group->parent) break; if (contact->type == CLCIT_GROUP) { //dropee is a group TCHAR szNewName[120]; mir_tstrncpy(szNewName, contact->szText, _countof(szNewName)); cli.pfnRenameGroup(contact->groupId, szNewName); } else if (contact->type == CLCIT_CONTACT) //dropee is a contact CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contact->hContact, 0); } } cli.pfnInvalidateRect(hwnd, NULL, FALSE); dat->iDragItem = -1; dat->iInsertionMark = -1; break; case WM_LBUTTONDBLCLK: ReleaseCapture(); dat->iHotTrack = -1; cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_RENAME); KillTimer(hwnd, TIMERID_INFOTIP); dat->selection = cli.pfnHitTest(hwnd, dat, (short)LOWORD(lParam), (short)HIWORD(lParam), &contact, NULL, &hitFlags); cli.pfnInvalidateRect(hwnd, NULL, FALSE); if (dat->selection != -1) cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL))) break; UpdateWindow(hwnd); cli.pfnDoSelectionDefaultAction(hwnd, dat); dat->szQuickSearch[0] = 0; if (dat->filterSearch) cli.pfnSaveStateAndRebuildList(hwnd, dat); break; case WM_CONTEXTMENU: cli.pfnEndRename(hwnd, dat, 1); cli.pfnHideInfoTip(hwnd, dat); KillTimer(hwnd, TIMERID_RENAME); KillTimer(hwnd, TIMERID_INFOTIP); if (GetFocus() != hwnd) SetFocus(hwnd); dat->iHotTrack = -1; if (!dat->filterSearch) dat->szQuickSearch[0] = 0; { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; if (pt.x == -1 && pt.y == -1) { dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL); if (dat->selection != -1) cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); pt.x = dat->iconXSpace + 15; pt.y = cli.pfnGetRowTopY(dat, dat->selection) - dat->yScroll + (int)(cli.pfnGetRowHeight(dat, dat->selection) * .7); hitFlags = (dat->selection == -1) ? CLCHT_NOWHERE : CLCHT_ONITEMLABEL; } else { ScreenToClient(hwnd, &pt); dat->selection = cli.pfnHitTest(hwnd, dat, pt.x, pt.y, &contact, NULL, &hitFlags); } cli.pfnInvalidateRect(hwnd, NULL, FALSE); if (dat->selection != -1) cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0); UpdateWindow(hwnd); if (dat->selection != -1 && hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMCHECK | CLCHT_ONITEMLABEL)) { HMENU hMenu; if (contact->type == CLCIT_GROUP) hMenu = cli.pfnBuildGroupPopupMenu(contact->group); else if (contact->type == CLCIT_CONTACT) hMenu = Menu_BuildContactMenu(contact->hContact); else return 0; ClientToScreen(hwnd, &pt); TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL); DestroyMenu(hMenu); } else //call parent for new group/hide offline menu SendMessage(GetParent(hwnd), WM_CONTEXTMENU, wParam, lParam); } return 0; case WM_MEASUREITEM: return Menu_MeasureItem((LPMEASUREITEMSTRUCT)lParam); case WM_DRAWITEM: return Menu_DrawItem((LPDRAWITEMSTRUCT)lParam); case WM_COMMAND: hit = cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL); if (hit == -1) break; if (contact->type == CLCIT_CONTACT) if (CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_CONTACTMENU), (LPARAM)contact->hContact)) break; switch (LOWORD(wParam)) { case POPUP_NEWSUBGROUP: if (contact->type != CLCIT_GROUP) break; SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS); CallService(MS_CLIST_GROUPCREATE, contact->groupId, 0); break; case POPUP_RENAMEGROUP: cli.pfnBeginRenameSelection(hwnd, dat); break; case POPUP_DELETEGROUP: if (contact->type != CLCIT_GROUP) break; CallService(MS_CLIST_GROUPDELETE, contact->groupId, 0); break; case POPUP_GROUPHIDEOFFLINE: if (contact->type != CLCIT_GROUP) break; CallService(MS_CLIST_GROUPSETFLAGS, contact->groupId, MAKELPARAM(contact->group->hideOffline ? 0 : GROUPF_HIDEOFFLINE, GROUPF_HIDEOFFLINE)); break; } break; case WM_DESTROY: cli.pfnHideInfoTip(hwnd, dat); for (int i = 0; i <= FONTID_MAX; i++) if (!dat->fontInfo[i].changed) DeleteObject(dat->fontInfo[i].hFont); if (dat->himlHighlight) ImageList_Destroy(dat->himlHighlight); if (dat->hwndRenameEdit) DestroyWindow(dat->hwndRenameEdit); if (dat->hBmpBackground) DeleteObject(dat->hBmpBackground); cli.pfnFreeGroup(&dat->list); mir_free(dat); cli.pfnUnregisterFileDropping(hwnd); WindowList_Remove(hClcWindowList, hwnd); } return DefWindowProc(hwnd, msg, wParam, lParam); }