Example #1
0
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;
}
Example #2
0
/** 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, "&lt;"); break;
            case '>': fprintf(f, "&gt;"); break;
            case '"': fprintf(f, "&quot;"); break;
            case '&': fprintf(f, "&amp;"); break;
            default:
                if(Utf8Decode(string, &code, &bytes))
                {
                    fprintf(f, "&#x%x;", code);
                    string += bytes - 1;
                }
                else
                {
                    fputc(*string, f);
                }
                break;

        }

        string++;
    }
}
Example #3
0
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; 
}
Example #4
0
/**
 * 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;
}
Example #5
0
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; 
}
Example #6
0
/**
 * 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';
}
Example #7
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';
}
Example #8
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');
}
Example #9
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';
}
Example #10
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
}
Example #11
0
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;
}
Example #12
0
/**
 * 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;
}
Example #13
0
/**
 * 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';
}
Example #14
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);
}
Example #15
0
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;
}
Example #16
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;
}
Example #18
0
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);
}