Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Guido Gunther
libhandy
Commits
ec017d03
Commit
ec017d03
authored
Sep 12, 2018
by
Adrien Plazas
Browse files
Add HdyHeaderGroup
parent
9d3c4705
Changes
5
Hide whitespace changes
Inline
Side-by-side
doc/handy-docs.xml
View file @
ec017d03
...
...
@@ -41,6 +41,7 @@
<xi:include
href=
"xml/hdy-dialer.xml"
/>
<xi:include
href=
"xml/hdy-dialer-button.xml"
/>
<xi:include
href=
"xml/hdy-dialer-cycle-button.xml"
/>
<xi:include
href=
"xml/hdy-header-group.xml"
/>
<xi:include
href=
"xml/hdy-leaflet.xml"
/>
<xi:include
href=
"xml/hdy-title-bar.xml"
/>
</chapter>
...
...
src/handy.h
View file @
ec017d03
...
...
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
#include
"hdy-dialer-cycle-button.h"
#include
"hdy-dialer.h"
#include
"hdy-fold.h"
#include
"hdy-header-group.h"
#include
"hdy-leaflet.h"
#include
"hdy-string-utf8.h"
#include
"hdy-title-bar.h"
...
...
src/hdy-header-group.c
0 → 100644
View file @
ec017d03
/*
* Copyright (C) 2018 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include
<glib/gi18n.h>
#include
"hdy-header-group.h"
typedef
struct
{
GSList
*
header_bars
;
GtkWidget
*
focus
;
}
HdyHeaderGroupPrivate
;
static
void
hdy_header_group_buildable_init
(
GtkBuildableIface
*
iface
);
static
gboolean
hdy_header_group_buildable_custom_tag_start
(
GtkBuildable
*
buildable
,
GtkBuilder
*
builder
,
GObject
*
child
,
const
gchar
*
tagname
,
GMarkupParser
*
parser
,
gpointer
*
data
);
static
void
hdy_header_group_buildable_custom_finished
(
GtkBuildable
*
buildable
,
GtkBuilder
*
builder
,
GObject
*
child
,
const
gchar
*
tagname
,
gpointer
user_data
);
G_DEFINE_TYPE_WITH_CODE
(
HdyHeaderGroup
,
hdy_header_group
,
G_TYPE_OBJECT
,
G_ADD_PRIVATE
(
HdyHeaderGroup
)
G_IMPLEMENT_INTERFACE
(
GTK_TYPE_BUILDABLE
,
hdy_header_group_buildable_init
))
enum
{
PROP_0
,
PROP_FOCUS
,
N_PROPS
};
static
GParamSpec
*
props
[
N_PROPS
];
static
void
update_decoration_layouts
(
HdyHeaderGroup
*
self
)
{
HdyHeaderGroupPrivate
*
priv
;
GSList
*
header_bars
;
GtkSettings
*
settings
;
GtkHeaderBar
*
start_headerbar
=
NULL
,
*
end_headerbar
=
NULL
;
g_autofree
gchar
*
layout
=
NULL
;
g_autofree
gchar
*
start_layout
=
NULL
;
g_autofree
gchar
*
end_layout
=
NULL
;
g_auto
(
GStrv
)
ends
=
NULL
;
g_message
(
"update"
);
g_return_if_fail
(
HDY_IS_HEADER_GROUP
(
self
));
priv
=
hdy_header_group_get_instance_private
(
self
);
header_bars
=
priv
->
header_bars
;
if
(
header_bars
==
NULL
)
return
;
settings
=
gtk_settings_get_default
();
g_object_get
(
G_OBJECT
(
settings
),
"gtk-decoration-layout"
,
&
layout
,
NULL
);
if
(
priv
->
focus
!=
NULL
)
{
for
(;
header_bars
!=
NULL
;
header_bars
=
header_bars
->
next
)
if
(
header_bars
->
data
==
priv
->
focus
&&
gtk_widget_get_mapped
(
header_bars
->
data
))
gtk_header_bar_set_decoration_layout
(
header_bars
->
data
,
layout
);
else
gtk_header_bar_set_decoration_layout
(
header_bars
->
data
,
":"
);
return
;
}
for
(;
header_bars
!=
NULL
;
header_bars
=
header_bars
->
next
)
{
gtk_header_bar_set_decoration_layout
(
header_bars
->
data
,
":"
);
if
(
!
gtk_widget_get_mapped
(
header_bars
->
data
))
continue
;
/* The headerbars are in reverse order in the list. */
start_headerbar
=
header_bars
->
data
;
if
(
end_headerbar
==
NULL
)
end_headerbar
=
header_bars
->
data
;
}
if
(
start_headerbar
==
NULL
||
end_headerbar
==
NULL
)
return
;
if
(
start_headerbar
==
end_headerbar
)
{
gtk_header_bar_set_decoration_layout
(
start_headerbar
,
layout
);
return
;
}
ends
=
g_strsplit
(
layout
,
":"
,
2
);
start_layout
=
g_strdup_printf
(
"%s:"
,
ends
[
0
]);
end_layout
=
g_strdup_printf
(
":%s"
,
ends
[
1
]);
gtk_header_bar_set_decoration_layout
(
start_headerbar
,
start_layout
);
gtk_header_bar_set_decoration_layout
(
end_headerbar
,
end_layout
);
}
HdyHeaderGroup
*
hdy_header_group_new
(
void
)
{
return
g_object_new
(
HDY_TYPE_HEADER_GROUP
,
NULL
);
}
/**
* hdy_header_group_add_header_bar:
* @self: a #HdyHeaderGroup
* @header_bar: the #GtkHeaderBar to add
*
* Adds a header bar to a #HdyHeaderGroup. The decoration layout of the
* widgets will be edited depending on their position in the composite header
* bar, the start widget displaying only the start of the user's decoration
* layout and the end widget displaying only its end while widgets in the middle
* won't display anything. A header bar can be set as having the focus to
* display all the decorations. See gtk_header_bar_set_decoration_layout().
*
* When the widget is destroyed or no longer referenced elsewhere, it will
* be removed from the header group.
*/
void
hdy_header_group_add_header_bar
(
HdyHeaderGroup
*
self
,
GtkHeaderBar
*
header_bar
)
{
HdyHeaderGroupPrivate
*
priv
;
g_return_if_fail
(
HDY_IS_HEADER_GROUP
(
self
));
g_return_if_fail
(
GTK_IS_HEADER_BAR
(
header_bar
));
priv
=
hdy_header_group_get_instance_private
(
self
);
g_signal_connect_swapped
(
header_bar
,
"map"
,
G_CALLBACK
(
update_decoration_layouts
),
self
);
g_signal_connect_swapped
(
header_bar
,
"unmap"
,
G_CALLBACK
(
update_decoration_layouts
),
self
);
priv
->
header_bars
=
g_slist_prepend
(
priv
->
header_bars
,
header_bar
);
update_decoration_layouts
(
self
);
}
void
hdy_header_group_set_focus
(
HdyHeaderGroup
*
self
,
GtkWidget
*
child
)
{
HdyHeaderGroupPrivate
*
priv
;
g_message
(
"focused"
);
g_return_if_fail
(
HDY_IS_HEADER_GROUP
(
self
));
priv
=
hdy_header_group_get_instance_private
(
self
);
g_set_object
(
&
priv
->
focus
,
child
);
update_decoration_layouts
(
self
);
g_object_notify_by_pspec
(
G_OBJECT
(
self
),
props
[
PROP_FOCUS
]);
}
GtkWidget
*
hdy_header_group_get_focus
(
HdyHeaderGroup
*
self
)
{
HdyHeaderGroupPrivate
*
priv
;
g_return_val_if_fail
(
HDY_IS_HEADER_GROUP
(
self
),
FALSE
);
priv
=
hdy_header_group_get_instance_private
(
self
);
return
priv
->
focus
;
}
typedef
struct
{
gchar
*
name
;
gint
line
;
gint
col
;
}
ItemData
;
static
void
item_data_free
(
gpointer
data
)
{
ItemData
*
item_data
=
data
;
g_free
(
item_data
->
name
);
g_free
(
item_data
);
}
typedef
struct
{
GObject
*
object
;
GtkBuilder
*
builder
;
GSList
*
items
;
}
GSListSubParserData
;
static
void
hdy_header_group_finalize
(
GObject
*
object
)
{
/* FIXME */
/* HdyHeaderGroup *self = (HdyHeaderGroup *)object; */
/* HdyHeaderGroupPrivate *priv = hdy_header_group_get_instance_private (self); */
G_OBJECT_CLASS
(
hdy_header_group_parent_class
)
->
finalize
(
object
);
}
static
void
hdy_header_group_get_property
(
GObject
*
object
,
guint
prop_id
,
GValue
*
value
,
GParamSpec
*
pspec
)
{
HdyHeaderGroup
*
self
=
HDY_HEADER_GROUP
(
object
);
switch
(
prop_id
)
{
case
PROP_FOCUS
:
g_value_set_object
(
value
,
hdy_header_group_get_focus
(
self
));
break
;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
object
,
prop_id
,
pspec
);
}
}
static
void
hdy_header_group_set_property
(
GObject
*
object
,
guint
prop_id
,
const
GValue
*
value
,
GParamSpec
*
pspec
)
{
HdyHeaderGroup
*
self
=
HDY_HEADER_GROUP
(
object
);
switch
(
prop_id
)
{
case
PROP_FOCUS
:
hdy_header_group_set_focus
(
self
,
g_value_get_object
(
value
));
break
;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
object
,
prop_id
,
pspec
);
}
}
/*< private >
* @builder: a #GtkBuilder
* @context: the #GMarkupParseContext
* @parent_name: the name of the expected parent element
* @error: return location for an error
*
* Checks that the parent element of the currently handled
* start tag is @parent_name and set @error if it isn't.
*
* This is intended to be called in start_element vfuncs to
* ensure that element nesting is as intended.
*
* Returns: %TRUE if @parent_name is the parent element
*/
/* This has been copied and modified from gtkbuilder.c. */
static
gboolean
_gtk_builder_check_parent
(
GtkBuilder
*
builder
,
GMarkupParseContext
*
context
,
const
gchar
*
parent_name
,
GError
**
error
)
{
const
GSList
*
stack
;
gint
line
,
col
;
const
gchar
*
parent
;
const
gchar
*
element
;
stack
=
g_markup_parse_context_get_element_stack
(
context
);
element
=
(
const
gchar
*
)
stack
->
data
;
parent
=
stack
->
next
?
(
const
gchar
*
)
stack
->
next
->
data
:
""
;
if
(
g_str_equal
(
parent_name
,
parent
)
||
(
g_str_equal
(
parent_name
,
"object"
)
&&
g_str_equal
(
parent
,
"template"
)))
return
TRUE
;
g_markup_parse_context_get_position
(
context
,
&
line
,
&
col
);
g_set_error
(
error
,
GTK_BUILDER_ERROR
,
GTK_BUILDER_ERROR_INVALID_TAG
,
".:%d:%d Can't use <%s> here"
,
line
,
col
,
element
);
return
FALSE
;
}
/*< private >
* _gtk_builder_prefix_error:
* @builder: a #GtkBuilder
* @context: the #GMarkupParseContext
* @error: an error
*
* Calls g_prefix_error() to prepend a filename:line:column marker
* to the given error. The filename is taken from @builder, and
* the line and column are obtained by calling
* g_markup_parse_context_get_position().
*
* This is intended to be called on errors returned by
* g_markup_collect_attributes() in a start_element vfunc.
*/
/* This has been copied and modified from gtkbuilder.c. */
static
void
_gtk_builder_prefix_error
(
GtkBuilder
*
builder
,
GMarkupParseContext
*
context
,
GError
**
error
)
{
gint
line
,
col
;
g_markup_parse_context_get_position
(
context
,
&
line
,
&
col
);
g_prefix_error
(
error
,
".:%d:%d "
,
line
,
col
);
}
/*< private >
* _gtk_builder_error_unhandled_tag:
* @builder: a #GtkBuilder
* @context: the #GMarkupParseContext
* @object: name of the object that is being handled
* @element_name: name of the element whose start tag is being handled
* @error: return location for the error
*
* Sets @error to a suitable error indicating that an @element_name
* tag is not expected in the custom markup for @object.
*
* This is intended to be called in a start_element vfunc.
*/
/* This has been copied and modified from gtkbuilder.c. */
static
void
_gtk_builder_error_unhandled_tag
(
GtkBuilder
*
builder
,
GMarkupParseContext
*
context
,
const
gchar
*
object
,
const
gchar
*
element_name
,
GError
**
error
)
{
gint
line
,
col
;
g_markup_parse_context_get_position
(
context
,
&
line
,
&
col
);
g_set_error
(
error
,
GTK_BUILDER_ERROR
,
GTK_BUILDER_ERROR_UNHANDLED_TAG
,
".:%d:%d Unsupported tag for %s: <%s>"
,
line
,
col
,
object
,
element_name
);
}
/* This has been copied and modified from gtksizegroup.c. */
static
void
header_group_start_element
(
GMarkupParseContext
*
context
,
const
gchar
*
element_name
,
const
gchar
**
names
,
const
gchar
**
values
,
gpointer
user_data
,
GError
**
error
)
{
GSListSubParserData
*
data
=
(
GSListSubParserData
*
)
user_data
;
if
(
strcmp
(
element_name
,
"headerbar"
)
==
0
)
{
const
gchar
*
name
;
ItemData
*
item_data
;
if
(
!
_gtk_builder_check_parent
(
data
->
builder
,
context
,
"headerbars"
,
error
))
return
;
if
(
!
g_markup_collect_attributes
(
element_name
,
names
,
values
,
error
,
G_MARKUP_COLLECT_STRING
,
"name"
,
&
name
,
G_MARKUP_COLLECT_INVALID
))
{
_gtk_builder_prefix_error
(
data
->
builder
,
context
,
error
);
return
;
}
item_data
=
g_new
(
ItemData
,
1
);
item_data
->
name
=
g_strdup
(
name
);
g_markup_parse_context_get_position
(
context
,
&
item_data
->
line
,
&
item_data
->
col
);
data
->
items
=
g_slist_prepend
(
data
->
items
,
item_data
);
}
else
if
(
strcmp
(
element_name
,
"headerbars"
)
==
0
)
{
if
(
!
_gtk_builder_check_parent
(
data
->
builder
,
context
,
"object"
,
error
))
return
;
if
(
!
g_markup_collect_attributes
(
element_name
,
names
,
values
,
error
,
G_MARKUP_COLLECT_INVALID
,
NULL
,
NULL
,
G_MARKUP_COLLECT_INVALID
))
_gtk_builder_prefix_error
(
data
->
builder
,
context
,
error
);
}
else
{
_gtk_builder_error_unhandled_tag
(
data
->
builder
,
context
,
"GtkSizeGroup"
,
element_name
,
error
);
}
}
/* This has been copied and modified from gtksizegroup.c. */
static
const
GMarkupParser
header_group_parser
=
{
header_group_start_element
};
/* This has been copied and modified from gtksizegroup.c. */
static
gboolean
hdy_header_group_buildable_custom_tag_start
(
GtkBuildable
*
buildable
,
GtkBuilder
*
builder
,
GObject
*
child
,
const
gchar
*
tagname
,
GMarkupParser
*
parser
,
gpointer
*
parser_data
)
{
GSListSubParserData
*
data
;
if
(
child
)
return
FALSE
;
if
(
strcmp
(
tagname
,
"headerbars"
)
==
0
)
{
data
=
g_slice_new0
(
GSListSubParserData
);
data
->
items
=
NULL
;
data
->
object
=
G_OBJECT
(
buildable
);
data
->
builder
=
builder
;
*
parser
=
header_group_parser
;
*
parser_data
=
data
;
return
TRUE
;
}
return
FALSE
;
}
/* This has been copied and modified from gtksizegroup.c. */
static
void
hdy_header_group_buildable_custom_finished
(
GtkBuildable
*
buildable
,
GtkBuilder
*
builder
,
GObject
*
child
,
const
gchar
*
tagname
,
gpointer
user_data
)
{
GSList
*
l
;
GSListSubParserData
*
data
;
GObject
*
object
;
if
(
strcmp
(
tagname
,
"headerbars"
)
!=
0
)
return
;
data
=
(
GSListSubParserData
*
)
user_data
;
data
->
items
=
g_slist_reverse
(
data
->
items
);
for
(
l
=
data
->
items
;
l
;
l
=
l
->
next
)
{
ItemData
*
item_data
=
l
->
data
;
object
=
gtk_builder_get_object
(
builder
,
item_data
->
name
);
if
(
!
object
)
continue
;
hdy_header_group_add_header_bar
(
HDY_HEADER_GROUP
(
data
->
object
),
GTK_HEADER_BAR
(
object
));
}
g_slist_free_full
(
data
->
items
,
item_data_free
);
g_slice_free
(
GSListSubParserData
,
data
);
}
static
void
hdy_header_group_class_init
(
HdyHeaderGroupClass
*
klass
)
{
GObjectClass
*
object_class
=
G_OBJECT_CLASS
(
klass
);
object_class
->
finalize
=
hdy_header_group_finalize
;
object_class
->
get_property
=
hdy_header_group_get_property
;
object_class
->
set_property
=
hdy_header_group_set_property
;
/**
* HdyHeaderGroup:focus:
*
* %TRUE if the header group is focused on a single headerbar.
*/
props
[
PROP_FOCUS
]
=
g_param_spec_boolean
(
"focus"
,
_
(
"Focus"
),
_
(
"The child that should have the focus"
),
FALSE
,
G_PARAM_READWRITE
|
G_PARAM_EXPLICIT_NOTIFY
);
g_object_class_install_properties
(
object_class
,
N_PROPS
,
props
);
}
static
void
hdy_header_group_init
(
HdyHeaderGroup
*
self
)
{
GtkSettings
*
settings
=
gtk_settings_get_default
();
g_signal_connect_swapped
(
settings
,
"notify::gtk-decoration-layout"
,
G_CALLBACK
(
update_decoration_layouts
),
self
);
}
static
void
hdy_header_group_buildable_init
(
GtkBuildableIface
*
iface
)
{
iface
->
custom_tag_start
=
hdy_header_group_buildable_custom_tag_start
;
iface
->
custom_finished
=
hdy_header_group_buildable_custom_finished
;
}
src/hdy-header-group.h
0 → 100644
View file @
ec017d03
/*
* Copyright (C) 2018 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#ifndef HDY_HEADER_GROUP_H
#define HDY_HEADER_GROUP_H
#if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION)
#error "Only <handy.h> can be included directly."
#endif
#include
<gtk/gtk.h>
G_BEGIN_DECLS
#define HDY_TYPE_HEADER_GROUP (hdy_header_group_get_type())
G_DECLARE_DERIVABLE_TYPE
(
HdyHeaderGroup
,
hdy_header_group
,
HDY
,
HEADER_GROUP
,
GObject
)
/**
* HdyHeaderGroupClass
* @parent_class: The parent class
*/
struct
_HdyHeaderGroupClass
{
GObjectClass
parent_class
;
};
HdyHeaderGroup
*
hdy_header_group_new
(
void
);
void
hdy_header_group_add_header_bar
(
HdyHeaderGroup
*
self
,
GtkHeaderBar
*
header_bar
);
GtkWidget
*
hdy_header_group_get_focus
(
HdyHeaderGroup
*
self
);
void
hdy_header_group_set_focus
(
HdyHeaderGroup
*
self
,
GtkWidget
*
child
);
G_END_DECLS
#endif
/* HDY_HEADER_GROUP_H */
src/meson.build
View file @
ec017d03
...
...
@@ -51,6 +51,7 @@ src_headers = [
'hdy-dialer-cycle-button.h',
'hdy-dialer.h',
'hdy-fold.h',
'hdy-header-group.h',
'hdy-leaflet.h',
'hdy-string-utf8.h',
'hdy-title-bar.h',
...
...
@@ -64,6 +65,7 @@ src_sources = [
'hdy-dialer-cycle-button.c',
'hdy-dialer.c',
'hdy-fold.c',
'hdy-header-group.c',
'hdy-leaflet.c',
'hdy-string-utf8.c',
'hdy-title-bar.c',
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment