Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
421 lines (353 sloc) 12.1 KB
/* gb-search-overlay.c
*
* Copyright (C) 2013 Christian Hergert <christian@hergert.me>
*
* This file is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This file 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
* Lesser 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 <http://www.gnu.org/licenses/>.
*/
#include <glib/gi18n.h>
#include "gb-search-overlay.h"
G_DEFINE_TYPE(GbSearchOverlay, gb_search_overlay, GTK_TYPE_DRAWING_AREA)
struct _GbSearchOverlayPrivate
{
GtkTextView *widget;
gchar *tag;
gdouble opacity;
};
enum
{
PROP_0,
PROP_OPACITY,
PROP_TAG,
PROP_WIDGET,
LAST_PROP
};
static GParamSpec *gParamSpecs[LAST_PROP];
GtkWidget *
gb_search_overlay_new (void)
{
return g_object_new(GB_TYPE_SEARCH_OVERLAY, NULL);
}
static void
subtract_region (cairo_region_t *region,
GtkTextView *view,
GtkTextBuffer *buffer,
GtkTextIter *begin,
GtkTextIter *end)
{
cairo_rectangle_int_t r;
GdkRectangle rect;
GdkRectangle rect2;
gtk_text_view_get_iter_location(view, begin, &rect);
gtk_text_view_get_iter_location(view, end, &rect2);
gtk_text_view_buffer_to_window_coords(view, GTK_TEXT_WINDOW_WIDGET, rect.x, rect.y, &rect.x, &rect.y);
gtk_text_view_buffer_to_window_coords(view, GTK_TEXT_WINDOW_WIDGET, rect2.x, rect2.y, &rect2.x, &rect2.y);
r.x = MIN(rect.x, rect2.x);
r.y = MIN(rect.y, rect2.y);
r.width = MAX(rect.x + rect.width, rect2.x) - r.x;
r.height = MAX(rect.y + rect.height, rect2.y + rect2.height) - r.y;
cairo_region_subtract_rectangle(region, &r);
}
static void
rounded_rectangle (cairo_t *cr,
gint x,
gint y,
gint width,
gint height,
gint x_radius,
gint y_radius)
{
gint x1, x2;
gint y1, y2;
gint xr1, xr2;
gint yr1, yr2;
x1 = x;
x2 = x1 + width;
y1 = y;
y2 = y1 + height;
x_radius = MIN (x_radius, width / 2.0);
y_radius = MIN (y_radius, width / 2.0);
xr1 = x_radius;
xr2 = x_radius / 2.0;
yr1 = y_radius;
yr2 = y_radius / 2.0;
cairo_move_to (cr, x1 + xr1, y1);
cairo_line_to (cr, x2 - xr1, y1);
cairo_curve_to (cr, x2 - xr2, y1, x2, y1 + yr2, x2, y1 + yr1);
cairo_line_to (cr, x2, y2 - yr1);
cairo_curve_to (cr, x2, y2 - yr2, x2 - xr2, y2, x2 - xr1, y2);
cairo_line_to (cr, x1 + xr1, y2);
cairo_curve_to (cr, x1 + xr2, y2, x1, y2 - yr2, x1, y2 - yr1);
cairo_line_to (cr, x1, y1 + yr1);
cairo_curve_to (cr, x1, y1 + yr2, x1 + xr2, y1, x1 + xr1, y1);
cairo_close_path (cr);
}
static void
draw_bevel (GbSearchOverlay *overlay,
cairo_t *cr,
guint rad,
GtkTextIter *begin,
GtkTextIter *end)
{
cairo_rectangle_int_t r;
GdkRectangle rect;
GdkRectangle rect2;
GtkTextView *view;
view = GTK_TEXT_VIEW(overlay->priv->widget);
gtk_text_view_get_iter_location(view, begin, &rect);
gtk_text_view_get_iter_location(view, end, &rect2);
gtk_text_view_buffer_to_window_coords(view, GTK_TEXT_WINDOW_WIDGET, rect.x, rect.y, &rect.x, &rect.y);
gtk_text_view_buffer_to_window_coords(view, GTK_TEXT_WINDOW_WIDGET, rect2.x, rect2.y, &rect2.x, &rect2.y);
r.x = MIN(rect.x, rect2.x);
r.y = MIN(rect.y, rect2.y);
r.width = MAX(rect.x + rect.width, rect2.x) - r.x;
r.height = MAX(rect.y + rect.height, rect2.y + rect2.height) - r.y;
r.x -= rad;
r.y -= rad;
r.width += rad * 2;
r.height += rad * 2;
rounded_rectangle(cr, r.x, r.y, r.width, r.height, rad, rad);
}
static void
get_visible_bounds (GtkTextView *view,
GtkTextIter *begin,
GtkTextIter *end)
{
GdkRectangle rect;
gtk_text_view_get_visible_rect(view, &rect);
gtk_text_view_get_iter_at_position(view, begin, NULL, rect.x, rect.y);
gtk_text_view_get_iter_at_position(view, end, NULL, rect.x + rect.width, rect.y + rect.height);
}
static void
draw_bevels (GbSearchOverlay *overlay,
cairo_t *cr,
GtkTextBuffer *buffer,
GtkTextTag *tag)
{
GtkTextIter begin;
GtkTextIter end;
GtkTextIter end_vis;
GtkTextIter iter;
get_visible_bounds(GTK_TEXT_VIEW(overlay->priv->widget), &iter, &end_vis);
cairo_set_source_rgb(cr, .768, .627, 0);
do {
if (gtk_text_iter_forward_to_tag_toggle(&iter, tag)) {
if (!gtk_text_iter_begins_tag(&iter, tag)) {
gtk_text_iter_backward_to_tag_toggle(&iter, tag);
}
gtk_text_iter_assign(&begin, &iter);
gtk_text_iter_forward_to_tag_toggle(&iter, tag);
gtk_text_iter_assign(&end, &iter);
draw_bevel(overlay, cr, 2, &begin, &end);
}
} while (gtk_text_iter_forward_to_tag_toggle(&iter, tag) && gtk_text_iter_compare(&iter, &end_vis) <= 0);
cairo_fill(cr);
get_visible_bounds(GTK_TEXT_VIEW(overlay->priv->widget), &iter, &end_vis);
cairo_set_source_rgb(cr, .964, .878, .270);
do {
if (gtk_text_iter_forward_to_tag_toggle(&iter, tag)) {
if (!gtk_text_iter_begins_tag(&iter, tag)) {
gtk_text_iter_backward_to_tag_toggle(&iter, tag);
}
gtk_text_iter_assign(&begin, &iter);
gtk_text_iter_forward_to_tag_toggle(&iter, tag);
gtk_text_iter_assign(&end, &iter);
draw_bevel(overlay, cr, 1, &begin, &end);
}
} while (gtk_text_iter_forward_to_tag_toggle(&iter, tag) && gtk_text_iter_compare(&iter, &end_vis) <= 0);
cairo_fill(cr);
}
static gboolean
gb_search_overlay_draw (GtkWidget *widget,
cairo_t *cr)
{
GbSearchOverlayPrivate *priv;
cairo_rectangle_int_t rect;
GbSearchOverlay *overlay = (GbSearchOverlay *)widget;
GtkTextTagTable *table;
cairo_region_t *region;
GtkAllocation alloc;
GtkTextBuffer *buffer;
GtkTextIter iter;
GtkTextIter begin;
GtkTextIter end;
GtkTextTag *tag;
GdkWindow *window;
gboolean has_matches = FALSE;
gint n_rectangles;
gint i;
g_assert(GB_IS_SEARCH_OVERLAY(overlay));
g_assert(cr);
priv = overlay->priv;
if (!GTK_IS_TEXT_VIEW(priv->widget) || !priv->tag) {
return FALSE;
}
if (!(buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->widget)))) {
return FALSE;
}
window = gtk_widget_get_window(widget);
if (!gtk_cairo_should_draw_window(cr, window)) {
return FALSE;
}
if (!(table = gtk_text_buffer_get_tag_table(buffer))) {
return FALSE;
}
if (!(tag = gtk_text_tag_table_lookup(table, priv->tag))) {
return FALSE;
}
gtk_widget_get_allocation(widget, &alloc);
rect.x = alloc.x;
rect.y = alloc.y;
rect.width = alloc.width;
rect.height = alloc.height;
region = cairo_region_create_rectangle(&rect);
gtk_text_buffer_get_bounds(buffer, &iter, &end);
do {
if (gtk_text_iter_begins_tag(&iter, tag)) {
gtk_text_iter_assign(&begin, &iter);
while (!gtk_text_iter_ends_tag(&iter, tag)) {
if (!gtk_text_iter_forward_char(&iter)) {
break;
}
}
gtk_text_iter_assign(&end, &iter);
subtract_region(region, GTK_TEXT_VIEW(priv->widget), buffer, &begin, &end);
has_matches = TRUE;
}
} while (gtk_text_iter_forward_char(&iter));
if (has_matches) {
cairo_save(cr);
n_rectangles = cairo_region_num_rectangles(region);
for (i = 0; i < n_rectangles; i++) {
cairo_region_get_rectangle(region, i, &rect);
cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
}
cairo_set_source_rgba(cr, 0, 0, 0, priv->opacity);
cairo_fill_preserve(cr);
cairo_clip(cr);
draw_bevels(overlay, cr, buffer, tag);
cairo_restore(cr);
}
cairo_region_destroy(region);
return FALSE;
}
static gboolean
gb_search_overlay_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
gtk_widget_hide(widget);
return FALSE;
}
static void
gb_search_overlay_finalize (GObject *object)
{
GbSearchOverlayPrivate *priv;
priv = GB_SEARCH_OVERLAY(object)->priv;
g_clear_object(&priv->widget);
g_clear_pointer(&priv->tag, g_free);
G_OBJECT_CLASS(gb_search_overlay_parent_class)->finalize(object);
}
static void
gb_search_overlay_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GbSearchOverlay *overlay = GB_SEARCH_OVERLAY(object);
switch (prop_id) {
case PROP_OPACITY:
g_value_set_double(value, overlay->priv->opacity);
break;
case PROP_WIDGET:
g_value_set_object(value, overlay->priv->widget);
break;
case PROP_TAG:
g_value_set_string(value, overlay->priv->tag);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
}
}
static void
gb_search_overlay_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GbSearchOverlay *overlay = GB_SEARCH_OVERLAY(object);
switch (prop_id) {
case PROP_OPACITY:
overlay->priv->opacity = g_value_get_double(value);
break;
case PROP_WIDGET:
g_clear_object(&overlay->priv->widget);
overlay->priv->widget = g_value_dup_object(value);
break;
case PROP_TAG:
g_clear_pointer(&overlay->priv->tag, g_free);
overlay->priv->tag = g_value_dup_string(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
}
}
static void
gb_search_overlay_class_init (GbSearchOverlayClass *klass)
{
GtkWidgetClass *widget_class;
GObjectClass *object_class;
object_class = G_OBJECT_CLASS(klass);
object_class->finalize = gb_search_overlay_finalize;
object_class->get_property = gb_search_overlay_get_property;
object_class->set_property = gb_search_overlay_set_property;
g_type_class_add_private(object_class, sizeof(GbSearchOverlayPrivate));
widget_class = GTK_WIDGET_CLASS(klass);
widget_class->draw = gb_search_overlay_draw;
widget_class->button_press_event = gb_search_overlay_button_press_event;
gParamSpecs[PROP_OPACITY] =
g_param_spec_double("opacity",
_("Opacity"),
_("The opacity of the background."),
0.0,
1.0,
0.8,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property(object_class, PROP_OPACITY,
gParamSpecs[PROP_OPACITY]);
gParamSpecs[PROP_WIDGET] =
g_param_spec_object("widget",
_("Widget"),
_("The GtkTextView widget."),
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property(object_class, PROP_WIDGET,
gParamSpecs[PROP_WIDGET]);
gParamSpecs[PROP_TAG] =
g_param_spec_string("tag",
_("Tag"),
_("The name of the search tag."),
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property(object_class, PROP_TAG,
gParamSpecs[PROP_TAG]);
}
static void
gb_search_overlay_init (GbSearchOverlay *overlay)
{
overlay->priv =
G_TYPE_INSTANCE_GET_PRIVATE(overlay,
GB_TYPE_SEARCH_OVERLAY,
GbSearchOverlayPrivate);
overlay->priv->opacity = 0.8;
gtk_widget_add_events(GTK_WIDGET(overlay), GDK_BUTTON_PRESS_MASK);
}