/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *  RawTherapee is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  RawTherapee is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "thumbbrowserentrybase.h"

#include "options.h"
#include "thumbbrowserbase.h"

#include "../rtengine/mytime.h"

namespace {

Glib::ustring getPaddedName(const Glib::ustring &name)
{
    enum class State { OTHER, NUMBER };

    constexpr unsigned int pad_width = 16;

    Glib::ustring res;

    State state = State::OTHER;
    Glib::ustring number;

    for (auto c : name) {
        switch (state) {
        case State::OTHER: {
            switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9': {
                number += c;

                state = State::NUMBER;
                break;
            }

            default: {
                res += c;
                break;
            }
            }
            break;
        }

        case State::NUMBER: {
            switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9': {
                number += c;
                break;
            }

            default: {
                if (number.size() < pad_width) {
                    res.append(pad_width - number.size(), '0');
                }
                res += number;
                res += c;
                number.clear();

                state = State::OTHER;
                break;
            }
            }
            break;
        }
        }
    }

    switch (state) {
    case State::OTHER: {
        break;
    }

    case State::NUMBER: {
        if (number.size() < pad_width) {
            res.append(pad_width - number.size(), '0');
        }
        res += number;
        break;
    }
    }

    return res;
}

} // namespace

ThumbBrowserEntryBase::ThumbBrowserEntryBase(const Glib::ustring &fname)
    : fnlabw(0), fnlabh(0), dtlabw(0), dtlabh(0), exlabw(0), exlabh(0), prew(0),
      preh(0), prex(0), prey(0), upperMargin(6), borderWidth(1), textGap(6),
      sideMargin(8), lowerMargin(8), preview(),
      dispname(Glib::path_get_basename(fname)), buttonSet(nullptr), width(0),
      height(0), exp_width(0), exp_height(0), startx(0), starty(0), ofsX(0),
      ofsY(0), redrawRequests(0), parent(nullptr), original(nullptr),
      bbSelected(false), bbFramed(false), bbPreview(nullptr),
      cursor_type(CSUndefined),
      collate_name(getPaddedName(dispname).casefold_collate_key()),
      thumbnail(nullptr), filename(fname), selected(false), drawable(false),
      filtered(false), framed(false), processing(false), italicstyle(false),
      edited(false), recentlysaved(false), updatepriority(false),
      open_in_editor(0),
      withFilename(WFNAME_NONE)
{
}

ThumbBrowserEntryBase::~ThumbBrowserEntryBase() {}

void ThumbBrowserEntryBase::addButtonSet(LWButtonSet *bs)
{
    buttonSet.reset(bs);
}


int ThumbBrowserEntryBase::getPreviewHeight() const
{
    return preh * parent->getThumbDisplayScale();
}

