29 мая 2006

Pessimization is evil

Многие знают, что ранняя оптимизация - корень всех бед. Но и явная пессимизация - тоже большое зло. Зачем писать явно неоптимально, когда можно написать нормально? Правильно, незачем.

Классический пример:
for
(
iterator It(begin()), End(end());
It != End;
++It
)
{
...
}
Здесь:
1. Использование для инициализации копирующего конструктора вместо присваивания. При написании iterator It = begin() компилятор может создать временный объект, хотя может и соптимизировать.

2. Префиксная форма инкремента. Постфиксный инкремент как правило реализуется через префиксный и временную переменную:
iterator operator++(int)
{
iterator _Tmp = *this;
++*this;
return (_Tmp);
}
В таком случае получаем создание временных объектов, которыми мы не пользуемся. Так что лучше сразу написать префиксную форму.

3. Сохранение вычисленного end(). Если написать условие в виде It != end(), то end() может вычисляться при каждой итерации заново. Поэтому лучше его прокэшировать.

Сеегодня нашел еще одну явную пессимизацию, которую я, оказывается, всегда использовал (WinAPI specific):
DC.FillSolidRect(..., WindowColor);
...
DC.SetBkMode(TRANSPARENT);
DC.SetTextColor(TextColor);
DC.DrawText(...);
То есть заполнял фон, затем писал на нем текст, используя "прозрачный" цвет фона при отрисовке текста.

А теперь подумаем о том, что сейчас у большинства пользователей Windows включен тот стандартный или ClearType антиалиасинг шрифтов. При такой отрисовке многие выводимые пиксели надписи должны отрисовываться с учетом их предыдущего цвета. DrawText() не знает, что мы до этого всю область окна зарисовали каким-то одним цветом. И при отрисовке текста в режиме TRANSPARENT будет считывать то, что уже отрисовано, накладывать на него текст... А ведь можно от этих мучений легко избавиться:
DC.FillSolidRect(..., WindowColor);
...
DC.SetBkMode(OPAQUE);
DC.SetBkColor(WindowColor);
DC.SetTextColor(TextColor);
DC.DrawText(...);

25 мая 2006

C# beats C++

Забавный текст в update history програмки Launchy:
This is the first C++ release. As I became frustrated with C#’s large .NET framework requirements and users lack of desire to install it, I decided to switch back to the faster language. This release is functionally and visually equivalent to the 0.5 C# release. The C# CVS version had some new features that I will soon add to the C++ branch.

Вобщем, Welcome back! ;)

Auto-sized editbox

При создании grid-а понадобилось автоматически расширять редактируемую ячейку (стандартный editbox) по высоте в зависимости от содержимого. A-la редактирование таблиц в Microsoft Word.

Загвоздка вся в том, что editbox - стандартный Windows control, в исходники которого залезть нельзя, а функциональность у него весьма ограниченная.

Задача такая: при изменении содержимого editbox-а установить соответствующую его высоту, чтобы весь текст был виден на экране.

Что имеется у стандартного edit-а:
1. Область format rectangle, которая задает область редактирования. Как правило, чуть меньше чем client area. При изменении размера edit-а, эта format rectangle ставится как ей угодно. (Нет чтобы сделать format margins, которые не меняются...)
2. Сообщение EM_GETLINECOUNT - можно узнать текущее количество строк в контроле.
3. Уведомления EN_UPDATE и EN_CHANGE - можно отслеживать изменения содержимого контрола. EN_UPDATE приходит до прорисовки, EN_CHANGE - после.

При реализации сразу начались проблемы.

Проблема 1. Не приходит уведомление EN_UPDATE. Решилось просто:
EditBox.SendMessage(EM_SETEVENTMASK, 0, ENM_UPDATE);

Проблема 2. После изменения размера контрола происходит изменение format rectangle, приходится возвращать его на место:
EditBox.GetClientRect(ClientRect);
EditBox.SendMessage(EM_SETRECTNP, 0, (LPARAM)(&ClientRect));


Проблема 3. Самая большая проблема. Суть ее такова: необходимая нам высота client area равна КоличествоСтрок * ВысотаСтроки. Количество строк мы получить можем, а вот как высоту строки от edit control-а получить? Долго пытался использовать разумные значения, но никак не выходило правильной отрисовки во всех случаях. Перекопал весь Google.Groups, советов много, но ни один точно не соответсвует тому, как рисует текст editbox.

Магическая формула оказалась такая:
ТребуемаяВысота = КоличествоСтрок * TEXTMETRIC.tmHeight + 2

Нет, представляете - "+ 2". Что это за magic number я не знаю, но его я нашел где-то в ньюсгруппах.

