Revision 53f2e14fc0fc015621bb0f49ea216c2c513ee2eb authored by johannes hanika on 23 December 2016, 10:38:36 UTC, committed by johannes hanika on 23 December 2016, 10:38:36 UTC
1 parent 4d54ba5
gradientslider.c
/*
This file is part of darktable,
copyright (c)2010--2012 Henrik Andersson.
copyright (c)2012 Ulrich Pegelow.
darktable 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.
darktable 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 darktable. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "common/darktable.h"
#include "develop/develop.h"
#include "gradientslider.h"
#include "gui/gtk.h"
#define CLAMP_RANGE(x, y, z) (CLAMP(x, y, z))
#define DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MAX 500
#define DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MIN 25
#define DTGTK_GRADIENT_SLIDER_DEFAULT_INCREMENT 0.01
static void _gradient_slider_class_init(GtkDarktableGradientSliderClass *klass);
static void _gradient_slider_init(GtkDarktableGradientSlider *slider);
static void _gradient_slider_realize(GtkWidget *widget);
static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr);
static void _gradient_slider_destroy(GtkWidget *widget);
// Events
static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event);
static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event);
static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event);
static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event);
static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event);
static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event);
enum
{
VALUE_CHANGED,
LAST_SIGNAL
};
static guint _signals[LAST_SIGNAL] = { 0 };
static gboolean _gradient_slider_postponed_value_change(gpointer data)
{
if(!GTK_IS_WIDGET(data)) return 0;
if(DTGTK_GRADIENT_SLIDER(data)->is_changed == TRUE)
{
g_signal_emit_by_name(G_OBJECT(data), "value-changed");
DTGTK_GRADIENT_SLIDER(data)->is_changed = FALSE;
}
if(!DTGTK_GRADIENT_SLIDER(data)->is_dragging) DTGTK_GRADIENT_SLIDER(data)->timeout_handle = 0;
return DTGTK_GRADIENT_SLIDER(data)->is_dragging; // This is called by the gtk mainloop and is threadsafe
}
static inline gdouble _screen_to_scale(GtkWidget *widget, gint screen)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
return ((gdouble)screen - gslider->margins) / ((gdouble)allocation.width - 2 * gslider->margins);
}
static inline gint _scale_to_screen(GtkWidget *widget, gdouble scale)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
return (gint)(scale * (allocation.width - 2 * gslider->margins) + gslider->margins);
}
static gdouble _slider_move(GtkWidget *widget, gint k, gdouble value, gint direction)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
gdouble newvalue = value;
gdouble leftnext = (k == 0) ? 0.0f : gslider->position[k - 1];
gdouble rightnext = (k == gslider->positions - 1) ? 1.0f : gslider->position[k + 1];
switch(direction)
{
case MOVE_LEFT:
if(value < leftnext)
{
newvalue = (k == 0) ? fmax(value, 0.0f) : _slider_move(widget, k - 1, value, direction);
}
break;
case MOVE_RIGHT:
if(value > rightnext)
{
newvalue = (k == gslider->positions - 1) ? fmin(value, 1.0f)
: _slider_move(widget, k + 1, value, direction);
}
break;
}
gslider->position[k] = newvalue;
return newvalue;
}
static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_PRELIGHT, TRUE);
gslider->is_entered = TRUE;
gtk_widget_queue_draw(widget);
DTGTK_GRADIENT_SLIDER(widget)->prev_x_root = event->x_root;
return FALSE;
}
static gboolean _gradient_slider_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE);
gslider->is_entered = FALSE;
gtk_widget_queue_draw(widget);
DTGTK_GRADIENT_SLIDER(widget)->prev_x_root = event->x_root;
return FALSE;
}
static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
if(event->button == 1 && event->type == GDK_2BUTTON_PRESS && gslider->is_resettable)
{
gslider->is_dragging = FALSE;
gslider->do_reset = TRUE;
gslider->selected = -1;
for(int k = 0; k < gslider->positions; k++) gslider->position[k] = gslider->resetvalue[k];
gtk_widget_queue_draw(widget);
g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
}
else if((event->button == 1 || event->button == 3) && event->type == GDK_BUTTON_PRESS)
{
gint lselected = -1;
gdouble newposition = roundf(_screen_to_scale(widget, event->x) / gslider->increment)
* gslider->increment;
gslider->prev_x_root = event->x_root;
assert(gslider->positions > 0);
if(gslider->positions == 1)
{
lselected = 0;
}
else if(newposition <= gslider->position[0])
{
lselected = 0;
}
else if(newposition >= gslider->position[gslider->positions - 1])
{
lselected = gslider->positions - 1;
}
else
for(int k = 0; k <= gslider->positions - 2; k++)
{
if(newposition >= gslider->position[k] && newposition <= gslider->position[k + 1])
{
lselected = newposition - gslider->position[k] < gslider->position[k + 1] - newposition ? k : k + 1;
break;
}
}
assert(lselected >= 0);
assert(lselected <= gslider->positions - 1);
if(event->button == 1 && lselected >= 0) // left mouse button : select and start dragging
{
gslider->selected = lselected;
gslider->do_reset = FALSE;
newposition = CLAMP_RANGE(newposition, 0.0, 1.0);
gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
_slider_move(widget, gslider->selected, newposition, direction);
gslider->min = gslider->selected == 0 ? 0.0f : gslider->position[gslider->selected - 1];
gslider->max = gslider->selected == gslider->positions - 1 ? 1.0f
: gslider->position[gslider->selected + 1];
gslider->is_changed = TRUE;
gslider->is_dragging = TRUE;
// timeout_handle should always be zero here, but check just in case
int delay = CLAMP_RANGE(darktable.develop->average_delay * 3 / 2,
DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MIN,
DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MAX);
if(!gslider->timeout_handle)
gslider->timeout_handle = g_timeout_add(delay, _gradient_slider_postponed_value_change, widget);
}
else if(gslider->positions
> 1) // right mouse button: switch on/off selection (only if we have more than one marker)
{
gslider->is_dragging = FALSE;
gslider->do_reset = FALSE;
if(gslider->selected != lselected)
{
gslider->selected = lselected;
gslider->min = gslider->selected == 0 ? 0.0f : gslider->position[gslider->selected - 1];
gslider->max = gslider->selected == gslider->positions - 1 ? 1.0f
: gslider->position[gslider->selected + 1];
}
else
gslider->selected = -1;
gtk_widget_queue_draw(widget);
}
}
return TRUE;
}
static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
if(gslider->is_dragging == TRUE && gslider->selected != -1 && gslider->do_reset == FALSE)
{
assert(gslider->timeout_handle > 0);
gdouble newposition = roundf(_screen_to_scale(widget, event->x) / gslider->increment)
* gslider->increment;
newposition = CLAMP_RANGE(newposition, 0.0, 1.0);
gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
_slider_move(widget, gslider->selected, newposition, direction);
gslider->min = gslider->selected == 0 ? 0.0f : gslider->position[gslider->selected - 1];
gslider->max = gslider->selected == gslider->positions - 1 ? 1.0f
: gslider->position[gslider->selected + 1];
gslider->is_changed = TRUE;
gtk_widget_queue_draw(widget);
}
if(gslider->selected != -1) gtk_widget_grab_focus(widget);
return TRUE;
}
static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
if(event->button == 1 && gslider->selected != -1 && gslider->do_reset == FALSE)
{
// First get some dimension info
gslider->is_changed = TRUE;
gdouble newposition = roundf(_screen_to_scale(widget, event->x) / gslider->increment)
* gslider->increment;
newposition = CLAMP_RANGE(newposition, 0.0, 1.0);
gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
_slider_move(widget, gslider->selected, newposition, direction);
gslider->min = gslider->selected == 0 ? 0.0f : gslider->position[gslider->selected - 1];
gslider->max = gslider->selected == gslider->positions - 1 ? 1.0f
: gslider->position[gslider->selected + 1];
gtk_widget_queue_draw(widget);
gslider->prev_x_root = event->x_root;
gslider->is_dragging = FALSE;
if(gslider->timeout_handle) g_source_remove(gslider->timeout_handle);
gslider->timeout_handle = 0;
g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
}
return TRUE;
}
static gboolean _gradient_slider_add_delta_internal(GtkWidget *widget, float delta, guint state)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
if(gslider->selected == -1) return TRUE;
float multiplier;
GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
if((state & modifiers) == GDK_SHIFT_MASK)
{
multiplier = dt_conf_get_float("darkroom/ui/scale_rough_step_multiplier");
}
else if((state & modifiers) == GDK_CONTROL_MASK)
{
multiplier = dt_conf_get_float("darkroom/ui/scale_precise_step_multiplier");
}
else
{
multiplier = dt_conf_get_float("darkroom/ui/scale_step_multiplier");
}
delta *= multiplier;
gdouble newvalue = gslider->position[gslider->selected] + delta;
gslider->position[gslider->selected] = CLAMP(newvalue, gslider->min, gslider->max);
gtk_widget_queue_draw(widget);
g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
return TRUE;
}
static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
if(gslider->selected == -1) return TRUE;
int handled = 0;
float delta = 0.0f;
if(event->direction == GDK_SCROLL_UP)
{
handled = 1;
delta = gslider->increment;
}
else if(event->direction == GDK_SCROLL_DOWN)
{
handled = 1;
delta = -gslider->increment;
}
if(!handled) return TRUE;
return _gradient_slider_add_delta_internal(widget, delta, event->state);
}
static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
if(gslider->selected == -1) return TRUE;
int handled = 0;
float delta = 0.0f;
if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up || event->keyval == GDK_KEY_Right
|| event->keyval == GDK_KEY_KP_Right)
{
handled = 1;
delta = gslider->increment;
}
else if(event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down || event->keyval == GDK_KEY_Left
|| event->keyval == GDK_KEY_KP_Left)
{
handled = 1;
delta = -gslider->increment;
}
if(!handled) return TRUE;
return _gradient_slider_add_delta_internal(widget, delta, event->state);
}
static void _gradient_slider_class_init(GtkDarktableGradientSliderClass *klass)
{
GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
widget_class->realize = _gradient_slider_realize;
widget_class->draw = _gradient_slider_draw;
widget_class->destroy = _gradient_slider_destroy;
widget_class->enter_notify_event = _gradient_slider_enter_notify_event;
widget_class->leave_notify_event = _gradient_slider_leave_notify_event;
widget_class->button_press_event = _gradient_slider_button_press;
widget_class->button_release_event = _gradient_slider_button_release;
widget_class->motion_notify_event = _gradient_slider_motion_notify;
widget_class->scroll_event = _gradient_slider_scroll_event;
widget_class->key_press_event = _gradient_slider_key_press_event;
_signals[VALUE_CHANGED] = g_signal_new("value-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0,
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}
static void _gradient_slider_init(GtkDarktableGradientSlider *slider)
{
slider->prev_x_root = slider->is_dragging = slider->is_changed = slider->do_reset = slider->is_entered = 0;
slider->timeout_handle = 0;
slider->selected = slider->positions == 1 ? 0 : -1;
}
static void _gradient_slider_realize(GtkWidget *widget)
{
GdkWindowAttr attributes;
guint attributes_mask;
g_return_if_fail(widget != NULL);
g_return_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget));
gtk_widget_set_realized(widget, TRUE);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = DT_PIXEL_APPLY_DPI(100);
attributes.height = DT_PIXEL_APPLY_DPI(17);
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
| GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y;
gtk_widget_set_can_focus(GTK_WIDGET(widget), TRUE);
gtk_widget_set_window(widget,
gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
}
static void _gradient_slider_destroy(GtkWidget *widget)
{
GtkDarktableGradientSliderClass *klass;
g_return_if_fail(widget != NULL);
g_return_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget));
if(DTGTK_GRADIENT_SLIDER(widget)->timeout_handle)
g_source_remove(DTGTK_GRADIENT_SLIDER(widget)->timeout_handle);
DTGTK_GRADIENT_SLIDER(widget)->timeout_handle = 0;
if(DTGTK_GRADIENT_SLIDER(widget)->colors)
{
g_list_free_full(DTGTK_GRADIENT_SLIDER(widget)->colors, g_free);
DTGTK_GRADIENT_SLIDER(widget)->colors = NULL;
}
// FIXME: or it should be g_type_class_ref () ?
klass = g_type_class_peek(gtk_widget_get_type());
if(GTK_WIDGET_CLASS(klass)->destroy)
{
(*GTK_WIDGET_CLASS(klass)->destroy)(widget);
}
}
static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr)
{
GtkDarktableGradientSlider *gslider = DTGTK_GRADIENT_SLIDER(widget);
assert(gslider->position > 0);
g_return_val_if_fail(widget != NULL, FALSE);
g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
GdkRGBA color;
GtkStyleContext *context = gtk_widget_get_style_context(widget);
gtk_style_context_get_color(context, gtk_widget_get_state_flags(widget), &color);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
int width = allocation.width;
int height = allocation.height;
int margins = gslider->margins;
// Begin cairo drawing
// First build the cairo gradient and then fill the gradient
float gheight = height / 2.0;
float gwidth = width - 2 * margins;
GList *current = NULL;
cairo_pattern_t *gradient = NULL;
if((current = g_list_first(gslider->colors)) != NULL)
{
gradient = cairo_pattern_create_linear(0, 0, gwidth, gheight);
do
{
_gradient_slider_stop_t *stop = (_gradient_slider_stop_t *)current->data;
cairo_pattern_add_color_stop_rgb(gradient, stop->position, stop->color.red, stop->color.green,
stop->color.blue);
} while((current = g_list_next(current)) != NULL);
}
if(gradient != NULL) // Do we got a gradient, lets draw it
{
cairo_set_line_width(cr, 0.1);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_source(cr, gradient);
cairo_rectangle(cr, margins, (height - gheight) / 2.0, gwidth, gheight);
cairo_fill(cr);
cairo_stroke(cr);
cairo_pattern_destroy(gradient);
}
// Lets draw position arrows
cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
// do we have a picker value to draw?
if(!isnan(gslider->picker[0]))
{
int vx_min = _scale_to_screen(widget, CLAMP_RANGE(gslider->picker[1], 0.0, 1.0));
int vx_max = _scale_to_screen(widget, CLAMP_RANGE(gslider->picker[2], 0.0, 1.0));
int vx_avg = _scale_to_screen(widget, CLAMP_RANGE(gslider->picker[0], 0.0, 1.0));
cairo_set_source_rgba(cr, color.red, color.green, color.blue, 0.33);
cairo_rectangle(cr, vx_min, (height - gheight) / 2.0, fmax((float)vx_max - vx_min, 0.0f), gheight);
cairo_fill(cr);
cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
cairo_move_to(cr, vx_avg, (height - gheight) / 2.0);
cairo_line_to(cr, vx_avg, (height + gheight) / 2.0);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
cairo_set_line_width(cr, 1.0);
cairo_stroke(cr);
}
int indirect[GRADIENT_SLIDER_MAX_POSITIONS];
for(int k = 0; k < gslider->positions; k++)
indirect[k] = gslider->selected == -1 ? k : (gslider->selected + 1 + k) % gslider->positions;
for(int k = 0; k < gslider->positions; k++)
{
int l = indirect[k];
int vx = _scale_to_screen(widget, gslider->position[l]);
int mk = gslider->marker[l];
int sz = (mk & (1 << 3)) ? 13 : 10; // big or small marker?
if(l == gslider->selected && (gslider->is_entered == TRUE || gslider->is_dragging == TRUE))
{
cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
}
else
{
cairo_set_source_rgba(cr, color.red * 0.8, color.green * 0.8, color.blue * 0.8, 1.0);
}
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
if(mk & 0x04) /* upper arrow */
{
if(mk & 0x01) /* filled */
dtgtk_cairo_paint_solid_triangle(cr, (vx - DT_PIXEL_APPLY_DPI(sz) * 0.5),
sz < 10 ? DT_PIXEL_APPLY_DPI(1) : DT_PIXEL_APPLY_DPI(-2),
DT_PIXEL_APPLY_DPI(sz), DT_PIXEL_APPLY_DPI(sz), CPF_DIRECTION_DOWN);
else
dtgtk_cairo_paint_triangle(cr, (vx - DT_PIXEL_APPLY_DPI(sz) * 0.5),
sz < 10 ? DT_PIXEL_APPLY_DPI(1) : DT_PIXEL_APPLY_DPI(-2),
DT_PIXEL_APPLY_DPI(sz), DT_PIXEL_APPLY_DPI(sz), CPF_DIRECTION_DOWN);
}
if(mk & 0x02) /* lower arrow */
{
if(mk & 0x01) /* filled */
dtgtk_cairo_paint_solid_triangle(cr, (vx - DT_PIXEL_APPLY_DPI(sz) * 0.5),
sz < 10 ? height - DT_PIXEL_APPLY_DPI(6) : height
- DT_PIXEL_APPLY_DPI(11),
DT_PIXEL_APPLY_DPI(sz), DT_PIXEL_APPLY_DPI(sz), CPF_DIRECTION_UP);
else
dtgtk_cairo_paint_triangle(cr, (vx - DT_PIXEL_APPLY_DPI(sz) * 0.5),
sz < 10 ? height - DT_PIXEL_APPLY_DPI(6) : height - DT_PIXEL_APPLY_DPI(11),
DT_PIXEL_APPLY_DPI(sz), DT_PIXEL_APPLY_DPI(sz), CPF_DIRECTION_UP);
}
}
return FALSE;
}
// Public functions for multivalue type
GtkWidget *dtgtk_gradient_slider_multivalue_new(gint positions)
{
assert(positions <= GRADIENT_SLIDER_MAX_POSITIONS);
GtkDarktableGradientSlider *gslider;
gslider = g_object_new(dtgtk_gradient_slider_get_type(), NULL);
gslider->positions = positions;
gslider->is_resettable = FALSE;
gslider->is_entered = FALSE;
gslider->picker[0] = gslider->picker[1] = gslider->picker[2] = NAN;
gslider->selected = positions == 1 ? 0 : -1;
gslider->min = 0.0;
gslider->max = 1.0;
gslider->increment = DTGTK_GRADIENT_SLIDER_DEFAULT_INCREMENT;
gslider->margins = GRADIENT_SLIDER_MARGINS_DEFAULT;
for(int k = 0; k < positions; k++) gslider->position[k] = 0.0;
for(int k = 0; k < positions; k++) gslider->resetvalue[k] = 0.0;
for(int k = 0; k < positions; k++) gslider->marker[k] = GRADIENT_SLIDER_MARKER_LOWER_FILLED;
return (GtkWidget *)gslider;
}
GtkWidget *dtgtk_gradient_slider_multivalue_new_with_color(GdkRGBA start, GdkRGBA end, gint positions)
{
assert(positions <= GRADIENT_SLIDER_MAX_POSITIONS);
GtkDarktableGradientSlider *gslider;
gslider = g_object_new(dtgtk_gradient_slider_get_type(), NULL);
gslider->positions = positions;
gslider->is_resettable = FALSE;
gslider->is_entered = FALSE;
gslider->picker[0] = gslider->picker[1] = gslider->picker[2] = NAN;
gslider->selected = positions == 1 ? 0 : -1;
gslider->min = 0.0;
gslider->max = 1.0;
gslider->increment = DTGTK_GRADIENT_SLIDER_DEFAULT_INCREMENT;
gslider->margins = GRADIENT_SLIDER_MARGINS_DEFAULT;
for(int k = 0; k < positions; k++) gslider->position[k] = 0.0;
for(int k = 0; k < positions; k++) gslider->resetvalue[k] = 0.0;
for(int k = 0; k < positions; k++) gslider->marker[k] = GRADIENT_SLIDER_MARKER_LOWER_FILLED;
// Construct gradient start color
_gradient_slider_stop_t *gc = (_gradient_slider_stop_t *)g_malloc(sizeof(_gradient_slider_stop_t));
gc->position = 0.0;
memcpy(&gc->color, &start, sizeof(GdkRGBA));
gslider->colors = g_list_append(gslider->colors, gc);
// Construct gradient stop color
gc = (_gradient_slider_stop_t *)g_malloc(sizeof(_gradient_slider_stop_t));
gc->position = 1.0;
memcpy(&gc->color, &end, sizeof(GdkRGBA));
gslider->colors = g_list_append(gslider->colors, gc);
return (GtkWidget *)gslider;
}
gint _list_find_by_position(gconstpointer a, gconstpointer b)
{
_gradient_slider_stop_t *stop = (_gradient_slider_stop_t *)a;
gfloat position = *((gfloat *)b);
return (gint)((stop->position * 100.0) - (position * 100.0));
}
void dtgtk_gradient_slider_multivalue_set_stop(GtkDarktableGradientSlider *gslider, gfloat position,
GdkRGBA color)
{
// First find color at position, if exists update color, otherwise create a new stop at position.
GList *current = g_list_find_custom(gslider->colors, (gpointer)&position, _list_find_by_position);
if(current != NULL)
{
memcpy(&((_gradient_slider_stop_t *)current->data)->color, &color, sizeof(GdkRGBA));
}
else
{
// stop didn't exist lets add it
_gradient_slider_stop_t *gc = (_gradient_slider_stop_t *)g_malloc(sizeof(_gradient_slider_stop_t));
gc->position = position;
memcpy(&gc->color, &color, sizeof(GdkRGBA));
gslider->colors = g_list_append(gslider->colors, gc);
}
}
void dtgtk_gradient_slider_multivalue_clear_stops(GtkDarktableGradientSlider *gslider)
{
g_list_free_full(gslider->colors, g_free);
gslider->colors = NULL;
}
GType dtgtk_gradient_slider_multivalue_get_type()
{
static GType dtgtk_gradient_slider_type = 0;
if(!dtgtk_gradient_slider_type)
{
static const GTypeInfo dtgtk_gradient_slider_info = {
sizeof(GtkDarktableGradientSliderClass), (GBaseInitFunc)NULL, (GBaseFinalizeFunc)NULL,
(GClassInitFunc)_gradient_slider_class_init, NULL, /* class_finalize */
NULL, /* class_data */
sizeof(GtkDarktableGradientSlider), 0, /* n_preallocs */
(GInstanceInitFunc)_gradient_slider_init,
};
dtgtk_gradient_slider_type = g_type_register_static(GTK_TYPE_WIDGET, "GtkDarktableGradientSlider",
&dtgtk_gradient_slider_info, 0);
}
return dtgtk_gradient_slider_type;
}
gdouble dtgtk_gradient_slider_multivalue_get_value(GtkDarktableGradientSlider *gslider, gint pos)
{
assert(pos <= gslider->positions);
return gslider->position[pos];
}
void dtgtk_gradient_slider_multivalue_set_value(GtkDarktableGradientSlider *gslider, gdouble value, gint pos)
{
assert(pos <= gslider->positions);
gslider->position[pos] = value;
gslider->selected = gslider->positions == 1 ? 0 : -1;
g_signal_emit_by_name(G_OBJECT(gslider), "value-changed");
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_multivalue_set_values(GtkDarktableGradientSlider *gslider, gdouble *values)
{
for(int k = 0; k < gslider->positions; k++) gslider->position[k] = values[k];
gslider->selected = gslider->positions == 1 ? 0 : -1;
g_signal_emit_by_name(G_OBJECT(gslider), "value-changed");
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_multivalue_set_marker(GtkDarktableGradientSlider *gslider, gint mark, gint pos)
{
assert(pos <= gslider->positions);
gslider->marker[pos] = mark;
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_multivalue_set_markers(GtkDarktableGradientSlider *gslider, gint *markers)
{
for(int k = 0; k < gslider->positions; k++) gslider->marker[k] = markers[k];
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_multivalue_set_resetvalue(GtkDarktableGradientSlider *gslider, gdouble value,
gint pos)
{
assert(pos <= gslider->positions);
gslider->resetvalue[pos] = value;
gslider->is_resettable = TRUE;
}
void dtgtk_gradient_slider_multivalue_set_resetvalues(GtkDarktableGradientSlider *gslider, gdouble *values)
{
for(int k = 0; k < gslider->positions; k++) gslider->resetvalue[k] = values[k];
gslider->is_resettable = TRUE;
}
void dtgtk_gradient_slider_multivalue_set_picker(GtkDarktableGradientSlider *gslider, gdouble value)
{
gslider->picker[0] = gslider->picker[1] = gslider->picker[2] = value;
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_multivalue_set_picker_meanminmax(GtkDarktableGradientSlider *gslider, gdouble mean,
gdouble min, gdouble max)
{
gslider->picker[0] = mean;
gslider->picker[1] = min;
gslider->picker[2] = max;
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_multivalue_set_margins(GtkDarktableGradientSlider *gslider, gint value)
{
gslider->margins = value;
}
gboolean dtgtk_gradient_slider_multivalue_is_dragging(GtkDarktableGradientSlider *gslider)
{
return gslider->is_dragging;
}
void dtgtk_gradient_slider_multivalue_set_increment(GtkDarktableGradientSlider *gslider, gdouble value)
{
gslider->increment = value;
}
// Public functions for single value type
GtkWidget *dtgtk_gradient_slider_new()
{
return dtgtk_gradient_slider_multivalue_new(1);
}
GtkWidget *dtgtk_gradient_slider_new_with_color(GdkRGBA start, GdkRGBA end)
{
return dtgtk_gradient_slider_multivalue_new_with_color(start, end, 1);
}
void dtgtk_gradient_slider_set_stop(GtkDarktableGradientSlider *gslider, gfloat position, GdkRGBA color)
{
dtgtk_gradient_slider_multivalue_set_stop(gslider, position, color);
}
GType dtgtk_gradient_slider_get_type()
{
return dtgtk_gradient_slider_multivalue_get_type();
}
gdouble dtgtk_gradient_slider_get_value(GtkDarktableGradientSlider *gslider)
{
return dtgtk_gradient_slider_multivalue_get_value(gslider, 0);
}
void dtgtk_gradient_slider_multivalue_get_values(GtkDarktableGradientSlider *gslider, gdouble *values)
{
for(int k = 0; k < gslider->positions; k++) values[k] = gslider->position[k];
}
void dtgtk_gradient_slider_set_value(GtkDarktableGradientSlider *gslider, gdouble value)
{
dtgtk_gradient_slider_multivalue_set_value(gslider, value, 0);
}
void dtgtk_gradient_slider_set_marker(GtkDarktableGradientSlider *gslider, gint mark)
{
dtgtk_gradient_slider_multivalue_set_marker(gslider, mark, 0);
}
void dtgtk_gradient_slider_set_resetvalue(GtkDarktableGradientSlider *gslider, gdouble value)
{
dtgtk_gradient_slider_multivalue_set_resetvalue(gslider, value, 0);
}
void dtgtk_gradient_slider_set_picker(GtkDarktableGradientSlider *gslider, gdouble value)
{
gslider->picker[0] = gslider->picker[1] = gslider->picker[2] = value;
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_set_picker_meanminmax(GtkDarktableGradientSlider *gslider, gdouble mean,
gdouble min, gdouble max)
{
gslider->picker[0] = mean;
gslider->picker[1] = min;
gslider->picker[2] = max;
gtk_widget_queue_draw(GTK_WIDGET(gslider));
}
void dtgtk_gradient_slider_set_margins(GtkDarktableGradientSlider *gslider, gint value)
{
gslider->margins = value;
}
gboolean dtgtk_gradient_slider_is_dragging(GtkDarktableGradientSlider *gslider)
{
return gslider->is_dragging;
}
void dtgtk_gradient_slider_set_increment(GtkDarktableGradientSlider *gslider, gdouble value)
{
gslider->increment = value;
}
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...