void ThumbBrowserEntryBase::updateBackBuffer()
{
    if (!parent) {
        return;
    }

    Gtk::Widget *w = parent->getDrawingArea();
    int scale = RTScalable::getDisplayScale(w);
    int scaled_width = exp_width * scale;
    int scaled_height = exp_height * scale;

    if (backBuffer && (backBuffer->getWidth() != scaled_width ||
                       backBuffer->getHeight() != scaled_height)) {
        // deleting the existing BackBuffer
        backBuffer.reset();
    }
    if (!backBuffer) {
        backBuffer = Glib::RefPtr<BackBuffer>(
            new BackBuffer(scaled_width, scaled_height));
    }

    // If thumbnail is hidden by a filter, drawing to it will crash
    // if either width or height is zero then return early
    if (!backBuffer->getWidth() || !backBuffer->getHeight()) {
        return;
    }

    Cairo::RefPtr<Cairo::ImageSurface> surface = backBuffer->getSurface();
    RTSurface::setDisplayScale(surface, scale);

    bbSelected = selected;
    bbFramed = framed || (open_in_editor > 0);
    bbPreview = !preview.empty() ? &preview[0] : nullptr;

    Cairo::RefPtr<Cairo::Context> cc = Cairo::Context::create(surface);

    Glib::RefPtr<Gtk::StyleContext> style = parent->getStyle();
    Gdk::RGBA textn = parent->getNormalTextColor();
    Gdk::RGBA texts = parent->getSelectedTextColor();
    Gdk::RGBA bgn = parent->getNormalBgColor();
    Gdk::RGBA bgs = parent->getSelectedBgColor();
    Gdk::RGBA bgp = parent->getPrelightBgColor();
    Gdk::RGBA hl = parent->getHighlightColor();

    // clear area, draw frames and background
    style->render_background(cc, 0., 0., exp_width, exp_height);

    cc->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);

    drawFrame(cc, selected ? bgs : bgp, hl,
              selected && options.highlight_selected_thumbnails);

    // calculate height of button set
    int bsHeight = 0;

    if (buttonSet) {
        int tmp;
        buttonSet->getAllocatedDimensions(tmp, bsHeight);
    }

    int infow, infoh;
    getTextSizes(infow, infoh);

    // draw preview frame
    // backBuffer->draw_rectangle (cc, false, (exp_width-prew)/2,
    // upperMargin+bsHeight, prew+1, preh+1);
    // draw thumbnail image
    if (!preview.empty()) {
        //assert(preview.size() == size_t(prew * 3 * preh));
        prex = borderWidth + (exp_width - prew) / 2;
        int hh = exp_height -
                 (upperMargin + bsHeight + borderWidth + infoh + lowerMargin);
        prey =
            upperMargin + bsHeight + borderWidth + std::max((hh - preh) / 2, 0);
        int thumb_scale = parent->getThumbDisplayScale();
        int tw = prew * thumb_scale;
        int th = preh * thumb_scale;
        preview_bb_.setDrawRectangle(Cairo::FORMAT_RGB24, 0, 0, tw, th);
        RTScalable::setDisplayScale(preview_bb_.getSurface(), 1);
        preview_bb_.copyRGBCharData(&preview[0], 0, 0, tw, th, tw * 3, 0, 0);
        RTScalable::setDisplayScale(preview_bb_.getSurface(), thumb_scale);
        preview_bb_.setDestPosition(prex, prey);
        preview_bb_.copySurface(surface);
        // if (scale > 1) {
        //     if (preview_bb_ && (preview_bb_->getWidth() != prew ||
        //                         preview_bb_->getHeight() != preh)) {
        //         preview_bb_.reset();
        //     }
        //     if (!preview_bb_) {
        //         preview_bb_ =
        //             Glib::RefPtr<BackBuffer>(new BackBuffer(prew, preh));
        //     }
        //     preview_bb_->copyRGBCharData(&preview[0], 0, 0, prew, preh,
        //                                  prew * 3, 0, 0);
        //     preview_bb_->setDestPosition(prex, prey);
        //     preview_bb_->copySurface(surface);
        // } else {
        //     backBuffer->copyRGBCharData(&preview[0], 0, 0, prew, preh, prew * 3,
        //                                 prex, prey);
        // }
    }

    customBackBufferUpdate(cc);

    // draw icons onto the thumbnail area
    bbIcons = getIconsOnImageArea();
    bbSpecificityIcons = getSpecificityIconsOnImageArea();

    int iofs_x = 4, iofs_y = 4;
    int istartx = prex;
    int istarty = prey;

    if ((parent->getLocation() != ThumbBrowserBase::THLOC_EDITOR &&
         options.showFileNames && options.overlayedFileNames) ||
        (parent->getLocation() == ThumbBrowserBase::THLOC_EDITOR &&
         options.filmStripShowFileNames &&
         options.filmStripOverlayedFileNames)) {
        cc->begin_new_path();
        cc->rectangle(istartx, istarty, prew,
                      fnlabh + dtlabh + exlabh + 2 * iofs_y);

        if ((texts.get_red() + texts.get_green() + texts.get_blue()) / 3 >
            0.5) {
            cc->set_source_rgba(0, 0, 0, 0.5);
        } else {
            cc->set_source_rgba(1, 1, 1, 0.5);
        }

        cc->fill();
    }

    istartx += iofs_x;
    istarty += iofs_y;

    if (!bbIcons.empty()) {
        int igap = 2;
        int iwidth = 0;
        int iheight = 0;

        for (size_t i = 0; i < bbIcons.size(); i++) {
            iwidth += bbIcons[i]->getWidth() + (i > 0 ? igap : 0);

            if (bbIcons[i]->getHeight() > iheight) {
                iheight = bbIcons[i]->getHeight();
            }
        }

        if ((parent->getLocation() != ThumbBrowserBase::THLOC_EDITOR &&
             (!options.showFileNames || !options.overlayedFileNames)) ||
            (parent->getLocation() == ThumbBrowserBase::THLOC_EDITOR &&
             (!options.filmStripShowFileNames ||
              !options.filmStripOverlayedFileNames))) {
            // Draw the transparent black background around icons
            cc->begin_new_path();
            cc->move_to(istartx - igap, istarty);
            cc->rel_line_to(igap, -igap);
            cc->rel_line_to(iwidth, 0);
            cc->rel_line_to(igap, igap);
            cc->rel_line_to(0, iheight);
            cc->rel_line_to(-igap, igap);
            cc->rel_line_to(-iwidth, 0);
            cc->rel_line_to(-igap, -igap);
            cc->rel_line_to(0, -iheight);
            cc->set_source_rgba(0, 0, 0, 0.6);
            cc->fill();
        }

        for (size_t i = 0; i < bbIcons.size(); i++) {
            // Draw the image at 110, 90, except for the outermost 10 pixels.
            cc->set_source(bbIcons[i]->surface, istartx, istarty);
            cc->rectangle(istartx, istarty, bbIcons[i]->getWidth(),
                          bbIcons[i]->getHeight());
            cc->fill();
            istartx += bbIcons[i]->getWidth() + igap;
        }
    }

    if (!bbSpecificityIcons.empty()) {
        int igap = 2;
        int istartx2 = prex + prew - 1 + igap;
        int istarty2 = prey + preh - igap - 1;

        for (size_t i = 0; i < bbSpecificityIcons.size(); ++i) {
            istartx2 -= bbSpecificityIcons[i]->getWidth() - igap;
            cc->set_source(
                bbSpecificityIcons[i]->surface, istartx2,
                istarty2 - bbSpecificityIcons[i]->getHeight());
            cc->rectangle(istartx2,
                          istarty2 - bbSpecificityIcons[i]->getHeight(),
                          bbSpecificityIcons[i]->getWidth(),
                          bbSpecificityIcons[i]->getHeight());
            cc->fill();
        }
    }

    if (((parent->getLocation() != ThumbBrowserBase::THLOC_EDITOR &&
          options.showFileNames) ||
         (parent->getLocation() == ThumbBrowserBase::THLOC_EDITOR &&
          options.filmStripShowFileNames)) &&
        withFilename > WFNAME_NONE) {
        int textposx_fn, textposx_ex, textposx_dt, textposy, textw;

        if (!((parent->getLocation() != ThumbBrowserBase::THLOC_EDITOR &&
               options.overlayedFileNames) ||
              (parent->getLocation() == ThumbBrowserBase::THLOC_EDITOR &&
               options.filmStripOverlayedFileNames))) {
            textposx_fn = exp_width / 2 - fnlabw / 2;

            if (textposx_fn < 0) {
                textposx_fn = 0;
            }

            textposx_ex = exp_width / 2 - exlabw / 2;

            if (textposx_ex < 0) {
                textposx_ex = 0;
            }

            textposx_dt = exp_width / 2 - dtlabw / 2;

            if (textposx_dt < 0) {
                textposx_dt = 0;
            }

            textposy = exp_height - lowerMargin -
                       infoh; // upperMargin + bsHeight + 2 * borderWidth + preh
                              // + borderWidth + textGap;
            textw = exp_width - 2 * textGap;

            if (selected) {
                cc->set_source_rgb(texts.get_red(), texts.get_green(),
                                   texts.get_blue());
            } else {
                cc->set_source_rgb(textn.get_red(), textn.get_green(),
                                   textn.get_blue());
            }
        } else {
            textposx_fn = istartx;
            textposx_ex = istartx;
            textposx_dt = istartx;
            textposy = istarty;
            textw = prew - (istartx - prex);
            cc->set_source_rgb(texts.get_red(), texts.get_green(),
                               texts.get_blue());
        }

        // draw file name
        Glib::RefPtr<Pango::Context> context = w->get_pango_context();
        Pango::FontDescription fontd = context->get_font_description();
        fontd.set_weight(Pango::WEIGHT_BOLD);

        if (italicstyle) {
            fontd.set_style(Pango::STYLE_ITALIC);
        } else {
            fontd.set_style(Pango::STYLE_NORMAL);
        }

        context->set_font_description(fontd);
        Glib::RefPtr<Pango::Layout> fn = w->create_pango_layout(dispname);
        fn->set_width(textw * Pango::SCALE);
        fn->set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
        cc->move_to(textposx_fn, textposy);
        fn->add_to_cairo_context(cc);
        cc->fill();

        fontd.set_weight(Pango::WEIGHT_NORMAL);
        fontd.set_style(Pango::STYLE_NORMAL);
        context->set_font_description(fontd);

        if (withFilename == WFNAME_FULL) {
            // draw date/time label
            int tpos = fnlabh;

            if (options.fbShowDateTime && datetimeline != "") {
                fn = w->create_pango_layout(datetimeline);
                fn->set_width(textw * Pango::SCALE);
                fn->set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
                cc->move_to(textposx_dt, textposy + tpos);
                fn->add_to_cairo_context(cc);
                cc->fill();
                tpos += dtlabh;
            }

            // draw basic exif info
            if (options.fbShowBasicExif && exifline != "") {
                fn = w->create_pango_layout(exifline);
                fn->set_width(textw * Pango::SCALE);
                fn->set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
                cc->move_to(textposx_ex, textposy + tpos);
                fn->add_to_cairo_context(cc);
                cc->fill();
            }
        }
    }

    if (selected) {
        constexpr int radius = 4;
        cc->set_source_rgb(hl.get_red(), hl.get_green(), hl.get_blue());
        const auto r = 2.5 * radius * RTScalable::getPseudoHiDPIScale();
        constexpr double gap = 1.5;
        cc->move_to(exp_width - gap, exp_height - gap - r);
        cc->line_to(exp_width - gap, exp_height - gap);
        cc->line_to(exp_width - gap - r, exp_height - gap);
        cc->fill_preserve();
    }

    backBuffer->setDirty(false);
}

