aboutsummaryrefslogblamecommitdiffstats
path: root/lib/widgets/gedit-overlay.c
blob: b80c0af1cd73f896bc9aa4549c26d0db8ebb204b (plain) (tree)
1
2
3
4
5
6
7
8



                             
                                               


                                                    




                                                                     

                                                                 





                                                                             


                          
                                


                                                                                                                         





                            


                               
                                   
                            





                         
                            

  
                                                               
 



































                                                     

                                           

                                            
 











                                                                       

                                                                          
                                                             


           

                                       

















                                                                      

                                                                          







                                                                                   




















                                                                  











                                                       
                 
                                                                       
 




                                                                

                                                                           




























                                                                                   
                                                                                                   
 
                                              
















                                                                       



                     
                              
         
                                                                                     








                                                                 



                     
                              
         
                                                                                      



           

                                                       
 


                                                       

                  
















                                                                                          
         

                                                    

                                    
                             
 

                                                              
                                               

                                 
                                                                  
                                                                                      
 

                                                                                       

                                                                                     
                                                                     
                                                                                                           

                                            

                                                                     

                                            

                                                                     
                                                                                             
                                      
                                                                     

                                                                                                           
                                      




                                            











                                                                                     
 
                                                         


         



                                         



                                                                          
                                                    
 

                                                              
                 
                                                                      


                 
                    

 



                                   






                                                                    


                                                                   
         





                                            
                                                                  

                  
                                                                 
         

                                                          
 
                                       

                                                     












                                                                                         

                                                                 










                                                      
                                                                  
                         
 
                                  
 

                        
                                 
                                                           
                                          

                                                              
 

                                                    







                                                






                                                                         






















                                                                                       





                                                                                                                
                                                                                       







                                                                              

                                                                  

 


                             
                                               
  


                                                                             


                                       
           

                                              
 

                                                                 

                                                                    
                                                                            


                                                













                                                      
 
                                 
 

                                                      
 
                                                            
 


                                                                       
 

                                                           
 
