2385 lines
78 KiB
C
2385 lines
78 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* Gradient editor module copyight (C) 1996-1997 Federico Mena Quintero
|
|
* federico@nuclecu.unam.mx
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 PURIGHTE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Special thanks to:
|
|
*
|
|
* Luis Albarran (luis4@mindspring.com) - Nice UI suggestions
|
|
*
|
|
* Miguel de Icaza (miguel@nuclecu.unam.mx) - Pop-up menu suggestion
|
|
*
|
|
* Marcelo Malheiros (malheiro@dca.fee.unicamp.br) - many, many
|
|
* suggestions, nice gradient files
|
|
*
|
|
* Adam Moss (adam@uunet.pipex.com) - idea for the hint bar
|
|
*
|
|
* Everyone on #pika - many suggestions
|
|
*/
|
|
|
|
/* TODO:
|
|
*
|
|
* - Add all of Marcelo's neat suggestions:
|
|
* - Hue rotate, saturation, brightness, contrast.
|
|
*
|
|
* - Better handling of bogus gradient files and inconsistent
|
|
* segments. Do not loop indefinitely in seg_get_segment_at() if
|
|
* there is a missing segment between two others.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikacolor/pikacolor.h"
|
|
#include "libpikamath/pikamath.h"
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "widgets-types.h"
|
|
|
|
#include "core/pika.h"
|
|
#include "core/pikacontainer.h"
|
|
#include "core/pikacontext.h"
|
|
#include "core/pikadatafactory.h"
|
|
#include "core/pikagradient.h"
|
|
|
|
#include "pikacolordialog.h"
|
|
#include "pikadialogfactory.h"
|
|
#include "pikadnd.h"
|
|
#include "pikadocked.h"
|
|
#include "pikagradienteditor.h"
|
|
#include "pikahelp-ids.h"
|
|
#include "pikauimanager.h"
|
|
#include "pikaview.h"
|
|
#include "pikaviewrenderergradient.h"
|
|
#include "pikawidgets-utils.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
#define EPSILON 1e-10
|
|
|
|
#define GRAD_SCROLLBAR_STEP_SIZE 0.05
|
|
#define GRAD_SCROLLBAR_PAGE_SIZE 0.5
|
|
|
|
#define GRAD_VIEW_SIZE 96
|
|
#define GRAD_CONTROL_HEIGHT 14
|
|
#define GRAD_CURRENT_COLOR_WIDTH 16
|
|
|
|
#define GRAD_MOVE_TIME 150 /* ms between mouse click and detection of movement in gradient control */
|
|
|
|
|
|
#define GRAD_VIEW_EVENT_MASK (GDK_EXPOSURE_MASK | \
|
|
GDK_LEAVE_NOTIFY_MASK | \
|
|
GDK_POINTER_MOTION_MASK | \
|
|
GDK_POINTER_MOTION_HINT_MASK | \
|
|
GDK_BUTTON_PRESS_MASK | \
|
|
GDK_BUTTON_RELEASE_MASK | \
|
|
GDK_SCROLL_MASK | \
|
|
GDK_SMOOTH_SCROLL_MASK | \
|
|
GDK_TOUCHPAD_GESTURE_MASK)
|
|
|
|
#define GRAD_CONTROL_EVENT_MASK (GDK_EXPOSURE_MASK | \
|
|
GDK_LEAVE_NOTIFY_MASK | \
|
|
GDK_POINTER_MOTION_MASK | \
|
|
GDK_POINTER_MOTION_HINT_MASK | \
|
|
GDK_BUTTON_PRESS_MASK | \
|
|
GDK_BUTTON_RELEASE_MASK | \
|
|
GDK_SCROLL_MASK | \
|
|
GDK_SMOOTH_SCROLL_MASK | \
|
|
GDK_BUTTON1_MOTION_MASK)
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void pika_gradient_editor_docked_iface_init (PikaDockedInterface *face);
|
|
|
|
static void pika_gradient_editor_constructed (GObject *object);
|
|
static void pika_gradient_editor_dispose (GObject *object);
|
|
|
|
static void pika_gradient_editor_unmap (GtkWidget *widget);
|
|
static void pika_gradient_editor_set_data (PikaDataEditor *editor,
|
|
PikaData *data);
|
|
|
|
static void pika_gradient_editor_set_context (PikaDocked *docked,
|
|
PikaContext *context);
|
|
|
|
static void pika_gradient_editor_update (PikaGradientEditor *editor);
|
|
static void pika_gradient_editor_gradient_dirty (PikaGradientEditor *editor,
|
|
PikaGradient *gradient);
|
|
static void gradient_editor_drop_gradient (GtkWidget *widget,
|
|
gint x,
|
|
gint y,
|
|
PikaViewable *viewable,
|
|
gpointer data);
|
|
static void gradient_editor_drop_color (GtkWidget *widget,
|
|
gint x,
|
|
gint y,
|
|
const PikaRGB *color,
|
|
gpointer data);
|
|
static void gradient_editor_control_drop_color (GtkWidget *widget,
|
|
gint x,
|
|
gint y,
|
|
const PikaRGB *color,
|
|
gpointer data);
|
|
static void gradient_editor_scrollbar_update (GtkAdjustment *adj,
|
|
PikaGradientEditor *editor);
|
|
|
|
static void gradient_editor_set_hint (PikaGradientEditor *editor,
|
|
const gchar *str1,
|
|
const gchar *str2,
|
|
const gchar *str3,
|
|
const gchar *str4);
|
|
|
|
static PikaGradientSegment *
|
|
gradient_editor_save_selection (PikaGradientEditor *editor);
|
|
static void gradient_editor_replace_selection (PikaGradientEditor *editor,
|
|
PikaGradientSegment *replace_seg);
|
|
|
|
static void gradient_editor_left_color_update (PikaColorDialog *dialog,
|
|
const PikaRGB *color,
|
|
PikaColorDialogState state,
|
|
PikaGradientEditor *editor);
|
|
static void gradient_editor_right_color_update (PikaColorDialog *dialog,
|
|
const PikaRGB *color,
|
|
PikaColorDialogState state,
|
|
PikaGradientEditor *editor);
|
|
|
|
|
|
/* Gradient view functions */
|
|
|
|
static gboolean view_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
PikaGradientEditor *editor);
|
|
static void view_set_hint (PikaGradientEditor *editor,
|
|
gint x);
|
|
|
|
static void view_pick_color (PikaGradientEditor *editor,
|
|
PikaColorPickTarget pick_target,
|
|
PikaColorPickState pick_state,
|
|
gint x);
|
|
|
|
static void view_zoom_gesture_begin (GtkGestureZoom *gesture,
|
|
GdkEventSequence *sequence,
|
|
PikaGradientEditor *editor);
|
|
|
|
static void view_zoom_gesture_update (GtkGestureZoom *gesture,
|
|
GdkEventSequence *sequence,
|
|
PikaGradientEditor *editor);
|
|
|
|
static gdouble view_get_normalized_last_x_pos (PikaGradientEditor *editor);
|
|
|
|
/* Gradient control functions */
|
|
|
|
static gboolean control_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
PikaGradientEditor *editor);
|
|
static gboolean control_draw (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
PikaGradientEditor *editor);
|
|
static void control_do_hint (PikaGradientEditor *editor,
|
|
gint x,
|
|
gint y);
|
|
static void control_button_press (PikaGradientEditor *editor,
|
|
GdkEventButton *bevent);
|
|
static gboolean control_point_in_handle (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
gint x,
|
|
gint y,
|
|
PikaGradientSegment *seg,
|
|
GradientEditorDragMode handle);
|
|
static void control_select_single_segment (PikaGradientEditor *editor,
|
|
PikaGradientSegment *seg);
|
|
static void control_extend_selection (PikaGradientEditor *editor,
|
|
PikaGradientSegment *seg,
|
|
gdouble pos);
|
|
static void control_motion (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
gint x);
|
|
|
|
static void control_compress_left (PikaGradient *gradient,
|
|
PikaGradientSegment *range_l,
|
|
PikaGradientSegment *range_r,
|
|
PikaGradientSegment *drag_seg,
|
|
gdouble pos);
|
|
|
|
static double control_move (PikaGradientEditor *editor,
|
|
PikaGradientSegment *range_l,
|
|
PikaGradientSegment *range_r,
|
|
gdouble delta);
|
|
|
|
static gdouble control_get_normalized_last_x_pos (PikaGradientEditor *editor);
|
|
|
|
/* Control update/redraw functions */
|
|
|
|
static void control_update (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
gboolean recalculate);
|
|
static void control_draw_all (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
cairo_t *cr,
|
|
gint width,
|
|
gint height,
|
|
gdouble left,
|
|
gdouble right);
|
|
static void control_draw_handle (PikaGradientEditor *editor,
|
|
GtkStyleContext *style,
|
|
cairo_t *cr,
|
|
gdouble pos,
|
|
gint height,
|
|
gboolean middle,
|
|
GtkStateFlags flags);
|
|
|
|
static gint control_calc_p_pos (PikaGradientEditor *editor,
|
|
gdouble pos);
|
|
static gdouble control_calc_g_pos (PikaGradientEditor *editor,
|
|
gint pos);
|
|
|
|
/* Segment functions */
|
|
|
|
static void seg_get_closest_handle (PikaGradient *grad,
|
|
gdouble pos,
|
|
PikaGradientSegment **seg,
|
|
GradientEditorDragMode *handle);
|
|
static gboolean seg_in_selection (PikaGradient *grad,
|
|
PikaGradientSegment *seg,
|
|
PikaGradientSegment *left,
|
|
PikaGradientSegment *right);
|
|
|
|
static GtkWidget * gradient_hint_label_add (GtkBox *box);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (PikaGradientEditor, pika_gradient_editor,
|
|
PIKA_TYPE_DATA_EDITOR,
|
|
G_IMPLEMENT_INTERFACE (PIKA_TYPE_DOCKED,
|
|
pika_gradient_editor_docked_iface_init))
|
|
|
|
#define parent_class pika_gradient_editor_parent_class
|
|
|
|
static PikaDockedInterface *parent_docked_iface = NULL;
|
|
|
|
|
|
static void
|
|
pika_gradient_editor_class_init (PikaGradientEditorClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
PikaDataEditorClass *editor_class = PIKA_DATA_EDITOR_CLASS (klass);
|
|
|
|
object_class->constructed = pika_gradient_editor_constructed;
|
|
object_class->dispose = pika_gradient_editor_dispose;
|
|
|
|
widget_class->unmap = pika_gradient_editor_unmap;
|
|
|
|
editor_class->set_data = pika_gradient_editor_set_data;
|
|
editor_class->title = _("Gradient Editor");
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_docked_iface_init (PikaDockedInterface *iface)
|
|
{
|
|
parent_docked_iface = g_type_interface_peek_parent (iface);
|
|
|
|
if (! parent_docked_iface)
|
|
parent_docked_iface = g_type_default_interface_peek (PIKA_TYPE_DOCKED);
|
|
|
|
iface->set_context = pika_gradient_editor_set_context;
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_init (PikaGradientEditor *editor)
|
|
{
|
|
PikaDataEditor *data_editor = PIKA_DATA_EDITOR (editor);
|
|
GtkWidget *frame;
|
|
GtkWidget *vbox;
|
|
GtkWidget *hbox;
|
|
GtkWidget *hint_vbox;
|
|
PikaRGB transp;
|
|
|
|
pika_rgba_set (&transp, 0.0, 0.0, 0.0, 0.0);
|
|
|
|
/* Frame for gradient view and gradient control */
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|
gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0);
|
|
gtk_widget_show (frame);
|
|
|
|
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_container_add (GTK_CONTAINER (frame), vbox);
|
|
gtk_widget_show (vbox);
|
|
|
|
data_editor->view = pika_view_new_full_by_types (NULL,
|
|
PIKA_TYPE_VIEW,
|
|
PIKA_TYPE_GRADIENT,
|
|
GRAD_VIEW_SIZE,
|
|
GRAD_VIEW_SIZE, 0,
|
|
FALSE, FALSE, FALSE);
|
|
gtk_widget_set_size_request (data_editor->view, -1, GRAD_VIEW_SIZE);
|
|
gtk_widget_set_events (data_editor->view, GRAD_VIEW_EVENT_MASK);
|
|
pika_view_set_expand (PIKA_VIEW (data_editor->view), TRUE);
|
|
gtk_box_pack_start (GTK_BOX (vbox), data_editor->view, TRUE, TRUE, 0);
|
|
gtk_widget_show (data_editor->view);
|
|
|
|
g_signal_connect (data_editor->view, "event",
|
|
G_CALLBACK (view_events),
|
|
editor);
|
|
|
|
pika_dnd_viewable_dest_add (GTK_WIDGET (data_editor->view),
|
|
PIKA_TYPE_GRADIENT,
|
|
gradient_editor_drop_gradient,
|
|
editor);
|
|
|
|
pika_dnd_color_dest_add (GTK_WIDGET (data_editor->view),
|
|
gradient_editor_drop_color,
|
|
editor);
|
|
|
|
editor->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (data_editor->view));
|
|
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (editor->zoom_gesture),
|
|
GTK_PHASE_CAPTURE);
|
|
|
|
|
|
g_signal_connect (editor->zoom_gesture, "begin",
|
|
G_CALLBACK (view_zoom_gesture_begin),
|
|
editor);
|
|
g_signal_connect (editor->zoom_gesture, "update",
|
|
G_CALLBACK (view_zoom_gesture_update),
|
|
editor);
|
|
|
|
/* Gradient control */
|
|
editor->control = gtk_drawing_area_new ();
|
|
gtk_widget_set_size_request (editor->control, -1, GRAD_CONTROL_HEIGHT);
|
|
gtk_widget_set_events (editor->control, GRAD_CONTROL_EVENT_MASK);
|
|
gtk_box_pack_start (GTK_BOX (vbox), editor->control, FALSE, FALSE, 0);
|
|
gtk_widget_show (editor->control);
|
|
|
|
g_signal_connect (editor->control, "event",
|
|
G_CALLBACK (control_events),
|
|
editor);
|
|
|
|
g_signal_connect (editor->control, "draw",
|
|
G_CALLBACK (control_draw),
|
|
editor);
|
|
|
|
pika_dnd_color_dest_add (GTK_WIDGET (editor->control),
|
|
gradient_editor_control_drop_color,
|
|
editor);
|
|
|
|
/* Scrollbar */
|
|
editor->zoom_factor = 1;
|
|
|
|
editor->scroll_data = gtk_adjustment_new (0.0, 0.0, 1.0,
|
|
GRAD_SCROLLBAR_STEP_SIZE,
|
|
GRAD_SCROLLBAR_PAGE_SIZE,
|
|
1.0);
|
|
|
|
g_signal_connect (editor->scroll_data, "value-changed",
|
|
G_CALLBACK (gradient_editor_scrollbar_update),
|
|
editor);
|
|
g_signal_connect (editor->scroll_data, "changed",
|
|
G_CALLBACK (gradient_editor_scrollbar_update),
|
|
editor);
|
|
|
|
editor->scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_HORIZONTAL,
|
|
editor->scroll_data);
|
|
gtk_box_pack_start (GTK_BOX (editor), editor->scrollbar, FALSE, FALSE, 0);
|
|
gtk_widget_show (editor->scrollbar);
|
|
|
|
/* Box for current color and the hint labels */
|
|
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
|
|
gtk_box_pack_start (GTK_BOX (editor), hbox, FALSE, FALSE, 0);
|
|
gtk_widget_show (hbox);
|
|
|
|
/* Frame showing current active color */
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|
gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
|
|
gtk_widget_show (frame);
|
|
|
|
editor->current_color = pika_color_area_new (&transp,
|
|
PIKA_COLOR_AREA_SMALL_CHECKS,
|
|
GDK_BUTTON1_MASK |
|
|
GDK_BUTTON2_MASK);
|
|
gtk_container_add (GTK_CONTAINER (frame), editor->current_color);
|
|
gtk_widget_set_size_request (editor->current_color,
|
|
GRAD_CURRENT_COLOR_WIDTH, -1);
|
|
gtk_widget_show (editor->current_color);
|
|
|
|
/* Hint box */
|
|
hint_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_box_pack_start (GTK_BOX (hbox), hint_vbox, TRUE, TRUE, 0);
|
|
gtk_widget_show (hint_vbox);
|
|
|
|
editor->hint_label1 = gradient_hint_label_add (GTK_BOX (hint_vbox));
|
|
editor->hint_label2 = gradient_hint_label_add (GTK_BOX (hint_vbox));
|
|
editor->hint_label3 = gradient_hint_label_add (GTK_BOX (hint_vbox));
|
|
editor->hint_label4 = gradient_hint_label_add (GTK_BOX (hint_vbox));
|
|
|
|
/* Black, 50% Gray, White, Clear */
|
|
pika_rgba_set (&editor->saved_colors[0], 0.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[1], 0.5, 0.5, 0.5, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[2], 1.0, 1.0, 1.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[3], 0.0, 0.0, 0.0, PIKA_OPACITY_TRANSPARENT);
|
|
|
|
/* Red, Yellow, Green, Cyan, Blue, Magenta */
|
|
pika_rgba_set (&editor->saved_colors[4], 1.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[5], 1.0, 1.0, 0.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[6], 0.0, 1.0, 0.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[7], 0.0, 1.0, 1.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[8], 0.0, 0.0, 1.0, PIKA_OPACITY_OPAQUE);
|
|
pika_rgba_set (&editor->saved_colors[9], 1.0, 0.0, 1.0, PIKA_OPACITY_OPAQUE);
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_constructed (GObject *object)
|
|
{
|
|
PikaGradientEditor *editor = PIKA_GRADIENT_EDITOR (object);
|
|
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
pika_editor_add_action_button (PIKA_EDITOR (editor), "gradient-editor",
|
|
"gradient-editor-zoom-out", NULL);
|
|
|
|
pika_editor_add_action_button (PIKA_EDITOR (editor), "gradient-editor",
|
|
"gradient-editor-zoom-in", NULL);
|
|
|
|
pika_editor_add_action_button (PIKA_EDITOR (editor), "gradient-editor",
|
|
"gradient-editor-zoom-all", NULL);
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_dispose (GObject *object)
|
|
{
|
|
PikaGradientEditor *editor = PIKA_GRADIENT_EDITOR (object);
|
|
|
|
if (editor->color_dialog)
|
|
gtk_dialog_response (GTK_DIALOG (editor->color_dialog),
|
|
GTK_RESPONSE_CANCEL);
|
|
|
|
g_clear_object (&editor->zoom_gesture);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_unmap (GtkWidget *widget)
|
|
{
|
|
PikaGradientEditor *editor = PIKA_GRADIENT_EDITOR (widget);
|
|
|
|
if (editor->color_dialog)
|
|
gtk_dialog_response (GTK_DIALOG (editor->color_dialog),
|
|
GTK_RESPONSE_CANCEL);
|
|
|
|
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_set_data (PikaDataEditor *editor,
|
|
PikaData *data)
|
|
{
|
|
PikaGradientEditor *gradient_editor = PIKA_GRADIENT_EDITOR (editor);
|
|
PikaData *old_data;
|
|
|
|
if (gradient_editor->color_dialog)
|
|
gtk_dialog_response (GTK_DIALOG (gradient_editor->color_dialog),
|
|
GTK_RESPONSE_CANCEL);
|
|
|
|
old_data = pika_data_editor_get_data (editor);
|
|
|
|
if (old_data)
|
|
g_signal_handlers_disconnect_by_func (old_data,
|
|
pika_gradient_editor_gradient_dirty,
|
|
gradient_editor);
|
|
|
|
PIKA_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data);
|
|
|
|
if (data)
|
|
g_signal_connect_swapped (data, "dirty",
|
|
G_CALLBACK (pika_gradient_editor_gradient_dirty),
|
|
gradient_editor);
|
|
|
|
pika_view_set_viewable (PIKA_VIEW (editor->view),
|
|
PIKA_VIEWABLE (data));
|
|
|
|
control_update (gradient_editor, PIKA_GRADIENT (data), TRUE);
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_set_context (PikaDocked *docked,
|
|
PikaContext *context)
|
|
{
|
|
PikaDataEditor *data_editor = PIKA_DATA_EDITOR (docked);
|
|
|
|
parent_docked_iface->set_context (docked, context);
|
|
|
|
pika_view_renderer_set_context (PIKA_VIEW (data_editor->view)->renderer,
|
|
context);
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
GtkWidget *
|
|
pika_gradient_editor_new (PikaContext *context,
|
|
PikaMenuFactory *menu_factory)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_GRADIENT_EDITOR,
|
|
"menu-factory", menu_factory,
|
|
"menu-identifier", "<GradientEditor>",
|
|
"ui-path", "/gradient-editor-popup",
|
|
"data-factory", context->pika->gradient_factory,
|
|
"context", context,
|
|
"data", pika_context_get_gradient (context),
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
pika_gradient_editor_get_selection (PikaGradientEditor *editor,
|
|
PikaGradient **gradient,
|
|
PikaGradientSegment **left,
|
|
PikaGradientSegment **right)
|
|
{
|
|
g_return_if_fail (PIKA_IS_GRADIENT_EDITOR (editor));
|
|
|
|
if (gradient)
|
|
*gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
if (left)
|
|
*left = editor->control_sel_l;
|
|
|
|
if (right)
|
|
*right = editor->control_sel_r;
|
|
}
|
|
|
|
void
|
|
pika_gradient_editor_set_selection (PikaGradientEditor *editor,
|
|
PikaGradientSegment *left,
|
|
PikaGradientSegment *right)
|
|
{
|
|
g_return_if_fail (PIKA_IS_GRADIENT_EDITOR (editor));
|
|
g_return_if_fail (left != NULL);
|
|
g_return_if_fail (right != NULL);
|
|
|
|
editor->control_sel_l = left;
|
|
editor->control_sel_r = right;
|
|
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
}
|
|
|
|
void
|
|
pika_gradient_editor_edit_left_color (PikaGradientEditor *editor)
|
|
{
|
|
PikaGradient *gradient;
|
|
|
|
g_return_if_fail (PIKA_IS_GRADIENT_EDITOR (editor));
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
if (! gradient ||
|
|
! editor->control_sel_l ||
|
|
editor->control_sel_l->left_color_type != PIKA_GRADIENT_COLOR_FIXED)
|
|
return;
|
|
|
|
editor->saved_dirty = pika_data_is_dirty (PIKA_DATA (gradient));
|
|
editor->saved_segments = gradient_editor_save_selection (editor);
|
|
|
|
editor->color_dialog =
|
|
pika_color_dialog_new (PIKA_VIEWABLE (gradient),
|
|
PIKA_DATA_EDITOR (editor)->context,
|
|
TRUE,
|
|
_("Left Endpoint Color"),
|
|
PIKA_ICON_GRADIENT,
|
|
_("Gradient Segment's Left Endpoint Color"),
|
|
GTK_WIDGET (editor),
|
|
pika_dialog_factory_get_singleton (),
|
|
"pika-gradient-editor-color-dialog",
|
|
&editor->control_sel_l->left_color,
|
|
TRUE, TRUE);
|
|
|
|
g_signal_connect (editor->color_dialog, "destroy",
|
|
G_CALLBACK (gtk_widget_destroyed),
|
|
&editor->color_dialog);
|
|
|
|
g_signal_connect (editor->color_dialog, "update",
|
|
G_CALLBACK (gradient_editor_left_color_update),
|
|
editor);
|
|
|
|
gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
|
|
gtk_window_present (GTK_WINDOW (editor->color_dialog));
|
|
}
|
|
|
|
void
|
|
pika_gradient_editor_edit_right_color (PikaGradientEditor *editor)
|
|
{
|
|
PikaGradient *gradient;
|
|
|
|
g_return_if_fail (PIKA_IS_GRADIENT_EDITOR (editor));
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
if (! gradient ||
|
|
! editor->control_sel_r ||
|
|
editor->control_sel_r->right_color_type != PIKA_GRADIENT_COLOR_FIXED)
|
|
return;
|
|
|
|
editor->saved_dirty = pika_data_is_dirty (PIKA_DATA (gradient));
|
|
editor->saved_segments = gradient_editor_save_selection (editor);
|
|
|
|
editor->color_dialog =
|
|
pika_color_dialog_new (PIKA_VIEWABLE (gradient),
|
|
PIKA_DATA_EDITOR (editor)->context,
|
|
TRUE,
|
|
_("Right Endpoint Color"),
|
|
PIKA_ICON_GRADIENT,
|
|
_("Gradient Segment's Right Endpoint Color"),
|
|
GTK_WIDGET (editor),
|
|
pika_dialog_factory_get_singleton (),
|
|
"pika-gradient-editor-color-dialog",
|
|
&editor->control_sel_l->right_color,
|
|
TRUE, TRUE);
|
|
|
|
g_signal_connect (editor->color_dialog, "destroy",
|
|
G_CALLBACK (gtk_widget_destroyed),
|
|
&editor->color_dialog);
|
|
|
|
g_signal_connect (editor->color_dialog, "update",
|
|
G_CALLBACK (gradient_editor_right_color_update),
|
|
editor);
|
|
|
|
gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
|
|
gtk_window_present (GTK_WINDOW (editor->color_dialog));
|
|
}
|
|
|
|
void
|
|
pika_gradient_editor_zoom (PikaGradientEditor *editor,
|
|
PikaZoomType zoom_type,
|
|
gdouble delta,
|
|
gdouble zoom_focus_x)
|
|
{
|
|
GtkAdjustment *adjustment;
|
|
gdouble old_value;
|
|
gdouble old_page_size;
|
|
gdouble value = 0.0;
|
|
gdouble page_size = 1.0;
|
|
|
|
g_return_if_fail (PIKA_IS_GRADIENT_EDITOR (editor));
|
|
|
|
if (zoom_type == PIKA_ZOOM_SMOOTH)
|
|
{
|
|
if (delta < 0)
|
|
zoom_type = PIKA_ZOOM_IN;
|
|
else if (delta > 0)
|
|
zoom_type = PIKA_ZOOM_OUT;
|
|
else
|
|
return;
|
|
|
|
delta = ABS (delta);
|
|
}
|
|
else if (zoom_type != PIKA_ZOOM_PINCH)
|
|
{
|
|
delta = 1.0;
|
|
}
|
|
|
|
adjustment = editor->scroll_data;
|
|
|
|
old_value = gtk_adjustment_get_value (adjustment);
|
|
old_page_size = gtk_adjustment_get_page_size (adjustment);
|
|
|
|
switch (zoom_type)
|
|
{
|
|
case PIKA_ZOOM_IN_MAX:
|
|
case PIKA_ZOOM_IN_MORE:
|
|
case PIKA_ZOOM_IN:
|
|
editor->zoom_factor += delta;
|
|
|
|
page_size = 1.0 / editor->zoom_factor;
|
|
value = old_value + (old_page_size - page_size) * zoom_focus_x;
|
|
break;
|
|
|
|
case PIKA_ZOOM_OUT_MORE:
|
|
case PIKA_ZOOM_OUT:
|
|
editor->zoom_factor -= delta;
|
|
|
|
if (editor->zoom_factor < 1)
|
|
editor->zoom_factor = 1;
|
|
|
|
page_size = 1.0 / editor->zoom_factor;
|
|
value = old_value - (page_size - old_page_size) * zoom_focus_x;
|
|
|
|
if (value < 0.0)
|
|
value = 0.0;
|
|
else if ((value + page_size) > 1.0)
|
|
value = 1.0 - page_size;
|
|
break;
|
|
|
|
case PIKA_ZOOM_OUT_MAX:
|
|
case PIKA_ZOOM_TO: /* abused as ZOOM_ALL */
|
|
editor->zoom_factor = 1;
|
|
|
|
value = 0.0;
|
|
page_size = 1.0;
|
|
break;
|
|
|
|
case PIKA_ZOOM_PINCH:
|
|
if (delta > 0.0)
|
|
editor->zoom_factor = editor->zoom_factor * (1.0 + delta);
|
|
else if (delta < 0.0)
|
|
editor->zoom_factor = editor->zoom_factor / (1.0 + -delta);
|
|
else
|
|
return;
|
|
|
|
if (editor->zoom_factor < 1)
|
|
editor->zoom_factor = 1;
|
|
|
|
page_size = 1.0 / editor->zoom_factor;
|
|
value = old_value + (old_page_size - page_size) * zoom_focus_x;
|
|
|
|
if (value < 0.0)
|
|
value = 0.0;
|
|
else if ((value + page_size) > 1.0)
|
|
value = 1.0 - page_size;
|
|
break;
|
|
|
|
case PIKA_ZOOM_SMOOTH: /* can't happen, see above switch() */
|
|
break;
|
|
}
|
|
|
|
gtk_adjustment_configure (adjustment,
|
|
value,
|
|
gtk_adjustment_get_lower (adjustment),
|
|
gtk_adjustment_get_upper (adjustment),
|
|
page_size * GRAD_SCROLLBAR_STEP_SIZE,
|
|
page_size * GRAD_SCROLLBAR_PAGE_SIZE,
|
|
page_size);
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
pika_gradient_editor_update (PikaGradientEditor *editor)
|
|
{
|
|
PikaGradient *gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
control_update (editor, gradient, FALSE);
|
|
}
|
|
|
|
static void
|
|
pika_gradient_editor_gradient_dirty (PikaGradientEditor *editor,
|
|
PikaGradient *gradient)
|
|
{
|
|
PikaGradientSegment *segment;
|
|
gboolean left_seen = FALSE;
|
|
gboolean right_seen = FALSE;
|
|
|
|
for (segment = gradient->segments; segment; segment = segment->next)
|
|
{
|
|
if (segment == editor->control_sel_l)
|
|
left_seen = TRUE;
|
|
|
|
if (segment == editor->control_sel_r)
|
|
right_seen = TRUE;
|
|
|
|
if (right_seen && ! left_seen)
|
|
{
|
|
PikaGradientSegment *tmp;
|
|
|
|
tmp = editor->control_sel_l;
|
|
editor->control_sel_l = editor->control_sel_r;
|
|
editor->control_sel_r = tmp;
|
|
|
|
right_seen = FALSE;
|
|
left_seen = TRUE;
|
|
}
|
|
}
|
|
|
|
control_update (editor, gradient, ! (left_seen && right_seen));
|
|
}
|
|
|
|
static void
|
|
gradient_editor_drop_gradient (GtkWidget *widget,
|
|
gint x,
|
|
gint y,
|
|
PikaViewable *viewable,
|
|
gpointer data)
|
|
{
|
|
pika_data_editor_set_data (PIKA_DATA_EDITOR (data), PIKA_DATA (viewable));
|
|
}
|
|
|
|
static void
|
|
gradient_editor_drop_color (GtkWidget *widget,
|
|
gint x,
|
|
gint y,
|
|
const PikaRGB *color,
|
|
gpointer data)
|
|
{
|
|
PikaGradientEditor *editor = PIKA_GRADIENT_EDITOR (data);
|
|
PikaGradient *gradient;
|
|
gdouble xpos;
|
|
PikaGradientSegment *seg, *lseg, *rseg;
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
xpos = control_calc_g_pos (editor, x);
|
|
seg = pika_gradient_get_segment_at (gradient, xpos);
|
|
|
|
pika_data_freeze (PIKA_DATA (gradient));
|
|
|
|
pika_gradient_segment_split_midpoint (gradient,
|
|
PIKA_DATA_EDITOR (editor)->context,
|
|
seg,
|
|
editor->blend_color_space,
|
|
&lseg, &rseg);
|
|
|
|
if (lseg)
|
|
{
|
|
lseg->right = xpos;
|
|
lseg->middle = (lseg->left + lseg->right) / 2.0;
|
|
lseg->right_color = *color;
|
|
}
|
|
|
|
if (rseg)
|
|
{
|
|
rseg->left = xpos;
|
|
rseg->middle = (rseg->left + rseg->right) / 2.0;
|
|
rseg->left_color = *color;
|
|
}
|
|
|
|
pika_data_thaw (PIKA_DATA (gradient));
|
|
}
|
|
|
|
static void
|
|
gradient_editor_control_drop_color (GtkWidget *widget,
|
|
gint x,
|
|
gint y,
|
|
const PikaRGB *color,
|
|
gpointer data)
|
|
{
|
|
PikaGradientEditor *editor = PIKA_GRADIENT_EDITOR (data);
|
|
PikaGradient *gradient;
|
|
gdouble xpos;
|
|
PikaGradientSegment *seg, *lseg, *rseg;
|
|
GradientEditorDragMode handle;
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
xpos = control_calc_g_pos (editor, x);
|
|
seg_get_closest_handle (gradient, xpos, &seg, &handle);
|
|
|
|
if (seg)
|
|
{
|
|
if (handle == GRAD_DRAG_LEFT)
|
|
{
|
|
lseg = seg->prev;
|
|
rseg = seg;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
lseg = pika_gradient_get_segment_at (gradient, xpos);
|
|
rseg = NULL;
|
|
}
|
|
|
|
pika_data_freeze (PIKA_DATA (gradient));
|
|
|
|
if (lseg)
|
|
lseg->right_color = *color;
|
|
|
|
if (rseg)
|
|
rseg->left_color = *color;
|
|
|
|
pika_data_thaw (PIKA_DATA (gradient));
|
|
}
|
|
|
|
static void
|
|
gradient_editor_scrollbar_update (GtkAdjustment *adjustment,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
PikaDataEditor *data_editor = PIKA_DATA_EDITOR (editor);
|
|
PikaViewRendererGradient *renderer;
|
|
gchar *str1;
|
|
gchar *str2;
|
|
|
|
str1 = g_strdup_printf (_("Zoom factor: %f:1"),
|
|
editor->zoom_factor);
|
|
|
|
str2 = g_strdup_printf (_("Displaying [%0.4f, %0.4f]"),
|
|
gtk_adjustment_get_value (adjustment),
|
|
gtk_adjustment_get_value (adjustment) +
|
|
gtk_adjustment_get_page_size (adjustment));
|
|
|
|
gradient_editor_set_hint (editor, str1, str2, NULL, NULL);
|
|
|
|
g_free (str1);
|
|
g_free (str2);
|
|
|
|
renderer = PIKA_VIEW_RENDERER_GRADIENT (PIKA_VIEW (data_editor->view)->renderer);
|
|
|
|
pika_view_renderer_gradient_set_offsets (renderer,
|
|
gtk_adjustment_get_value (adjustment),
|
|
gtk_adjustment_get_value (adjustment) +
|
|
gtk_adjustment_get_page_size (adjustment));
|
|
|
|
pika_view_renderer_update (PIKA_VIEW_RENDERER (renderer));
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
|
|
static void
|
|
gradient_editor_set_hint (PikaGradientEditor *editor,
|
|
const gchar *str1,
|
|
const gchar *str2,
|
|
const gchar *str3,
|
|
const gchar *str4)
|
|
{
|
|
gtk_label_set_text (GTK_LABEL (editor->hint_label1), str1);
|
|
gtk_label_set_text (GTK_LABEL (editor->hint_label2), str2);
|
|
gtk_label_set_text (GTK_LABEL (editor->hint_label3), str3);
|
|
gtk_label_set_text (GTK_LABEL (editor->hint_label4), str4);
|
|
}
|
|
|
|
static PikaGradientSegment *
|
|
gradient_editor_save_selection (PikaGradientEditor *editor)
|
|
{
|
|
PikaGradientSegment *seg, *prev, *tmp;
|
|
PikaGradientSegment *oseg, *oaseg;
|
|
|
|
prev = NULL;
|
|
oseg = editor->control_sel_l;
|
|
tmp = NULL;
|
|
|
|
do
|
|
{
|
|
seg = pika_gradient_segment_new ();
|
|
|
|
*seg = *oseg; /* Copy everything */
|
|
|
|
if (prev == NULL)
|
|
tmp = seg; /* Remember first segment */
|
|
else
|
|
prev->next = seg;
|
|
|
|
seg->prev = prev;
|
|
seg->next = NULL;
|
|
|
|
prev = seg;
|
|
oaseg = oseg;
|
|
oseg = oseg->next;
|
|
}
|
|
while (oaseg != editor->control_sel_r);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
static void
|
|
gradient_editor_replace_selection (PikaGradientEditor *editor,
|
|
PikaGradientSegment *replace_seg)
|
|
{
|
|
PikaGradient *gradient;
|
|
PikaGradientSegment *lseg, *rseg;
|
|
PikaGradientSegment *replace_last;
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
/* Remember left and right segments */
|
|
|
|
lseg = editor->control_sel_l->prev;
|
|
rseg = editor->control_sel_r->next;
|
|
|
|
replace_last = pika_gradient_segment_get_last (replace_seg);
|
|
|
|
/* Free old selection */
|
|
|
|
editor->control_sel_r->next = NULL;
|
|
|
|
pika_gradient_segments_free (editor->control_sel_l);
|
|
|
|
/* Link in new segments */
|
|
|
|
if (lseg)
|
|
lseg->next = replace_seg;
|
|
else
|
|
gradient->segments = replace_seg;
|
|
|
|
replace_seg->prev = lseg;
|
|
|
|
if (rseg)
|
|
rseg->prev = replace_last;
|
|
|
|
replace_last->next = rseg;
|
|
|
|
editor->control_sel_l = replace_seg;
|
|
editor->control_sel_r = replace_last;
|
|
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
}
|
|
|
|
static void
|
|
gradient_editor_left_color_update (PikaColorDialog *dialog,
|
|
const PikaRGB *color,
|
|
PikaColorDialogState state,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
PikaGradient *gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
switch (state)
|
|
{
|
|
case PIKA_COLOR_DIALOG_UPDATE:
|
|
pika_gradient_segment_range_blend (gradient,
|
|
editor->control_sel_l,
|
|
editor->control_sel_r,
|
|
color,
|
|
&editor->control_sel_r->right_color,
|
|
TRUE, TRUE);
|
|
break;
|
|
|
|
case PIKA_COLOR_DIALOG_OK:
|
|
pika_gradient_segment_range_blend (gradient,
|
|
editor->control_sel_l,
|
|
editor->control_sel_r,
|
|
color,
|
|
&editor->control_sel_r->right_color,
|
|
TRUE, TRUE);
|
|
pika_gradient_segments_free (editor->saved_segments);
|
|
gtk_widget_destroy (editor->color_dialog);
|
|
editor->color_dialog = NULL;
|
|
gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
break;
|
|
|
|
case PIKA_COLOR_DIALOG_CANCEL:
|
|
gradient_editor_replace_selection (editor, editor->saved_segments);
|
|
if (! editor->saved_dirty)
|
|
pika_data_clean (PIKA_DATA (gradient));
|
|
pika_viewable_invalidate_preview (PIKA_VIEWABLE (gradient));
|
|
gtk_widget_destroy (editor->color_dialog);
|
|
editor->color_dialog = NULL;
|
|
gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gradient_editor_right_color_update (PikaColorDialog *dialog,
|
|
const PikaRGB *color,
|
|
PikaColorDialogState state,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
PikaGradient *gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
switch (state)
|
|
{
|
|
case PIKA_COLOR_DIALOG_UPDATE:
|
|
pika_gradient_segment_range_blend (gradient,
|
|
editor->control_sel_l,
|
|
editor->control_sel_r,
|
|
&editor->control_sel_l->left_color,
|
|
color,
|
|
TRUE, TRUE);
|
|
break;
|
|
|
|
case PIKA_COLOR_DIALOG_OK:
|
|
pika_gradient_segment_range_blend (gradient,
|
|
editor->control_sel_l,
|
|
editor->control_sel_r,
|
|
&editor->control_sel_l->left_color,
|
|
color,
|
|
TRUE, TRUE);
|
|
pika_gradient_segments_free (editor->saved_segments);
|
|
gtk_widget_destroy (editor->color_dialog);
|
|
editor->color_dialog = NULL;
|
|
gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
break;
|
|
|
|
case PIKA_COLOR_DIALOG_CANCEL:
|
|
gradient_editor_replace_selection (editor, editor->saved_segments);
|
|
if (! editor->saved_dirty)
|
|
pika_data_clean (PIKA_DATA (gradient));
|
|
pika_viewable_invalidate_preview (PIKA_VIEWABLE (gradient));
|
|
gtk_widget_destroy (editor->color_dialog);
|
|
editor->color_dialog = NULL;
|
|
gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/***** Gradient view functions *****/
|
|
|
|
static gboolean
|
|
view_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
PikaDataEditor *data_editor = PIKA_DATA_EDITOR (editor);
|
|
|
|
if (! data_editor->data)
|
|
return TRUE;
|
|
|
|
switch (event->type)
|
|
{
|
|
case GDK_LEAVE_NOTIFY:
|
|
gradient_editor_set_hint (editor, NULL, NULL, NULL, NULL);
|
|
editor->view_last_x = -1;
|
|
break;
|
|
|
|
case GDK_MOTION_NOTIFY:
|
|
{
|
|
GdkEventMotion *mevent = (GdkEventMotion *) event;
|
|
|
|
if (mevent->x != editor->view_last_x)
|
|
{
|
|
editor->view_last_x = mevent->x;
|
|
|
|
if (editor->view_button_down)
|
|
{
|
|
view_pick_color (editor,
|
|
(mevent->state & pika_get_toggle_behavior_mask ()) ?
|
|
PIKA_COLOR_PICK_TARGET_BACKGROUND :
|
|
PIKA_COLOR_PICK_TARGET_FOREGROUND,
|
|
PIKA_COLOR_PICK_STATE_UPDATE,
|
|
mevent->x);
|
|
}
|
|
else
|
|
{
|
|
view_set_hint (editor, mevent->x);
|
|
}
|
|
}
|
|
|
|
gdk_event_request_motions (mevent);
|
|
}
|
|
break;
|
|
|
|
case GDK_BUTTON_PRESS:
|
|
{
|
|
GdkEventButton *bevent = (GdkEventButton *) event;
|
|
|
|
if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
|
|
{
|
|
pika_editor_popup_menu_at_pointer (PIKA_EDITOR (editor), event);
|
|
}
|
|
else if (bevent->button == 1)
|
|
{
|
|
editor->view_last_x = bevent->x;
|
|
editor->view_button_down = TRUE;
|
|
|
|
view_pick_color (editor,
|
|
(bevent->state & pika_get_toggle_behavior_mask ()) ?
|
|
PIKA_COLOR_PICK_TARGET_BACKGROUND :
|
|
PIKA_COLOR_PICK_TARGET_FOREGROUND,
|
|
PIKA_COLOR_PICK_STATE_START,
|
|
bevent->x);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GDK_SCROLL:
|
|
{
|
|
GdkEventScroll *sevent = (GdkEventScroll *) event;
|
|
|
|
if (sevent->state & pika_get_toggle_behavior_mask ())
|
|
{
|
|
gdouble delta;
|
|
|
|
switch (sevent->direction)
|
|
{
|
|
case GDK_SCROLL_UP:
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_IN, 1.0,
|
|
view_get_normalized_last_x_pos (editor));
|
|
break;
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_OUT, 1.0,
|
|
view_get_normalized_last_x_pos (editor));
|
|
break;
|
|
|
|
case GDK_SCROLL_SMOOTH:
|
|
gdk_event_get_scroll_deltas (event, NULL, &delta);
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_SMOOTH, delta,
|
|
view_get_normalized_last_x_pos (editor));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gdouble value;
|
|
|
|
pika_scroll_adjustment_values (sevent,
|
|
editor->scroll_data, NULL,
|
|
&value, NULL);
|
|
|
|
gtk_adjustment_set_value (editor->scroll_data, value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
if (editor->view_button_down)
|
|
{
|
|
GdkEventButton *bevent = (GdkEventButton *) event;
|
|
|
|
editor->view_last_x = bevent->x;
|
|
editor->view_button_down = FALSE;
|
|
|
|
view_pick_color (editor,
|
|
(bevent->state & pika_get_toggle_behavior_mask ()) ?
|
|
PIKA_COLOR_PICK_TARGET_BACKGROUND :
|
|
PIKA_COLOR_PICK_TARGET_FOREGROUND,
|
|
PIKA_COLOR_PICK_STATE_END,
|
|
bevent->x);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
view_set_hint (PikaGradientEditor *editor,
|
|
gint x)
|
|
{
|
|
PikaDataEditor *data_editor = PIKA_DATA_EDITOR (editor);
|
|
PikaRGB rgb;
|
|
PikaHSV hsv;
|
|
gdouble xpos;
|
|
gchar *str1;
|
|
gchar *str2;
|
|
gchar *str3;
|
|
gchar *str4;
|
|
|
|
xpos = control_calc_g_pos (editor, x);
|
|
|
|
pika_gradient_get_color_at (PIKA_GRADIENT (data_editor->data),
|
|
data_editor->context, NULL,
|
|
xpos, FALSE, FALSE, &rgb);
|
|
|
|
pika_color_area_set_color (PIKA_COLOR_AREA (editor->current_color), &rgb);
|
|
|
|
pika_rgb_to_hsv (&rgb, &hsv);
|
|
|
|
str1 = g_strdup_printf (_("Position: %0.4f"), xpos);
|
|
str2 = g_strdup_printf (_("RGB (%0.3f, %0.3f, %0.3f)"),
|
|
rgb.r, rgb.g, rgb.b);
|
|
str3 = g_strdup_printf (_("HSV (%0.1f, %0.1f, %0.1f)"),
|
|
hsv.h * 360.0, hsv.s * 100.0, hsv.v * 100.0);
|
|
str4 = g_strdup_printf (_("Luminance: %0.1f Opacity: %0.1f"),
|
|
PIKA_RGB_LUMINANCE (rgb.r, rgb.g, rgb.b) * 100.0,
|
|
rgb.a * 100.0);
|
|
|
|
gradient_editor_set_hint (editor, str1, str2, str3, str4);
|
|
|
|
g_free (str1);
|
|
g_free (str2);
|
|
g_free (str3);
|
|
g_free (str4);
|
|
}
|
|
|
|
static void
|
|
view_pick_color (PikaGradientEditor *editor,
|
|
PikaColorPickTarget pick_target,
|
|
PikaColorPickState pick_state,
|
|
gint x)
|
|
{
|
|
PikaDataEditor *data_editor = PIKA_DATA_EDITOR (editor);
|
|
PikaRGB color;
|
|
gdouble xpos;
|
|
gchar *str2;
|
|
gchar *str3;
|
|
|
|
xpos = control_calc_g_pos (editor, x);
|
|
|
|
pika_gradient_get_color_at (PIKA_GRADIENT (data_editor->data),
|
|
data_editor->context, NULL,
|
|
xpos, FALSE, FALSE, &color);
|
|
|
|
pika_color_area_set_color (PIKA_COLOR_AREA (editor->current_color), &color);
|
|
|
|
str2 = g_strdup_printf (_("RGB (%d, %d, %d)"),
|
|
(gint) (color.r * 255.0),
|
|
(gint) (color.g * 255.0),
|
|
(gint) (color.b * 255.0));
|
|
|
|
str3 = g_strdup_printf ("(%0.3f, %0.3f, %0.3f)", color.r, color.g, color.b);
|
|
|
|
if (pick_target == PIKA_COLOR_PICK_TARGET_FOREGROUND)
|
|
{
|
|
pika_context_set_foreground (data_editor->context, &color);
|
|
|
|
gradient_editor_set_hint (editor, _("Foreground color set to:"),
|
|
str2, str3, NULL);
|
|
}
|
|
else
|
|
{
|
|
pika_context_set_background (data_editor->context, &color);
|
|
|
|
gradient_editor_set_hint (editor, _("Background color set to:"),
|
|
str2, str3, NULL);
|
|
}
|
|
|
|
g_free (str2);
|
|
g_free (str3);
|
|
}
|
|
|
|
static void
|
|
view_zoom_gesture_begin (GtkGestureZoom *gesture,
|
|
GdkEventSequence *sequence,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
editor->last_zoom_scale = gtk_gesture_zoom_get_scale_delta (gesture);
|
|
}
|
|
|
|
static void
|
|
view_zoom_gesture_update (GtkGestureZoom *gesture,
|
|
GdkEventSequence *sequence,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
gdouble current_scale = gtk_gesture_zoom_get_scale_delta (gesture);
|
|
gdouble delta = (current_scale - editor->last_zoom_scale) / editor->last_zoom_scale;
|
|
editor->last_zoom_scale = current_scale;
|
|
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_PINCH, delta,
|
|
view_get_normalized_last_x_pos (editor));
|
|
}
|
|
|
|
static gdouble
|
|
view_get_normalized_last_x_pos (PikaGradientEditor *editor)
|
|
{
|
|
GtkAllocation allocation;
|
|
gdouble normalized;
|
|
if (editor->view_last_x < 0)
|
|
return 0.5;
|
|
|
|
gtk_widget_get_allocation (PIKA_DATA_EDITOR (editor)->view, &allocation);
|
|
normalized = (double) editor->view_last_x / (allocation.width - 1);
|
|
|
|
if (normalized < 0)
|
|
return 0;
|
|
if (normalized > 1.0)
|
|
return 1;
|
|
return normalized;
|
|
}
|
|
|
|
/***** Gradient control functions *****/
|
|
|
|
static gboolean
|
|
control_events (GtkWidget *widget,
|
|
GdkEvent *event,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
PikaGradient *gradient;
|
|
PikaGradientSegment *seg;
|
|
|
|
if (! PIKA_DATA_EDITOR (editor)->data)
|
|
return TRUE;
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
switch (event->type)
|
|
{
|
|
case GDK_LEAVE_NOTIFY:
|
|
gradient_editor_set_hint (editor, NULL, NULL, NULL, NULL);
|
|
editor->control_last_x = -1;
|
|
break;
|
|
|
|
case GDK_BUTTON_PRESS:
|
|
if (editor->control_drag_mode == GRAD_DRAG_NONE)
|
|
{
|
|
GdkEventButton *bevent = (GdkEventButton *) event;
|
|
|
|
editor->control_last_x = bevent->x;
|
|
editor->control_click_time = bevent->time;
|
|
|
|
control_button_press (editor, bevent);
|
|
|
|
if (editor->control_drag_mode != GRAD_DRAG_NONE)
|
|
{
|
|
gtk_grab_add (widget);
|
|
|
|
if (PIKA_DATA_EDITOR (editor)->data_editable)
|
|
{
|
|
g_signal_handlers_block_by_func (gradient,
|
|
pika_gradient_editor_gradient_dirty,
|
|
editor);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GDK_SCROLL:
|
|
{
|
|
GdkEventScroll *sevent = (GdkEventScroll *) event;
|
|
|
|
if (sevent->state & pika_get_toggle_behavior_mask ())
|
|
{
|
|
gdouble delta;
|
|
|
|
switch (sevent->direction)
|
|
{
|
|
case GDK_SCROLL_UP:
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_IN, 1.0,
|
|
control_get_normalized_last_x_pos (editor));
|
|
break;
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_OUT, 1.0,
|
|
control_get_normalized_last_x_pos (editor));
|
|
break;
|
|
|
|
case GDK_SCROLL_SMOOTH:
|
|
gdk_event_get_scroll_deltas (event, NULL, &delta);
|
|
pika_gradient_editor_zoom (editor, PIKA_ZOOM_SMOOTH, delta,
|
|
control_get_normalized_last_x_pos (editor));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gdouble value;
|
|
|
|
pika_scroll_adjustment_values (sevent,
|
|
editor->scroll_data, NULL,
|
|
&value, NULL);
|
|
|
|
gtk_adjustment_set_value (editor->scroll_data, value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
{
|
|
GdkEventButton *bevent = (GdkEventButton *) event;
|
|
|
|
gradient_editor_set_hint (editor, NULL, NULL, NULL, NULL);
|
|
|
|
if (editor->control_drag_mode != GRAD_DRAG_NONE)
|
|
{
|
|
if (PIKA_DATA_EDITOR (editor)->data_editable)
|
|
{
|
|
g_signal_handlers_unblock_by_func (gradient,
|
|
pika_gradient_editor_gradient_dirty,
|
|
editor);
|
|
}
|
|
|
|
gtk_grab_remove (widget);
|
|
|
|
if ((bevent->time - editor->control_click_time) >= GRAD_MOVE_TIME)
|
|
{
|
|
/* stuff was done in motion */
|
|
}
|
|
else if ((editor->control_drag_mode == GRAD_DRAG_MIDDLE) ||
|
|
(editor->control_drag_mode == GRAD_DRAG_ALL))
|
|
{
|
|
seg = editor->control_drag_segment;
|
|
|
|
if ((editor->control_drag_mode == GRAD_DRAG_ALL) &&
|
|
editor->control_compress)
|
|
{
|
|
control_extend_selection (editor, seg,
|
|
control_calc_g_pos (editor,
|
|
bevent->x));
|
|
}
|
|
else
|
|
{
|
|
control_select_single_segment (editor, seg);
|
|
}
|
|
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
|
|
editor->control_drag_mode = GRAD_DRAG_NONE;
|
|
editor->control_compress = FALSE;
|
|
|
|
control_do_hint (editor, bevent->x, bevent->y);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GDK_MOTION_NOTIFY:
|
|
{
|
|
GdkEventMotion *mevent = (GdkEventMotion *) event;
|
|
|
|
if (mevent->x != editor->control_last_x)
|
|
{
|
|
editor->control_last_x = mevent->x;
|
|
|
|
if (PIKA_DATA_EDITOR (editor)->data_editable &&
|
|
editor->control_drag_mode != GRAD_DRAG_NONE)
|
|
{
|
|
|
|
if ((mevent->time - editor->control_click_time) >= GRAD_MOVE_TIME)
|
|
control_motion (editor, gradient, mevent->x);
|
|
}
|
|
else
|
|
{
|
|
pika_gradient_editor_update (editor);
|
|
|
|
control_do_hint (editor, mevent->x, mevent->y);
|
|
}
|
|
}
|
|
|
|
gdk_event_request_motions (mevent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
control_draw (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
PikaGradientEditor *editor)
|
|
{
|
|
GtkAdjustment *adj = editor->scroll_data;
|
|
GtkAllocation allocation;
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
control_draw_all (editor,
|
|
PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data),
|
|
cr,
|
|
allocation.width,
|
|
allocation.height,
|
|
gtk_adjustment_get_value (adj),
|
|
gtk_adjustment_get_value (adj) +
|
|
gtk_adjustment_get_page_size (adj));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
control_do_hint (PikaGradientEditor *editor,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
PikaGradient *gradient;
|
|
PikaGradientSegment *seg;
|
|
GradientEditorDragMode handle;
|
|
gboolean in_handle;
|
|
gdouble pos;
|
|
gchar *str;
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
pos = control_calc_g_pos (editor, x);
|
|
|
|
if ((pos < 0.0) || (pos > 1.0))
|
|
return;
|
|
|
|
seg_get_closest_handle (gradient, pos, &seg, &handle);
|
|
|
|
in_handle = control_point_in_handle (editor, gradient,
|
|
x, y, seg, handle);
|
|
|
|
if (in_handle)
|
|
{
|
|
switch (handle)
|
|
{
|
|
case GRAD_DRAG_LEFT:
|
|
if (seg != NULL)
|
|
{
|
|
if (seg->prev != NULL)
|
|
{
|
|
str = g_strdup_printf (_("%s-Drag: move & compress"),
|
|
pika_get_mod_string (GDK_SHIFT_MASK));
|
|
|
|
gradient_editor_set_hint (editor,
|
|
NULL,
|
|
_("Drag: move"),
|
|
str,
|
|
NULL);
|
|
g_free (str);
|
|
}
|
|
else
|
|
{
|
|
str = g_strdup_printf (_("%s-Click: extend selection"),
|
|
pika_get_mod_string (GDK_SHIFT_MASK));
|
|
|
|
gradient_editor_set_hint (editor,
|
|
NULL,
|
|
_("Click: select"),
|
|
str,
|
|
NULL);
|
|
g_free (str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str = g_strdup_printf (_("%s-Click: extend selection"),
|
|
pika_get_mod_string (GDK_SHIFT_MASK));
|
|
|
|
gradient_editor_set_hint (editor,
|
|
NULL,
|
|
_("Click: select"),
|
|
str,
|
|
NULL);
|
|
g_free (str);
|
|
}
|
|
break;
|
|
|
|
case GRAD_DRAG_MIDDLE:
|
|
str = g_strdup_printf (_("%s-Click: extend selection"),
|
|
pika_get_mod_string (GDK_SHIFT_MASK));
|
|
|
|
gradient_editor_set_hint (editor,
|
|
NULL,
|
|
_("Click: select Drag: move"),
|
|
str,
|
|
NULL);
|
|
g_free (str);
|
|
break;
|
|
|
|
default:
|
|
g_warning ("%s: in_handle is true, but received handle type %d.",
|
|
G_STRFUNC, in_handle);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gchar *str2;
|
|
|
|
str = g_strdup_printf (_("%s-Click: extend selection"),
|
|
pika_get_mod_string (GDK_SHIFT_MASK));
|
|
str2 = g_strdup_printf (_("%s-Drag: move & compress"),
|
|
pika_get_mod_string (GDK_SHIFT_MASK));
|
|
|
|
gradient_editor_set_hint (editor,
|
|
_("Click: select Drag: move"),
|
|
str,
|
|
str2,
|
|
NULL);
|
|
g_free (str);
|
|
g_free (str2);
|
|
}
|
|
}
|
|
|
|
static void
|
|
control_button_press (PikaGradientEditor *editor,
|
|
GdkEventButton *bevent)
|
|
{
|
|
PikaGradient *gradient;
|
|
PikaGradientSegment *seg;
|
|
GradientEditorDragMode handle;
|
|
gdouble xpos;
|
|
gboolean in_handle;
|
|
|
|
gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
|
|
{
|
|
pika_editor_popup_menu_at_pointer (PIKA_EDITOR (editor), (GdkEvent *) bevent);
|
|
return;
|
|
}
|
|
|
|
/* Find the closest handle */
|
|
|
|
xpos = control_calc_g_pos (editor, bevent->x);
|
|
|
|
seg_get_closest_handle (gradient, xpos, &seg, &handle);
|
|
|
|
in_handle = control_point_in_handle (editor, gradient, bevent->x, bevent->y, seg, handle);
|
|
|
|
/* Now see what we have */
|
|
|
|
if (in_handle)
|
|
{
|
|
switch (handle)
|
|
{
|
|
case GRAD_DRAG_LEFT:
|
|
if (seg != NULL)
|
|
{
|
|
/* Left handle of some segment */
|
|
if (bevent->state & GDK_SHIFT_MASK)
|
|
{
|
|
if (seg->prev != NULL)
|
|
{
|
|
editor->control_drag_mode = GRAD_DRAG_LEFT;
|
|
editor->control_drag_segment = seg;
|
|
editor->control_compress = TRUE;
|
|
}
|
|
else
|
|
{
|
|
control_extend_selection (editor, seg, xpos);
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
}
|
|
else if (seg->prev != NULL)
|
|
{
|
|
editor->control_drag_mode = GRAD_DRAG_LEFT;
|
|
editor->control_drag_segment = seg;
|
|
}
|
|
else
|
|
{
|
|
control_select_single_segment (editor, seg);
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
}
|
|
else /* seg == NULL */
|
|
{
|
|
/* Right handle of last segment */
|
|
seg = pika_gradient_segment_get_last (gradient->segments);
|
|
|
|
if (bevent->state & GDK_SHIFT_MASK)
|
|
{
|
|
control_extend_selection (editor, seg, xpos);
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
else
|
|
{
|
|
control_select_single_segment (editor, seg);
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case GRAD_DRAG_MIDDLE:
|
|
if (bevent->state & GDK_SHIFT_MASK)
|
|
{
|
|
control_extend_selection (editor, seg, xpos);
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
else
|
|
{
|
|
editor->control_drag_mode = GRAD_DRAG_MIDDLE;
|
|
editor->control_drag_segment = seg;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
g_warning ("%s: in_handle is true, but received handle type %d.",
|
|
G_STRFUNC, in_handle);
|
|
}
|
|
}
|
|
else /* !in_handle */
|
|
{
|
|
seg = pika_gradient_get_segment_at (gradient, xpos);
|
|
|
|
editor->control_drag_mode = GRAD_DRAG_ALL;
|
|
editor->control_drag_segment = seg;
|
|
editor->control_last_gx = xpos;
|
|
editor->control_orig_pos = xpos;
|
|
|
|
if (bevent->state & GDK_SHIFT_MASK)
|
|
editor->control_compress = TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
control_point_in_handle (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
gint x,
|
|
gint y,
|
|
PikaGradientSegment *seg,
|
|
GradientEditorDragMode handle)
|
|
{
|
|
gint handle_pos;
|
|
|
|
switch (handle)
|
|
{
|
|
case GRAD_DRAG_LEFT:
|
|
if (seg)
|
|
{
|
|
handle_pos = control_calc_p_pos (editor, seg->left);
|
|
}
|
|
else
|
|
{
|
|
seg = pika_gradient_segment_get_last (gradient->segments);
|
|
|
|
handle_pos = control_calc_p_pos (editor, seg->right);
|
|
}
|
|
|
|
break;
|
|
|
|
case GRAD_DRAG_MIDDLE:
|
|
handle_pos = control_calc_p_pos (editor, seg->middle);
|
|
break;
|
|
|
|
default:
|
|
g_warning ("%s: Cannot handle drag mode %d.", G_STRFUNC, handle);
|
|
return FALSE;
|
|
}
|
|
|
|
y /= 2;
|
|
|
|
if ((x >= (handle_pos - y)) && (x <= (handle_pos + y)))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/*****/
|
|
|
|
static void
|
|
control_select_single_segment (PikaGradientEditor *editor,
|
|
PikaGradientSegment *seg)
|
|
{
|
|
editor->control_sel_l = seg;
|
|
editor->control_sel_r = seg;
|
|
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
}
|
|
|
|
static void
|
|
control_extend_selection (PikaGradientEditor *editor,
|
|
PikaGradientSegment *seg,
|
|
gdouble pos)
|
|
{
|
|
if (fabs (pos - editor->control_sel_l->left) <
|
|
fabs (pos - editor->control_sel_r->right))
|
|
editor->control_sel_l = seg;
|
|
else
|
|
editor->control_sel_r = seg;
|
|
|
|
pika_ui_manager_update (pika_editor_get_ui_manager (PIKA_EDITOR (editor)),
|
|
pika_editor_get_popup_data (PIKA_EDITOR (editor)));
|
|
}
|
|
|
|
/*****/
|
|
|
|
static void
|
|
control_motion (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
gint x)
|
|
{
|
|
PikaGradientSegment *seg = editor->control_drag_segment;
|
|
gdouble pos;
|
|
gdouble delta;
|
|
gchar *str = NULL;
|
|
|
|
switch (editor->control_drag_mode)
|
|
{
|
|
case GRAD_DRAG_LEFT:
|
|
pos = control_calc_g_pos (editor, x);
|
|
|
|
if (! editor->control_compress)
|
|
pika_gradient_segment_set_left_pos (gradient, seg, pos);
|
|
else
|
|
control_compress_left (gradient,
|
|
editor->control_sel_l,
|
|
editor->control_sel_r,
|
|
seg, pos);
|
|
|
|
str = g_strdup_printf (_("Handle position: %0.4f"), seg->left);
|
|
break;
|
|
|
|
case GRAD_DRAG_MIDDLE:
|
|
pos = control_calc_g_pos (editor, x);
|
|
|
|
pika_gradient_segment_set_middle_pos (gradient, seg, pos);
|
|
|
|
str = g_strdup_printf (_("Handle position: %0.4f"), seg->middle);
|
|
break;
|
|
|
|
case GRAD_DRAG_ALL:
|
|
pos = control_calc_g_pos (editor, x);
|
|
delta = pos - editor->control_last_gx;
|
|
|
|
if ((seg->left >= editor->control_sel_l->left) &&
|
|
(seg->right <= editor->control_sel_r->right))
|
|
delta = control_move (editor,
|
|
editor->control_sel_l,
|
|
editor->control_sel_r, delta);
|
|
else
|
|
delta = control_move (editor, seg, seg, delta);
|
|
|
|
editor->control_last_gx += delta;
|
|
|
|
str = g_strdup_printf (_("Distance: %0.4f"),
|
|
editor->control_last_gx -
|
|
editor->control_orig_pos);
|
|
break;
|
|
|
|
default:
|
|
g_warning ("%s: Attempting to move bogus handle %d.",
|
|
G_STRFUNC, editor->control_drag_mode);
|
|
break;
|
|
}
|
|
|
|
gradient_editor_set_hint (editor, str, NULL, NULL, NULL);
|
|
g_free (str);
|
|
|
|
pika_gradient_editor_update (editor);
|
|
}
|
|
|
|
static void
|
|
control_compress_left (PikaGradient *gradient,
|
|
PikaGradientSegment *range_l,
|
|
PikaGradientSegment *range_r,
|
|
PikaGradientSegment *drag_seg,
|
|
gdouble pos)
|
|
{
|
|
PikaGradientSegment *seg;
|
|
gdouble lbound, rbound;
|
|
gint k;
|
|
|
|
/* Check what we have to compress */
|
|
|
|
if (!((drag_seg->left >= range_l->left) &&
|
|
((drag_seg->right <= range_r->right) || (drag_seg == range_r->next))))
|
|
{
|
|
/* We are compressing a segment outside the selection */
|
|
|
|
range_l = range_r = drag_seg;
|
|
}
|
|
|
|
/* Calculate left bound for dragged hadle */
|
|
|
|
if (drag_seg == range_l)
|
|
lbound = range_l->prev->left + 2.0 * EPSILON;
|
|
else
|
|
{
|
|
/* Count number of segments to the left of the dragged handle */
|
|
|
|
seg = drag_seg;
|
|
k = 0;
|
|
|
|
while (seg != range_l)
|
|
{
|
|
k++;
|
|
seg = seg->prev;
|
|
}
|
|
|
|
/* 2*k handles have to fit */
|
|
|
|
lbound = range_l->left + 2.0 * k * EPSILON;
|
|
}
|
|
|
|
/* Calculate right bound for dragged handle */
|
|
|
|
if (drag_seg == range_r->next)
|
|
rbound = range_r->next->right - 2.0 * EPSILON;
|
|
else
|
|
{
|
|
/* Count number of segments to the right of the dragged handle */
|
|
|
|
seg = drag_seg;
|
|
k = 1;
|
|
|
|
while (seg != range_r)
|
|
{
|
|
k++;
|
|
seg = seg->next;
|
|
}
|
|
|
|
/* 2*k handles have to fit */
|
|
|
|
rbound = range_r->right - 2.0 * k * EPSILON;
|
|
}
|
|
|
|
/* Calculate position */
|
|
|
|
pos = CLAMP (pos, lbound, rbound);
|
|
|
|
/* Compress segments to the left of the handle */
|
|
|
|
if (drag_seg == range_l)
|
|
pika_gradient_segment_range_compress (gradient,
|
|
range_l->prev, range_l->prev,
|
|
range_l->prev->left, pos);
|
|
else
|
|
pika_gradient_segment_range_compress (gradient,
|
|
range_l, drag_seg->prev,
|
|
range_l->left, pos);
|
|
|
|
/* Compress segments to the right of the handle */
|
|
|
|
if (drag_seg != range_r->next)
|
|
pika_gradient_segment_range_compress (gradient,
|
|
drag_seg, range_r,
|
|
pos, range_r->right);
|
|
else
|
|
pika_gradient_segment_range_compress (gradient,
|
|
drag_seg, drag_seg,
|
|
pos, drag_seg->right);
|
|
}
|
|
|
|
/*****/
|
|
|
|
static gdouble
|
|
control_move (PikaGradientEditor *editor,
|
|
PikaGradientSegment *range_l,
|
|
PikaGradientSegment *range_r,
|
|
gdouble delta)
|
|
{
|
|
PikaGradient *gradient = PIKA_GRADIENT (PIKA_DATA_EDITOR (editor)->data);
|
|
|
|
return pika_gradient_segment_range_move (gradient,
|
|
range_l,
|
|
range_r,
|
|
delta,
|
|
editor->control_compress);
|
|
}
|
|
|
|
/*****/
|
|
|
|
static gdouble
|
|
control_get_normalized_last_x_pos (PikaGradientEditor *editor)
|
|
{
|
|
GtkAllocation allocation;
|
|
gdouble normalized;
|
|
if (editor->control_last_x < 0)
|
|
return 0.5;
|
|
|
|
gtk_widget_get_allocation (editor->control, &allocation);
|
|
normalized = (double) editor->control_last_x / (allocation.width - 1);
|
|
|
|
if (normalized < 0)
|
|
return 0;
|
|
if (normalized > 1.0)
|
|
return 1;
|
|
return normalized;
|
|
}
|
|
|
|
/*****/
|
|
|
|
static void
|
|
control_update (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
gboolean reset_selection)
|
|
{
|
|
if (! editor->control_sel_l || ! editor->control_sel_r)
|
|
reset_selection = TRUE;
|
|
|
|
if (reset_selection)
|
|
{
|
|
if (gradient)
|
|
control_select_single_segment (editor, gradient->segments);
|
|
else
|
|
control_select_single_segment (editor, NULL);
|
|
}
|
|
|
|
gtk_widget_queue_draw (editor->control);
|
|
}
|
|
|
|
static void
|
|
control_draw_all (PikaGradientEditor *editor,
|
|
PikaGradient *gradient,
|
|
cairo_t *cr,
|
|
gint width,
|
|
gint height,
|
|
gdouble left,
|
|
gdouble right)
|
|
{
|
|
GtkStyleContext *style;
|
|
PikaGradientSegment *seg;
|
|
GradientEditorDragMode handle;
|
|
gint sel_l;
|
|
gint sel_r;
|
|
gdouble g_pos;
|
|
GtkStateFlags flags;
|
|
|
|
if (! gradient)
|
|
return;
|
|
|
|
/* Draw selection */
|
|
|
|
style = gtk_widget_get_style_context (editor->control);
|
|
|
|
sel_l = control_calc_p_pos (editor, editor->control_sel_l->left);
|
|
sel_r = control_calc_p_pos (editor, editor->control_sel_r->right);
|
|
|
|
gtk_style_context_save (style);
|
|
|
|
gtk_style_context_add_class (style, GTK_STYLE_CLASS_VIEW);
|
|
gtk_render_background (style, cr, 0, 0, width, height);
|
|
|
|
gtk_style_context_set_state (style, GTK_STATE_FLAG_SELECTED);
|
|
gtk_render_background (style, cr,sel_l, 0, sel_r - sel_l + 1, height);
|
|
|
|
gtk_style_context_restore (style);
|
|
|
|
/* Draw handles */
|
|
|
|
flags = GTK_STATE_FLAG_NORMAL;
|
|
|
|
for (seg = gradient->segments; seg; seg = seg->next)
|
|
{
|
|
if (seg == editor->control_sel_l)
|
|
flags = GTK_STATE_FLAG_SELECTED;
|
|
|
|
control_draw_handle (editor, style, cr, seg->left,
|
|
height, FALSE, flags);
|
|
control_draw_handle (editor, style, cr, seg->middle,
|
|
height, TRUE, flags);
|
|
|
|
/* Draw right handle only if this is the last segment */
|
|
if (seg->next == NULL)
|
|
control_draw_handle (editor, style, cr, seg->right,
|
|
height, FALSE, flags);
|
|
|
|
if (seg == editor->control_sel_r)
|
|
flags = GTK_STATE_FLAG_NORMAL;
|
|
}
|
|
|
|
/* Draw the handle which is closest to the mouse position */
|
|
|
|
flags = GTK_STATE_FLAG_PRELIGHT;
|
|
|
|
g_pos = control_calc_g_pos (editor, editor->control_last_x);
|
|
|
|
seg_get_closest_handle (gradient, CLAMP (g_pos, 0.0, 1.0), &seg, &handle);
|
|
|
|
if (seg && seg_in_selection (gradient, seg,
|
|
editor->control_sel_l, editor->control_sel_r))
|
|
flags |= GTK_STATE_FLAG_SELECTED;
|
|
|
|
switch (handle)
|
|
{
|
|
case GRAD_DRAG_LEFT:
|
|
if (seg)
|
|
{
|
|
control_draw_handle (editor, style, cr, seg->left,
|
|
height, FALSE, flags);
|
|
}
|
|
else
|
|
{
|
|
seg = pika_gradient_segment_get_last (gradient->segments);
|
|
|
|
if (seg == editor->control_sel_r)
|
|
flags |= GTK_STATE_FLAG_SELECTED;
|
|
|
|
control_draw_handle (editor, style, cr, seg->right,
|
|
height, FALSE, flags);
|
|
}
|
|
|
|
break;
|
|
|
|
case GRAD_DRAG_MIDDLE:
|
|
control_draw_handle (editor, style, cr, seg->middle,
|
|
height, TRUE, flags);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
control_draw_handle (PikaGradientEditor *editor,
|
|
GtkStyleContext *style,
|
|
cairo_t *cr,
|
|
gdouble pos,
|
|
gint height,
|
|
gboolean middle,
|
|
GtkStateFlags flags)
|
|
{
|
|
GdkRGBA color;
|
|
gint xpos = control_calc_p_pos (editor, pos);
|
|
gboolean selected = (flags & GTK_STATE_FLAG_SELECTED) != 0;
|
|
|
|
cairo_save (cr);
|
|
|
|
cairo_move_to (cr, xpos, 0);
|
|
cairo_line_to (cr, xpos - height / 2.0, height);
|
|
cairo_line_to (cr, xpos + height / 2.0, height);
|
|
cairo_line_to (cr, xpos, 0);
|
|
|
|
gtk_style_context_save (style);
|
|
|
|
gtk_style_context_add_class (style, GTK_STYLE_CLASS_VIEW);
|
|
|
|
gtk_style_context_save (style);
|
|
|
|
gtk_style_context_set_state (style, flags);
|
|
|
|
gtk_style_context_get_color (style, gtk_style_context_get_state (style),
|
|
&color);
|
|
|
|
gtk_style_context_restore (style);
|
|
|
|
if (middle)
|
|
color.alpha = 0.5;
|
|
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
cairo_fill_preserve (cr);
|
|
|
|
if (selected)
|
|
gtk_style_context_set_state (style, flags &= ~GTK_STATE_FLAG_SELECTED);
|
|
else
|
|
gtk_style_context_set_state (style, flags |= GTK_STATE_FLAG_SELECTED);
|
|
|
|
gtk_style_context_get_color (style, gtk_style_context_get_state (style),
|
|
&color);
|
|
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
cairo_set_line_width (cr, 1);
|
|
cairo_stroke (cr);
|
|
|
|
gtk_style_context_restore (style);
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
/*****/
|
|
|
|
static gint
|
|
control_calc_p_pos (PikaGradientEditor *editor,
|
|
gdouble pos)
|
|
{
|
|
GtkAdjustment *adjustment = editor->scroll_data;
|
|
GtkAllocation allocation;
|
|
gint pwidth;
|
|
|
|
gtk_widget_get_allocation (editor->control, &allocation);
|
|
|
|
pwidth = allocation.width;
|
|
|
|
/* Calculate the position (in widget's coordinates) of the
|
|
* requested point from the gradient. Rounding is done to
|
|
* minimize mismatches between the rendered gradient view
|
|
* and the gradient control's handles.
|
|
*/
|
|
|
|
return RINT ((pwidth - 1) * (pos - gtk_adjustment_get_value (adjustment)) /
|
|
gtk_adjustment_get_page_size (adjustment));
|
|
}
|
|
|
|
static gdouble
|
|
control_calc_g_pos (PikaGradientEditor *editor,
|
|
gint pos)
|
|
{
|
|
GtkAdjustment *adjustment = editor->scroll_data;
|
|
GtkAllocation allocation;
|
|
gint pwidth;
|
|
|
|
gtk_widget_get_allocation (editor->control, &allocation);
|
|
|
|
pwidth = allocation.width;
|
|
|
|
/* Calculate the gradient position that corresponds to widget's coordinates */
|
|
|
|
return (gtk_adjustment_get_page_size (adjustment) * pos / (pwidth - 1) +
|
|
gtk_adjustment_get_value (adjustment));
|
|
}
|
|
|
|
/***** Segment functions *****/
|
|
|
|
static void
|
|
seg_get_closest_handle (PikaGradient *grad,
|
|
gdouble pos,
|
|
PikaGradientSegment **seg,
|
|
GradientEditorDragMode *handle)
|
|
{
|
|
gdouble l_delta, m_delta, r_delta;
|
|
|
|
*seg = pika_gradient_get_segment_at (grad, pos);
|
|
|
|
m_delta = fabs (pos - (*seg)->middle);
|
|
|
|
if (pos < (*seg)->middle)
|
|
{
|
|
l_delta = fabs (pos - (*seg)->left);
|
|
|
|
if (l_delta < m_delta)
|
|
*handle = GRAD_DRAG_LEFT;
|
|
else
|
|
*handle = GRAD_DRAG_MIDDLE;
|
|
}
|
|
else
|
|
{
|
|
r_delta = fabs (pos - (*seg)->right);
|
|
|
|
if (m_delta < r_delta)
|
|
{
|
|
*handle = GRAD_DRAG_MIDDLE;
|
|
}
|
|
else
|
|
{
|
|
*seg = (*seg)->next;
|
|
*handle = GRAD_DRAG_LEFT;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
seg_in_selection (PikaGradient *grad,
|
|
PikaGradientSegment *seg,
|
|
PikaGradientSegment *left,
|
|
PikaGradientSegment *right)
|
|
{
|
|
PikaGradientSegment *s;
|
|
|
|
for (s = left; s; s = s->next)
|
|
{
|
|
if (s == seg)
|
|
return TRUE;
|
|
|
|
if (s == right)
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GtkWidget *
|
|
gradient_hint_label_add (GtkBox *box)
|
|
{
|
|
GtkWidget *label = g_object_new (GTK_TYPE_LABEL,
|
|
"xalign", 0.0,
|
|
"yalign", 0.5,
|
|
"single-line-mode", TRUE,
|
|
NULL);
|
|
gtk_box_pack_start (box, label, FALSE, FALSE, 0);
|
|
gtk_widget_show (label);
|
|
|
|
return label;
|
|
}
|