Итого получилось:
void AdjustEditSize()
{
int nLines = (int) EditBox.SendMessage(EM_GETLINECOUNT);
// if (nLines == PrevLines) return;

CRect ClientRect, WinRect;
EditBox.GetClientRect(ClientRect);
EditBox.GetWindowRect(WinRect);
EditBox.SendMessage(EM_GETRECT, 0, (LPARAM)(&FormatRect));

CClientDC DC(EditBox);
DC.SelectFont((HFONT) EditBox.SendMessage(WM_GETFONT));

TEXTMETRIC tm;
DC.GetTextMetrics(&tm);
int LineHeight = tm.tmHeight;

MapWindowPoints(HWND_DESKTOP, EditBox.GetParent(), (LPPOINT)&WinRect, 2);
WinRect.bottom = WinRect.top
+ (WinRect.Height() - ClientRect.Height()) // adding non-client area
+ nLines * LineHeight + 2; // what da hell is "2" here?
EditBox.MoveWindow(WinRect);

// restore fucking format margins. set format rect = client area (i.e. margins=0)
EditBox.GetClientRect(ClientRect);
EditBox.SendMessage(EM_SETRECTNP, 0, (LPARAM)(&ClientRect));
}

15 мая 2006

throw()

Забавная дискуссия получилась по поводу спецификатора throw().

Начали с throw, закончили спором на счет оптимизации.

10 мая 2006

Scintilla

Копаясь в исходниках TortoiseSVN обнаружил хороший edit control, да еще с очень правильной лицензией.

PS. Ложка дегтя.

09 мая 2006

BitBlt does clipping. It does it bad.

Цитата из MSDN: "BitBlt does clipping on the destination DC".
Так вот при этом clipping-е BitBlt забывает про viewport, и режет как будто его нет.
Пришлось извратиться:
void RenderBuffer(HDC PaintDC, const RECT& SourceRect, const POINT& Destination)
{
// BitBlt does clipping on the destination DC.
// But BitBlt's clipping know nothing about view ports :(
// So, we need to set viewport to (0,0)
// and handle original viewport manually when calling BitBlt
POINT OriginalViewport;
::SetViewportOrgEx(PaintDC, 0, 0, &OriginalViewport);

// perform rendering
::BitBlt(PaintDC,
SourceRect.left,
SourceRect.top,
SourceRect.right - SourceRect.left,
SourceRect.bottom - SourceRect.top,
BufferDC,
Destination.x + OriginalViewport.x,
Destination.y + OriginalViewport.y,
SRCCOPY);

// restore original viewport
::SetViewportOrgEx(PaintDC, OriginalViewport.x, OriginalViewport.y, NULL);
}

06 мая 2006

WTL revisited

Вчера я было разочаровался в WTL. Причины были следующие:
1. У него нет класса CIcon. Пришлось хранить HICON и прибивать его (DeleteObject()) собственноручно в деструкторе объекта.
2. У класса CDC нет метода ReleaseDC(). Пришлось делать руками, через WinAPI:
int LogPixelsY;
{
CDCHandle ScreenDC(::GetDC(NULL));
LogPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
::ReleaseDC(NULL, ScreenDC.Detach());
}

Сегодня почесав репу и пошерстив Гугл понял, что остал от жизни - вышел уже WTL 7.5, а у меня все 7.1 стоит. Скачал, поставил.

1. CIcon появился.
2. Понял, что CDC не предназначен для ReleaseDC(). Для него есть CClientDC:
int LogPixelsY;
{
CClientDC ScreenDC(NULL);
LogPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
}

Жизнь налаживается...

05 мая 2006

Имя файла и его расширение

Почему-то boost::filesystem::path не понимает wide chars :(
Пришлось писать свои шаблоны:

template <typename StringType>
typename StringType::const_iterator FindFileName(const StringType& FilePath)
{
typename StringType::const_reverse_iterator rend(FilePath.rend()), rit(FilePath.rbegin());
for (; rit != rend; ++rit)
{
switch (*rit)
{
case '/':
case '\\':
return rit.base();
}
}

return FilePath.begin();
}

template <typename StringType>
typename StringType::const_iterator FindFileExtension(const StringType& FilePath)
{
typename StringType::const_reverse_iterator rend(FilePath.rend()), rit(FilePath.rbegin());
for (; rit != rend; ++rit)
{
switch (*rit)
{
case '/':
case '\\':
return FilePath.end();

case '.':
return rit.base();
}
}

return FilePath.end();
}

template <typename StringType>
StringType ExtractFileName(const StringType& FilePath)
{
return StringType(FindFileName(FilePath), FilePath.end());
}

template <typename StringType>
StringType ExtractFileExtension(const StringType& FilePath)
{
return StringType(FindFileExtension(FilePath), FilePath.end());
}

Использование:

std::wstring Path = L"c:\\foo\\bar.xml";
std::wcout << L"file: " << ExtractFileName(Path) << L", ext: " << ExtractFileExtension(Path) << std::endl;


Используемо с basic_string, vector и другими контейнерами.
Понимает как char, так и wchar_t.