void ThumbBrowserEntryBase::getTextSizes(int &infow, int &infoh)
{

    if (!parent) {
        return;
    }

    Gtk::Widget *w = parent->getDrawingArea();

    // calculate dimensions of the text based fields

    Glib::RefPtr<Pango::Context> context = w->get_pango_context();
    context->set_font_description(w->get_style_context()->get_font());

    // filename:
    Pango::FontDescription fontd = context->get_font_description();
    fontd.set_weight(Pango::WEIGHT_BOLD);
    context->set_font_description(fontd);
    Glib::RefPtr<Pango::Layout> fn = w->create_pango_layout(dispname);
    fn->get_pixel_size(fnlabw, fnlabh);

    // calculate cummulated height of all info fields
    infoh = fnlabh;
    infow = 0;

    if (withFilename == WFNAME_FULL) {
        // datetime
        fontd.set_weight(Pango::WEIGHT_NORMAL);
        context->set_font_description(fontd);
        fn = w->create_pango_layout(datetimeline);
        fn->get_pixel_size(dtlabw, dtlabh);

        // basic exif data
        fn = w->create_pango_layout(exifline);
        fn->get_pixel_size(exlabw, exlabh);

        // add date/tile size:
        if (options.fbShowDateTime) {
            infoh += dtlabh;

            if (dtlabw + 2 * sideMargin > infow) {
                infow = dtlabw + 2 * sideMargin;
            }
        } else {
            dtlabw = dtlabh = 0;
        }

        if (options.fbShowBasicExif) {
            infoh += exlabh;

            if (exlabw + 2 * sideMargin > infow) {
                infow = exlabw + 2 * sideMargin;
            }
        } else {
            exlabw = exlabh = 0;
        }
    } else {
        dtlabw = dtlabh = exlabw = exlabh = 0;
    }
}