/*
 * gedit-overlay.c
 * This file is part of gedit
 *
 * Copyright (C) 2011 - Ignacio Casal Quinteiro
 *
 * Based on Mike Krüger <mkrueger@novell.com> work.
 *
 * gedit 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.
 * 
 * gedit 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 Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "gedit-overlay.h"
#include "gedit-overlay-child.h"

#define GEDIT_OVERLAY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_OVERLAY, GeditOverlayPrivate))

typedef struct
{
    GtkWidget *child;
    GtkWidget *original;
} ChildContainer;

struct _GeditOverlayPrivate
{
    GtkWidget *main_widget;
    GtkWidget *relative_widget;
    GSList    *children;
};

enum
{
    PROP_0,
    PROP_MAIN_WIDGET,
    PROP_RELATIVE_WIDGET
};

G_DEFINE_TYPE (GeditOverlay, gedit_overlay, GTK_TYPE_CONTAINER)

static ChildContainer *
child_container_new (GtkWidget *child,
                     GtkWidget *original)
{
    ChildContainer *ret;

    ret = g_slice_new (ChildContainer);
    ret->child = child;
    ret->original = original;

    return ret;
}

static void
child_container_free (ChildContainer *container)
{
    g_slice_free (ChildContainer, container);
}

static GtkWidget *
child_container_get_child (ChildContainer *container)
{
    GtkWidget *child;

    if (container->child != NULL)
    {
        child = container->child;
    }
    else
    {
        child = container->original;
    }

    return child;
}

static void
add_toplevel_widget (GeditOverlay *overlay,
                     GtkWidget    *child,
                     GtkWidget    *original)
{
    ChildContainer *container;

    if (child != NULL)
    {
        gtk_widget_set_parent (child, GTK_WIDGET (overlay));
    }
    else
    {
        gtk_widget_set_parent (original, GTK_WIDGET (overlay));
    }

    container = child_container_new (child, original);

    overlay->priv->children = g_slist_append (overlay->priv->children,
                                              container);
}

static void
gedit_overlay_dispose (GObject *object)
{
    G_OBJECT_CLASS (gedit_overlay_parent_class)->dispose (object);
}

static void
gedit_overlay_get_property (GObject    *object,
                            guint       prop_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
    GeditOverlay *overlay = GEDIT_OVERLAY (object);
    GeditOverlayPrivate *priv = overlay->priv;

    switch (prop_id)
    {
        case PROP_MAIN_WIDGET:
            g_value_set_object (value, priv->main_widget);
            break;

        case PROP_RELATIVE_WIDGET:
            g_value_set_object (value, priv->relative_widget);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static GtkWidget *
wrap_child_if_needed (GtkWidget *widget)
{
    GtkWidget *child;

    if (GEDIT_IS_OVERLAY_CHILD (widget))
    {
        return widget;
    }

    child = GTK_WIDGET (gedit_overlay_child_new (widget));
    gtk_widget_show (child);

    g_signal_connect_swapped (widget,
                              "destroy",
                              G_CALLBACK (gtk_widget_destroy),
                              child);

    return child;
}

static void
gedit_overlay_set_property (GObject      *object,
                            guint         prop_id,
                            const GValue *value,
                            GParamSpec   *pspec)
{
    GeditOverlay *overlay = GEDIT_OVERLAY (object);
    GeditOverlayPrivate *priv = overlay->priv;

    switch (prop_id)
    {
        case PROP_MAIN_WIDGET:
        {
            priv->main_widget = g_value_get_object (value);

            add_toplevel_widget (overlay,
                                 NULL,
                                 priv->main_widget);
            break;
        }
        case PROP_RELATIVE_WIDGET:
            priv->relative_widget = g_value_get_object (value);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }
}

static void
gedit_overlay_realize (GtkWidget *widget)
{
    GtkAllocation allocation;
    GdkWindow *window;
    GdkWindowAttr attributes;
    gint attributes_mask;
    GtkStyleContext *context;

    gtk_widget_set_realized (widget, TRUE);

    gtk_widget_get_allocation (widget, &allocation);

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.x = allocation.x;
    attributes.y = allocation.y;
    attributes.width = allocation.width;
    attributes.height = allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual (widget);
    attributes.event_mask = gtk_widget_get_events (widget);
    attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK;

    attributes_mask = GDK_WA_X | GDK_WA_Y;

    window = gdk_window_new (gtk_widget_get_parent_window (widget),
                             &attributes, attributes_mask);
    gtk_widget_set_window (widget, window);
    gdk_window_set_user_data (window, widget);

    context = gtk_widget_get_style_context (widget);
    gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
    gtk_style_context_set_background (context, window);
}

static void
gedit_overlay_get_preferred_width (GtkWidget *widget,
                                   gint      *minimum,
                                   gint      *natural)
{
    GeditOverlayPrivate *priv = GEDIT_OVERLAY (widget)->priv;

    *minimum = 0;
    *natural = 0;

    if (priv->main_widget)
    {
        gtk_widget_get_preferred_width (priv->main_widget, minimum, natural);
    }
}

static void
gedit_overlay_get_preferred_height (GtkWidget *widget,
                                    gint      *minimum,
                                    gint      *natural)
{
    GeditOverlayPrivate *priv = GEDIT_OVERLAY (widget)->priv;

    *minimum = 0;
    *natural = 0;

    if (priv->main_widget)
    {
        gtk_widget_get_preferred_height (priv->main_widget, minimum, natural);
    }
}

static void
gedit_overlay_size_allocate (GtkWidget     *widget,
                             GtkAllocation *allocation)
{
    GeditOverlay *overlay = GEDIT_OVERLAY (widget);
    GeditOverlayPrivate *priv = overlay->priv;
    GtkAllocation main_alloc;
    GSList *l;

    GTK_WIDGET_CLASS (gedit_overlay_parent_class)->size_allocate (widget, allocation);

    /* main widget allocation */
    main_alloc.x = 0;
    main_alloc.y = 0;
    main_alloc.width = allocation->width;
    main_alloc.height = allocation->height;

    gtk_widget_size_allocate (overlay->priv->main_widget, &main_alloc);

    /* if a relative widget exists place the floating widgets in relation to it */
    if (priv->relative_widget)
    {
        gtk_widget_get_allocation (priv->relative_widget, &main_alloc);
    }

    for (l = priv->children; l != NULL; l = g_slist_next (l))
    {
        ChildContainer *container = l->data;
        GtkWidget *child;
        GtkRequisition req;
        GtkAllocation alloc;
        guint offset;

        child = child_container_get_child (container);

        if (child == priv->main_widget)
            continue;

        gtk_widget_get_preferred_size (child, NULL, &req);
        offset = gedit_overlay_child_get_offset (GEDIT_OVERLAY_CHILD (child));

        /* FIXME: Add all the positions here */
        switch (gedit_overlay_child_get_position (GEDIT_OVERLAY_CHILD (child)))
        {
            /* The gravity is treated as position and not as a gravity */
            case GEDIT_OVERLAY_CHILD_POSITION_NORTH_EAST:
                alloc.x = MAX (main_alloc.x, main_alloc.width - req.width - (gint) offset);
                alloc.y = 0;
                break;
            case GEDIT_OVERLAY_CHILD_POSITION_NORTH_WEST:
                alloc.x = offset;
                alloc.y = 0;
                break;
            case GEDIT_OVERLAY_CHILD_POSITION_SOUTH_WEST:
                alloc.x = offset;
                alloc.y = MAX (main_alloc.y, main_alloc.height - req.height);
                break;
            case GEDIT_OVERLAY_CHILD_POSITION_SOUTH_EAST:
                alloc.x = MAX (main_alloc.x, main_alloc.width - req.width - (gint) offset);
                alloc.y = MAX (main_alloc.y, main_alloc.height - req.height);
                break;
            default:
                alloc.x = 0;
                alloc.y = 0;
        }

        if (main_alloc.width < req.width || main_alloc.height < req.height)
        {
            GdkWindow *child_window = gtk_widget_get_window (child);
            if (child_window)
            gdk_window_move_resize (child_window,
                        alloc.x, alloc.y,
                        MIN (main_alloc.width, req.width),
                        MIN (main_alloc.height, req.height));
        }

        alloc.width = req.width;
        alloc.height = req.height;

        gtk_widget_size_allocate (child, &alloc);
    }
}

static GeditOverlayChild *
get_overlay_child (GeditOverlay *overlay,
                   GtkWidget    *widget)
{
    GSList *l;

    for (l = overlay->priv->children; l != NULL; l = g_slist_next (l))
    {
        ChildContainer *container = l->data;

        if (container->original == widget &&
            GEDIT_IS_OVERLAY_CHILD (container->child))
        {
            return GEDIT_OVERLAY_CHILD (container->child);
        }
    }

    return NULL;
}

static void
overlay_add (GtkContainer *overlay,
             GtkWidget    *widget)
{
    GeditOverlayChild *child;

    /* check that the widget is not added yet */
    child = get_overlay_child (GEDIT_OVERLAY (overlay), widget);

    if (child == NULL)
    {
        add_toplevel_widget (GEDIT_OVERLAY (overlay),
                             wrap_child_if_needed (widget),
                             widget);
    }
}

static void
gedit_overlay_remove (GtkContainer *overlay,
                      GtkWidget    *widget)
{
    GeditOverlayPrivate *priv = GEDIT_OVERLAY (overlay)->priv;
    GSList *l;

    for (l = priv->children; l != NULL; l = g_slist_next (l))
    {
        ChildContainer *container = l->data;
        GtkWidget *original = container->original;

        if (original == widget)
        {
            gtk_widget_unparent (widget);

            if (container->child != NULL &&
                original != container->child)
            {
                g_signal_handlers_disconnect_by_func (original,
                                                      gtk_widget_destroy,
                                                      container->child);

                gtk_widget_destroy (container->child);
            }

            child_container_free (container);
            priv->children = g_slist_delete_link (priv->children,
                                                  l);

            break;
        }
    }
}

static void
gedit_overlay_forall (GtkContainer *overlay,
                      gboolean      include_internals,
                      GtkCallback   callback,
                      gpointer      callback_data)
{
    GeditOverlayPrivate *priv = GEDIT_OVERLAY (overlay)->priv;
    GSList *children;

    children = priv->children;

    while (children)
    {
        GtkWidget *child;
        ChildContainer *container = children->data;
        children = children->next;

        child = child_container_get_child (container);

        (* callback) (child, callback_data);
    }
}

static GType
gedit_overlay_child_type (GtkContainer *overlay)
{
    return GTK_TYPE_WIDGET;
}

static void
gedit_overlay_class_init (GeditOverlayClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

    object_class->dispose = gedit_overlay_dispose;
    object_class->get_property = gedit_overlay_get_property;
    object_class->set_property = gedit_overlay_set_property;

    widget_class->realize = gedit_overlay_realize;
    widget_class->get_preferred_width = gedit_overlay_get_preferred_width;
    widget_class->get_preferred_height = gedit_overlay_get_preferred_height;
    widget_class->size_allocate = gedit_overlay_size_allocate;

    container_class->add = overlay_add;
    container_class->remove = gedit_overlay_remove;
    container_class->forall = gedit_overlay_forall;
    container_class->child_type = gedit_overlay_child_type;

    g_object_class_install_property (object_class, PROP_MAIN_WIDGET,
                                     g_param_spec_object ("main-widget",
                                                          "Main Widget",
                                                          "The Main Widget",
                                                          GTK_TYPE_WIDGET,
                                                          G_PARAM_READWRITE |
                                                          G_PARAM_CONSTRUCT_ONLY |
                                                          G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (object_class, PROP_RELATIVE_WIDGET,
                                     g_param_spec_object ("relative-widget",
                                                          "Relative Widget",
                                                          "Widget on which the floating widgets are placed",
                                                          GTK_TYPE_WIDGET,
                                                          G_PARAM_READWRITE |
                                                          G_PARAM_STATIC_STRINGS));

    g_type_class_add_private (object_class, sizeof (GeditOverlayPrivate));
}

static void
gedit_overlay_init (GeditOverlay *overlay)
{
    overlay->priv = GEDIT_OVERLAY_GET_PRIVATE (overlay);

    gtk_widget_set_app_paintable (GTK_WIDGET (overlay), TRUE);
}

/**
 * gedit_overlay_new:
 * @main_widget: a #GtkWidget
 * @relative_widget: (allow-none): a #Gtkwidget
 *
 * Creates a new #GeditOverlay. If @relative_widget is not %NULL the floating
 * widgets will be placed in relation to it, if not @main_widget will be use
 * for this purpose.
 *
 * Returns: a new #GeditOverlay object.
 */
GtkWidget *
gedit_overlay_new (GtkWidget *main_widget,
                   GtkWidget *relative_widget)
{
    g_return_val_if_fail (GTK_IS_WIDGET (main_widget), NULL);

    return GTK_WIDGET (g_object_new (GEDIT_TYPE_OVERLAY,
                                     "main-widget", main_widget,
                                     "relative-widget", relative_widget,
                                     NULL));
}

/**
 * gedit_overlay_add:
 * @overlay: a #GeditOverlay
 * @widget: a #GtkWidget to be added to the container
 * @position: a #GeditOverlayChildPosition
 * @offset: offset for @widget
 *
 * Adds @widget to @overlay in a specific position.
 */
void
gedit_overlay_add (GeditOverlay             *overlay,
                   GtkWidget                *widget,
                   GeditOverlayChildPosition position,
                   guint                     offset)
{
    GeditOverlayChild *child;

    g_return_if_fail (GEDIT_IS_OVERLAY (overlay));
    g_return_if_fail (GTK_IS_WIDGET (widget));

    gtk_container_add (GTK_CONTAINER (overlay), widget);

    /* NOTE: can we improve this without exposing overlay child? */
    child = get_overlay_child (overlay, widget);
    g_assert (child != NULL);

    gedit_overlay_child_set_position (child, position);
    gedit_overlay_child_set_offset (child, offset);
}