// SPDX-License-Identifier: GPL-2.0-or-later
/**
 * @file
 * Combobox for selecting dash patterns - implementation.
 */
/* Author:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   bulia byak <buliabyak@users.sf.net>
 *   Maximilian Albert <maximilian.albert@gmail.com>
 *
 * Copyright (C) 2002 Lauris Kaplinski
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "dash-selector.h"

#include <cstring>

#include <glibmm/i18n.h>

#include <2geom/coord.h>

#include "preferences.h"

#include "display/cairo-utils.h"

#include "style.h"

#include "ui/dialog-events.h"
#include "ui/widget/spinbutton.h"

namespace Inkscape {
namespace UI {
namespace Widget {

gchar const *const DashSelector::_prefs_path = "/palette/dashes";

static double dash_0[] = {-1.0};
static double dash_1_1[] = {1.0, 1.0, -1.0};
static double dash_2_1[] = {2.0, 1.0, -1.0};
static double dash_4_1[] = {4.0, 1.0, -1.0};
static double dash_1_2[] = {1.0, 2.0, -1.0};
static double dash_1_4[] = {1.0, 4.0, -1.0};

static size_t BD_LEN = 7;  // must correspond to the number of entries in the next line
static double *builtin_dashes[] = {dash_0, dash_1_1, dash_2_1, dash_4_1, dash_1_2, dash_1_4, nullptr};

static double **dashes = nullptr;

DashSelector::DashSelector()
    : preview_width(80),
      preview_height(16),
      preview_lineheight(2)
{
    set_spacing(4);

    // TODO: find something more sensible here!!
    init_dashes();

    dash_store = Gtk::ListStore::create(dash_columns);
    dash_combo.set_model(dash_store);
    dash_combo.pack_start(image_renderer);
    dash_combo.set_cell_data_func(image_renderer, sigc::mem_fun(*this, &DashSelector::prepareImageRenderer));
    dash_combo.set_tooltip_text(_("Dash pattern"));
    dash_combo.get_style_context()->add_class("combobright");
    dash_combo.show();
    dash_combo.signal_changed().connect( sigc::mem_fun(*this, &DashSelector::on_selection) );

    this->pack_start(dash_combo, true, true, 0);

    offset = Gtk::Adjustment::create(0.0, 0.0, 10.0, 0.1, 1.0, 0.0);
    offset->signal_value_changed().connect(sigc::mem_fun(*this, &DashSelector::offset_value_changed));
    auto sb = new Inkscape::UI::Widget::SpinButton(offset, 0.1, 2);
    sb->set_tooltip_text(_("Pattern offset"));
    sp_dialog_defocus_on_enter_cpp(sb);
    sb->show();

    this->pack_start(*sb, false, false, 0);

    int np=0;
    while (dashes[np]){ np++;}
    for (int i = 0; i<np-1; i++) {  // all but the custom one go this way
        // Add the dashes to the combobox
        Gtk::TreeModel::Row row = *(dash_store->append());
        row[dash_columns.dash] = dashes[i];
        row[dash_columns.pixbuf] = Glib::wrap(sp_dash_to_pixbuf(dashes[i]));
    }
    // add the custom one
    Gtk::TreeModel::Row row = *(dash_store->append());
    row[dash_columns.dash] = dashes[np-1];
    row[dash_columns.pixbuf] = Glib::wrap(sp_text_to_pixbuf((char *)"Custom"));

    this->set_data("pattern", dashes[0]);
}

DashSelector::~DashSelector() {
    // FIXME: for some reason this doesn't get called; does the call to manage() in
    // sp_stroke_style_line_widget_new() not processed correctly?
}

void DashSelector::prepareImageRenderer( Gtk::TreeModel::const_iterator const &row ) {

    Glib::RefPtr<Gdk::Pixbuf> pixbuf = (*row)[dash_columns.pixbuf];
    image_renderer.property_pixbuf() = pixbuf;
}

void DashSelector::init_dashes() {

    if (!dashes) {
        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
        std::vector<Glib::ustring> dash_prefs = prefs->getAllDirs(_prefs_path);
        
        int pos = 0;
        if (!dash_prefs.empty()) {
            SPStyle style;
            dashes = g_new (double *, dash_prefs.size() + 2); // +1 for custom slot, +1 for terminator slot
            
            for (auto & dash_pref : dash_prefs) {
                style.readFromPrefs( dash_pref );
                
                if (!style.stroke_dasharray.values.empty()) {
                    dashes[pos] = g_new (double, style.stroke_dasharray.values.size() + 1);
                    double *d = dashes[pos];
                    unsigned i = 0;
                    for (; i < style.stroke_dasharray.values.size(); i++) {
                        d[i] = style.stroke_dasharray.values[i].value;
                    }
                    d[i] = -1;
                } else {
                    dashes[pos] = dash_0;
                }
                pos += 1;
            }
        } else {  //  This code may never execute - a new preferences.xml is created for a new user.  Maybe if the user deletes dashes from preferences.xml?
            dashes = g_new (double *, BD_LEN + 2); // +1 for custom slot, +1 for terminator slot
            unsigned i;
            for(i=0;i<BD_LEN;i++) {
               dashes[i] = builtin_dashes[i];
            }
            pos = BD_LEN;
        }
        // make a place to hold the custom dashes, up to 15 positions long (+ terminator)
        dashes[pos] = g_new (double, 16);
        double *d = dashes[pos];
        int i=0;
        for(i=0;i<15;i++){ d[i]=i; } // have to put something in there, this is a pattern hopefully nobody would choose
        d[15]=-1.0;
        // final terminator
        dashes[++pos]   = nullptr;
    }
}

void DashSelector::set_dash (int ndash, double *dash, double o)
{
    int pos = -1;    // Allows custom patterns to remain unscathed by this.
    int count = 0;   // will hold the NULL terminator at the end of the dashes list 
    if (ndash > 0) {
        double delta = 0.0;
        for (int i = 0; i < ndash; i++)
            delta += dash[i];
        delta /= 1000.0;

        for (int i = 0; dashes[i]; i++,count++) {
            double *pattern = dashes[i];
            int np = 0;
            while (pattern[np] >= 0.0)
                np += 1;
            if (np == ndash) {
                int j;
                for (j = 0; j < ndash; j++) {
                    if (!Geom::are_near(dash[j], pattern[j], delta)) {
                        break;
                    }
                }
                if (j == ndash) {
                    pos = i;
                    break;
                }
            }
        }
    }
    else  if(ndash==0) {
       pos = 0;
    }
    if(pos>=0){
       this->set_data("pattern", dashes[pos]);
       this->dash_combo.set_active(pos);
       this->offset->set_value(o);
       if(pos == 10) {
           this->offset->set_value(10.0);
       }
    }
    else { // Hit a custom pattern in the SVG, write it into the combobox.
       count--;  // the one slot for custom patterns
       double *d = dashes[count];
       int i=0;
       for(i=0;i< (ndash > 15 ? 15 : ndash) ;i++) {
          d[i]=dash[i];
       } // store the custom pattern
       d[ndash]=-1.0;  //terminate it
       this->set_data("pattern", dashes[count]);
       this->dash_combo.set_active(count);
       this->offset->set_value(o);  // what does this do????
    }
}

void DashSelector::get_dash(int *ndash, double **dash, double *off)
{
    double *pattern = (double*) this->get_data("pattern");

    int nd = 0;
    while (pattern[nd] >= 0.0)
        nd += 1;

    if (nd > 0) {
        if (ndash)
            *ndash = nd;
        if (dash) {
            *dash = g_new (double, nd);
            memcpy (*dash, pattern, nd * sizeof (double));
        }
        if (off)
            *off = offset->get_value();
    } else {
        if (ndash)
            *ndash = 0;
        if (dash)
            *dash = nullptr;
        if (off)
            *off = 0.0;
    }
}

/**
 * Fill a pixbuf with the dash pattern using standard cairo drawing
 */