void ThumbBrowserEntryBase::resize(int h)
{
    MYWRITERLOCK(l, lockRW);

    height = h;
    int old_preh = preh;

    // dimensions of the button set
    int bsw = 0, bsh = 0;

    if (buttonSet) {
        buttonSet->getMinimalDimensions(bsw, bsh);
    }

    if (parent->getLocation() == ThumbBrowserBase::THLOC_FILEBROWSER) {
        if (options.showFileNames) {
            withFilename = WFNAME_FULL;
        } else {
            withFilename = WFNAME_NONE;
        }
    } else if (parent->getLocation() == ThumbBrowserBase::THLOC_BATCHQUEUE) {
        withFilename = WFNAME_REDUCED;
    } else {
        if (options.filmStripShowFileNames) {
            withFilename = WFNAME_REDUCED;
        } else {
            withFilename = WFNAME_NONE;
        }
    }

    // calculate the height remaining for the thumbnail image
    preh = height - upperMargin - 2 * borderWidth - lowerMargin - bsh;
    int infow = 0;
    int infoh = 0;

    if ((parent->getLocation() != ThumbBrowserBase::THLOC_EDITOR &&
         options.showFileNames && !options.overlayedFileNames) ||
        (parent->getLocation() == ThumbBrowserBase::THLOC_EDITOR &&
         options.filmStripShowFileNames &&
         !options.filmStripOverlayedFileNames)) {
        // dimensions of the info text
        getTextSizes(infow, infoh);
        infoh += textGap;
        // preh -= infoh;
        height += infoh;
    }

    // Minimum size for thumbs
    if (preh < 24) {
        preh = 24;
        height =
            preh + (upperMargin + 2 * borderWidth + lowerMargin) + bsh + infoh;
    }

    calcThumbnailSize(); // recalculates prew

    width = prew + 2 * sideMargin + 2 * borderWidth;

    if ((parent->getLocation() != ThumbBrowserBase::THLOC_EDITOR &&
         options.showFileNames && !options.overlayedFileNames) ||
        (parent->getLocation() == ThumbBrowserBase::THLOC_EDITOR &&
         options.filmStripShowFileNames &&
         !options.filmStripOverlayedFileNames)) {
        width = prew + 2 * sideMargin + 2 * borderWidth;

        if (width < infow + 2 * sideMargin + 2 * borderWidth) {
            width = infow + 2 * sideMargin + 2 * borderWidth;
        }
    }

    if (width < bsw + 2 * sideMargin + 2 * borderWidth) {
        width = bsw + 2 * sideMargin + 2 * borderWidth;
    }

    if (preh != old_preh) {
        preview.clear();
        refreshThumbnailImage();
    } else if (backBuffer) {
        backBuffer->setDirty(
            true); // This will force a backBuffer update on queue_draw
    }

    drawable = true;
}

