示例#1
0
COLORREF AdjustLightness(COLORREF c, float factor)
{
    BYTE R = GetRValueSafe(c), G = GetGValueSafe(c), B = GetBValueSafe(c);
    // cf. http://en.wikipedia.org/wiki/HSV_color_space#Hue_and_chroma
    BYTE M = max(max(R, G), B), m = min(min(R, G), B);
    if (M == m) {
        // for grayscale values, lightness is proportional to the color value
        BYTE X = (BYTE)limitValue((int)floorf(M * factor + 0.5f), 0, 255);
        return RGB(X, X, X);
    }
    BYTE C = M - m;
    BYTE Ha = (BYTE)abs(M == R ? G - B : M == G ? B - R : R - G);
    // cf. http://en.wikipedia.org/wiki/HSV_color_space#Lightness
    float L2 = (float)(M + m);
    // cf. http://en.wikipedia.org/wiki/HSV_color_space#Saturation
    float S = C / (L2 > 255.0f ? 510.0f - L2 : L2);

    L2 = limitValue(L2 * factor, 0.0f, 510.0f);
    // cf. http://en.wikipedia.org/wiki/HSV_color_space#From_HSL
    float C1 = (L2 > 255.0f ? 510.0f - L2 : L2) * S;
    float X1 = C1 * Ha / C;
    float m1 = (L2 - C1) / 2;
    R = (BYTE)floorf((M == R ? C1 : m != R ? X1 : 0) + m1 + 0.5f);
    G = (BYTE)floorf((M == G ? C1 : m != G ? X1 : 0) + m1 + 0.5f);
    B = (BYTE)floorf((M == B ? C1 : m != B ? X1 : 0) + m1 + 0.5f);
    return RGB(R, G, B);
}
    virtual void update(int inKey) {
      // TODO: AT_ASSERT mValuePtr != 0... 
      T & valRef = *mValuePtr;

      switch(inKey) {
      case 45: /*-*/
      case KEY_LEFT: /*<-*/
	if (mStepMode == StepModeT::LINEAR) {
	  valRef -= mSteps;
	  valRef = limitValue(valRef);
	  if (mUpdateFunc) {
	    mUpdateFunc(& valRef);
	  }
	} else if (mStepMode == StepModeT::FACTOR) {
	  if (mSteps) {
	    valRef /= mSteps;
	    valRef = limitValue(valRef);
	    if (mUpdateFunc) {
	      mUpdateFunc(& valRef);
	    }
	  }
	} else {
	  // Not implemented -> TODO: AT_ASSERT
	}
	break;
      
      case 43: /*+*/
      case KEY_RIGHT: /*+*/
	if (mStepMode == StepModeT::LINEAR) {
	  valRef += mSteps;
	  valRef = limitValue(valRef);
	  if (mUpdateFunc) {
	    mUpdateFunc(& valRef);
	  }
	} else if (mStepMode == StepModeT::FACTOR) {
	  valRef *= mSteps;
	  valRef = limitValue(valRef);
	  if (mUpdateFunc) {
	    mUpdateFunc(& valRef);
	  }
	} else {
	  // Not implemented -> TODO: AT_ASSERT
	}
	break;
      case 27: /*ESC*/ {
	if (mAbortFunc) {
	  mAbortFunc();
	}
      }
      default: /* nothing */
	break;
      }
    }