GdkPixbuf* DashSelector::sp_dash_to_pixbuf(double *pattern)
{
    int n_dashes;
    for (n_dashes = 0; pattern[n_dashes] >= 0.0; n_dashes ++) ;

    cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, preview_width, preview_height);
    cairo_t *ct = cairo_create(s);

    cairo_set_line_width (ct, preview_lineheight);
    cairo_scale (ct, preview_lineheight, 1);
    //cairo_set_source_rgb (ct, 0, 0, 0);
    cairo_move_to (ct, 0, preview_height/2);
    cairo_line_to (ct, preview_width, preview_height/2);
    cairo_set_dash(ct, pattern, n_dashes, 0);
    cairo_stroke (ct);

    cairo_destroy(ct);
    cairo_surface_flush(s);

    GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s);
    return pixbuf;
}

/**
 * Fill a pixbuf with a text label using standard cairo drawing
 */
GdkPixbuf* DashSelector::sp_text_to_pixbuf(char *text)
{
    cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, preview_width, preview_height);
    cairo_t *ct = cairo_create(s);

    cairo_select_font_face (ct, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_set_font_size (ct, 12.0);
    cairo_set_source_rgb (ct, 0.0, 0.0, 0.0);
    cairo_move_to (ct, 16.0, 13.0);
    cairo_show_text (ct, text);

    cairo_stroke (ct);

    cairo_destroy(ct);
    cairo_surface_flush(s);

    GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s);
    return pixbuf;
}

void DashSelector::on_selection ()
{
    double *pattern = dash_combo.get_active()->get_value(dash_columns.dash);
    this->set_data ("pattern", pattern);

    changed_signal.emit();
}

void DashSelector::offset_value_changed()
{
    changed_signal.emit();
}
} // namespace Widget
} // namespace UI
} // namespace Inkscape

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