void ThumbBrowserEntryBase::drawFrame(Cairo::RefPtr<Cairo::Context> cc,
                                      const Gdk::RGBA &bg, const Gdk::RGBA &fg,
                                      bool highlight)
{
    Glib::RefPtr<Gtk::StyleContext> style = parent->getStyle();

    static int radius = -1;
    if (radius < 0) {
        Gtk::Button btn("");
        auto ctx = btn.get_style_context();

        gtk_style_context_get(ctx->gobj(),
                              gtk_style_context_get_state(ctx->gobj()),
                              GTK_STYLE_PROPERTY_BORDER_RADIUS, &radius, NULL);
        radius *= 1.5;
    }

    cc->move_to(radius, 0);
    cc->arc(exp_width - 1 - radius, radius, radius, -rtengine::RT_PI / 2, 0);
    cc->arc(exp_width - 1 - radius, exp_height - 1 - radius, radius, 0,
            rtengine::RT_PI / 2);
    cc->arc(radius, exp_height - 1 - radius, radius, rtengine::RT_PI / 2,
            rtengine::RT_PI);
    cc->arc(radius, radius, radius, rtengine::RT_PI, -rtengine::RT_PI / 2);
    cc->close_path();

    cc->set_source_rgba(bg.get_red(), bg.get_green(), bg.get_blue(),
                        bg.get_alpha());
    cc->fill_preserve();

    cc->set_source_rgb(bg.get_red() * 2 / 3, bg.get_green() * 2 / 3,
                       bg.get_blue() * 2 / 3);
    cc->set_line_width(1.0);
    cc->stroke();

    if (highlight) {
        cc->move_to(+2 + 0.5 + radius, +2 + 0.5);
        cc->arc(-2 + 0.5 + exp_width - 1 - radius, +2 + 0.5 + radius, radius,
                -rtengine::RT_PI / 2, 0);
        cc->arc(-2 + 0.5 + exp_width - 1 - radius,
                -2 + 0.5 + exp_height - 1 - radius, radius, 0,
                rtengine::RT_PI / 2);
        cc->arc(+2 + 0.5 + radius, -2 + exp_height - 1 - radius, radius,
                rtengine::RT_PI / 2, rtengine::RT_PI);
        cc->arc(+2 + 0.5 + radius, +2 + 0.5 + radius, radius, rtengine::RT_PI,
                -rtengine::RT_PI / 2);
        cc->close_path();
        cc->set_source_rgb(fg.get_red(), fg.get_green(), fg.get_blue());
        cc->set_line_width(2.0);
        cc->stroke();
    }

    if (open_in_editor) {
        cc->set_source_rgb(fg.get_red(), fg.get_green(), fg.get_blue());

        const auto off = 1.5;
        cc->move_to(0.5 + exp_width / 2.0 - 6, off + 0.5);
        cc->line_to(0.5 + exp_width / 2.0 + 6, off + 0.5);
        cc->line_to(0.5 + exp_width / 2.0, off + 6.5);
        cc->close_path();
        cc->fill();        
    }
}

