qulogic/talkatu
Clone
Summary
Browse
Changes
Graph
Somehow maiku was missing, so I added him
2019-08-05, Gary Kramlich
2e691dcd9200
Somehow maiku was missing, so I added him
/*
* talkatu
* Copyright (C) 2017-2018 Gary Kramlich <grim@reaperworld.com>
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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, see <http://www.gnu.org/licenses/>.
*/
#include
<stdio.h>
#include
"talkatu/talkatumenutoolbutton.h"
/**
* SECTION:talkatumenutoolbutton
* @Title: Menu Tool Button
* @Short_description: A simple menu tool button.
*
* The normal #GtkMenuToolButton forces you to have an action on the always
* visible button. This #GtkToolItem instead just uses a label with no action.
*/
/**
* TALKATU_TYPE_MENU_TOOL_BUTTON:
*
* The standard _get_type macro for #TalkatuMenuToolButton.
*/
/**
* TalkatuMenuToolButton:
*
* A #GtkToolButton subclass that behaves like a #GtkComboBox.
*/
struct
_TalkatuMenuToolButton
{
GtkToolButton
parent
;
GtkWidget
*
menu
;
};
G_DEFINE_TYPE
(
TalkatuMenuToolButton
,
talkatu_menu_tool_button
,
GTK_TYPE_TOOL_BUTTON
)
enum
{
PROP_0
=
0
,
PROP_MENU
,
N_PROPERTIES
,
};
static
GParamSpec
*
properties
[
N_PROPERTIES
]
=
{
NULL
,};
/******************************************************************************
* GObject Stuff
*****************************************************************************/
#if !GTK_CHECK_VERSION(3, 22, 0)
static
void
talkatu_menu_position_func_helper
(
GtkMenu
*
menu
,
gint
*
x
,
gint
*
y
,
gboolean
*
push_in
,
gpointer
data
)
{
GtkWidget
*
widget
;
GtkRequisition
requisition
;
GdkScreen
*
screen
;
GdkRectangle
monitor
;
gint
monitor_num
;
gint
space_left
,
space_right
,
space_above
,
space_below
;
gboolean
rtl
;
g_return_if_fail
(
GTK_IS_MENU
(
menu
));
widget
=
GTK_WIDGET
(
menu
);
screen
=
gtk_widget_get_screen
(
widget
);
rtl
=
(
gtk_widget_get_direction
(
widget
)
==
GTK_TEXT_DIR_RTL
);
/*
* We need the requisition to figure out the right place to
* popup the menu. In fact, we always need to ask here, since
* if a size_request was queued while we weren't popped up,
* the requisition won't have been recomputed yet.
*/
gtk_widget_get_preferred_size
(
widget
,
NULL
,
&
requisition
);
monitor_num
=
gdk_screen_get_monitor_at_point
(
screen
,
*
x
,
*
y
);
*
push_in
=
FALSE
;
/*
* The placement of popup menus horizontally works like this (with
* RTL in parentheses)
*
* - If there is enough room to the right (left) of the mouse cursor,
* position the menu there.
*
* - Otherwise, if if there is enough room to the left (right) of the
* mouse cursor, position the menu there.
*
* - Otherwise if the menu is smaller than the monitor, position it
* on the side of the mouse cursor that has the most space available
*
* - Otherwise (if there is simply not enough room for the menu on the
* monitor), position it as far left (right) as possible.
*
* Positioning in the vertical direction is similar: first try below
* mouse cursor, then above.
*/
gdk_screen_get_monitor_geometry
(
screen
,
monitor_num
,
&
monitor
);
space_left
=
*
x
-
monitor
.
x
;
space_right
=
monitor
.
x
+
monitor
.
width
-
*
x
-
1
;
space_above
=
*
y
-
monitor
.
y
;
space_below
=
monitor
.
y
+
monitor
.
height
-
*
y
-
1
;
/* position horizontally */
if
(
requisition
.
width
<=
space_left
||
requisition
.
width
<=
space_right
)
{
if
((
rtl
&&
requisition
.
width
<=
space_left
)
||
(
!
rtl
&&
requisition
.
width
>
space_right
))
{
/* position left */
*
x
=
*
x
-
requisition
.
width
+
1
;
}
/* x is clamped on-screen further down */
}
else
if
(
requisition
.
width
<=
monitor
.
width
)
{
/* the menu is too big to fit on either side of the mouse
* cursor, but smaller than the monitor. Position it on
* the side that has the most space
*/
if
(
space_left
>
space_right
)
{
/* left justify */
*
x
=
monitor
.
x
;
}
else
{
/* right justify */
*
x
=
monitor
.
x
+
monitor
.
width
-
requisition
.
width
;
}
}
else
{
/* menu is simply too big for the monitor */
if
(
rtl
)
{
/* right justify */
*
x
=
monitor
.
x
+
monitor
.
width
-
requisition
.
width
;
}
else
{
/* left justify */
*
x
=
monitor
.
x
;
}
}
/* Position vertically. The algorithm is the same as above, but
* simpler because we don't have to take RTL into account.
*/
if
(
requisition
.
height
<=
space_above
||
requisition
.
height
<=
space_below
)
{
if
(
requisition
.
height
>
space_below
)
{
*
y
=
*
y
-
requisition
.
height
+
1
;
}
*
y
=
CLAMP
(
*
y
,
monitor
.
y
,
monitor
.
y
+
monitor
.
height
-
requisition
.
height
);
}
else
if
(
requisition
.
height
>
space_below
&&
requisition
.
height
>
space_above
)
{
if
(
space_below
>=
space_above
)
{
*
y
=
monitor
.
y
+
monitor
.
height
-
requisition
.
height
;
}
else
{
*
y
=
monitor
.
y
;
}
}
else
{
*
y
=
monitor
.
y
;
}
}
/* This comes from gtkmenutoolbutton.c from gtk+
* Copyright (C) 2003 Ricardo Fernandez Pascual
* Copyright (C) 2004 Paolo Borelli
*/
static
void
talkatu_menu_position_func
(
GtkMenu
*
menu
,
gint
*
x
,
gint
*
y
,
gboolean
*
push_in
,
gpointer
data
)
{
GtkWidget
*
widget
=
GTK_WIDGET
(
data
);
GtkAllocation
allocation
;
gint
savy
;
gtk_widget_get_allocation
(
widget
,
&
allocation
);
gdk_window_get_origin
(
gtk_widget_get_window
(
widget
),
x
,
y
);
*
x
+=
allocation
.
x
;
*
y
+=
allocation
.
y
+
allocation
.
height
;
savy
=
*
y
;
talkatu_menu_position_func_helper
(
menu
,
x
,
y
,
push_in
,
data
);
if
(
savy
>
*
y
+
1
)
*
y
-=
allocation
.
height
;
}
#endif
static
void
talkatu_menu_tool_button_clicked
(
GtkToolButton
*
button
)
{
TalkatuMenuToolButton
*
menu_button
=
TALKATU_MENU_TOOL_BUTTON
(
button
);
#if GTK_CHECK_VERSION(3, 22, 0)
gtk_menu_popup_at_widget
(
GTK_MENU
(
menu_button
->
menu
),
GTK_WIDGET
(
button
),
GDK_GRAVITY_SOUTH_WEST
,
GDK_GRAVITY_NORTH_WEST
,
NULL
);
#else
gtk_menu_popup
(
GTK_MENU
(
menu_button
->
menu
),
NULL
,
NULL
,
talkatu_menu_position_func
,
button
,
GDK_LEFTBUTTON
,
gtk_get_current_event_time
()
);
#endif
}
static
void
talkatu_menu_tool_button_get_property
(
GObject
*
obj
,
guint
prop_id
,
GValue
*
value
,
GParamSpec
*
pspec
)
{
TalkatuMenuToolButton
*
menu_button
=
TALKATU_MENU_TOOL_BUTTON
(
obj
);
switch
(
prop_id
)
{
case
PROP_MENU
:
g_value_set_object
(
value
,
talkatu_menu_tool_button_get_menu
(
menu_button
));
break
;
default
:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
obj
,
prop_id
,
pspec
);
break
;
}
}
static
void
talkatu_menu_tool_button_set_property
(
GObject
*
obj
,
guint
prop_id
,
const
GValue
*
value
,
GParamSpec
*
pspec
)
{
TalkatuMenuToolButton
*
menu_button
=
TALKATU_MENU_TOOL_BUTTON
(
obj
);
switch
(
prop_id
)
{
case
PROP_MENU
:
talkatu_menu_tool_button_set_menu
(
menu_button
,
g_value_get_object
(
value
));
break
;
default
:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
obj
,
prop_id
,
pspec
);
break
;
}
}
static
void
talkatu_menu_tool_button_init
(
TalkatuMenuToolButton
*
menu_button
)
{
}
static
void
talkatu_menu_tool_button_class_init
(
TalkatuMenuToolButtonClass
*
klass
)
{
GObjectClass
*
obj_class
=
G_OBJECT_CLASS
(
klass
);
GtkToolButtonClass
*
button_class
=
GTK_TOOL_BUTTON_CLASS
(
klass
);
obj_class
->
get_property
=
talkatu_menu_tool_button_get_property
;
obj_class
->
set_property
=
talkatu_menu_tool_button_set_property
;
button_class
->
clicked
=
talkatu_menu_tool_button_clicked
;
properties
[
PROP_MENU
]
=
g_param_spec_object
(
"menu"
,
"menu"
,
"The menu to show"
,
GTK_TYPE_MENU
,
G_PARAM_READWRITE
|
G_PARAM_CONSTRUCT
);
g_object_class_install_properties
(
obj_class
,
N_PROPERTIES
,
properties
);
}
/******************************************************************************
* Public API
*****************************************************************************/
/**
* talkatu_menu_tool_button_new:
* @label: The label to display.
* @icon_name: The optional name of the icon to display.
* @menu: The menu to display.
*
* Creates a new #TalkatuMenuToolButton with the given @label, @icon_name, and
* @menu.
*
* Returns: (transfer full): The new #TalkatuMenuToolButton instance.
*/
GtkToolItem
*
talkatu_menu_tool_button_new
(
const
gchar
*
label
,
const
gchar
*
icon_name
,
GtkWidget
*
menu
)
{
return
g_object_new
(
TALKATU_TYPE_MENU_TOOL_BUTTON
,
"label"
,
label
,
"icon-name"
,
icon_name
,
"menu"
,
menu
,
NULL
);
}
/**
* talkatu_menu_tool_button_get_menu:
* @menu_button: The #TalkatuMenuToolButton instance.
*
* Get's the menu that this tool button will display on click or #NULL if no
* menu is set.
*
* Returns: (transfer full): The menu.
*/
GtkWidget
*
talkatu_menu_tool_button_get_menu
(
TalkatuMenuToolButton
*
menu_button
)
{
g_return_val_if_fail
(
TALKATU_IS_MENU_TOOL_BUTTON
(
menu_button
),
FALSE
);
if
(
menu_button
->
menu
)
{
return
g_object_ref
(
menu_button
->
menu
);
}
return
NULL
;
}
/**
* talkatu_menu_tool_button_set_menu:
* @menu_button: The #TalkatuMenuToolButton instance.
* @menu: The menu to set.
*
* Sets the menu to be displayed when the user clicks the button.
*/
void
talkatu_menu_tool_button_set_menu
(
TalkatuMenuToolButton
*
menu_button
,
GtkWidget
*
menu
)
{
g_return_if_fail
(
TALKATU_IS_MENU_TOOL_BUTTON
(
menu_button
));
if
(
menu_button
->
menu
)
{
gtk_menu_detach
(
GTK_MENU
(
menu_button
->
menu
));
g_object_unref
(
G_OBJECT
(
menu_button
->
menu
));
}
if
(
menu
)
{
menu_button
->
menu
=
GTK_WIDGET
(
g_object_ref
(
G_OBJECT
(
menu
)));
gtk_menu_attach_to_widget
(
GTK_MENU
(
menu_button
->
menu
),
GTK_WIDGET
(
menu_button
),
NULL
);
}
else
{
menu_button
->
menu
=
NULL
;
}
}