示例#3
0
static void ApplyPrintSettings(const WCHAR *settings, int pageCount, Vec<PRINTPAGERANGE> &ranges,
                               Print_Advanced_Data &advanced, LPDEVMODE devMode) {
    WStrVec rangeList;
    if (settings)
        rangeList.Split(settings, L",", true);

    for (size_t i = 0; i < rangeList.Count(); i++) {
        int val;
        PRINTPAGERANGE pr;
        if (str::Parse(rangeList.At(i), L"%d-%d%$", &pr.nFromPage, &pr.nToPage)) {
            pr.nFromPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount);
            pr.nToPage = limitValue(pr.nToPage, (DWORD)1, (DWORD)pageCount);
            ranges.Append(pr);
        } else if (str::Parse(rangeList.At(i), L"%d%$", &pr.nFromPage)) {
            pr.nFromPage = pr.nToPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount);
            ranges.Append(pr);
        } else if (str::EqI(rangeList.At(i), L"even"))
            advanced.range = PrintRangeEven;
        else if (str::EqI(rangeList.At(i), L"odd"))
            advanced.range = PrintRangeOdd;
        else if (str::EqI(rangeList.At(i), L"noscale"))
            advanced.scale = PrintScaleNone;
        else if (str::EqI(rangeList.At(i), L"shrink"))
            advanced.scale = PrintScaleShrink;
        else if (str::EqI(rangeList.At(i), L"fit"))
            advanced.scale = PrintScaleFit;
        else if (str::Parse(rangeList.At(i), L"%dx%$", &val) && 0 < val && val < 1000)
            devMode->dmCopies = (short)val;
        else if (str::EqI(rangeList.At(i), L"simplex"))
            devMode->dmDuplex = DMDUP_SIMPLEX;
        else if (str::EqI(rangeList.At(i), L"duplex") || str::EqI(rangeList.At(i), L"duplexlong"))
            devMode->dmDuplex = DMDUP_VERTICAL;
        else if (str::EqI(rangeList.At(i), L"duplexshort"))
            devMode->dmDuplex = DMDUP_HORIZONTAL;
        else if (str::EqI(rangeList.At(i), L"color"))
            devMode->dmColor = DMCOLOR_COLOR;
        else if (str::EqI(rangeList.At(i), L"monochrome"))
            devMode->dmColor = DMCOLOR_MONOCHROME;
        else if (str::StartsWithI(rangeList.At(i), L"bin="))
            devMode->dmDefaultSource = GetPaperSourceByName(rangeList.At(i) + 4, devMode);
        else if (str::StartsWithI(rangeList.At(i), L"paper="))
            devMode->dmPaperSize = GetPaperByName(rangeList.At(i) + 6);
    }

    if (ranges.Count() == 0) {
        PRINTPAGERANGE pr = { 1, (DWORD)pageCount };
        ranges.Append(pr);
    }
}
示例#4
0
// Adjusts lightness by 1/255 units.
COLORREF AdjustLightness2(COLORREF c, float units) {
    float lightness = GetLightness(c);
    units = limitValue(units, -lightness, 255.0f - lightness);
    if (0.0f == lightness)
        return RGB(BYTE(units + 0.5f), BYTE(units + 0.5f), BYTE(units + 0.5f));
    return AdjustLightness(c, 1.0f + units / lightness);
}
示例#5
0
void HtmlFormatter::HandleTagFont(HtmlToken* t) {
    if (t->IsEndTag()) {
        RevertStyleChange();
        return;
    }

    AttrInfo* attr = t->GetAttrByName("face");
    const WCHAR* faceName = CurrFont()->GetName();
    if (attr) {
        size_t strLen = str::Utf8ToWcharBuf(attr->val, attr->valLen, buf, dimof(buf));
        // multiple font names can be comma separated
        if (strLen > 0 && *buf != ',') {
            str::TransChars(buf, L",", L"\0");
            faceName = buf;
        }
    }

    float fontSize = CurrFont()->GetSize();
    attr = t->GetAttrByName("size");
    if (attr) {
        // the sizes are in the range from 1 (tiny) to 7 (huge)
        int size = 3; // normal size
        str::Parse(attr->val, attr->valLen, "%d", &size);
        // sizes can also be relative to the current size
        if (attr->valLen > 0 && ('-' == *attr->val || '+' == *attr->val))
            size += 3;
        size = limitValue(size, 1, 7);
        float scale = pow(1.2f, size - 3);
        fontSize = defaultFontSize * scale;
    }

    SetFont(faceName, (FontStyle)CurrFont()->GetStyle(), fontSize);
}
示例#6
0
 bool FLIConverter::setProgressPercentage(int n)
 {
   limitValue(n, 0, 100);
   if (n != prvProgressPercentage) {
     prvProgressPercentage = n;
     return progressPercentageCallback(progressPercentageUserData, n);
   }
   return true;
 }