void ThumbBrowserEntryBase::draw(Cairo::RefPtr<Cairo::Context> cc)
{

    if (!drawable || !parent) {
        return;
    }

    MYREADERLOCK(l, lockRW); // No resizes, position moves etc. inbetween

    int bbWidth = 0, bbHeight = 0;

    if (backBuffer) {
        bbWidth = backBuffer->getWidth();
        bbHeight = backBuffer->getHeight();
    }

    if (!backBuffer || selected != bbSelected ||
        (framed || (open_in_editor > 0)) != bbFramed ||
        (!preview.empty() ? &preview[0] != bbPreview : !bbPreview) ||
        exp_width != bbWidth || exp_height != bbHeight ||
        getIconsOnImageArea() != bbIcons ||
        getSpecificityIconsOnImageArea() != bbSpecificityIcons ||
        backBuffer->isDirty()) {
        updateBackBuffer();
    }

    if (backBuffer->surfaceCreated()) {
        int w_ = startx + ofsX;
        int h_ = starty + ofsY;
        cc->set_source(backBuffer->getSurface(), w_, h_);
        cc->rectangle(w_, h_, backBuffer->getWidth(), backBuffer->getHeight());
        cc->fill();
    }

    // check icon set changes!!!

    //    drawProgressBar (window, cc, selected ? texts : textn, selected ? bgs
    //    : bgn, ofsX+startx, exp_width, ofsY+starty +
    //    upperMargin+bsHeight+borderWidth+preh+borderWidth+textGap+tpos,
    //    fnlabh);

    // redraw button set above the thumbnail
    if (buttonSet) {
        buttonSet->setColors(selected ? parent->getSelectedBgColor()
                                      : parent->getNormalBgColor(),
                             selected ? parent->getNormalBgColor()
                                      : parent->getSelectedBgColor());
        buttonSet->redraw(cc);
    }
}