示例#7
0
void ChmModel::SetZoomVirtual(float zoom, PointI *fixPt)
{
    if (zoom > 0)
        zoom = limitValue(zoom, ZOOM_MIN, ZOOM_MAX);
    if (zoom <= 0 || !IsValidZoom(zoom))
        zoom = 100.0f;
    ZoomTo(zoom);
    initZoom = zoom;
}
示例#8
0
static void ApplyPrintSettings(const WCHAR *settings, int pageCount, Vec<PRINTPAGERANGE>& ranges, Print_Advanced_Data& advanced, LPDEVMODE devMode)
{
    WStrVec rangeList;
    if (settings)
        rangeList.Split(settings, L",", true);

    for (size_t i = 0; i < rangeList.Count(); i++) {
        int val;
        PRINTPAGERANGE pr;
        if (str::Parse(rangeList.At(i), L"%d-%d%$", &pr.nFromPage, &pr.nToPage)) {
            pr.nFromPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount);
            pr.nToPage = limitValue(pr.nToPage, (DWORD)1, (DWORD)pageCount);
            ranges.Append(pr);
        }
        else if (str::Parse(rangeList.At(i), L"%d%$", &pr.nFromPage)) {
            pr.nFromPage = pr.nToPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount);
            ranges.Append(pr);
        }
        else if (str::Eq(rangeList.At(i), L"even"))
            advanced.range = PrintRangeEven;
        else if (str::Eq(rangeList.At(i), L"odd"))
            advanced.range = PrintRangeOdd;
        else if (str::Eq(rangeList.At(i), L"noscale"))
            advanced.scale = PrintScaleNone;
        else if (str::Eq(rangeList.At(i), L"shrink"))
            advanced.scale = PrintScaleShrink;
        else if (str::Eq(rangeList.At(i), L"fit"))
            advanced.scale = PrintScaleFit;
        else if (str::Eq(rangeList.At(i), L"compat"))
            advanced.asImage = true;
        else if (str::Parse(rangeList.At(i), L"%dx%$", &val) && 0 < val && val < 1000)
            devMode->dmCopies = (short)val;
        else if (str::Eq(rangeList.At(i), L"duplex") || str::Eq(rangeList.At(i), L"duplexlong"))
            devMode->dmDuplex = DMDUP_VERTICAL;
        else if (str::Eq(rangeList.At(i), L"duplexshort"))
            devMode->dmDuplex = DMDUP_HORIZONTAL;
    }

    if (ranges.Count() == 0) {
        PRINTPAGERANGE pr = { 1, pageCount };
        ranges.Append(pr);
    }
}
void NotificationWnd::UpdateProgress(int current, int total)
{
    CrashIf(total <= 0);
    if (total <= 0)
        total = 1;
    progress = limitValue(100 * current / total, 0, 100);
    if (hasProgress && progressMsg) {
        ScopedMem<WCHAR> message(str::Format(progressMsg, current, total));
        UpdateMessage(message);
    }
}
示例#10
0
static void ApplyPrintSettings(const WCHAR *settings, int pageCount, Vec<PRINTPAGERANGE>& ranges, Print_Advanced_Data& advanced)
{
    WStrVec rangeList;
    if (settings)
        rangeList.Split(settings, L",", true);

    for (size_t i = 0; i < rangeList.Count(); i++) {
        PRINTPAGERANGE pr;
        if (str::Parse(rangeList.At(i), L"%d-%d%$", &pr.nFromPage, &pr.nToPage)) {
            pr.nFromPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount);
            pr.nToPage = limitValue(pr.nToPage, (DWORD)1, (DWORD)pageCount);
            ranges.Append(pr);
        }
        else if (str::Parse(rangeList.At(i), L"%d%$", &pr.nFromPage)) {
            pr.nFromPage = pr.nToPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount);
            ranges.Append(pr);
        }
        else if (str::Eq(rangeList.At(i), L"even"))
            advanced.range = PrintRangeEven;
        else if (str::Eq(rangeList.At(i), L"odd"))
            advanced.range = PrintRangeOdd;
        else if (str::Eq(rangeList.At(i), L"noscale"))
            advanced.scale = PrintScaleNone;
        else if (str::Eq(rangeList.At(i), L"shrink"))
            advanced.scale = PrintScaleShrink;
        else if (str::Eq(rangeList.At(i), L"fit"))
            advanced.scale = PrintScaleFit;
        else if (str::Eq(rangeList.At(i), L"compat"))
            advanced.asImage = true;
    }

    if (ranges.Count() == 0) {
        PRINTPAGERANGE pr = { 1, pageCount };
        ranges.Append(pr);
    }
}
示例#11
0
static float GetZoomComboBoxValue(HWND hDlg, UINT idComboBox, bool forChm, float defaultZoom)
{
    float newZoom = defaultZoom;

    int idx = ComboBox_GetCurSel(GetDlgItem(hDlg, idComboBox));
    if (idx == -1) {
        ScopedMem<WCHAR> customZoom(win::GetText(GetDlgItem(hDlg, idComboBox)));
        float zoom = (float)_wtof(customZoom);
        if (zoom > 0)
            newZoom = limitValue(zoom, ZOOM_MIN, ZOOM_MAX);
    } else {
        if (forChm)
            idx += 7;

        if (0 != gItemZoom[idx])
            newZoom = gItemZoom[idx];
    }

    return newZoom;
}
示例#12
0
PaperFormat GetPaperFormat(SizeD size)
{
    SizeD sizeP = size.dx < size.dy ? size : SizeD(size.dy, size.dx);
    // common ISO 216 formats (metric)
    if (limitValue(sizeP.dx, 8.26, 8.28) == sizeP.dx && limitValue(sizeP.dy, 11.68, 11.70) == sizeP.dy)
        return Paper_A4;
    if (limitValue(sizeP.dx, 11.68, 11.70) == sizeP.dx && limitValue(sizeP.dy, 16.53, 16.55) == sizeP.dy)
        return Paper_A3;
    if (limitValue(sizeP.dx, 5.82, 5.85) == sizeP.dx && limitValue(sizeP.dy, 8.26, 8.28) == sizeP.dy)
        return Paper_A5;
    // common US/ANSI formats (imperial)
    if (limitValue(sizeP.dx, 8.49, 8.51) == sizeP.dx && limitValue(sizeP.dy, 10.99, 11.01) == sizeP.dy)
        return Paper_Letter;
    if (limitValue(sizeP.dx, 8.49, 8.51) == sizeP.dx && limitValue(sizeP.dy, 13.99, 14.01) == sizeP.dy)
        return Paper_Legal;
    if (limitValue(sizeP.dx, 10.99, 11.01) == sizeP.dx && limitValue(sizeP.dy, 16.99, 17.01) == sizeP.dy)
        return Paper_Tabloid;
    if (limitValue(sizeP.dx, 5.49, 5.51) == sizeP.dx && limitValue(sizeP.dy, 8.49, 8.51) == sizeP.dy)
        return Paper_Statement;
    return Paper_Other;
}
示例#13
0
// format page size according to locale (e.g. "29.7 x 21.0 cm" or "11.69 x 8.27 in")
// Caller needs to free the result
static WCHAR *FormatPageSize(BaseEngine *engine, int pageNo, int rotation)
{
    RectD mediabox = engine->PageMediabox(pageNo);
    SizeD size = engine->Transform(mediabox, pageNo, 1.0f / engine->GetFileDPI(), rotation).Size();

    const WCHAR *formatName = L"";
    SizeD sizeP = size.dx < size.dy ? size : SizeD(size.dy, size.dx);
    // common ISO 216 formats (metric)
    if (limitValue(sizeP.dx, 8.26, 8.28) == sizeP.dx && limitValue(sizeP.dy, 11.68, 11.70) == sizeP.dy)
        formatName = L" (A4)";
    else if (limitValue(sizeP.dx, 11.68, 11.70) == sizeP.dx && limitValue(sizeP.dy, 16.53, 16.55) == sizeP.dy)
        formatName = L" (A3)";
    else if (limitValue(sizeP.dx, 5.82, 5.85) == sizeP.dx && limitValue(sizeP.dy, 8.26, 8.28) == sizeP.dy)
        formatName = L" (A5)";
    // common US/ANSI formats (imperial)
    else if (limitValue(sizeP.dx, 8.49, 8.51) == sizeP.dx && limitValue(sizeP.dy, 10.99, 11.01) == sizeP.dy)
        formatName = L" (Letter)";
    else if (limitValue(sizeP.dx, 8.49, 8.51) == sizeP.dx && limitValue(sizeP.dy, 13.99, 14.01) == sizeP.dy)
        formatName = L" (Legal)";
    else if (limitValue(sizeP.dx, 10.99, 11.01) == sizeP.dx && limitValue(sizeP.dy, 16.99, 17.01) == sizeP.dy)
        formatName = L" (Tabloid)";

    WCHAR unitSystem[2];
    GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, unitSystem, dimof(unitSystem));
    bool isMetric = unitSystem[0] == '0';
    double unitsPerInch = isMetric ? 2.54 : 1.0;
    const WCHAR *unit = isMetric ? L"cm" : L"in";

    double width = size.dx * unitsPerInch;
    double height = size.dy * unitsPerInch;
    if (((int)(width * 100)) % 100 == 99)
        width += 0.01;
    if (((int)(height * 100)) % 100 == 99)
        height += 0.01;

    ScopedMem<WCHAR> strWidth(str::FormatFloatWithThousandSep(width));
    ScopedMem<WCHAR> strHeight(str::FormatFloatWithThousandSep(height));

    return str::Format(L"%s x %s %s%s", strWidth, strHeight, unit, formatName);
}
示例#14
0
void DrawStartPage(WindowInfo& win, HDC hdc, FileHistory& fileHistory, COLORREF colorRange[2])
{
    HPEN penBorder = CreatePen(PS_SOLID, DOCLIST_SEPARATOR_DY, WIN_COL_BLACK);
    HPEN penThumbBorder = CreatePen(PS_SOLID, DOCLIST_THUMBNAIL_BORDER_W, WIN_COL_BLACK);
    HPEN penLinkLine = CreatePen(PS_SOLID, 1, COL_BLUE_LINK);

    ScopedFont fontSumatraTxt(GetSimpleFont(hdc, L"MS Shell Dlg", 24));
    ScopedFont fontLeftTxt(GetSimpleFont(hdc, L"MS Shell Dlg", 14));

    HGDIOBJ origFont = SelectObject(hdc, fontSumatraTxt); /* Just to remember the orig font */

    ClientRect rc(win.hwndCanvas);
    FillRect(hdc, &rc.ToRECT(), gBrushLogoBg);

    SelectObject(hdc, gBrushLogoBg);
    SelectObject(hdc, penBorder);

    bool isRtl = IsUIRightToLeft();

    /* render title */
    RectI titleBox = RectI(PointI(0, 0), CalcSumatraVersionSize(hdc));
    titleBox.x = rc.dx - titleBox.dx - 3;
    DrawSumatraVersion(hdc, titleBox);
    PaintLine(hdc, RectI(0, titleBox.dy, rc.dx, 0));

    /* render recent files list */
    SelectObject(hdc, penThumbBorder);
    SetBkMode(hdc, TRANSPARENT);
    SetTextColor(hdc, WIN_COL_BLACK);

    rc.y += titleBox.dy;
    rc.dy -= titleBox.dy;
    FillRect(hdc, &rc.ToRECT(), gBrushAboutBg);
    rc.dy -= DOCLIST_BOTTOM_BOX_DY;

    Vec<DisplayState *> list;
    fileHistory.GetFrequencyOrder(list);

    int width = limitValue((rc.dx - DOCLIST_MARGIN_LEFT - DOCLIST_MARGIN_RIGHT + DOCLIST_MARGIN_BETWEEN_X) / (THUMBNAIL_DX + DOCLIST_MARGIN_BETWEEN_X), 1, DOCLIST_MAX_THUMBNAILS_X);
    int height = min((rc.dy - DOCLIST_MARGIN_TOP - DOCLIST_MARGIN_BOTTOM + DOCLIST_MARGIN_BETWEEN_Y) / (THUMBNAIL_DY + DOCLIST_MARGIN_BETWEEN_Y), FILE_HISTORY_MAX_FREQUENT / width);
    PointI offset(rc.x + DOCLIST_MARGIN_LEFT + (rc.dx - width * THUMBNAIL_DX - (width - 1) * DOCLIST_MARGIN_BETWEEN_X - DOCLIST_MARGIN_LEFT - DOCLIST_MARGIN_RIGHT) / 2, rc.y + DOCLIST_MARGIN_TOP);
    if (offset.x < ABOUT_INNER_PADDING)
        offset.x = ABOUT_INNER_PADDING;
    else if (list.Count() == 0)
        offset.x = DOCLIST_MARGIN_LEFT;

    SelectObject(hdc, fontSumatraTxt);
    SIZE txtSize;
    const WCHAR *txt = _TR("Frequently Read");
    GetTextExtentPoint32(hdc, txt, (int)str::Len(txt), &txtSize);
    RectI headerRect(offset.x, rc.y + (DOCLIST_MARGIN_TOP - txtSize.cy) / 2, txtSize.cx, txtSize.cy);
    if (isRtl)
        headerRect.x = rc.dx - offset.x - headerRect.dx;
    DrawText(hdc, txt, -1, &headerRect.ToRECT(), (isRtl ? DT_RTLREADING : DT_LEFT) | DT_NOPREFIX);

    SelectObject(hdc, fontLeftTxt);
    SelectObject(hdc, GetStockBrush(NULL_BRUSH));

    win.staticLinks.Reset();
    for (int h = 0; h < height; h++) {
        for (int w = 0; w < width; w++) {
            if (h * width + w >= (int)list.Count()) {
                // display the "Open a document" link right below the last row
                height = w > 0 ? h + 1 : h;
                break;
            }
            DisplayState *state = list.At(h * width + w);

            RectI page(offset.x + w * (int)(THUMBNAIL_DX + DOCLIST_MARGIN_BETWEEN_X * win.uiDPIFactor),
                       offset.y + h * (int)(THUMBNAIL_DY + DOCLIST_MARGIN_BETWEEN_Y * win.uiDPIFactor),
                       THUMBNAIL_DX, THUMBNAIL_DY);
            if (isRtl)
                page.x = rc.dx - page.x - page.dx;
            bool loadOk = true;
            if (!state->thumbnail)
                loadOk = LoadThumbnail(*state);
            if (loadOk && state->thumbnail) {
                SizeI thumbSize = state->thumbnail->Size();
                if (thumbSize.dx != THUMBNAIL_DX || thumbSize.dy != THUMBNAIL_DY) {
                    page.dy = thumbSize.dy * THUMBNAIL_DX / thumbSize.dx;
                    page.y += THUMBNAIL_DY - page.dy;
                }
                HRGN clip = CreateRoundRectRgn(page.x, page.y, page.x + page.dx, page.y + page.dy, 10, 10);
                SelectClipRgn(hdc, clip);
                RenderedBitmap *clone = state->thumbnail->Clone();
                UpdateBitmapColorRange(clone->GetBitmap(), colorRange);
                clone->StretchDIBits(hdc, page);
                SelectClipRgn(hdc, NULL);
                DeleteObject(clip);
                delete clone;
            }
            RoundRect(hdc, page.x, page.y, page.x + page.dx, page.y + page.dy, 10, 10);

            int iconSpace = (int)(20 * win.uiDPIFactor);
            RectI rect(page.x + iconSpace, page.y + page.dy + 3, page.dx - iconSpace, iconSpace);
            if (isRtl)
                rect.x -= iconSpace;
            DrawText(hdc, path::GetBaseName(state->filePath), -1, &rect.ToRECT(), DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX | (isRtl ? DT_RIGHT : DT_LEFT));

            SHFILEINFO sfi;
            HIMAGELIST himl = (HIMAGELIST)SHGetFileInfo(state->filePath, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES);
            ImageList_Draw(himl, sfi.iIcon, hdc,
                           isRtl ? page.x + page.dx - (int)(16 * win.uiDPIFactor) : page.x,
                           rect.y, ILD_TRANSPARENT);

            win.staticLinks.Append(StaticLinkInfo(rect.Union(page), state->filePath, state->filePath));
        }
    }

    /* render bottom links */
    rc.y += DOCLIST_MARGIN_TOP + height * THUMBNAIL_DY + (height - 1) * DOCLIST_MARGIN_BETWEEN_Y + DOCLIST_MARGIN_BOTTOM;
    rc.dy = DOCLIST_BOTTOM_BOX_DY;

    SetTextColor(hdc, COL_BLUE_LINK);
    SelectObject(hdc, penLinkLine);

    HIMAGELIST himl = (HIMAGELIST)SendMessage(win.hwndToolbar, TB_GETIMAGELIST, 0, 0);
    RectI rectIcon(offset.x, rc.y, 0, 0);
    ImageList_GetIconSize(himl, &rectIcon.dx, &rectIcon.dy);
    rectIcon.y += (rc.dy - rectIcon.dy) / 2;
    if (isRtl)
        rectIcon.x = rc.dx - offset.x - rectIcon.dx;
    ImageList_Draw(himl, 0 /* index of Open icon */, hdc, rectIcon.x, rectIcon.y, ILD_NORMAL);

    txt = _TR("Open a document...");
    GetTextExtentPoint32(hdc, txt, (int)str::Len(txt), &txtSize);
    RectI rect(offset.x + rectIcon.dx + 3, rc.y + (rc.dy - txtSize.cy) / 2, txtSize.cx, txtSize.cy);
    if (isRtl)
        rect.x = rectIcon.x - rect.dx - 3;
    DrawText(hdc, txt, -1, &rect.ToRECT(), isRtl ? DT_RTLREADING : DT_LEFT);
    PaintLine(hdc, RectI(rect.x, rect.y + rect.dy, rect.dx, 0));
    // make the click target larger
    rect = rect.Union(rectIcon);
    rect.Inflate(10, 10);
    win.staticLinks.Append(StaticLinkInfo(rect, SLINK_OPEN_FILE));

    rect = DrawBottomRightLink(win.hwndCanvas, hdc, _TR("Hide frequently read"));
    win.staticLinks.Append(StaticLinkInfo(rect, SLINK_LIST_HIDE));

    SelectObject(hdc, origFont);

    DeleteObject(penBorder);
    DeleteObject(penThumbBorder);
    DeleteObject(penLinkLine);
}