void ThumbBrowserEntryBase::setPosition(int x, int y, int w, int h)
{
    MYWRITERLOCK(l, lockRW);

    exp_width = w;
    exp_height = h;
    startx = x;
    starty = y;

    if (buttonSet) {
        buttonSet->arrangeButtons(ofsX + x + sideMargin, ofsY + y + upperMargin,
                                  w - 2 * sideMargin, -1);
    }
}

void ThumbBrowserEntryBase::setOffset(int x, int y)
{
    MYWRITERLOCK(l, lockRW);

    ofsX = -x;
    ofsY = -y;

    if (buttonSet) {
        buttonSet->move(ofsX + startx + sideMargin,
                        ofsY + starty + upperMargin);
    }
}

bool ThumbBrowserEntryBase::inside(int x, int y) const
{

    return x > ofsX + startx && x < ofsX + startx + exp_width &&
           y > ofsY + starty && y < ofsY + starty + exp_height;
}

rtengine::Coord2D ThumbBrowserEntryBase::getPosInImgSpace(int x, int y) const
{
    rtengine::Coord2D coord(-1., -1.);

    if (!preview.empty()) {
        x -= ofsX + startx;
        y -= ofsY + starty;

        if (x >= prex && x <= prex + prew && y >= prey && y <= prey + preh) {
            coord.x = double(x - prex) / double(prew);
            coord.y = double(y - prey) / double(preh);
        }
    }
    return coord;
}

bool ThumbBrowserEntryBase::insideWindow(int x, int y, int w, int h) const
{

    return !(ofsX + startx > x + w || ofsX + startx + exp_width < x ||
             ofsY + starty > y + h || ofsY + starty + exp_height < y);
}

std::vector<std::shared_ptr<RTSurface>>
ThumbBrowserEntryBase::getIconsOnImageArea()
{
    return std::vector<std::shared_ptr<RTSurface>>();
}

std::vector<std::shared_ptr<RTSurface>>
ThumbBrowserEntryBase::getSpecificityIconsOnImageArea()
{
    return std::vector<std::shared_ptr<RTSurface>>();
}

bool ThumbBrowserEntryBase::motionNotify(int x, int y)
{

    return buttonSet ? buttonSet->motionNotify(x, y) : false;
}

bool ThumbBrowserEntryBase::pressNotify(int button, int type, int bstate, int x,
                                        int y)
{

    return buttonSet ? buttonSet->pressNotify(x, y) : false;
}

bool ThumbBrowserEntryBase::releaseNotify(int button, int type, int bstate,
                                          int x, int y)
{

    return buttonSet ? buttonSet->releaseNotify(x, y, bstate) : false;
}

Glib::ustring ThumbBrowserEntryBase::getToolTip(int x, int y) const
{
    Glib::ustring tooltip;

    if (buttonSet) {
        tooltip = buttonSet->getToolTip(x, y);
    }

    // Always show the filename in the tooltip since the filename in the
    // thumbnail could be truncated. If "Show Exif info" is disabled, also show
    // Exif info in the tooltip.
    if (inside(x, y) && tooltip.empty()) {
        tooltip = dispname;

        if (withFilename < WFNAME_FULL) {
            if (options.fbShowDateTime && !datetimeline.empty()) {
                tooltip += Glib::ustring("\n") + datetimeline;
            }

            if (options.fbShowBasicExif && !exifline.empty()) {
                tooltip += Glib::ustring("\n") + exifline;
            }
        }
    }

    return tooltip;
}
