Compare commits

...

59 Commits

Author SHA1 Message Date
Timm Bäder
0496c8050c demo: Use a listview as sidebar 2019-10-15 15:39:59 +02:00
Benjamin Otte
a92ed687e1 treeexpander: Implement input support
This implements all the keybindings from GtkTreeView that can be
supported.

It does not implement expand-all, because supporting that means
causing the TreeListModel to emit lots of create_model vfuncs which in
turn would cause many items-changed signal which in turn would cause
many signal handlers to run which in turn would make "expand-all" very
reentrant, and I'm uneasy about supporting that.

For the mouse, just add a click gesture to the expander icon that toggles
expanded state.
2019-10-15 07:17:31 +02:00
Benjamin Otte
4e5a4f6ab4 listitem: Change focus handling
Focus in the listitem now works like this:
1. If any child can take focus, do not ever attempt
   to take focus.
2. Otherwise, if this item is selectable or activatable,
   allow focusing this widget.

This makes sure every item in a list is focusable for
activation and selection handling, but no useless widgets
get focused and moving focus is as fast as possible.
2019-10-15 07:17:31 +02:00
Benjamin Otte
b03496d404 popover: Remove unneeded vfunc
The vfunc is identical to the GtkWidget implementation it replaces. So
just keep using that one.
2019-10-15 07:17:31 +02:00
Benjamin Otte
d06ab5f367 inspector: Make the recorder node list use a ListView
It's quite a bit faster now, but the code is also a bit more awkward.

Pain points:

- GtkTreeListModel cannot be created in UI files because it needs
  a CreateModelFunc.
  Using a signal for this doesn't work because autoexpand wants to
  expand the model before the signal handler is connected.

- The list item factory usage is still awkward. It's bearable here
  because the list items are very simple, but still.
2019-10-15 07:17:31 +02:00
Benjamin Otte
718f66ada4 inspector: Use a GtkTreeExpander in the object tree 2019-10-15 07:17:31 +02:00
Benjamin Otte
82736005d1 inspector: Use a treeexpander in the recorder 2019-10-15 07:17:31 +02:00
Benjamin Otte
c84138c63f demo: Add a GSettings tree demo
It is meant to look somewhat like dconf-editor when it is done.

So far, it's just a list.
2019-10-15 07:17:31 +02:00
Benjamin Otte
0a9ac877ba Add GtkTreeExpander
This is a container widget that takes over all the duties of tree
expanding and collapsing.
It has to be a container so it can capture keybindings while focus is
inside the listitem.

So far, this widget does not allow interacting with it, but it shows the
expander arrow in its correct state.

Also, testlistview uses this widget now instead of implementing
expanding itself.
2019-10-15 07:17:31 +02:00
Benjamin Otte
73625cb157 gridview: Actually do something
Implement measuring and allocating items - which makes the items appear
when drawing and allows interacting with the items.

However, the gridview still does not allow any user interaction
(including scrolling).
2019-10-15 07:17:31 +02:00
Benjamin Otte
22dcdc073f listview: Pass the CSS name of listitems to the manager
... instead of hardcoding "row".
2019-10-15 07:17:31 +02:00
Benjamin Otte
c69499f98b gridview: Implement GtkOrientable
Again, this is just the skeleton, because the Gridview does nothing yet.
2019-10-15 07:17:31 +02:00
Benjamin Otte
13a6b3c64e gridview: Add factory handling
Just copy the listview APIs.

Code still doesn't do anything with it.
2019-10-15 07:17:31 +02:00
Benjamin Otte
0d2f70bd16 listview: Expose GtkListItemFactory APIs
Due to the many different ways to set factories, it makes sense to
expose them as custom objects.

This makes the actual APIs for the list widgets simpler, because they
can just have a regular "factory" property.

As a convenience function, gtk_list_view_new_with_factory() was added
to make this whole approach easy to use from C.
2019-10-15 07:17:31 +02:00
Benjamin Otte
2158d97800 textview: Make cursor work when blinking is disabled 2019-10-15 07:17:31 +02:00
Benjamin Otte
7ff18b7b5b gtk-demo: Add a rough start at a Weather demo
This demos a horizontal listview.
2019-10-15 07:17:31 +02:00
Benjamin Otte
ed19c3eeee listview: Implement GtkOrientable 2019-10-15 07:17:31 +02:00
Benjamin Otte
c633d01e4f tests: Add a rough form of multiselection
Just store a "filechooser::selected" attribute in the GFileInfo if
the file is meant to be selected.
2019-10-15 07:17:31 +02:00
Benjamin Otte
8b12e8acd4 listview: Implement extending selections
Shift-clicking to extend selections now also works, imitating the
behavior of normal clicking and Windows Explorer (but not treeview):

1. We track the last selected item (normally, not via extend-clicking).

2. When shift-selecting, we modify the range from the last selected item
   to this item the same way we modify the regular item when not using
   shift:

2a. If Ctrl is not pressed, we select the range and unselect everything
    else.

2b. If Ctrl is pressed, we make the range have the same selection state
    as the last selected item:
    - If the last selected item is selected, select the range.
    - If the last selected item is not selected, unselect the range.
2019-10-15 07:17:31 +02:00
Benjamin Otte
1532dff5a8 listview: Add list.scroll_to_item action
The action scrolls the given item into view.

Listitems activate this action when they gain focus.
2019-10-15 07:17:31 +02:00
Benjamin Otte
7666d41a79 testlistview: Load icons async
Speeds up loading by 4x, because out of view icons aren't loaded
anymore.
2019-10-15 07:17:31 +02:00
Benjamin Otte
54bbccf0de directorylist: Add
Adds a new listmodel called GtkDirectoryList that lists the children of
a GFile as GFileInfos.

This is supposed to be used by the filechooser.
2019-10-15 07:17:31 +02:00
Benjamin Otte
5974aaa59b gtk-demo: Introduce awards
We need a way to get a useful listbox, so here we go!
2019-10-15 07:17:31 +02:00
Benjamin Otte
9cb49d23c8 listitemfactory: Add a factory for ui files
Reuse <template> magic to initialize GtkListItems. This feels
amazingly hacky, but it also amazingly worked on the first try.
2019-10-15 07:17:31 +02:00
Benjamin Otte
2301a18987 listitemfactory: Split implementation out
.. into gtkfunctionslistitemfactory.c

Now we can add a different implmenetation.
2019-10-15 07:17:31 +02:00
Benjamin Otte
7fbbdcb2af listitemfactory: vfuncify
No functional changes other than a new indirection.
2019-10-15 07:17:31 +02:00
Benjamin Otte
46165449ef listitemfactory: Sanitize APIs
Make sure the APIs follow a predictable path:

setup
  bind
    rebind/update (0-N times)
  unbind
teardown

This is the first step towards providing multiple different factories.
2019-10-15 07:17:31 +02:00
Benjamin Otte
19be08854f listview: Add gtk_list_view_set_show_separators()
Do the same thing that GtkListBox does in commit
0249bd4f8a
2019-10-15 07:17:31 +02:00
Benjamin Otte
f2e20904f1 listitemmanager: Add trackers
... and replace the anchor tracking with a tracker.

Trackers track an item through the list across changes and ensure that
this item (and potentially siblings before/after it) are always backed
by a GtkListItem and that if the item gets removed a replacement gets
chosen.

This is now used for tracking the anchor but can also be used to add
trackers for the cursor later.
2019-10-15 07:17:31 +02:00
Benjamin Otte
95e462e181 listitemmanager: Simplify
Remove a bunch of API from the headers that isn't used anymore and then
refactor code to not call it anymore.

In particular, get rid of GtkListItemManagerChange and replace it with a
GHashTable.
2019-10-15 07:17:31 +02:00
Benjamin Otte
2a54786be8 gridview: Implement GtkScrollable
We can now scroll all the nothing we display.

We also clip it properly.
2019-10-15 07:17:31 +02:00
Benjamin Otte
9c55ac513c listitemmanager: Move list of listitems here
All the listview infrastructure moved with it, so the next step is
moving that back...
2019-10-15 07:17:31 +02:00
Benjamin Otte
1f1f5e2aed wayland: Remove function declaration for nonexisting function 2019-10-15 07:17:31 +02:00
Benjamin Otte
18a03fa043 gridview: Add API for setting number of columns
The API isn't used yet.
2019-10-15 07:17:31 +02:00
Benjamin Otte
a0e4be18a0 gtk: Add a GtkGridView skeleton 2019-10-15 07:17:30 +02:00
Benjamin Otte
30a471bc31 listitem: Add a press gesture to select the item
This is implemented by using actions, which are a neat trick to get to
allow the ListItem to call functions on the ListView without actually
needing to be aware of it.
2019-10-15 07:17:30 +02:00
Benjamin Otte
7346d69217 listview: Add initial support for displaying selections 2019-10-15 07:17:30 +02:00
Benjamin Otte
0a85576377 listview: Reset listitems' CSS animations when rebinding
This way, newly displayed rows don't play an unselect animation (text
fading in) when they are unselected, but the row was previously used for
a selected item.
2019-10-15 07:17:30 +02:00
Benjamin Otte
ca6aa9e549 listview: Add selection properties to ListItem
This just brings the infrastructure into place, we're not using the
properties yet.
2019-10-15 07:17:30 +02:00
Benjamin Otte
97db41c8a0 listview: Try to keep the list items in order when scrolling
Instead of just destroying all items and then recreating them (or even
hide()ing and then show()ing them again (or even even repositioning
them in the widget tree)), just try to reust them in the order they are.

This works surprisingly well when scrolling and most/all widgets
just moved.
2019-10-15 07:17:30 +02:00
Benjamin Otte
41815d3d26 listlistmodel: Add gtk_list_list_model_item_moved()
Use it to fix a case that just said g_warning ("oops").

Apparently I had forgotten the case where a container moved a child
in the widget tree.
2019-10-15 07:17:30 +02:00
Benjamin Otte
c60b4fd031 listitemmanager: Switch from "insert_before" to "insert_after" argumnet
We reorder widgets start to end, so when reusing a list item, we
correctly know the previous sibling for that list item, but not the
next sibling yet. We just know the widget it should ultimately be in
front of.
So we can do a more correct guess of the list item's place in the widget
tree if we think about where to place an item like this.

Actually using this change will come in the next commit.
2019-10-15 07:17:30 +02:00
Benjamin Otte
d227a713ed testlistview: Create widgets only once
Previously, we were recreating all widgets every time the list item was
rebound, which caused a lot of extra work every time we scrolled.

Now we keep the widgets around and only set their properties again when
the item changes.
2019-10-15 07:17:30 +02:00
Benjamin Otte
112c5aa1ff testlistview: Show the row number
Always show the current row. This is mostly useful for debugging, not
for beauty.
2019-10-15 07:17:30 +02:00
Benjamin Otte
88f43cdff8 listview: Only allocate necesary rows
This is the big one.

The listview only allocates 200 rows around the visible row now.
Everything else is kept in ListRow instances with row->widget == NULL.

For rows without a widget, we assign the median height of the child
widgets as the row's height and then do all calculations as if there
were widgets that had requested that height (like setting adjustment
values or reacting to adjustment value changes).

When the view is scrolled, we bind the 200 rows to the new visible area,
so that the part of the listview that can be seen is always allocated.
2019-10-15 07:17:30 +02:00
Benjamin Otte
e90758a43f listview: Change anchor handling again
The anchor is now a tuple of { listitem, align }.

Using the actual list item allows keeping the anchor across changes
in position (ie when lists get resorted) while still being able to fall
back to positions (list items store their position) when an item gets
removed.

The align value is in the range [0..1] and defines where in the visible
area to do the alignment.
0.0 means to align the top of the row with the top of the visible area,
1.0 aligns the bottom of the widget with the visible area and 0.5 keeps
the center of the widget at the center of the visible area.
It works conceptually the same as percentages in CSS background-position
(where the background area and the background image's size are matched
the same way) or CSS transform-origin.
2019-10-15 07:17:30 +02:00
Benjamin Otte
b2a6938805 listview: Change how binding is done
We now don't let the functions create widgets for the item from the
listmodel, instead we hand out a GtkListItem for them to add a widget
to.

GtkListItems are created in advance and can only be filled in by the
binding code by gtk_container_add()ing a widget.
However, they are GObjects, so they can provide properties that the
binding code can make use of - either via notify signals or GBinding.
2019-10-15 07:17:30 +02:00
Benjamin Otte
bc61ad4bc4 listitem: Add gtk_list_item_get_position()
Also refactor the whole list item management yet again.

Now, list item APIs doesn't have bind/unbind functions anymore, but only
property setters.

The item factory is the only one doing the binding.
As before, the item manager manages when items need to be bound.
2019-10-15 07:17:30 +02:00
Benjamin Otte
f967055224 tests: Make animating listview do random resorts 2019-10-15 07:17:30 +02:00
Benjamin Otte
c387f76e64 listview: Change change management
Add a GtkListItemManagerChange object that tracks all removed list
rows during an item-changed signal so they can be added back later.
2019-10-15 07:17:30 +02:00
Benjamin Otte
c22a6d355f listview: Make the listitemmanager stricter
Require that items created with the manager get destroyed via the
manager.

To that purpose, renamed create_list_item() to acquire_list_item() and
add a matching release_list_item() function.

This way, the manager can in the future keep track of all items and
cache information about them.
2019-10-15 07:17:30 +02:00
Benjamin Otte
a6a93ba340 listview: Add GtkListItem
GtkListItem is a generic row widget that is supposed to replace
GtkListBoxRow and GtkFlowBoxChild.
2019-10-15 07:17:30 +02:00
Benjamin Otte
f32c010904 listview: Add GtkListItemManager
It's all stubs for now, but here's the basic ideas about what
this object is supposed to do:

(1) It's supposed to be handling all the child GtkWidgets that are
    used by the listview, so that the listview can concern
    itself with how many items it needs and where to put them.
(2) It's meant to do the caching of widgets that are not (currently)
    used.
(3) It's meant to track items that remain in the model across
    items-changed emissions and just change position.
(2) It's code that can be shared between listview and potential
    other widgets like a GridView.

It's also free to assume that the number of items it's supposed to
manage doesn't grow too much, so it's free to use O(N) algorithms.
2019-10-15 07:17:30 +02:00
Benjamin Otte
e72eb6c302 listview: Implement an anchor
The anchor selection is very basic: just anchor the top row.

That's vastly better than any other widget already though.
2019-10-15 07:17:30 +02:00
Benjamin Otte
4d19b7d612 tests: Add a test for a permanently changing listview
This is mostly for dealing with proper anchoring and can be used to
check that things don't scroll or that selection and focus handling
properly works.

For comparison purposes, a ListBox is provided next to it.
2019-10-15 07:17:30 +02:00
Benjamin Otte
42b04251d5 listview: Implement GtkScrollable
Scrolling in a very basic form is also supported
2019-10-15 07:17:30 +02:00
Benjamin Otte
a53afb694d listview: Make widget actually do something
The thing we're actually doing is create and maintain a widget for every
row. That's it.

Also add a testcase using this. The testcase quickly allocates too many
rows though and then becomes unresponsive though. You have been warned.
2019-10-15 07:17:30 +02:00
Benjamin Otte
477fa48557 listview: Introduce GtkListItemFactory
Thisis the abstraction I intend to use for creating widgets and binding
them to the item out of the listview.

For now this is a very dumb wrapper around the functions that exist in
the API.

But it leaves the freedom to turn this into public API, make an
interface out of it and most of all write different implementations, in
particular one that uses GtkBuilder.
2019-10-15 07:17:30 +02:00
Benjamin Otte
389eb0de56 gtk: Add a GtkListView skeleton 2019-10-15 07:17:30 +02:00
58 changed files with 78951 additions and 357 deletions

248
demos/gtk-demo/award.c Normal file
View File

@@ -0,0 +1,248 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "award.h"
struct _GtkAward
{
GObject parent;
char *explanation;
char *name;
char *title;
GDateTime *granted; /* or NULL if not granted */
};
enum {
PROP_0,
PROP_EXPLANATION,
PROP_NAME,
PROP_TITLE,
PROP_GRANTED,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkAward, gtk_award, G_TYPE_OBJECT)
static void
gtk_award_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkAward *self = GTK_AWARD (object);
switch (prop_id)
{
case PROP_EXPLANATION:
self->explanation = g_value_dup_string (value);
break;
case PROP_NAME:
self->name = g_value_dup_string (value);
break;
case PROP_TITLE:
self->title = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_award_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkAward *self = GTK_AWARD (object);
switch (prop_id)
{
case PROP_EXPLANATION:
g_value_set_string (value, self->explanation);
break;
case PROP_NAME:
g_value_set_string (value, self->name);
break;
case PROP_TITLE:
g_value_set_string (value, self->title);
break;
case PROP_GRANTED:
g_value_set_boxed (value, self->granted);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_award_dispose (GObject *object)
{
GtkAward *self = GTK_AWARD (object);
g_clear_pointer (&self->name, g_free);
g_clear_pointer (&self->title, g_free);
g_clear_pointer (&self->granted, g_date_time_unref);
G_OBJECT_CLASS (gtk_award_parent_class)->dispose (object);
}
static void
gtk_award_class_init (GtkAwardClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_award_set_property;
gobject_class->get_property = gtk_award_get_property;
gobject_class->dispose = gtk_award_dispose;
properties[PROP_EXPLANATION] =
g_param_spec_string ("explanation",
"Explanation",
"How to get the title",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_NAME] =
g_param_spec_string ("name",
"Name",
"internal name of the award",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_TITLE] =
g_param_spec_string ("title",
"Title",
"user-visible title",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_GRANTED] =
g_param_spec_boxed ("granted",
"Granted",
"Timestamp the award was granted or NULL if not granted yet",
G_TYPE_DATE_TIME,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_award_init (GtkAward *self)
{
}
GListModel *
gtk_award_get_list (void)
{
static GListModel *list = NULL;
if (list == NULL)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_AWARD);
builder = gtk_builder_new_from_resource ("/awards.ui");
gtk_builder_connect_signals (builder, NULL);
list = G_LIST_MODEL (gtk_builder_get_object (builder, "list"));
g_object_ref (list);
g_object_unref (builder);
}
return g_object_ref (list);
}
const char *
gtk_award_get_name (GtkAward *award)
{
return award->name;
}
const char *
gtk_award_get_title (GtkAward *award)
{
return award->title;
}
GDateTime *
gtk_award_get_granted (GtkAward *award)
{
return award->granted;
}
GtkAward *
award_find (const char *name)
{
GListModel *list;
GtkAward *self;
guint i;
list = gtk_award_get_list ();
g_object_unref (list);
for (i = 0; i < g_list_model_get_n_items (list); i++)
{
self = g_list_model_get_item (list, i);
g_object_unref (self);
if (g_ascii_strcasecmp (name, self->name) == 0)
return self;
}
return NULL;
}
void
award (const char *name)
{
GtkAward *self;
GNotification *notification;
self = award_find (name);
if (self == NULL)
{
g_warning ("Did not find award \"%s\"", name);
return;
}
if (self->granted)
return;
self->granted = g_date_time_new_now_utc ();
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_GRANTED]);
notification = g_notification_new ("You won an award!");
g_notification_set_body (notification, self->title);
g_application_send_notification (g_application_get_default (), NULL, notification);
g_object_unref (notification);
}

18
demos/gtk-demo/award.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef __AWARD_H__
#define __AWARD_H__
#include <gtk/gtk.h>
#define GTK_TYPE_AWARD (gtk_award_get_type ())
G_DECLARE_FINAL_TYPE (GtkAward, gtk_award, GTK, AWARD, GObject)
GListModel * gtk_award_get_list (void);
const char * gtk_award_get_name (GtkAward *award);
const char * gtk_award_get_title (GtkAward *award);
GDateTime * gtk_award_get_granted (GtkAward *award);
void award (const char *name);
#endif /* __AWARD_H__ */

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="gtk40">
<template class="GtkListItem">
<child>
<object class="GtkLabel">
<property name="label" bind-source="GtkListItem" bind-property="position"></property>
<property name="margin">6</property>
</object>
</child>
</template>
</interface>

89
demos/gtk-demo/awards.ui Normal file
View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GListStore" id="list">
<property name="item-type">GtkAward</property>
<child>
<object class="GtkAward">
<property name="name">demo-inspector</property>
<!-- Transformers -->
<property name="title" translatable="yes">You got a high-rise double-pump carburetor.</property>
<property name="explanation" translatable="yes">Launch the inspector</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">demo-start</property>
<!-- The Matrix -->
<property name="title" translatable="yes">After this, there is no turning back.</property>
<property name="explanation" translatable="yes">Start gtk-demo</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">listbox-reshare</property>
<!-- Mean Girls -->
<property name="title" translatable="yes">Trying to make fetch happen</property>
<property name="explanation" translatable="yes">Reshare a tweet</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">listbox-100th-row</property>
<!-- Aladdin -->
<property name="title" translatable="yes">The ever impressive, long contained, often imitated, but never duplicated Genie of the lamp.</property>
<property name="explanation" translatable="yes">Select a 100th row in a list</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">password-best</property>
<!-- Spaceballs -->
<property name="title" translatable="yes">I've got the same combination on my luggage!</property>
<property name="explanation" translatable="yes">Use "12345" as the password</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">password-correct</property>
<!-- Stanley Parable -->
<property name="title" translatable="yes">Night Shark 1-1-5</property>
<property name="explanation" translatable="yes">Correctly enter a password</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">puzzle-give-up</property>
<!-- Pretty Woman -->
<property name="title" translatable="yes">Big Mistake. Big. Huge!</property>
<property name="explanation" translatable="yes">Close the puzzle without finishing it</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">puzzle-solve</property>
<!-- The Incredibles -->
<property name="title" translatable="yes">That was totally wicked!</property>
<property name="explanation" translatable="yes">Solve a puzzle</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">puzzle-solve-animated</property>
<!-- The Phantom Menace -->
<property name="title" translatable="yes">A surprise to be sure but a welcome one.</property>
<property name="explanation" translatable="yes">Solve an animated puzzle</property>
</object>
</child>
<child>
<object class="GtkAward">
<property name="name">puzzle-solve-large</property>
<!-- Portal -->
<property name="title" translatable="yes">Science isn't about WHY. It's about WHY NOT?!</property>
<property name="explanation" translatable="yes">Solve a puzzle with at least 20 pieces</property>
</object>
</child>
</object>
</interface>

View File

@@ -0,0 +1,79 @@
/* Awards
*
* This demo demonstrates how to use lists to show the awards you have collected
* while exploring this demo.
*
*/
#include <gtk/gtk.h>
/* Include the header for accessing the awards */
#include "award.h"
static GtkWidget *window = NULL;
#if 0
static void
create_listitem (GtkListItem *item, gpointer user_data)
{
GtkWidget *label;
label = gtk_label_new (NULL);
g_object_set (label, "margin", 6, NULL); /* omg, why do we need to do this?! */
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_container_add (GTK_CONTAINER (item), label);
}
static void
bind_listitem (GtkListItem *item, gpointer user_data)
{
GtkAward *award = gtk_list_item_get_item (item);
gtk_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (item))),
gtk_award_get_title (award));
}
#endif
GtkWidget *
do_awardview (GtkWidget *do_widget)
{
if (!window)
{
GtkWidget *sw, *listview;
GListModel *list;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Awards");
gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_widget_destroyed), &window);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_container_add (GTK_CONTAINER (window), sw);
#if 0
listview = gtk_list_view_new ();
gtk_list_view_set_functions (GTK_LIST_VIEW (listview),
create_listitem,
bind_listitem,
NULL, NULL);
#else
listview = gtk_list_view_new_with_factory (
gtk_builder_list_item_factory_new_from_resource ("/awardview/awardlistitem.ui"));
#endif
list = gtk_award_get_list ();
gtk_list_view_set_model (GTK_LIST_VIEW (listview), list);
g_object_unref (list);
gtk_list_view_set_show_separators (GTK_LIST_VIEW (listview), TRUE);
gtk_container_add (GTK_CONTAINER (sw), listview);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/">
<file>awards.ui</file>
</gresource>
<gresource prefix="/ui">
<file preprocess="xml-stripblanks">main.ui</file>
<file preprocess="xml-stripblanks">appmenu.ui</file>
@@ -9,6 +12,9 @@
<file>application.ui</file>
<file>menus.ui</file>
</gresource>
<gresource prefix="/awardview">
<file>awardlistitem.ui</file>
</gresource>
<gresource prefix="/builder">
<file>demo.ui</file>
</gresource>
@@ -111,6 +117,9 @@
<file>gnome-fs-directory.png</file>
<file>gnome-fs-regular.png</file>
</gresource>
<gresource prefix="/listview_weather">
<file compressed="true">listview_weather.txt</file>
</gresource>
<gresource prefix="/shortcuts">
<file>shortcuts.ui</file>
<file>shortcuts-builder.ui</file>
@@ -147,6 +156,7 @@
</gresource>
<gresource prefix="/sources">
<file>application_demo.c</file>
<file>awardview.c</file>
<file>assistant.c</file>
<file>builder.c</file>
<file>changedisplay.c</file>
@@ -189,6 +199,8 @@
<file>infobar.c</file>
<file>links.c</file>
<file>listbox.c</file>
<file>listview_settings.c</file>
<file>listview_weather.c</file>
<file>list_store.c</file>
<file>markup.c</file>
<file>menus.c</file>

View File

@@ -13,17 +13,16 @@ in_files = sys.argv[2:]
file_output = """
typedef GtkWidget *(*GDoDemoFunc) (GtkWidget *do_widget);
typedef struct _Demo Demo;
typedef struct _DemoData DemoData;
struct _Demo
struct _DemoData
{
gchar *name;
gchar *title;
gchar *filename;
char *name;
char *title;
char *filename;
GDoDemoFunc func;
Demo *children;
DemoData *children;
};
"""
# Demo = namedtuple('Demo', ['name', 'title', 'file', 'func'])
@@ -67,7 +66,7 @@ for demo in demos:
i = 0
for parent in parents:
id = parent_ids[i]
file_output += "\nDemo child" + str(id) + "[] = {\n"
file_output += "\nDemoData child" + str(id) + "[] = {\n"
# iterate over all demos and check if the name starts with the given parent name
for child in demos:
if child[1].startswith(parent + "/"):
@@ -82,7 +81,7 @@ for parent in parents:
# Sort demos by title
demos = sorted(demos, key=lambda x: x[1])
file_output += "\nDemo gtk_demos[] = {\n"
file_output += "\nDemoData gtk_demos[] = {\n"
for demo in demos:
# Do not generate one of these for demos with a parent demo
if "/" not in demo[1]:

View File

@@ -8,6 +8,7 @@
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include "award.h"
static GdkPixbuf *avatar_pixbuf_other;
static GtkWidget *window = NULL;
@@ -234,9 +235,9 @@ reshare_clicked (GtkMessageRow *row,
{
GtkMessageRowPrivate *priv = row->priv;
award ("listbox-reshare");
priv->message->n_reshares++;
gtk_message_row_update (row);
}
static void
@@ -255,11 +256,14 @@ gtk_message_row_state_flags_changed (GtkWidget *widget,
{
GtkMessageRowPrivate *priv = GTK_MESSAGE_ROW (widget)->priv;
GtkStateFlags flags;
gboolean visible;
flags = gtk_widget_get_state_flags (widget);
gtk_widget_set_visible (priv->extra_buttons_box,
flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED));
visible = flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED) ? TRUE : FALSE;
gtk_widget_set_visible (priv->extra_buttons_box, visible);
if (visible && gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (widget)) % 100 == 99)
award ("listbox-100th-row");
GTK_WIDGET_CLASS (gtk_message_row_parent_class)->state_flags_changed (widget, previous_state_flags);
}

View File

@@ -0,0 +1,147 @@
/* Lists/Settings
*
* This demo shows a settings viwer for GSettings.
*
* It demonstrates how to implement support for trees with listview.
*/
#include <gtk/gtk.h>
static int
strvcmp (gconstpointer p1,
gconstpointer p2)
{
const char * const *s1 = p1;
const char * const *s2 = p2;
return strcmp (*s1, *s2);
}
static GListModel *
create_settings_model (gpointer item,
gpointer unused)
{
GSettings *settings = item;
char **schemas;
GListStore *result;
guint i;
if (settings == NULL)
{
g_settings_schema_source_list_schemas (g_settings_schema_source_get_default (),
TRUE,
&schemas,
NULL);
}
else
{
schemas = g_settings_list_children (settings);
}
if (schemas == NULL || schemas[0] == NULL)
{
g_free (schemas);
return NULL;
}
qsort (schemas, g_strv_length (schemas), sizeof (char *), strvcmp);
result = g_list_store_new (G_TYPE_SETTINGS);
for (i = 0; schemas[i] != NULL; i++)
{
GSettings *child;
if (settings == NULL)
child = g_settings_new (schemas[i]);
else
child = g_settings_get_child (settings, schemas[i]);
g_list_store_append (result, child);
g_object_unref (child);
}
return G_LIST_MODEL (result);
}
static void
setup_widget (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *label, *expander;
expander = gtk_tree_expander_new ();
gtk_container_add (GTK_CONTAINER (list_item), expander);
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), label);
}
static void
bind_widget (GtkListItem *list_item,
gpointer unused)
{
GSettings *settings;
GtkWidget *label, *expander;
GSettingsSchema *schema;
expander = gtk_bin_get_child (GTK_BIN (list_item));
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), gtk_list_item_get_item (list_item));
label = gtk_tree_expander_get_child (GTK_TREE_EXPANDER (expander));
settings = gtk_tree_expander_get_item (GTK_TREE_EXPANDER (expander));
g_object_get (settings, "settings-schema", &schema, NULL);
gtk_label_set_label (GTK_LABEL (label), g_settings_schema_get_id (schema));
g_settings_schema_unref (schema);
}
static GtkWidget *window = NULL;
GtkWidget *
do_listview_settings (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *listview, *sw;;
GListModel *model;
GtkTreeListModel *treemodel;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Settings");
g_signal_connect (window, "destroy",
G_CALLBACK(gtk_widget_destroyed), &window);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_container_add (GTK_CONTAINER (window), sw);
listview = gtk_list_view_new_with_factory (
gtk_functions_list_item_factory_new (setup_widget,
bind_widget,
NULL, NULL));
model = create_settings_model (NULL, NULL);
treemodel = gtk_tree_list_model_new (FALSE,
model,
TRUE,
create_settings_model,
NULL,
NULL);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (treemodel));
g_object_unref (treemodel);
g_object_unref (model);
gtk_container_add (GTK_CONTAINER (sw), listview);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

View File

@@ -0,0 +1,316 @@
/* Lists/Weather
*
* This demo shows a few of the rarer features of GtkListView and
* how they can be used to display weather information.
*
* The hourly weather info uses a horizontal listview. This is easy
* to achieve because GtkListView implements the GtkOrientable interface.
*
* To make the items in the list stand out more, the listview uses
* separators.
*
* A GtkNoSelectionModel is used to make sure no item in the list can be
* selected. All other interactions with the items is still possible.
*/
#include <gtk/gtk.h>
#define GTK_TYPE_WEATHER_INFO (gtk_weather_info_get_type ())
G_DECLARE_FINAL_TYPE (GtkWeatherInfo, gtk_weather_info, GTK, WEATHER_INFO, GObject)
typedef enum {
GTK_WEATHER_CLEAR,
GTK_WEATHER_FEW_CLOUDS,
GTK_WEATHER_FOG,
GTK_WEATHER_OVERCAST,
GTK_WEATHER_SCATTERED_SHOWERS,
GTK_WEATHER_SHOWERS,
GTK_WEATHER_SNOW,
GTK_WEATHER_STORM
} GtkWeatherType;
struct _GtkWeatherInfo
{
GObject parent_instance;
gint64 timestamp;
int temperature;
GtkWeatherType weather_type;
};
struct _GtkWeatherInfoClass
{
GObjectClass parent_class;
};
static void
gtk_weather_info_class_init (GtkWeatherInfoClass *klass)
{
}
static void
gtk_weather_info_init (GtkWeatherInfo *self)
{
}
G_DEFINE_TYPE (GtkWeatherInfo, gtk_weather_info, G_TYPE_OBJECT);
static GtkWeatherInfo *
gtk_weather_info_new (GDateTime *timestamp,
GtkWeatherInfo *copy_from)
{
GtkWeatherInfo *result;
result = g_object_new (GTK_TYPE_WEATHER_INFO, NULL);
result->timestamp = g_date_time_to_unix (timestamp);
if (copy_from)
{
result->temperature = copy_from->temperature;
result->weather_type = copy_from->weather_type;
g_object_unref (copy_from);
}
return result;
}
static GDateTime *
parse_timestamp (const char *string,
GTimeZone *timezone)
{
char *with_seconds;
GDateTime *result;
with_seconds = g_strconcat (string, ":00", NULL);
result = g_date_time_new_from_iso8601 (with_seconds, timezone);
g_free (with_seconds);
return result;
}
static GtkWeatherType
parse_weather_type (const char *clouds,
const char *precip,
GtkWeatherType fallback)
{
if (strstr (precip, "SN"))
return GTK_WEATHER_SNOW;
if (strstr (precip, "TS"))
return GTK_WEATHER_STORM;
if (strstr (precip, "DZ"))
return GTK_WEATHER_SCATTERED_SHOWERS;
if (strstr (precip, "SH") || strstr (precip, "RA"))
return GTK_WEATHER_SHOWERS;
if (strstr (precip, "FG"))
return GTK_WEATHER_FOG;
if (g_str_equal (clouds, "M") ||
g_str_equal (clouds, ""))
return fallback;
if (strstr (clouds, "OVC") ||
strstr (clouds, "BKN"))
return GTK_WEATHER_OVERCAST;
if (strstr (clouds, "BKN") ||
strstr (clouds, "SCT"))
return GTK_WEATHER_FEW_CLOUDS;
if (strstr (clouds, "VV"))
return GTK_WEATHER_FOG;
return GTK_WEATHER_CLEAR;
}
static double
parse_temperature (const char *s,
double fallback)
{
char *endptr;
double d;
d = g_ascii_strtod (s, &endptr);
if (*endptr != '\0')
return fallback;
return d;
}
static GListModel *
create_weather_model (void)
{
GListStore *store;
GTimeZone *utc;
GDateTime *timestamp;
GtkWeatherInfo *info;
GBytes *data;
char **lines;
guint i;
store = g_list_store_new (GTK_TYPE_WEATHER_INFO);
data = g_resources_lookup_data ("/listview_weather/listview_weather.txt", 0, NULL);
lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
utc = g_time_zone_new_utc ();
timestamp = g_date_time_new (utc, 2011, 1, 1, 0, 0, 0);
info = gtk_weather_info_new (timestamp, NULL);
g_list_store_append (store, info);
for (i = 0; lines[i] != NULL && *lines[i]; i++)
{
char **fields;
GDateTime *date;
fields = g_strsplit (lines[i], ",", 0);
date = parse_timestamp (fields[0], utc);
while (g_date_time_difference (date, timestamp) > 30 * G_TIME_SPAN_MINUTE)
{
GDateTime *new_timestamp = g_date_time_add_hours (timestamp, 1);
g_date_time_unref (timestamp);
timestamp = new_timestamp;
info = gtk_weather_info_new (timestamp, info);
g_list_store_append (store, info);
}
info->temperature = parse_temperature (fields[1], info->temperature);
info->weather_type = parse_weather_type (fields[2], fields[3], info->weather_type);
g_date_time_unref (date);
g_strfreev (fields);
}
g_strfreev (lines);
g_bytes_unref (data);
g_time_zone_unref (utc);
return G_LIST_MODEL (store);
}
static void
setup_widget (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *box, *child;
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (list_item), box);
child = gtk_label_new (NULL);
gtk_label_set_width_chars (GTK_LABEL (child), 5);
gtk_container_add (GTK_CONTAINER (box), child);
child = gtk_image_new ();
gtk_image_set_icon_size (GTK_IMAGE (child), GTK_ICON_SIZE_LARGE);
gtk_container_add (GTK_CONTAINER (box), child);
child = gtk_label_new (NULL);
gtk_widget_set_vexpand (child, TRUE);
gtk_widget_set_valign (child, GTK_ALIGN_END);
gtk_label_set_width_chars (GTK_LABEL (child), 4);
gtk_container_add (GTK_CONTAINER (box), child);
}
static void
bind_widget (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *box, *child;
GtkWeatherInfo *info;
GDateTime *timestamp;
char *s;
box = gtk_bin_get_child (GTK_BIN (list_item));
info = gtk_list_item_get_item (list_item);
child = gtk_widget_get_first_child (box);
timestamp = g_date_time_new_from_unix_utc (info->timestamp);
s = g_date_time_format (timestamp, "%R");
gtk_label_set_text (GTK_LABEL (child), s);
g_free (s);
g_date_time_unref (timestamp);
child = gtk_widget_get_next_sibling (child);
switch (info->weather_type)
{
case GTK_WEATHER_CLEAR:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-clear-symbolic");
break;
case GTK_WEATHER_FEW_CLOUDS:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-few-clouds-symbolic");
break;
case GTK_WEATHER_FOG:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-fog-symbolic");
break;
case GTK_WEATHER_OVERCAST:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-overcast-symbolic");
break;
case GTK_WEATHER_SCATTERED_SHOWERS:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-showers-scattered-symbolic");
break;
case GTK_WEATHER_SHOWERS:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-showers-symbolic");
break;
case GTK_WEATHER_SNOW:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-snow-symbolic");
break;
case GTK_WEATHER_STORM:
gtk_image_set_from_icon_name (GTK_IMAGE (child), "weather-storm-symbolic");
break;
default:
gtk_image_clear (GTK_IMAGE (child));
break;
}
child = gtk_widget_get_next_sibling (child);
s = g_strdup_printf ("%d°", info->temperature);
gtk_label_set_text (GTK_LABEL (child), s);
g_free (s);
}
static GtkWidget *window = NULL;
GtkWidget *
do_listview_weather (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *listview, *sw;;
GListModel *model, *selection;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Weather");
g_signal_connect (window, "destroy",
G_CALLBACK(gtk_widget_destroyed), &window);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_container_add (GTK_CONTAINER (window), sw);
listview = gtk_list_view_new_with_factory (
gtk_functions_list_item_factory_new (setup_widget,
bind_widget,
NULL, NULL));
gtk_orientable_set_orientation (GTK_ORIENTABLE (listview), GTK_ORIENTATION_HORIZONTAL);
gtk_list_view_set_show_separators (GTK_LIST_VIEW (listview), TRUE);
model = create_weather_model ();
selection = G_LIST_MODEL (gtk_no_selection_new (model));
gtk_list_view_set_model (GTK_LIST_VIEW (listview), selection);
g_object_unref (selection);
g_object_unref (model);
gtk_container_add (GTK_CONTAINER (sw), listview);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_widget_destroy (window);
return window;
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include "award.h"
#include "demos.h"
static GtkWidget *info_view;
@@ -15,10 +16,32 @@ static GtkWidget *source_view;
static gchar *current_file = NULL;
static GtkWidget *window;
static GtkWidget *notebook;
static GtkWidget *treeview;
static GtkWidget *listview;
static GtkSingleSelection *selection;
static GtkWidget *headerbar;
typedef struct _GtkDemo GtkDemo;
struct _GtkDemo
{
GObject parent_instance;
const char *name;
const char *title;
const char *filename;
GDoDemoFunc func;
GListModel *children_model;
};
# define GTK_TYPE_DEMO (gtk_demo_get_type ())
G_DECLARE_FINAL_TYPE (GtkDemo, gtk_demo, GTK, DEMO, GObject);
G_DEFINE_TYPE (GtkDemo, gtk_demo, G_TYPE_OBJECT);
static void gtk_demo_init (GtkDemo *self) {}
static void gtk_demo_class_init (GtkDemoClass *klass) {}
enum {
NAME_COLUMN,
TITLE_COLUMN,
@@ -111,69 +134,7 @@ activate_inspector (GSimpleAction *action,
gpointer user_data)
{
gtk_window_set_interactive_debugging (TRUE);
}
static void
window_closed_cb (GtkWidget *window, gpointer data)
{
CallbackData *cbdata = data;
GtkTreeIter iter;
PangoStyle style;
gtk_tree_model_get_iter (cbdata->model, &iter, cbdata->path);
gtk_tree_model_get (GTK_TREE_MODEL (cbdata->model), &iter,
STYLE_COLUMN, &style,
-1);
if (style == PANGO_STYLE_ITALIC)
gtk_tree_store_set (GTK_TREE_STORE (cbdata->model), &iter,
STYLE_COLUMN, PANGO_STYLE_NORMAL,
-1);
gtk_tree_path_free (cbdata->path);
g_free (cbdata);
}
static void
run_example_for_row (GtkWidget *window,
GtkTreeModel *model,
GtkTreeIter *iter)
{
PangoStyle style;
GDoDemoFunc func;
GtkWidget *demo;
gtk_tree_model_get (GTK_TREE_MODEL (model),
iter,
FUNC_COLUMN, &func,
STYLE_COLUMN, &style,
-1);
if (func)
{
gtk_tree_store_set (GTK_TREE_STORE (model),
iter,
STYLE_COLUMN, (style == PANGO_STYLE_ITALIC ? PANGO_STYLE_NORMAL : PANGO_STYLE_ITALIC),
-1);
demo = (func) (window);
if (demo != NULL)
{
CallbackData *cbdata;
cbdata = g_new (CallbackData, 1);
cbdata->model = model;
cbdata->path = gtk_tree_model_get_path (model, iter);
if (GTK_IS_WINDOW (demo))
{
gtk_window_set_transient_for (GTK_WINDOW (demo), GTK_WINDOW (window));
gtk_window_set_modal (GTK_WINDOW (demo), TRUE);
}
g_signal_connect (demo, "destroy",
G_CALLBACK (window_closed_cb), cbdata);
}
}
award ("demo-inspector");
}
static void
@@ -181,14 +142,19 @@ activate_run (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
GtkWidget *window = user_data;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
GtkTreeListRow *row = gtk_single_selection_get_selected_item (selection);
GtkDemo *demo = gtk_tree_list_row_get_item (row);
GtkWidget *result;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
if (gtk_tree_selection_get_selected (selection, &model, &iter))
run_example_for_row (window, model, &iter);
if (!demo->func)
return;
result = demo->func(window);
if (result != NULL && GTK_IS_WINDOW (result))
{
gtk_window_set_transient_for (GTK_WINDOW (result), GTK_WINDOW (window));
gtk_window_set_modal (GTK_WINDOW (result), TRUE);
}
}
/* Stupid syntax highlighting.
@@ -888,81 +854,74 @@ load_file (const gchar *demoname,
}
static void
selection_cb (GtkTreeSelection *selection,
GtkTreeModel *model)
selection_cb (GtkSingleSelection *selection,
GParamSpec *pspec,
gpointer user_data)
{
GtkTreeIter iter;
char *name;
char *filename;
char *title;
GtkTreeListRow *row = gtk_single_selection_get_selected_item (selection);
GtkDemo *demo = gtk_tree_list_row_get_item (row);
if (! gtk_tree_selection_get_selected (selection, NULL, &iter))
return;
if (demo->filename)
load_file (demo->name, demo->filename);
gtk_tree_model_get (model, &iter,
NAME_COLUMN, &name,
TITLE_COLUMN, &title,
FILENAME_COLUMN, &filename,
-1);
if (filename)
load_file (name, filename);
gtk_header_bar_set_title (GTK_HEADER_BAR (headerbar), title);
g_free (name);
g_free (title);
g_free (filename);
gtk_header_bar_set_title (GTK_HEADER_BAR (headerbar), demo->title);
}
static void
populate_model (GtkTreeModel *model)
static GListModel *
create_demo_model (void)
{
Demo *d = gtk_demos;
GListStore *store = g_list_store_new (GTK_TYPE_DEMO);
DemoData *demo = gtk_demos;
/* this code only supports 1 level of children. If we
* want more we probably have to use a recursing function.
*/
while (d->title)
while (demo->title)
{
Demo *children = d->children;
GtkTreeIter iter;
GtkDemo *d = GTK_DEMO (g_object_new (GTK_TYPE_DEMO, NULL));
DemoData *children = demo->children;
gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
d->name = demo->name;
d->title = demo->title;
d->filename = demo->filename;
d->func = demo->func;
gtk_tree_store_set (GTK_TREE_STORE (model),
&iter,
NAME_COLUMN, d->name,
TITLE_COLUMN, d->title,
FILENAME_COLUMN, d->filename,
FUNC_COLUMN, d->func,
STYLE_COLUMN, PANGO_STYLE_NORMAL,
-1);
g_list_store_append (store, d);
d++;
if (!children)
continue;
while (children->title)
if (children)
{
GtkTreeIter child_iter;
d->children_model = G_LIST_MODEL (g_list_store_new (GTK_TYPE_DEMO));
gtk_tree_store_append (GTK_TREE_STORE (model), &child_iter, &iter);
while (children->title)
{
GtkDemo *child = GTK_DEMO (g_object_new (GTK_TYPE_DEMO, NULL));
gtk_tree_store_set (GTK_TREE_STORE (model),
&child_iter,
NAME_COLUMN, children->name,
TITLE_COLUMN, children->title,
FILENAME_COLUMN, children->filename,
FUNC_COLUMN, children->func,
STYLE_COLUMN, PANGO_STYLE_NORMAL,
-1);
child->name = children->name;
child->title = children->title;
child->filename = children->filename;
child->func = children->func;
children++;
g_list_store_append (G_LIST_STORE (d->children_model), child);
children++;
}
}
demo++;
}
return G_LIST_MODEL (store);
}
static GListModel *
get_child_model (gpointer item,
gpointer user_data)
{
GtkDemo *demo = item;
if (demo->children_model)
return g_object_ref (G_LIST_MODEL (demo->children_model));
return NULL;
}
static void
@@ -982,22 +941,6 @@ startup (GApplication *app)
g_object_unref (builder);
}
static void
row_activated_cb (GtkWidget *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column)
{
GtkTreeIter iter;
GtkWidget *window;
GtkTreeModel *model;
window = GTK_WIDGET (gtk_widget_get_root (tree_view));
model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
gtk_tree_model_get_iter (model, &iter, path);
run_example_for_row (window, model, &iter);
}
static void
start_cb (GtkMenuItem *item, GtkWidget *scrollbar)
{
@@ -1024,19 +967,50 @@ scrollbar_popup (GtkWidget *scrollbar, GtkWidget *menu)
return TRUE;
}
static void
setup_demo_row_func (GtkListItem *list_item,
gpointer user_data)
{
GtkWidget *label;
GtkWidget *expander;
expander = gtk_tree_expander_new ();
label = gtk_label_new ("");
gtk_widget_set_halign (label, GTK_ALIGN_START);
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), label);
gtk_container_add (GTK_CONTAINER (list_item), expander);
}
static void
bind_demo_row_func (GtkListItem *list_item,
gpointer user_data)
{
GtkWidget *label, *expander;
GtkTreeListRow *row;
GtkDemo *demo;
row = gtk_list_item_get_item (list_item);
demo = gtk_tree_list_row_get_item (row);
expander = gtk_bin_get_child (GTK_BIN (list_item));
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), row);
label = gtk_tree_expander_get_child (GTK_TREE_EXPANDER (expander));
gtk_label_set_label (GTK_LABEL (label), demo->title);
}
static void
activate (GApplication *app)
{
GtkBuilder *builder;
GtkWindow *window;
GtkWidget *widget;
GtkTreeModel *model;
GtkTreeIter iter;
GError *error = NULL;
GtkWidget *sw;
GtkWidget *scrollbar;
GtkWidget *menu;
GtkWidget *item;
GListModel *listmodel;
GtkTreeListModel *treemodel;
static GActionEntry win_entries[] = {
{ "run", activate_run, NULL, NULL, NULL }
@@ -1050,8 +1024,8 @@ activate (GApplication *app)
exit (1);
}
window = (GtkWindow *)gtk_builder_get_object (builder, "window");
gtk_application_add_window (GTK_APPLICATION (app), window);
window = (GtkWidget *)gtk_builder_get_object (builder, "window");
gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (window));
g_action_map_add_action_entries (G_ACTION_MAP (window),
win_entries, G_N_ELEMENTS (win_entries),
window);
@@ -1061,8 +1035,7 @@ activate (GApplication *app)
info_view = (GtkWidget *)gtk_builder_get_object (builder, "info-textview");
source_view = (GtkWidget *)gtk_builder_get_object (builder, "source-textview");
headerbar = (GtkWidget *)gtk_builder_get_object (builder, "headerbar");
treeview = (GtkWidget *)gtk_builder_get_object (builder, "treeview");
model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
listview = (GtkWidget *)gtk_builder_get_object (builder, "listview");
sw = (GtkWidget *)gtk_builder_get_object (builder, "source-scrolledwindow");
scrollbar = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
@@ -1081,17 +1054,25 @@ activate (GApplication *app)
load_file (gtk_demos[0].name, gtk_demos[0].filename);
populate_model (model);
listmodel = create_demo_model ();
treemodel = gtk_tree_list_model_new (FALSE,
G_LIST_MODEL (listmodel),
FALSE,
get_child_model,
NULL,
NULL);
g_signal_connect (treeview, "row-activated", G_CALLBACK (row_activated_cb), model);
gtk_list_view_set_factory (GTK_LIST_VIEW (listview),
gtk_functions_list_item_factory_new (setup_demo_row_func,
bind_demo_row_func,
NULL,
NULL));
selection = gtk_single_selection_new (G_LIST_MODEL (treemodel));
g_signal_connect (selection, "notify::selected-item", G_CALLBACK (selection_cb), NULL);
gtk_list_view_set_model (GTK_LIST_VIEW (listview),
G_LIST_MODEL (selection));
widget = (GtkWidget *)gtk_builder_get_object (builder, "treeview-selection");
g_signal_connect (widget, "changed", G_CALLBACK (selection_cb), model);
gtk_tree_model_get_iter_first (gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)), &iter);
gtk_tree_selection_select_iter (GTK_TREE_SELECTION (widget), &iter);
gtk_tree_view_collapse_all (GTK_TREE_VIEW (treeview));
award ("demo-start");
gtk_widget_show (GTK_WIDGET (window));
@@ -1108,7 +1089,7 @@ auto_quit (gpointer data)
static void
list_demos (void)
{
Demo *d, *c;
DemoData *d, *c;
d = gtk_demos;
@@ -1135,7 +1116,7 @@ command_line (GApplication *app,
const gchar *name = NULL;
gboolean autoquit = FALSE;
gboolean list = FALSE;
Demo *d, *c;
DemoData *d, *c;
GDoDemoFunc func = 0;
GtkWidget *window, *demo;

View File

@@ -66,32 +66,9 @@
<property name="can-focus">1</property>
<property name="hscrollbar-policy">never</property>
<property name="min-content-width">150</property>
<child>
<object class="GtkTreeView" id="treeview">
<property name="can-focus">1</property>
<property name="model">treestore</property>
<property name="headers-visible">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection">
<property name="mode">browse</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="style">4</attribute>
<attribute name="text">1</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText">
<property name="text"> </property>
</object>
</child>
</object>
</child>
<object class="GtkListView" id="listview">
</object>
</child>
</object>

View File

@@ -3,6 +3,7 @@
demos = files([
'application_demo.c',
'assistant.c',
'awardview.c',
'builder.c',
'changedisplay.c',
'clipboard.c',
@@ -43,6 +44,8 @@ demos = files([
'listbox.c',
'flowbox.c',
'list_store.c',
'listview_settings.c',
'listview_weather.c',
'markup.c',
'menus.c',
'modelbutton.c',
@@ -85,6 +88,7 @@ demos = files([
gtkdemo_deps = [ libgtk_dep, ]
extra_demo_sources = files(['main.c',
'award.c',
'gtkfishbowl.c',
'fontplane.c',
'gtkgears.c',

View File

@@ -11,6 +11,8 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "award.h"
static GtkWidget *entry;
static GtkWidget *entry2;
static GtkWidget *button;
@@ -25,6 +27,18 @@ update_button (GObject *object,
gtk_widget_set_sensitive (button,
text[0] != '\0' && g_str_equal (text, text2));
if (g_str_equal (text, text2) &&
g_ascii_strcasecmp (text, "12345") == 0)
award ("password-best");
}
static void
button_pressed (GtkButton *button,
GtkWidget *window)
{
award ("password-correct");
gtk_widget_destroy (window);
}
GtkWidget *
@@ -72,7 +86,7 @@ do_password_entry (GtkWidget *do_widget)
button = gtk_button_new_with_mnemonic ("_Done");
gtk_style_context_add_class (gtk_widget_get_style_context (button), "suggested-action");
g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
g_signal_connect (button, "clicked", G_CALLBACK (button_pressed), window);
gtk_widget_set_sensitive (button, FALSE);
gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button);

View File

@@ -11,6 +11,9 @@
#include "puzzlepiece.h"
#include "paintable.h"
/* Give out awards */
#include "award.h"
static GtkWidget *window = NULL;
static GtkWidget *frame = NULL;
static GtkWidget *choices = NULL;
@@ -156,6 +159,14 @@ check_solved (GtkWidget *grid)
picture = gtk_grid_get_child_at (GTK_GRID (grid), pos_x, pos_y);
gtk_picture_set_paintable (GTK_PICTURE (picture), piece);
/* Hand out a bunch of awards
*/
award ("puzzle-solve");
if ((gdk_paintable_get_flags (piece) & GDK_PAINTABLE_STATIC_CONTENTS) == 0)
award ("puzzle-solve-animated");
if (height * width > 20)
award ("puzzle-solve-large");
return TRUE;
}
@@ -401,6 +412,18 @@ add_choice (GtkWidget *choices,
gtk_container_add (GTK_CONTAINER (choices), icon);
}
static void
widget_destroyed (GtkWidget *widget,
GtkWidget **widget_pointer)
{
if (widget_pointer)
*widget_pointer = NULL;
if (!solved)
award ("puzzle-give-up");
}
GtkWidget *
do_sliding_puzzle (GtkWidget *do_widget)
{
@@ -469,7 +492,7 @@ do_sliding_puzzle (GtkWidget *do_widget)
gtk_window_set_titlebar (GTK_WINDOW (window), header);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_widget_destroyed), &window);
G_CALLBACK (widget_destroyed), &window);
frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, (float) gdk_paintable_get_intrinsic_aspect_ratio (puzzle), FALSE);
gtk_container_add (GTK_CONTAINER (window), frame);

View File

@@ -53,6 +53,7 @@
<xi:include href="xml/gtkselectionmodel.xml" />
<xi:include href="xml/gtknoselection.xml" />
<xi:include href="xml/gtksingleselection.xml" />
<xi:include href="xml/gtkdirectorylist.xml" />
</chapter>
<chapter id="Application">
@@ -87,6 +88,9 @@
<xi:include href="xml/gtkrevealer.xml" />
<xi:include href="xml/gtklistbox.xml" />
<xi:include href="xml/gtkflowbox.xml" />
<xi:include href="xml/gtklistview.xml" />
<xi:include href="xml/gtkgridview.xml" />
<xi:include href="xml/gtktreeexpander.xml" />
<xi:include href="xml/gtkstack.xml" />
<xi:include href="xml/gtkstackswitcher.xml" />
<xi:include href="xml/gtkstacksidebar.xml" />

View File

@@ -493,6 +493,71 @@ gtk_single_selection_set_can_unselect
gtk_single_selection_get_type
</SECTION>
<SECTION>
<FILE>gtklistitem</FILE>
<TITLE>GtkListItem</TITLE>
GtkListItem
gtk_list_item_get_item
gtk_list_item_get_position
gtk_list_item_get_selected
gtk_list_item_get_selectable
gtk_list_item_set_selectable
<SUBSECTION Standard>
GTK_LIST_ITEM
GTK_LIST_ITEM_CLASS
GTK_LIST_ITEM_GET_CLASS
GTK_IS_LIST_ITEM
GTK_IS_LIST_ITEM_CLASS
GTK_TYPE_LIST_ITEM
<SUBSECTION Private>
gtk_list_item_get_type
</SECTION>
<SECTION>
<FILE>gtklistview</FILE>
<TITLE>GtkListView</TITLE>
GtkListView
gtk_list_view_new
gtk_list_view_new_with_factory
gtk_list_view_set_factory
gtk_list_view_get_factory
gtk_list_view_set_model
gtk_list_view_get_model
gtk_list_view_set_show_separators
gtk_list_view_get_show_separators
<SUBSECTION Standard>
GTK_LIST_VIEW
GTK_LIST_VIEW_CLASS
GTK_LIST_VIEW_GET_CLASS
GTK_IS_LIST_VIEW
GTK_IS_LIST_VIEW_CLASS
GTK_TYPE_LIST_VIEW
<SUBSECTION Private>
gtk_list_view_get_type
</SECTION>
<SECTION>
<FILE>gtkgridview</FILE>
<TITLE>GtkGridView</TITLE>
GtkGridView
gtk_grid_view_new
gtk_grid_view_set_model
gtk_grid_view_get_model
gtk_grid_view_set_max_columns
gtk_grid_view_get_max_columns
gtk_grid_view_set_min_columns
gtk_grid_view_get_min_columns
<SUBSECTION Standard>
GTK_GRID_VIEW
GTK_GRID_VIEW_CLASS
GTK_GRID_VIEW_GET_CLASS
GTK_IS_GRID_VIEW
GTK_IS_GRID_VIEW_CLASS
GTK_TYPE_GRID_VIEW
<SUBSECTION Private>
gtk_grid_view_get_type
</SECTION>
<SECTION>
<FILE>gtkbuildable</FILE>
GtkBuildable
@@ -1322,6 +1387,32 @@ GTK_TYPE_FILE_FILTER
gtk_file_filter_get_type
</SECTION>
<SECTION>
<FILE>gtkdirectorylist</FILE>
<TITLE>GtkDirectoryList</TITLE>
GtkDirectoryList
gtk_directory_list_new
gtk_directory_list_get_attributes
gtk_directory_list_set_attributes
gtk_directory_list_get_file
gtk_directory_list_set_file
gtk_directory_list_get_io_priority
gtk_directory_list_set_io_priority
gtk_directory_list_is_loading
gtk_directory_list_get_error
<SUBSECTION Standard>
GTK_DIRECTORY_LIST
GTK_IS_DIRECTORY_LIST
GTK_TYPE_DIRECTORY_LIST
GTK_DIRECTORY_LIST_CLASS
GTK_IS_DIRECTORY_LIST_CLASS
GTK_DIRECTORY_LIST_GET_CLASS
<SUBSECTION Private>
gtk_directory_list_get_type
</SECTION>
<SECTION>
<FILE>gtkspinbutton</FILE>
<SECTION>
<FILE>gtkfilterlistmodel</FILE>
<TITLE>GtkFilterListModel</TITLE>
@@ -3464,6 +3555,26 @@ gtk_tree_list_model_get_type
gtk_tree_list_row_get_type
</SECTION>
<SECTION>
<FILE>gtktreeexpander</FILE>
<TITLE>GtkTreeExpander</TITLE>
gtk_tree_expander_new
gtk_tree_expander_get_child
gtk_tree_expander_set_child
gtk_tree_expander_get_item
gtk_tree_expander_get_list_row
gtk_tree_expander_set_list_row
<SUBSECTION Standard>
GTK_TREE_EXPANDER
GTK_IS_TREE_EXPANDER
GTK_TYPE_TREE_EXPANDER
GTK_TREE_EXPANDER_CLASS
GTK_IS_TREE_EXPANDER_CLASS
GTK_TREE_EXPANDER_GET_CLASS
<SUBSECTION Private>
gtk_tree_expander_get_type
</SECTION>
<SECTION>
<FILE>gtktreemodel</FILE>
<TITLE>GtkTreeModel</TITLE>

View File

@@ -45,9 +45,6 @@ typedef struct _GdkWaylandSurfaceClass GdkWaylandSurfaceClass;
GDK_AVAILABLE_IN_ALL
GType gdk_wayland_surface_get_type (void);
GDK_AVAILABLE_IN_ALL
GdkSurface * gdk_wayland_surface_new_subsurface (GdkDisplay *display,
const GdkRectangle *position);
GDK_AVAILABLE_IN_ALL
struct wl_surface *gdk_wayland_surface_get_wl_surface (GdkSurface *surface);

View File

@@ -55,6 +55,7 @@
#include <gtk/gtkbox.h>
#include <gtk/gtkbuildable.h>
#include <gtk/gtkbuilder.h>
#include <gtk/gtkbuilderlistitemfactory.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkcalendar.h>
#include <gtk/gtkcellarea.h>
@@ -90,6 +91,7 @@
#include <gtk/gtkcustomlayout.h>
#include <gtk/gtkdebug.h>
#include <gtk/gtkdialog.h>
#include <gtk/gtkdirectorylist.h>
#include <gtk/gtkdnd.h>
#include <gtk/gtkdragdest.h>
#include <gtk/gtkdragsource.h>
@@ -121,6 +123,7 @@
#include <gtk/gtkfontchooserdialog.h>
#include <gtk/gtkfontchooserwidget.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkfunctionslistitemfactory.h>
#include <gtk/gtkgesture.h>
#include <gtk/gtkgestureclick.h>
#include <gtk/gtkgesturedrag.h>
@@ -134,6 +137,7 @@
#include <gtk/gtkglarea.h>
#include <gtk/gtkgrid.h>
#include <gtk/gtkgridlayout.h>
#include <gtk/gtkgridview.h>
#include <gtk/gtkheaderbar.h>
#include <gtk/gtkicontheme.h>
#include <gtk/gtkiconview.h>
@@ -148,7 +152,10 @@
#include <gtk/gtklevelbar.h>
#include <gtk/gtklinkbutton.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistitem.h>
#include <gtk/gtklistitemfactory.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtklistview.h>
#include <gtk/gtklockbutton.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkmaplistmodel.h>
@@ -243,6 +250,7 @@
#include <gtk/gtktooltip.h>
#include <gtk/gtktestutils.h>
#include <gtk/gtktreednd.h>
#include <gtk/gtktreeexpander.h>
#include <gtk/gtktreelistmodel.h>
#include <gtk/gtktreemodel.h>
#include <gtk/gtktreemodelfilter.h>

View File

@@ -0,0 +1,137 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkbuilderlistitemfactory.h"
#include "gtkbuilder.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistitemprivate.h"
struct _GtkBuilderListItemFactory
{
GtkListItemFactory parent_instance;
GBytes *bytes;
};
struct _GtkBuilderListItemFactoryClass
{
GtkListItemFactoryClass parent_class;
};
G_DEFINE_TYPE (GtkBuilderListItemFactory, gtk_builder_list_item_factory, GTK_TYPE_LIST_ITEM_FACTORY)
static void
gtk_builder_list_item_factory_setup (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkBuilderListItemFactory *self = GTK_BUILDER_LIST_ITEM_FACTORY (factory);
GtkBuilder *builder;
GError *error = NULL;
GTK_LIST_ITEM_FACTORY_CLASS (gtk_builder_list_item_factory_parent_class)->setup (factory, list_item);
builder = gtk_builder_new ();
if (!gtk_builder_extend_with_template (builder, GTK_WIDGET (list_item), G_OBJECT_TYPE (list_item),
(const gchar *)g_bytes_get_data (self->bytes, NULL),
g_bytes_get_size (self->bytes),
&error))
{
g_critical ("Error building template for list item: %s", error->message);
g_error_free (error);
/* This should never happen, if the template XML cannot be built
* then it is a critical programming error.
*/
g_object_unref (builder);
return;
}
gtk_builder_connect_signals (builder, list_item);
g_object_unref (builder);
}
static void
gtk_builder_list_item_factory_finalize (GObject *object)
{
GtkBuilderListItemFactory *self = GTK_BUILDER_LIST_ITEM_FACTORY (object);
g_bytes_unref (self->bytes);
G_OBJECT_CLASS (gtk_builder_list_item_factory_parent_class)->finalize (object);
}
static void
gtk_builder_list_item_factory_class_init (GtkBuilderListItemFactoryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkListItemFactoryClass *factory_class = GTK_LIST_ITEM_FACTORY_CLASS (klass);
object_class->finalize = gtk_builder_list_item_factory_finalize;
factory_class->setup = gtk_builder_list_item_factory_setup;
}
static void
gtk_builder_list_item_factory_init (GtkBuilderListItemFactory *self)
{
}
GtkListItemFactory *
gtk_builder_list_item_factory_new_from_bytes (GBytes *bytes)
{
GtkBuilderListItemFactory *self;
g_return_val_if_fail (bytes != NULL, NULL);
self = g_object_new (GTK_TYPE_BUILDER_LIST_ITEM_FACTORY, NULL);
self->bytes = g_bytes_ref (bytes);
return GTK_LIST_ITEM_FACTORY (self);
}
GtkListItemFactory *
gtk_builder_list_item_factory_new_from_resource (const char *resource_path)
{
GtkListItemFactory *result;
GError *error = NULL;
GBytes *bytes;
g_return_val_if_fail (resource_path != NULL, NULL);
bytes = g_resources_lookup_data (resource_path, 0, &error);
if (!bytes)
{
g_critical ("Unable to load resource for list item template: %s", error->message);
g_error_free (error);
return NULL;
}
result = gtk_builder_list_item_factory_new_from_bytes (bytes);
g_bytes_unref (bytes);
return result;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_BUILDER_LIST_ITEM_FACTORY_H__
#define __GTK_BUILDER_LIST_ITEM_FACTORY_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtklistitemfactory.h>
G_BEGIN_DECLS
#define GTK_TYPE_BUILDER_LIST_ITEM_FACTORY (gtk_builder_list_item_factory_get_type ())
#define GTK_BUILDER_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_BUILDER_LIST_ITEM_FACTORY, GtkBuilderListItemFactory))
#define GTK_BUILDER_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_BUILDER_LIST_ITEM_FACTORY, GtkBuilderListItemFactoryClass))
#define GTK_IS_BUILDER_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_BUILDER_LIST_ITEM_FACTORY))
#define GTK_IS_BUILDER_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_BUILDER_LIST_ITEM_FACTORY))
#define GTK_BUILDER_LIST_ITEM_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_BUILDER_LIST_ITEM_FACTORY, GtkBuilderListItemFactoryClass))
typedef struct _GtkBuilderListItemFactory GtkBuilderListItemFactory;
typedef struct _GtkBuilderListItemFactoryClass GtkBuilderListItemFactoryClass;
GDK_AVAILABLE_IN_ALL
GType gtk_builder_list_item_factory_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GtkListItemFactory * gtk_builder_list_item_factory_new_from_bytes (GBytes *bytes);
GDK_AVAILABLE_IN_ALL
GtkListItemFactory * gtk_builder_list_item_factory_new_from_resource (const char *resource_path);
G_END_DECLS
#endif /* __GTK_BUILDER_LIST_ITEM_FACTORY_H__ */

681
gtk/gtkdirectorylist.c Normal file
View File

@@ -0,0 +1,681 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkdirectorylist.h"
#include "gtkintl.h"
#include "gtkprivate.h"
/**
* SECTION:gtkdirectorylist
* @title: GtkDirectoryList
* @short_description: A list model for directory listings
* @see_also: #GListModel, g_file_enumerate_children()
*
* #GtkDirectoryList is a list model that wraps g_file_enumerate_children_async().
* It presents a #GListModel and fills it asynchronously with the #GFileInfos
* returned from that function.
*
* Enumeration will start automatically when a the GtkDirectoryList:file property
* is set.
*
* While the #GtkDirectoryList is being filled, the GtkDirectoryList:loading
* property will be set to %TRUE. You can listen to that property if you want
* to show information like a #GtkSpinner or a "Loading..." text.
* If loading fails at any point, the GtkDirectoryList:error property will be set
* to give more indication about the failure.
*
* The #GFileInfos returned from a #GtkDirectoryList have the "standard::file"
* attribute set to the #GFile they refer to. This way you can get at the file
* that is referred to in the same way you would via g_file_enumerator_get_child().
* This means you do not need access to the #GtkDirectoryList but can access
* the #GFile directly from the #GFileInfo when operating with a #GtkListView or
* similar.
*/
/* random number that everyone else seems to use, too */
#define FILES_PER_QUERY 100
enum {
PROP_0,
PROP_ATTRIBUTES,
PROP_ERROR,
PROP_FILE,
PROP_IO_PRIORITY,
PROP_ITEM_TYPE,
PROP_LOADING,
NUM_PROPERTIES
};
struct _GtkDirectoryList
{
GObject parent_instance;
char *attributes;
int io_priority;
GFile *file;
GCancellable *cancellable;
GError *error; /* Error while loading */
GSequence *items; /* Use GPtrArray or GListStore here? */
};
struct _GtkDirectoryListClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_directory_list_get_item_type (GListModel *list)
{
return G_TYPE_FILE_INFO;
}
static guint
gtk_directory_list_get_n_items (GListModel *list)
{
GtkDirectoryList *self = GTK_DIRECTORY_LIST (list);
return g_sequence_get_length (self->items);
}
static gpointer
gtk_directory_list_get_item (GListModel *list,
guint position)
{
GtkDirectoryList *self = GTK_DIRECTORY_LIST (list);
GSequenceIter *iter;
iter = g_sequence_get_iter_at_pos (self->items, position);
if (g_sequence_iter_is_end (iter))
return NULL;
else
return g_object_ref (g_sequence_get (iter));
}
static void
gtk_directory_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_directory_list_get_item_type;
iface->get_n_items = gtk_directory_list_get_n_items;
iface->get_item = gtk_directory_list_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkDirectoryList, gtk_directory_list, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_directory_list_model_init))
static void
gtk_directory_list_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkDirectoryList *self = GTK_DIRECTORY_LIST (object);
switch (prop_id)
{
case PROP_ATTRIBUTES:
gtk_directory_list_set_attributes (self, g_value_get_string (value));
break;
case PROP_FILE:
gtk_directory_list_set_file (self, g_value_get_object (value));
break;
case PROP_IO_PRIORITY:
gtk_directory_list_set_io_priority (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_directory_list_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkDirectoryList *self = GTK_DIRECTORY_LIST (object);
switch (prop_id)
{
case PROP_ATTRIBUTES:
g_value_set_string (value, self->attributes);
break;
case PROP_ERROR:
g_value_set_boxed (value, self->error);
break;
case PROP_FILE:
g_value_set_object (value, self->file);
break;
case PROP_IO_PRIORITY:
g_value_set_int (value, self->io_priority);
break;
case PROP_ITEM_TYPE:
g_value_set_gtype (value, G_TYPE_FILE_INFO);
break;
case PROP_LOADING:
g_value_set_boolean (value, gtk_directory_list_is_loading (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
gtk_directory_list_stop_loading (GtkDirectoryList *self)
{
if (self->cancellable == NULL)
return FALSE;
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
return TRUE;
}
static void
gtk_directory_list_dispose (GObject *object)
{
GtkDirectoryList *self = GTK_DIRECTORY_LIST (object);
gtk_directory_list_stop_loading (self);
g_clear_object (&self->file);
g_clear_pointer (&self->attributes, g_free);
g_clear_error (&self->error);
g_clear_pointer (&self->items, g_sequence_free);
G_OBJECT_CLASS (gtk_directory_list_parent_class)->dispose (object);
}
static void
gtk_directory_list_class_init (GtkDirectoryListClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_directory_list_set_property;
gobject_class->get_property = gtk_directory_list_get_property;
gobject_class->dispose = gtk_directory_list_dispose;
/**
* GtkDirectoryList:attributes:
*
* The attributes to query
*/
properties[PROP_ATTRIBUTES] =
g_param_spec_string ("attributes",
P_("attributes"),
P_("Attributes to query"),
NULL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDirectoryList:error:
*
* Error encountered while loading files
*/
properties[PROP_ERROR] =
g_param_spec_boxed ("error",
P_("error"),
P_("Error encountered while loading files"),
G_TYPE_ERROR,
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDirectoryList:file:
*
* File to query
*/
properties[PROP_FILE] =
g_param_spec_object ("file",
P_("File"),
P_("The file to query"),
G_TYPE_FILE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDirectoryList:io-priority:
*
* Priority used when loading
*/
properties[PROP_IO_PRIORITY] =
g_param_spec_int ("io-priority",
P_("IO priority"),
P_("Priority used when loading"),
-G_MAXINT, G_MAXINT, G_PRIORITY_DEFAULT,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDirectoryList:item-type:
*
* The #GType for elements of this object
*/
properties[PROP_ITEM_TYPE] =
g_param_spec_gtype ("item-type",
P_("Item type"),
P_("The type of elements of this object"),
G_TYPE_FILE_INFO,
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDirectoryList:loading:
*
* %TRUE if files are being loaded
*/
properties[PROP_LOADING] =
g_param_spec_boolean ("loading",
P_("loading"),
P_("TRUE if files are being loaded"),
FALSE,
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_directory_list_init (GtkDirectoryList *self)
{
self->items = g_sequence_new (g_object_unref);
self->io_priority = G_PRIORITY_DEFAULT;
}
/**
* gtk_directory_list_new:
* @file: (allow-none): The file to query
* @attributes: (allow-none): The attributes to query with
*
* Creates a new #GtkDirectoryList querying the given @file with the given
* @attributes.
*
* Returns: a new #GtkDirectoryList
**/
GtkDirectoryList *
gtk_directory_list_new (const char *attributes,
GFile *file)
{
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
return g_object_new (GTK_TYPE_DIRECTORY_LIST,
"attributes", attributes,
"file", file,
NULL);
}
static void
gtk_directory_list_clear_items (GtkDirectoryList *self)
{
guint n_items;
n_items = g_sequence_get_length (self->items);
if (n_items > 0)
{
g_sequence_remove_range (g_sequence_get_begin_iter (self->items),
g_sequence_get_end_iter (self->items));
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0);
}
if (self->error)
{
g_clear_error (&self->error);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
}
}
static void
gtk_directory_list_enumerator_closed_cb (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
g_file_enumerator_close_finish (G_FILE_ENUMERATOR (source), res, NULL);
}
static void
gtk_directory_list_got_files_cb (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
GtkDirectoryList *self = user_data; /* invalid if cancelled */
GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source);
GError *error = NULL;
GList *l, *files;
guint n;
files = g_file_enumerator_next_files_finish (enumerator, res, &error);
if (files == NULL)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_clear_error (&error);
return;
}
g_file_enumerator_close_async (enumerator,
self->io_priority,
NULL,
gtk_directory_list_enumerator_closed_cb,
NULL);
g_object_freeze_notify (G_OBJECT (self));
g_clear_object (&self->cancellable);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
if (error)
{
self->error = error;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
}
g_object_thaw_notify (G_OBJECT (self));
return;
}
n = 0;
for (l = files; l; l = l->next)
{
GFileInfo *info;
GFile *file;
info = l->data;
file = g_file_enumerator_get_child (enumerator, info);
g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
g_object_unref (file);
g_sequence_append (self->items, info);
n++;
}
g_list_free (files);
g_file_enumerator_next_files_async (enumerator,
g_file_is_native (self->file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
self->io_priority,
self->cancellable,
gtk_directory_list_got_files_cb,
self);
if (n > 0)
g_list_model_items_changed (G_LIST_MODEL (self), g_sequence_get_length (self->items) - n, 0, n);
}
static void
gtk_directory_list_got_enumerator_cb (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
GtkDirectoryList *self = user_data; /* invalid if cancelled */
GFile *file = G_FILE (source);
GFileEnumerator *enumerator;
GError *error = NULL;
enumerator = g_file_enumerate_children_finish (file, res, &error);
if (enumerator == NULL)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_clear_error (&error);
return;
}
g_object_freeze_notify (G_OBJECT (self));
self->error = error;
g_clear_object (&self->cancellable);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ERROR]);
g_object_thaw_notify (G_OBJECT (self));
return;
}
g_file_enumerator_next_files_async (enumerator,
g_file_is_native (file) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY,
self->io_priority,
self->cancellable,
gtk_directory_list_got_files_cb,
self);
g_object_unref (enumerator);
}
static void
gtk_directory_list_start_loading (GtkDirectoryList *self)
{
gboolean was_loading;
was_loading = gtk_directory_list_stop_loading (self);
gtk_directory_list_clear_items (self);
if (self->file == NULL)
{
if (was_loading)
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
return;
}
self->cancellable = g_cancellable_new ();
g_file_enumerate_children_async (self->file,
self->attributes,
G_FILE_QUERY_INFO_NONE,
self->io_priority,
self->cancellable,
gtk_directory_list_got_enumerator_cb,
self);
if (!was_loading)
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
}
/**
* gtk_directory_list_set_file:
* @self: a #GtkDirectoryList
* @file: (allow-none): the #GFile to be enumerated
*
* Sets the @file to be enumerated and starts the enumeration.
*
* If @file is %NULL, the result will be an empty list.
*/
void
gtk_directory_list_set_file (GtkDirectoryList *self,
GFile *file)
{
g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
g_return_if_fail (file == NULL || G_IS_FILE (file));
if (self->file == file ||
(self->file && file && g_file_equal (self->file, file)))
return;
g_object_freeze_notify (G_OBJECT (self));
g_set_object (&self->file, file);
gtk_directory_list_start_loading (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_directory_list_get_file:
* @self: a #GtkDirectoryList
*
* Gets the file whose children are currently enumerated.
*
* Returns: (nullable) (transfer none): The file whose children are enumerated
**/
GFile *
gtk_directory_list_get_file (GtkDirectoryList *self)
{
g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL);
return self->file;
}
/**
* gtk_directory_list_set_attributes:
* @self: a #GtkDirectoryList
* @attributes: (allow-none): the attributes to enumerate
*
* Sets the @attributes to be enumerated and starts the enumeration.
*
* If @attributes is %NULL, no attributes will be queried, but a list
* of #GFileInfos will still be created.
*/
void
gtk_directory_list_set_attributes (GtkDirectoryList *self,
const char *attributes)
{
g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
if (self->attributes == attributes)
return;
g_object_freeze_notify (G_OBJECT (self));
g_free (self->attributes);
self->attributes = g_strdup (attributes);
gtk_directory_list_start_loading (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ATTRIBUTES]);
g_object_thaw_notify (G_OBJECT (self));
}
/**
* gtk_directory_list_get_attributes:
* @self: a #GtkDirectoryList
*
* Gets the attributes queried on the children.
*
* Returns: (nullable) (transfer none): The queried attributes
*/
const char *
gtk_directory_list_get_attributes (GtkDirectoryList *self)
{
g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), NULL);
return self->attributes;
}
/**
* gtk_directory_list_set_io_priority:
* @self: a #GtkDirectoryList
* @io_priority: IO priority to use
*
* Sets the IO priority to use while loading directories.
*
* Setting the priority while @self is loading will reprioritize the
* ongoing load as soon as possible.
*
* The default IO priority is %G_PRIORITY_DEFAULT, which is higher than
* the GTK redraw priority. If you are loading a lot of directories in
* parrallel, lowering it to something like %G_PRIORITY_DEFAULT_IDLE
* may increase responsiveness.
*/
void
gtk_directory_list_set_io_priority (GtkDirectoryList *self,
int io_priority)
{
g_return_if_fail (GTK_IS_DIRECTORY_LIST (self));
if (self->io_priority == io_priority)
return;
self->io_priority = io_priority;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IO_PRIORITY]);
}
/**
* gtk_directory_list_get_io_priority:
* @self: a #GtkDirectoryList
*
* Gets the IO priority set via gtk_directory_list_set_io_priority().
*
* Returns: The IO priority.
*/
int
gtk_directory_list_get_io_priority (GtkDirectoryList *self)
{
g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), G_PRIORITY_DEFAULT);
return self->io_priority;
}
/**
* gtk_directory_list_is_loading:
* @self: a #GtkDirectoryList
*
* Returns %TRUE if the children enumeration is currently in
* progress.
* Files will be added to @self from time to time while loading is
* going on. The order in which are added is undefined and may change
* inbetween runs.
*
* Returns: %TRUE if @self is loading
*/
gboolean
gtk_directory_list_is_loading (GtkDirectoryList *self)
{
g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE);
return self->cancellable != NULL;
}
/**
* gtk_directory_list_get_error:
* @self: a #GtkDirectoryList
*
* Gets the loading error, if any.
*
* If an error occurs during the loading process, the loading process
* will finish and this property allows querying the error that happened.
* This error will persist until a file is loaded again.
*
* An error being set does not mean that no files were loaded, and all
* successfully queried files will remain in the list.
*
* Returns: (nullable) (transfer none): The loading error or %NULL if
* loading finished successfully.
*/
const GError *
gtk_directory_list_get_error (GtkDirectoryList *self)
{
g_return_val_if_fail (GTK_IS_DIRECTORY_LIST (self), FALSE);
return self->error;
}

67
gtk/gtkdirectorylist.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_DIRECTORY_LIST_H__
#define __GTK_DIRECTORY_LIST_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
/* for GDK_AVAILABLE_IN_ALL */
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define GTK_TYPE_DIRECTORY_LIST (gtk_directory_list_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkDirectoryList, gtk_directory_list, GTK, DIRECTORY_LIST, GObject)
GDK_AVAILABLE_IN_ALL
GtkDirectoryList * gtk_directory_list_new (const char *attributes,
GFile *file);
GDK_AVAILABLE_IN_ALL
void gtk_directory_list_set_file (GtkDirectoryList *self,
GFile *file);
GDK_AVAILABLE_IN_ALL
GFile * gtk_directory_list_get_file (GtkDirectoryList *self);
GDK_AVAILABLE_IN_ALL
void gtk_directory_list_set_attributes (GtkDirectoryList *self,
const char *attributes);
GDK_AVAILABLE_IN_ALL
const char * gtk_directory_list_get_attributes (GtkDirectoryList *self);
GDK_AVAILABLE_IN_ALL
void gtk_directory_list_set_io_priority (GtkDirectoryList *self,
int io_priority);
GDK_AVAILABLE_IN_ALL
int gtk_directory_list_get_io_priority (GtkDirectoryList *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_directory_list_is_loading (GtkDirectoryList *self);
GDK_AVAILABLE_IN_ALL
const GError * gtk_directory_list_get_error (GtkDirectoryList *self);
G_END_DECLS
#endif /* __GTK_DIRECTORY_LIST_H__ */

View File

@@ -0,0 +1,135 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkfunctionslistitemfactory.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistitemprivate.h"
struct _GtkFunctionsListItemFactory
{
GtkListItemFactory parent_instance;
GtkListItemSetupFunc setup_func;
GtkListItemBindFunc bind_func;
gpointer user_data;
GDestroyNotify user_destroy;
};
struct _GtkFunctionsListItemFactoryClass
{
GtkListItemFactoryClass parent_class;
};
G_DEFINE_TYPE (GtkFunctionsListItemFactory, gtk_functions_list_item_factory, GTK_TYPE_LIST_ITEM_FACTORY)
static void
gtk_functions_list_item_factory_setup (GtkListItemFactory *factory,
GtkListItem *list_item)
{
GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (factory);
GTK_LIST_ITEM_FACTORY_CLASS (gtk_functions_list_item_factory_parent_class)->setup (factory, list_item);
if (self->setup_func)
self->setup_func (list_item, self->user_data);
}
static void
gtk_functions_list_item_factory_bind (GtkListItemFactory *factory,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (factory);
GTK_LIST_ITEM_FACTORY_CLASS (gtk_functions_list_item_factory_parent_class)->bind (factory, list_item, position, item, selected);
if (self->bind_func)
self->bind_func (list_item, self->user_data);
}
static void
gtk_functions_list_item_factory_rebind (GtkListItemFactory *factory,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (factory);
GTK_LIST_ITEM_FACTORY_CLASS (gtk_functions_list_item_factory_parent_class)->bind (factory, list_item, position, item, selected);
if (self->bind_func)
self->bind_func (list_item, self->user_data);
}
static void
gtk_functions_list_item_factory_finalize (GObject *object)
{
GtkFunctionsListItemFactory *self = GTK_FUNCTIONS_LIST_ITEM_FACTORY (object);
if (self->user_destroy)
self->user_destroy (self->user_data);
G_OBJECT_CLASS (gtk_functions_list_item_factory_parent_class)->finalize (object);
}
static void
gtk_functions_list_item_factory_class_init (GtkFunctionsListItemFactoryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkListItemFactoryClass *factory_class = GTK_LIST_ITEM_FACTORY_CLASS (klass);
object_class->finalize = gtk_functions_list_item_factory_finalize;
factory_class->setup = gtk_functions_list_item_factory_setup;
factory_class->bind = gtk_functions_list_item_factory_bind;
factory_class->rebind = gtk_functions_list_item_factory_rebind;
}
static void
gtk_functions_list_item_factory_init (GtkFunctionsListItemFactory *self)
{
}
GtkListItemFactory *
gtk_functions_list_item_factory_new (GtkListItemSetupFunc setup_func,
GtkListItemBindFunc bind_func,
gpointer user_data,
GDestroyNotify user_destroy)
{
GtkFunctionsListItemFactory *self;
g_return_val_if_fail (setup_func || bind_func, NULL);
g_return_val_if_fail (user_data != NULL || user_destroy == NULL, NULL);
self = g_object_new (GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, NULL);
self->setup_func = setup_func;
self->bind_func = bind_func;
self->user_data = user_data;
self->user_destroy = user_destroy;
return GTK_LIST_ITEM_FACTORY (self);
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_FUNCTIONS_LIST_ITEM_FACTORY_H__
#define __GTK_FUNCTIONS_LIST_ITEM_FACTORY_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtklistitemfactory.h>
#include <gtk/gtklistitem.h>
G_BEGIN_DECLS
#define GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY (gtk_functions_list_item_factory_get_type ())
#define GTK_FUNCTIONS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, GtkFunctionsListItemFactory))
#define GTK_FUNCTIONS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, GtkFunctionsListItemFactoryClass))
#define GTK_IS_FUNCTIONS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY))
#define GTK_IS_FUNCTIONS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY))
#define GTK_FUNCTIONS_LIST_ITEM_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_FUNCTIONS_LIST_ITEM_FACTORY, GtkFunctionsListItemFactoryClass))
typedef struct _GtkFunctionsListItemFactory GtkFunctionsListItemFactory;
typedef struct _GtkFunctionsListItemFactoryClass GtkFunctionsListItemFactoryClass;
/**
* GtkListItemSetupFunc:
* @item: the #GtkListItem to set up
* @user_data: (closure): user data
*
* Called whenever a new list item needs to be setup for managing a row in
* the list.
*
* At this point, the list item is not bound yet, so gtk_list_item_get_item()
* will return %NULL.
* The list item will later be bound to an item via the #GtkListItemBindFunc.
*/
typedef void (* GtkListItemSetupFunc) (GtkListItem *item, gpointer user_data);
/**
* GtkListItemBindFunc:
* @item: the #GtkListItem to bind
* @user_data: (closure): user data
*
* Binds a#GtkListItem previously set up via a #GtkListItemSetupFunc to
* an @item.
*
* Rebinding a @item to different @items is supported as well as
* unbinding it by setting @item to %NULL.
*/
typedef void (* GtkListItemBindFunc) (GtkListItem *item,
gpointer user_data);
GDK_AVAILABLE_IN_ALL
GType gtk_functions_list_item_factory_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GtkListItemFactory * gtk_functions_list_item_factory_new (GtkListItemSetupFunc setup_func,
GtkListItemBindFunc bind_func,
gpointer user_data,
GDestroyNotify user_destroy);
G_END_DECLS
#endif /* __GTK_FUNCTIONS_LIST_ITEM_FACTORY_H__ */

1129
gtk/gtkgridview.c Normal file

File diff suppressed because it is too large Load Diff

66
gtk/gtkgridview.h Normal file
View File

@@ -0,0 +1,66 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_GRID_VIEW_H__
#define __GTK_GRID_VIEW_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_GRID_VIEW (gtk_grid_view_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkGridView, gtk_grid_view, GTK, GRID_VIEW, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_grid_view_new (void);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_grid_view_new_with_factory (GtkListItemFactory *factory);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_grid_view_get_model (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_model (GtkGridView *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_factory (GtkGridView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_grid_view_get_factory (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
guint gtk_grid_view_get_min_columns (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_min_columns (GtkGridView *self,
guint min_columns);
GDK_AVAILABLE_IN_ALL
guint gtk_grid_view_get_max_columns (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
void gtk_grid_view_set_max_columns (GtkGridView *self,
guint max_columns);
G_END_DECLS
#endif /* __GTK_GRID_VIEW_H__ */

512
gtk/gtklistitem.c Normal file
View File

@@ -0,0 +1,512 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistitemprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtkeventcontrollerkey.h"
#include "gtkgestureclick.h"
#include "gtkintl.h"
#include "gtkmain.h"
#include "gtkwidgetprivate.h"
/**
* SECTION:gtklistitem
* @title: GtkListItem
* @short_description: Widget used to represent items of a ListModel
* @see_also: #GtkListView, #GListModel
*
* #GtkListItem is the widget that GTK list-handling containers such
* as #GtkListView create to represent items in a #GListModel.
* They are managed by the container and cannot be created by application
* code.
*
* #GtkListIems are container widgets that need to be populated by
* application code. The container provides functions to do that.
*
* #GtkListItems exist in 2 stages:
*
* 1. The unbound stage where the listitem is not currently connected to
* an item in the list. In that case, the GtkListItem:item property is
* set to %NULL.
*
* 2. The bound stage where the listitem references an item from the list.
* The GtkListItem:item property is not %NULL.
*/
struct _GtkListItem
{
GtkBin parent_instance;
GObject *item;
guint position;
guint selectable : 1;
guint selected : 1;
};
enum
{
PROP_0,
PROP_ITEM,
PROP_POSITION,
PROP_SELECTABLE,
PROP_SELECTED,
N_PROPS
};
G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_BIN)
static GParamSpec *properties[N_PROPS] = { NULL, };
static gboolean
gtk_list_item_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkListItem *self = GTK_LIST_ITEM (widget);
GtkWidget *child;
/* The idea of this function is the following:
* 1. If any child can take focus, do not ever attempt
* to take focus.
* 2. Otherwise, if this item is selectable or activatable,
* allow focusing this widget.
*
* This makes sure every item in a list is focusable for
* activation and selection handling, but no useless widgets
* get focused and moving focus is as fast as possible.
*/
child = gtk_bin_get_child (GTK_BIN (self));
if (child)
{
if (gtk_widget_get_focus_child (widget))
return FALSE;
if (gtk_widget_child_focus (child, direction))
return TRUE;
}
if (!gtk_widget_get_can_focus (widget) ||
!self->selectable)
return FALSE;
gtk_widget_grab_focus (widget);
return TRUE;
}
static void
gtk_list_item_dispose (GObject *object)
{
GtkListItem *self = GTK_LIST_ITEM (object);
g_assert (self->item == NULL);
G_OBJECT_CLASS (gtk_list_item_parent_class)->dispose (object);
}
static void
gtk_list_item_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkListItem *self = GTK_LIST_ITEM (object);
switch (property_id)
{
case PROP_ITEM:
g_value_set_object (value, self->item);
break;
case PROP_POSITION:
g_value_set_uint (value, self->position);
break;
case PROP_SELECTABLE:
g_value_set_boolean (value, self->selectable);
break;
case PROP_SELECTED:
g_value_set_boolean (value, self->selected);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_item_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListItem *self = GTK_LIST_ITEM (object);
switch (property_id)
{
case PROP_SELECTABLE:
gtk_list_item_set_selectable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_item_class_init (GtkListItemClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
widget_class->focus = gtk_list_item_focus;
gobject_class->dispose = gtk_list_item_dispose;
gobject_class->get_property = gtk_list_item_get_property;
gobject_class->set_property = gtk_list_item_set_property;
/**
* GtkListItem:item:
*
* Displayed item
*/
properties[PROP_ITEM] =
g_param_spec_object ("item",
P_("Item"),
P_("Displayed item"),
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListItem:position:
*
* Position in the item
*/
properties[PROP_POSITION] =
g_param_spec_uint ("position",
P_("Position"),
P_("Position of the item"),
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListItem:selectable:
*
* If the item can be selected by the user
*/
properties[PROP_SELECTABLE] =
g_param_spec_boolean ("selectable",
P_("Selectable"),
P_("If the item can be selected by the user"),
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListItem:selected:
*
* If the item is currently selected
*/
properties[PROP_SELECTED] =
g_param_spec_boolean ("selected",
P_("Selected"),
P_("If the item is currently selected"),
FALSE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/* This gets overwritten by gtk_list_item_new() but better safe than sorry */
gtk_widget_class_set_css_name (widget_class, I_("row"));
}
static void
gtk_list_item_click_gesture_pressed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
GtkListItem *self)
{
GtkWidget *widget = GTK_WIDGET (self);
GdkModifierType state;
GdkModifierType mask;
gboolean extend = FALSE, modify = FALSE;
if (!self->selectable)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
if (gtk_get_current_event_state (&state))
{
mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION);
if ((state & mask) == mask)
modify = TRUE;
mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
if ((state & mask) == mask)
extend = TRUE;
}
gtk_widget_activate_action (GTK_WIDGET (self),
"list.select-item",
"(ubb)",
self->position, modify, extend);
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE);
if (gtk_widget_get_focus_on_click (widget))
gtk_widget_grab_focus (widget);
}
static void
gtk_list_item_focus_changed_cb (GtkEventControllerKey *controller,
GParamSpec *psepc,
GtkListItem *self)
{
GtkWidget *widget = GTK_WIDGET (self);
if (gtk_event_controller_key_contains_focus (controller))
{
gtk_widget_activate_action (widget,
"list.scroll-to-item",
"u",
self->position);
}
}
static void
gtk_list_item_click_gesture_released (GtkGestureClick *gesture,
int n_press,
double x,
double y,
GtkListItem *self)
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_list_item_click_gesture_canceled (GtkGestureClick *gesture,
GdkEventSequence *sequence,
GtkListItem *self)
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_list_item_init (GtkListItem *self)
{
GtkEventController *controller;
GtkGesture *gesture;
self->selectable = TRUE;
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
gesture = gtk_gesture_click_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
GTK_PHASE_BUBBLE);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
FALSE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
GDK_BUTTON_PRIMARY);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gtk_list_item_click_gesture_pressed), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gtk_list_item_click_gesture_released), self);
g_signal_connect (gesture, "cancel",
G_CALLBACK (gtk_list_item_click_gesture_canceled), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
controller = gtk_event_controller_key_new ();
g_signal_connect (controller, "notify::contains-focus", G_CALLBACK (gtk_list_item_focus_changed_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
GtkListItem *
gtk_list_item_new (const char *css_name)
{
GtkListItem *result;
g_return_val_if_fail (css_name != NULL, NULL);
result = g_object_new (GTK_TYPE_LIST_ITEM,
"css-name", css_name,
NULL);
return result;
}
/**
* gtk_list_item_get_item:
* @self: a #GtkListItem
*
* Gets the item that is currently displayed in model that @self is
* currently bound to or %NULL if @self is unbound.
*
* Returns: (nullable) (transfer none) (type GObject): The item displayed
**/
gpointer
gtk_list_item_get_item (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), NULL);
return self->item;
}
/**
* gtk_list_item_get_position:
* @self: a #GtkListItem
*
* Gets the position in the model that @self currently displays.
* If @self is unbound, 0 is returned.
*
* Returns: The position of this item
**/
guint
gtk_list_item_get_position (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), 0);
return self->position;
}
void
gtk_list_item_set_item (GtkListItem *self,
gpointer item)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
g_return_if_fail (item == NULL || G_IS_OBJECT (item));
if (self->item == item)
return;
g_clear_object (&self->item);
if (item)
self->item = g_object_ref (item);
gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
}
void
gtk_list_item_set_position (GtkListItem *self,
guint position)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
if (self->position == position)
return;
self->position = position;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POSITION]);
}
/**
* gtk_list_item_get_selected:
* @self: a #GtkListItem
*
* Checks if the item is displayed as selected. The selected state is
* maintained by the container and its list model and cannot be set
* otherwise.
*
* Returns: %TRUE if the item is selected.
**/
gboolean
gtk_list_item_get_selected (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE);
return self->selected;
}
void
gtk_list_item_set_selected (GtkListItem *self,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
if (self->selected == selected)
return;
self->selected = selected;
if (selected)
gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
else
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
}
/**
* gtk_list_item_get_selectable:
* @self: a #GtkListItem
*
* Checks if a list item has been set to be selectable via
* gtk_list_item_set_selectable().
*
* Do not confuse this function with gtk_list_item_get_selected().
*
* Returns: %TRUE if the item is selectable
**/
gboolean
gtk_list_item_get_selectable (GtkListItem *self)
{
g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE);
return self->selectable;
}
/**
* gtk_list_item_set_selectable:
* @self: a #GtkListItem
* @selectable: if the item should be selectable
*
* Sets @self to be selectable. If an item is selectable, clicking
* on the item or using the keyboard will try to select or unselect
* the item. If this succeeds is up to the model to determine, as
* it is managing the selected state.
*
* Note that this means that making an item non-selectable has no
* influence on the selected state at all. A non-selectable item
* may still be selected.
*
* By default, list items are selectable. When rebinding them to
* a new item, they will also be reset to be selectable by GTK.
**/
void
gtk_list_item_set_selectable (GtkListItem *self,
gboolean selectable)
{
g_return_if_fail (GTK_IS_LIST_ITEM (self));
if (self->selectable == selectable)
return;
self->selectable = selectable;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTABLE]);
}

51
gtk/gtklistitem.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_H__
#define __GTK_LIST_ITEM_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkbin.h>
G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkListItem, gtk_list_item, GTK, LIST_ITEM, GtkBin)
#define GTK_TYPE_LIST_ITEM (gtk_list_item_get_type ())
GDK_AVAILABLE_IN_ALL
gpointer gtk_list_item_get_item (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
guint gtk_list_item_get_position (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_item_get_selected (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_item_get_selectable (GtkListItem *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_item_set_selectable (GtkListItem *self,
gboolean selectable);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_H__ */

187
gtk/gtklistitemfactory.c Normal file
View File

@@ -0,0 +1,187 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistitemprivate.h"
G_DEFINE_TYPE (GtkListItemFactory, gtk_list_item_factory, G_TYPE_OBJECT)
static void
gtk_list_item_factory_default_setup (GtkListItemFactory *self,
GtkListItem *list_item)
{
}
static void
gtk_list_item_factory_default_teardown (GtkListItemFactory *self,
GtkListItem *list_item)
{
GtkWidget *child;
child = gtk_bin_get_child (GTK_BIN (list_item));
if (child)
gtk_container_remove (GTK_CONTAINER (list_item), child);
gtk_list_item_set_selectable (list_item, TRUE);
}
static void
gtk_list_item_factory_default_bind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
gtk_list_item_set_item (list_item, item);
gtk_list_item_set_position (list_item, position);
gtk_list_item_set_selected (list_item, selected);
}
static void
gtk_list_item_factory_default_rebind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
gtk_list_item_set_item (list_item, item);
gtk_list_item_set_position (list_item, position);
gtk_list_item_set_selected (list_item, selected);
}
static void
gtk_list_item_factory_default_update (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gboolean selected)
{
gtk_list_item_set_position (list_item, position);
gtk_list_item_set_selected (list_item, selected);
}
static void
gtk_list_item_factory_default_unbind (GtkListItemFactory *self,
GtkListItem *list_item)
{
gtk_list_item_set_item (list_item, NULL);
gtk_list_item_set_position (list_item, 0);
gtk_list_item_set_selected (list_item, FALSE);
}
static void
gtk_list_item_factory_class_init (GtkListItemFactoryClass *klass)
{
klass->setup = gtk_list_item_factory_default_setup;
klass->teardown = gtk_list_item_factory_default_teardown;
klass->bind = gtk_list_item_factory_default_bind;
klass->rebind = gtk_list_item_factory_default_rebind;
klass->update = gtk_list_item_factory_default_update;
klass->unbind = gtk_list_item_factory_default_unbind;
}
static void
gtk_list_item_factory_init (GtkListItemFactory *self)
{
}
void
gtk_list_item_factory_setup (GtkListItemFactory *self,
GtkListItem *list_item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->setup (self, list_item);
}
void
gtk_list_item_factory_teardown (GtkListItemFactory *self,
GtkListItem *list_item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->teardown (self, list_item);
}
void
gtk_list_item_factory_bind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->bind (self, list_item, position, item, selected);
g_object_thaw_notify (G_OBJECT (list_item));
}
void
gtk_list_item_factory_rebind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->rebind (self, list_item, position, item, selected);
g_object_thaw_notify (G_OBJECT (list_item));
}
void
gtk_list_item_factory_update (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gboolean selected)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->update (self, list_item, position, selected);
g_object_thaw_notify (G_OBJECT (list_item));
}
void
gtk_list_item_factory_unbind (GtkListItemFactory *self,
GtkListItem *list_item)
{
g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (self));
g_return_if_fail (GTK_IS_LIST_ITEM (list_item));
g_object_freeze_notify (G_OBJECT (list_item));
GTK_LIST_ITEM_FACTORY_GET_CLASS (self)->unbind (self, list_item);
g_object_thaw_notify (G_OBJECT (list_item));
}

48
gtk/gtklistitemfactory.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_FACTORY_H__
#define __GTK_LIST_ITEM_FACTORY_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
typedef struct _GtkListItemFactoryClass GtkListItemFactoryClass;
#include <gdk/gdk.h>
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_LIST_ITEM_FACTORY (gtk_list_item_factory_get_type ())
#define GTK_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactory))
#define GTK_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactoryClass))
#define GTK_IS_LIST_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_FACTORY))
#define GTK_IS_LIST_ITEM_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_FACTORY))
#define GTK_LIST_ITEM_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_FACTORY, GtkListItemFactoryClass))
GDK_AVAILABLE_IN_ALL
GType gtk_list_item_factory_get_type (void) G_GNUC_CONST;
G_END_DECLS
#endif /* __GTK_LIST_ITEM_FACTORY_H__ */

View File

@@ -0,0 +1,93 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_FACTORY_PRIVATE_H__
#define __GTK_LIST_ITEM_FACTORY_PRIVATE_H__
#include <gtk/gtklistitem.h>
#include <gtk/gtklistitemfactory.h>
#include <gtk/gtklistview.h>
G_BEGIN_DECLS
struct _GtkListItemFactory
{
GObject parent_instance;
};
struct _GtkListItemFactoryClass
{
GObjectClass parent_class;
/* setup @list_item so it can be bound */
void (* setup) (GtkListItemFactory *self,
GtkListItem *list_item);
/* undo the effects of GtkListItemFactoryClass::setup() */
void (* teardown) (GtkListItemFactory *self,
GtkListItem *list_item);
/* bind @list_item to the given @item, which is in @position and @selected state */
void (* bind) (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected);
/* unbind the current item and bind a new one */
void (* rebind) (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected);
/* like GtkListItemFactoryClass::rebind(), but the item didn't change */
void (* update) (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gboolean selected);
/* undo the effects of GtkListItemFactoryClass::bind() */
void (* unbind) (GtkListItemFactory *self,
GtkListItem *list_item);
};
void gtk_list_item_factory_setup (GtkListItemFactory *self,
GtkListItem *list_item);
void gtk_list_item_factory_teardown (GtkListItemFactory *self,
GtkListItem *list_item);
void gtk_list_item_factory_bind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected);
void gtk_list_item_factory_rebind (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gpointer item,
gboolean selected);
void gtk_list_item_factory_update (GtkListItemFactory *self,
GtkListItem *list_item,
guint position,
gboolean selected);
void gtk_list_item_factory_unbind (GtkListItemFactory *self,
GtkListItem *list_item);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_FACTORY_PRIVATE_H__ */

1136
gtk/gtklistitemmanager.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_MANAGER_H__
#define __GTK_LIST_ITEM_MANAGER_H__
#include "gtk/gtktypes.h"
#include "gtk/gtklistitemfactoryprivate.h"
#include "gtk/gtkrbtreeprivate.h"
#include "gtk/gtkselectionmodel.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_ITEM_MANAGER (gtk_list_item_manager_get_type ())
#define GTK_LIST_ITEM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManager))
#define GTK_LIST_ITEM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManagerClass))
#define GTK_IS_LIST_ITEM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_MANAGER))
#define GTK_IS_LIST_ITEM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_MANAGER))
#define GTK_LIST_ITEM_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_MANAGER, GtkListItemManagerClass))
typedef struct _GtkListItemManager GtkListItemManager;
typedef struct _GtkListItemManagerClass GtkListItemManagerClass;
typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */
typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment;
typedef struct _GtkListItemTracker GtkListItemTracker;
struct _GtkListItemManagerItem
{
GtkWidget *widget;
guint n_items;
};
struct _GtkListItemManagerItemAugment
{
guint n_items;
};
GType gtk_list_item_manager_get_type (void) G_GNUC_CONST;
GtkListItemManager * gtk_list_item_manager_new_for_size (GtkWidget *widget,
const char *item_css_name,
gsize element_size,
gsize augment_size,
GtkRbTreeAugmentFunc augment_func);
#define gtk_list_item_manager_new(widget, item_css_name, type, augment_type, augment_func) \
gtk_list_item_manager_new_for_size (widget, item_css_name, sizeof (type), sizeof (augment_type), (augment_func))
void gtk_list_item_manager_augment_node (GtkRbTree *tree,
gpointer node_augment,
gpointer node,
gpointer left,
gpointer right);
gpointer gtk_list_item_manager_get_root (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_first (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_nth (GtkListItemManager *self,
guint position,
guint *offset);
guint gtk_list_item_manager_get_item_position (GtkListItemManager *self,
gpointer item);
gpointer gtk_list_item_manager_get_item_augment (GtkListItemManager *self,
gpointer item);
void gtk_list_item_manager_set_factory (GtkListItemManager *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self);
void gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model);
GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
guint gtk_list_item_manager_get_size (GtkListItemManager *self);
GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self);
void gtk_list_item_tracker_free (GtkListItemManager *self,
GtkListItemTracker *tracker);
void gtk_list_item_tracker_set_position (GtkListItemManager *self,
GtkListItemTracker *tracker,
guint position,
guint n_before,
guint n_after);
guint gtk_list_item_tracker_get_position (GtkListItemManager *self,
GtkListItemTracker *tracker);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_MANAGER_H__ */

40
gtk/gtklistitemprivate.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_ITEM_PRIVATE_H__
#define __GTK_LIST_ITEM_PRIVATE_H__
#include "gtklistitem.h"
#include "gtklistitemmanagerprivate.h"
G_BEGIN_DECLS
GtkListItem * gtk_list_item_new (const char *css_name);
void gtk_list_item_set_item (GtkListItem *self,
gpointer item);
void gtk_list_item_set_position (GtkListItem *self,
guint position);
void gtk_list_item_set_selected (GtkListItem *self,
gboolean selected);
G_END_DECLS
#endif /* __GTK_LIST_ITEM_PRIVATE_H__ */

View File

@@ -206,15 +206,12 @@ gtk_list_list_model_new_with_size (GType item_type,
return result;
}
void
gtk_list_list_model_item_added (GtkListListModel *self,
gpointer item)
static guint
gtk_list_list_model_find (GtkListListModel *self,
gpointer item)
{
gpointer x;
guint position;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != NULL);
gpointer x;
position = 0;
for (x = self->get_first (self->data);
@@ -222,7 +219,17 @@ gtk_list_list_model_item_added (GtkListListModel *self,
x = self->get_next (x, self->data))
position++;
gtk_list_list_model_item_added_at (self, position);
return position;
}
void
gtk_list_list_model_item_added (GtkListListModel *self,
gpointer item)
{
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != NULL);
gtk_list_list_model_item_added_at (self, gtk_list_list_model_find (self, item));
}
void
@@ -241,26 +248,49 @@ void
gtk_list_list_model_item_removed (GtkListListModel *self,
gpointer previous)
{
gpointer x;
guint position;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
if (previous == NULL)
position = 0;
else
position = 1 + gtk_list_list_model_find (self, previous);
gtk_list_list_model_item_removed_at (self, position);
}
void
gtk_list_list_model_item_moved (GtkListListModel *self,
gpointer item,
gpointer previous_previous)
{
guint position, previous_position;
guint min, max;
g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
g_return_if_fail (item != previous_previous);
position = gtk_list_list_model_find (self, item);
if (previous_previous == NULL)
{
position = 0;
previous_position = 0;
}
else
{
position = 1;
for (x = self->get_first (self->data);
x != previous;
x = self->get_next (x, self->data))
position++;
previous_position = gtk_list_list_model_find (self, previous_previous);
if (position > previous_position)
previous_position++;
}
gtk_list_list_model_item_removed_at (self, position);
/* item didn't move */
if (position == previous_position)
return;
min = MIN (position, previous_position);
max = MAX (position, previous_position) + 1;
g_list_model_items_changed (G_LIST_MODEL (self), min, max - min, max - min);
}
void

View File

@@ -64,6 +64,9 @@ void gtk_list_list_model_item_removed (GtkListListMode
gpointer previous);
void gtk_list_list_model_item_removed_at (GtkListListModel *self,
guint position);
void gtk_list_list_model_item_moved (GtkListListModel *self,
gpointer item,
gpointer previous_previous);
void gtk_list_list_model_clear (GtkListListModel *self);

1219
gtk/gtklistview.c Normal file

File diff suppressed because it is too large Load Diff

62
gtk/gtklistview.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_LIST_VIEW_H__
#define __GTK_LIST_VIEW_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkwidget.h>
#include <gtk/gtklistitem.h>
G_BEGIN_DECLS
#define GTK_TYPE_LIST_VIEW (gtk_list_view_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkListView, gtk_list_view, GTK, LIST_VIEW, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_list_view_new (void);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_list_view_new_with_factory (GtkListItemFactory *factory);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_list_view_get_model (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_model (GtkListView *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_factory (GtkListView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_list_view_get_factory (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_show_separators (GtkListView *self,
gboolean show_separators);
GDK_AVAILABLE_IN_ALL
gboolean gtk_list_view_get_show_separators (GtkListView *self);
G_END_DECLS
#endif /* __GTK_LIST_VIEW_H__ */

View File

@@ -685,13 +685,6 @@ gtk_popover_unrealize (GtkWidget *widget)
g_clear_object (&priv->surface);
}
static void
gtk_popover_move_focus (GtkWidget *widget,
GtkDirectionType direction)
{
g_signal_emit_by_name (gtk_widget_get_root (widget), "move-focus", direction);
}
static void
gtk_popover_show (GtkWidget *widget)
{
@@ -1374,7 +1367,6 @@ gtk_popover_class_init (GtkPopoverClass *klass)
widget_class->measure = gtk_popover_measure;
widget_class->size_allocate = gtk_popover_size_allocate;
widget_class->snapshot = gtk_popover_snapshot;
widget_class->move_focus = gtk_popover_move_focus;
container_class->add = gtk_popover_add;
container_class->remove = gtk_popover_remove;

View File

@@ -33,6 +33,18 @@ G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel)
/**
* GTK_INVALID_LIST_POSITION:
*
* The value used to refer to a guaranteed invalid position in a #GListModel. This
* value may be returned from some functions, others may accept it as input.
* Its interpretion may differ for different functions.
*
* Refer to each function's documentation for if this value is allowed and what it
* does.
*/
#define GTK_INVALID_LIST_POSITION (G_MAXUINT)
/**
* GtkSelectionModelInterface:
* @is_selected: Return if the item at the given position is selected.

View File

@@ -26,18 +26,6 @@ G_BEGIN_DECLS
#define GTK_TYPE_SINGLE_SELECTION (gtk_single_selection_get_type ())
/**
* GTK_INVALID_LIST_POSITION:
*
* The value used to refer to a guaranteed invalid position in a #GListModel. This
* value may be returned from some functions, others may accept it as input.
* Its interpretion may differ for different functions.
*
* Refer to each function's documentation for if this value is allowed and what it
* does.
*/
#define GTK_INVALID_LIST_POSITION (G_MAXUINT)
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkSingleSelection, gtk_single_selection, GTK, SINGLE_SELECTION, GObject)

View File

@@ -1632,6 +1632,7 @@ gtk_text_view_init (GtkTextView *text_view)
priv->indent = 0;
priv->tabs = NULL;
priv->editable = TRUE;
priv->cursor_alpha = 1.0;
priv->scroll_after_paste = FALSE;

682
gtk/gtktreeexpander.c Normal file
View File

@@ -0,0 +1,682 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtktreeexpander.h"
#include "gtkbindings.h"
#include "gtkboxlayout.h"
#include "gtkgestureclick.h"
#include "gtkiconprivate.h"
#include "gtkintl.h"
#include "gtktreelistmodel.h"
/**
* SECTION:gtktreeexpander
* @title: GtkTreeExpander
* @short_description: An indenting expander button for use in a tree list
* @see_also: #GtkTreeListModel
*
* GtkTreeExpander is a widget that provides an expander for a list.
*
* It is typically placed as a bottommost child into a #GtkListView to allow
* users to expand and collapse children in a list with a #GtkTreeListModel.
* It will provide the common UI elements, gestures and keybindings for this
* purpose.
*
* On top of this, the "listitem.expand", "listitem.collapse" and
* "listitem.toggle-expand" actions are provided to allow adding custom UI
* for managing expanded state.
*
* The #GtkTreeListModel must be set to not be passthrough. Then it will provide
* #GtkTreeListRow items which can be set via gtk_tree_expander_set_list_row()
* on the expander. The expander will then watch that row item automatically.
* gtk_tree_expander_set_child() sets the widget that displays the actual row
* contents.
*
* # CSS nodes
*
* |[<!-- language="plain" -->
* treeexpander
* ├── [indent]*
* ├── [expander]
* ╰── <child>
* ]|
*
* GtkTreeExpander has zero or one CSS nodes with the name "expander" that should
* display the expander icon. The node will be `:checked` when it is expanded.
* If the node is not expandable, an "indent" node will be displayed instead.
*
* For every level of depth, another "indent" node is prepended.
*/
struct _GtkTreeExpander
{
GtkWidget parent_instance;
GtkTreeListRow *list_row;
GtkWidget *child;
GtkWidget *expander;
guint notify_handler;
};
enum
{
PROP_0,
PROP_CHILD,
PROP_ITEM,
PROP_LIST_ROW,
N_PROPS
};
G_DEFINE_TYPE (GtkTreeExpander, gtk_tree_expander, GTK_TYPE_WIDGET)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_tree_expander_click_gesture_pressed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
gpointer unused)
{
GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
gtk_widget_activate_action (widget, "listitem.toggle-expand", NULL);
gtk_widget_set_state_flags (widget,
GTK_STATE_FLAG_ACTIVE,
FALSE);
}
static void
gtk_tree_expander_click_gesture_released (GtkGestureClick *gesture,
int n_press,
double x,
double y,
gpointer unused)
{
gtk_widget_unset_state_flags (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)),
GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_tree_expander_click_gesture_canceled (GtkGestureClick *gesture,
GdkEventSequence *sequence,
gpointer unused)
{
gtk_widget_unset_state_flags (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)),
GTK_STATE_FLAG_ACTIVE);
}
static void
gtk_tree_expander_update_for_list_row (GtkTreeExpander *self)
{
if (self->list_row == NULL)
{
GtkWidget *child;
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != self->child;
child = gtk_widget_get_first_child (GTK_WIDGET (self)))
{
gtk_widget_unparent (child);
}
self->expander = NULL;
}
else
{
GtkWidget *child;
guint i, depth;
depth = gtk_tree_list_row_get_depth (self->list_row);
if (gtk_tree_list_row_is_expandable (self->list_row))
{
if (self->expander == NULL)
{
GtkGesture *gesture;
self->expander = gtk_icon_new ("expander");
gesture = gtk_gesture_click_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
GTK_PHASE_BUBBLE);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
FALSE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
GDK_BUTTON_PRIMARY);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gtk_tree_expander_click_gesture_pressed), NULL);
g_signal_connect (gesture, "released",
G_CALLBACK (gtk_tree_expander_click_gesture_released), NULL);
g_signal_connect (gesture, "cancel",
G_CALLBACK (gtk_tree_expander_click_gesture_canceled), NULL);
gtk_widget_add_controller (self->expander, GTK_EVENT_CONTROLLER (gesture));
gtk_widget_insert_before (self->expander,
GTK_WIDGET (self),
self->child);
}
if (gtk_tree_list_row_get_expanded (self->list_row))
gtk_widget_set_state_flags (self->expander, GTK_STATE_FLAG_CHECKED, FALSE);
else
gtk_widget_unset_state_flags (self->expander, GTK_STATE_FLAG_CHECKED);
child = gtk_widget_get_prev_sibling (self->expander);
}
else
{
g_clear_pointer (&self->expander, gtk_widget_unparent);
depth++;
if (self->child)
child = gtk_widget_get_prev_sibling (self->child);
else
child = gtk_widget_get_last_child (GTK_WIDGET (self));
}
for (i = 0; i < depth; i++)
{
if (child)
child = gtk_widget_get_prev_sibling (child);
else
gtk_widget_insert_after (gtk_icon_new ("indent"), GTK_WIDGET (self), NULL);
}
while (child)
{
GtkWidget *prev = gtk_widget_get_prev_sibling (child);
gtk_widget_unparent (child);
child = prev;
}
}
}
static void
gtk_tree_expander_list_row_notify_cb (GtkTreeListRow *list_row,
GParamSpec *pspec,
GtkTreeExpander *self)
{
if (pspec->name == g_intern_static_string ("expanded"))
{
if (self->expander)
{
if (gtk_tree_list_row_get_expanded (list_row))
gtk_widget_set_state_flags (self->expander, GTK_STATE_FLAG_CHECKED, FALSE);
else
gtk_widget_unset_state_flags (self->expander, GTK_STATE_FLAG_CHECKED);
}
}
else if (pspec->name == g_intern_static_string ("item"))
{
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
}
else
{
/* can this happen other than when destroying the row? */
gtk_tree_expander_update_for_list_row (self);
}
}
static gboolean
gtk_tree_expander_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
/* The idea of this function is the following:
* 1. If any child can take focus, do not ever attempt
* to take focus.
* 2. Otherwise, if this item is selectable or activatable,
* allow focusing this widget.
*
* This makes sure every item in a list is focusable for
* activation and selection handling, but no useless widgets
* get focused and moving focus is as fast as possible.
*/
if (self->child)
{
if (gtk_widget_get_focus_child (widget))
return FALSE;
if (gtk_widget_child_focus (self->child, direction))
return TRUE;
}
if (!gtk_widget_get_can_focus (widget))
return FALSE;
gtk_widget_grab_focus (widget);
return TRUE;
}
static void
gtk_tree_expander_clear_list_row (GtkTreeExpander *self)
{
if (self->list_row == NULL)
return;
g_signal_handler_disconnect (self->list_row, self->notify_handler);
self->notify_handler = 0;
g_clear_object (&self->list_row);
}
static void
gtk_tree_expander_dispose (GObject *object)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (object);
gtk_tree_expander_clear_list_row (self);
gtk_tree_expander_update_for_list_row (self);
g_clear_pointer (&self->child, gtk_widget_unparent);
g_assert (self->expander == NULL);
G_OBJECT_CLASS (gtk_tree_expander_parent_class)->dispose (object);
}
static void
gtk_tree_expander_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (object);
switch (property_id)
{
case PROP_CHILD:
g_value_set_object (value, self->child);
break;
case PROP_ITEM:
g_value_set_object (value, gtk_tree_expander_get_item (self));
break;
case PROP_LIST_ROW:
g_value_set_object (value, self->list_row);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_tree_expander_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (object);
switch (property_id)
{
case PROP_CHILD:
gtk_tree_expander_set_child (self, g_value_get_object (value));
break;
case PROP_LIST_ROW:
gtk_tree_expander_set_list_row (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_tree_expander_expand (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
if (self->list_row == NULL)
return;
gtk_tree_list_row_set_expanded (self->list_row, TRUE);
}
static void
gtk_tree_expander_collapse (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
if (self->list_row == NULL)
return;
gtk_tree_list_row_set_expanded (self->list_row, FALSE);
}
static void
gtk_tree_expander_toggle_expand (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
if (self->list_row == NULL)
return;
gtk_tree_list_row_set_expanded (self->list_row, !gtk_tree_list_row_get_expanded (self->list_row));
}
static void
expand_collapse_right (GtkWidget *widget,
gpointer unused)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
if (self->list_row == NULL)
return;
gtk_tree_list_row_set_expanded (self->list_row, gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL);
}
static void
expand_collapse_left (GtkWidget *widget,
gpointer unused)
{
GtkTreeExpander *self = GTK_TREE_EXPANDER (widget);
if (self->list_row == NULL)
return;
gtk_tree_list_row_set_expanded (self->list_row, gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
}
static void
gtk_tree_expander_class_init (GtkTreeExpanderClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkBindingSet *binding_set;
widget_class->focus = gtk_tree_expander_focus;
gobject_class->dispose = gtk_tree_expander_dispose;
gobject_class->get_property = gtk_tree_expander_get_property;
gobject_class->set_property = gtk_tree_expander_set_property;
/**
* GtkTreeExpander:child:
*
* The child widget with the actual contents
*/
properties[PROP_CHILD] =
g_param_spec_object ("child",
P_("Child"),
P_("The child widget with the actual contents"),
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkTreeExpander:item:
*
* The item held by this expander's row
*/
properties[PROP_ITEM] =
g_param_spec_object ("item",
P_("Item"),
P_("The item held by this expander's row"),
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkTreeExpander:list-row:
*
* The list row to track for expander state
*/
properties[PROP_LIST_ROW] =
g_param_spec_object ("list-row",
P_("List row"),
P_("The list row to track for expander state"),
GTK_TYPE_TREE_LIST_ROW,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
* GtkTreeExpander|listitem.expand:
*
* Expands the expander if it can be expanded.
*/
gtk_widget_class_install_action (widget_class,
"listitem.expand",
NULL,
gtk_tree_expander_expand);
/**
* GtkTreeExpander|listitem.collapse:
*
* Collapses the expander.
*/
gtk_widget_class_install_action (widget_class,
"listitem.collapse",
NULL,
gtk_tree_expander_collapse);
/**
* GtkTreeExpander|listitem.toggle-expand:
*
* Tries to expand the expander if it was collapsed or collapses it if
* it was expanded.
*/
gtk_widget_class_install_action (widget_class,
"listitem.toggle-expand",
NULL,
gtk_tree_expander_toggle_expand);
binding_set = gtk_binding_set_by_class (klass);
gtk_binding_entry_add_action (binding_set, GDK_KEY_plus, 0,
"listitem.expand", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Add, 0,
"listitem.expand", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_asterisk, 0,
"listitem.expand", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Multiply, 0,
"listitem.expand", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_minus, 0,
"listitem.collapse", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Subtract, 0,
"listitem.collapse", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_slash, 0,
"listitem.collapse", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Divide, 0,
"listitem.collapse", NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_Right, GDK_SHIFT_MASK,
expand_collapse_right, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_KP_Right, GDK_SHIFT_MASK,
expand_collapse_right, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
expand_collapse_right, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
expand_collapse_right, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_Left, GDK_SHIFT_MASK,
expand_collapse_left, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_KP_Left, GDK_SHIFT_MASK,
expand_collapse_left, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
expand_collapse_left, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
expand_collapse_left, NULL, NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
"listitem.toggle-expand", NULL);
gtk_binding_entry_add_action (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
"listitem.toggle-expand", NULL);
#if 0
/* These can't be implementes yet. */
gtk_binding_entry_add_callback (binding_set, GDK_KEY_BackSpace, 0, go_to_parent_row, NULL, NULL);
gtk_binding_entry_add_callback (binding_set, GDK_KEY_BackSpace, GDK_CONTROL_MASK, go_to_parent_row, NULL, NULL);
#endif
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("treeexpander"));
}
static void
gtk_tree_expander_init (GtkTreeExpander *self)
{
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
}
/**
* gtk_tree_expander_new:
*
* Creates a new #GtkTreeExpander
*
* Returns: a new #GtkTreeExpander
**/
GtkWidget *
gtk_tree_expander_new (void)
{
return g_object_new (GTK_TYPE_TREE_EXPANDER,
NULL);
}
/**
* gtk_tree_expander_get_child
* @self: a #GtkTreeExpander
*
* Gets the child widget displayed by @self.
*
* Returns: (nullable) (transfer none): The child displayed by @self
**/
GtkWidget *
gtk_tree_expander_get_child (GtkTreeExpander *self)
{
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), NULL);
return self->child;
}
/**
* gtk_tree_expander_set_child:
* @self: a #GtkTreeExpander widget
* @child: (nullable): a #GtkWidget, or %NULL
*
* Sets the content widget to display.
*/
void
gtk_tree_expander_set_child (GtkTreeExpander *self,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
if (self->child == child)
return;
g_clear_pointer (&self->child, gtk_widget_unparent);
if (child)
{
self->child = child;
gtk_widget_set_parent (child, GTK_WIDGET (self));
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
}
/**
* gtk_tree_expander_get_item
* @self: a #GtkTreeExpander
*
* Forwards the item set on the #GtkTreeListRow that @self is managing.
*
* This call is essentially equivalent to calling
* `gtk_tree_list_row_get_item (gtk_tree_expander_get_list_row (@self))`.
*
* Returns: (nullable) (transfer none): The item of the row
**/
gpointer
gtk_tree_expander_get_item (GtkTreeExpander *self)
{
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), NULL);
if (self->list_row == NULL)
return NULL;
return gtk_tree_list_row_get_item (self->list_row);
}
/**
* gtk_tree_expander_get_list_row
* @self: a #GtkTreeExpander
*
* Gets the list row managed by @self.
*
* Returns: (nullable) (transfer none): The list row displayed by @self
**/
GtkTreeListRow *
gtk_tree_expander_get_list_row (GtkTreeExpander *self)
{
g_return_val_if_fail (GTK_IS_TREE_EXPANDER (self), NULL);
return self->list_row;
}
/**
* gtk_tree_expander_set_list_row:
* @self: a #GtkTreeExpander widget
* @list_row: (nullable): a #GtkTreeListRow, or %NULL
*
* Sets the tree list row that this expander should manage.
*/
void
gtk_tree_expander_set_list_row (GtkTreeExpander *self,
GtkTreeListRow *list_row)
{
g_return_if_fail (GTK_IS_TREE_EXPANDER (self));
g_return_if_fail (list_row == NULL || GTK_IS_TREE_LIST_ROW (list_row));
if (self->list_row == list_row)
return;
g_object_freeze_notify (G_OBJECT (self));
gtk_tree_expander_clear_list_row (self);
if (list_row)
{
self->list_row = g_object_ref (list_row);
self->notify_handler = g_signal_connect (list_row,
"notify",
G_CALLBACK (gtk_tree_expander_list_row_notify_cb),
self);
}
gtk_tree_expander_update_for_list_row (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LIST_ROW]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
g_object_thaw_notify (G_OBJECT (self));
}

56
gtk/gtktreeexpander.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* Copyright © 2019 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_TREE_EXPANDER_H__
#define __GTK_TREE_EXPANDER_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktreelistmodel.h>
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_TREE_EXPANDER (gtk_tree_expander_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkTreeExpander, gtk_tree_expander, GTK, TREE_EXPANDER, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_tree_expander_new (void);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_tree_expander_get_child (GtkTreeExpander *self);
GDK_AVAILABLE_IN_ALL
void gtk_tree_expander_set_child (GtkTreeExpander *self,
GtkWidget *child);
GDK_AVAILABLE_IN_ALL
gpointer gtk_tree_expander_get_item (GtkTreeExpander *self);
GDK_AVAILABLE_IN_ALL
GtkTreeListRow * gtk_tree_expander_get_list_row (GtkTreeExpander *self);
GDK_AVAILABLE_IN_ALL
void gtk_tree_expander_set_list_row (GtkTreeExpander *self,
GtkTreeListRow *list_row);
G_END_DECLS
#endif /* __GTK_TREE_EXPANDER_H__ */

View File

@@ -39,6 +39,7 @@ typedef struct _GtkClipboard GtkClipboard;
typedef struct _GtkEventController GtkEventController;
typedef struct _GtkGesture GtkGesture;
typedef struct _GtkLayoutManager GtkLayoutManager;
typedef struct _GtkListItemFactory GtkListItemFactory;
typedef struct _GtkNative GtkNative;
typedef struct _GtkRequisition GtkRequisition;
typedef struct _GtkRoot GtkRoot;

View File

@@ -6524,7 +6524,7 @@ gtk_widget_reposition_after (GtkWidget *widget,
if (parent->priv->children_observer)
{
if (prev_previous)
g_warning ("oops");
gtk_list_list_model_item_moved (parent->priv->children_observer, widget, prev_previous);
else
gtk_list_list_model_item_added (parent->priv->children_observer, widget);
}

View File

@@ -46,6 +46,7 @@
#include "gtksizegroup.h"
#include "gtktextview.h"
#include "gtktogglebutton.h"
#include "gtktreeexpander.h"
#include "gtktreelistmodel.h"
#include "gtktreeview.h"
#include "gtktreeselection.h"
@@ -1019,7 +1020,6 @@ gtk_inspector_object_tree_create_list_widget (gpointer row_item,
GtkInspectorObjectTree *wt = user_data;
gpointer item;
GtkWidget *row, *box, *column, *child;
guint depth;
item = gtk_tree_list_row_get_item (row_item);
@@ -1036,37 +1036,14 @@ gtk_inspector_object_tree_create_list_widget (gpointer row_item,
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_container_add (GTK_CONTAINER (row), box);
column = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_size_group_add_widget (wt->priv->type_size_group, column);
gtk_container_add (GTK_CONTAINER (box), column);
/* expander */
depth = gtk_tree_list_row_get_depth (row_item);
if (depth > 0)
{
child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_size_request (child, 16 * depth, 0);
gtk_container_add (GTK_CONTAINER (column), child);
}
if (gtk_tree_list_row_is_expandable (row_item))
{
GtkWidget *title, *arrow;
child = gtk_tree_expander_new ();
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (child), row_item);
gtk_size_group_add_widget (wt->priv->type_size_group, child);
gtk_container_add (GTK_CONTAINER (box), child);
child = g_object_new (GTK_TYPE_BOX, "css-name", "expander", NULL);
title = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "title", NULL);
gtk_button_set_relief (GTK_BUTTON (title), GTK_RELIEF_NONE);
g_object_bind_property (row_item, "expanded", title, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
gtk_container_add (GTK_CONTAINER (child), title);
arrow = gtk_icon_new ("arrow");
gtk_container_add (GTK_CONTAINER (title), arrow);
}
else
{
child = gtk_image_new (); /* empty whatever */
}
gtk_container_add (GTK_CONTAINER (column), child);
column = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (child), column);
/* 1st column: type name */
child = gtk_label_new (G_OBJECT_TYPE_NAME (item));

View File

@@ -21,12 +21,16 @@
#include <gtk/gtkbox.h>
#include <gtk/gtkfilechooserdialog.h>
#include <gtk/gtkfunctionslistitemfactory.h>
#include <gtk/gtklabel.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistview.h>
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtkpicture.h>
#include <gtk/gtkpopover.h>
#include <gtk/gtksingleselection.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtktreeexpander.h>
#include <gtk/gtktreelistmodel.h>
#include <gtk/gtktreemodel.h>
#include <gtk/gtktreeview.h>
@@ -49,6 +53,8 @@ struct _GtkInspectorRecorderPrivate
{
GListModel *recordings;
GtkTreeListModel *render_node_model;
GListStore *render_node_root_model;
GtkSingleSelection *render_node_selection;
GtkWidget *recordings_list;
GtkWidget *render_node_view;
@@ -293,64 +299,56 @@ node_name (GskRenderNode *node)
}
}
static GtkWidget *
create_widget_for_render_node (gpointer row_item,
gpointer unused)
static void
setup_widget_for_render_node (GtkListItem *list_item,
gpointer unused)
{
GdkPaintable *paintable;
GskRenderNode *node;
GtkWidget *row, *box, *child;
char *name;
guint depth;
paintable = gtk_tree_list_row_get_item (row_item);
node = gtk_render_node_paintable_get_render_node (GTK_RENDER_NODE_PAINTABLE (paintable));
row = gtk_list_box_row_new ();
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
gtk_container_add (GTK_CONTAINER (row), box);
GtkWidget *expander, *box, *child;
/* expander */
depth = gtk_tree_list_row_get_depth (row_item);
if (depth > 0)
{
child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_size_request (child, 16 * depth, 0);
gtk_container_add (GTK_CONTAINER (box), child);
}
if (gtk_tree_list_row_is_expandable (row_item))
{
GtkWidget *title, *arrow;
expander = gtk_tree_expander_new ();
gtk_container_add (GTK_CONTAINER (list_item), expander);
child = g_object_new (GTK_TYPE_BOX, "css-name", "expander", NULL);
title = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "css-name", "title", NULL);
gtk_button_set_relief (GTK_BUTTON (title), GTK_RELIEF_NONE);
g_object_bind_property (row_item, "expanded", title, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
gtk_container_add (GTK_CONTAINER (child), title);
g_object_set_data_full (G_OBJECT (row), "make-sure-its-not-unreffed", g_object_ref (row_item), g_object_unref);
arrow = gtk_icon_new ("arrow");
gtk_container_add (GTK_CONTAINER (title), arrow);
}
else
{
child = gtk_image_new (); /* empty whatever */
}
gtk_container_add (GTK_CONTAINER (box), child);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), box);
/* icon */
child = gtk_image_new_from_paintable (paintable);
child = gtk_image_new ();
gtk_container_add (GTK_CONTAINER (box), child);
/* name */
name = node_name (node);
child = gtk_label_new (name);
g_free (name);
child = gtk_label_new (NULL);
gtk_container_add (GTK_CONTAINER (box), child);
}
g_object_unref (paintable);
static void
bind_widget_for_render_node (GtkListItem *list_item,
gpointer unused)
{
GdkPaintable *paintable;
GskRenderNode *node;
GtkTreeListRow *row_item;
GtkWidget *expander, *box, *child;
char *name;
return row;
row_item = gtk_list_item_get_item (list_item);
paintable = gtk_tree_list_row_get_item (row_item);
node = gtk_render_node_paintable_get_render_node (GTK_RENDER_NODE_PAINTABLE (paintable));
/* expander */
expander = gtk_bin_get_child (GTK_BIN (list_item));
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), row_item);
box = gtk_tree_expander_get_child (GTK_TREE_EXPANDER (expander));
/* icon */
child = gtk_widget_get_first_child (box);
gtk_image_set_from_paintable (GTK_IMAGE (child), paintable);
/* name */
name = node_name (node);
child = gtk_widget_get_last_child (box);
gtk_label_set_label (GTK_LABEL (child), name);
g_free (name);
}
static void
@@ -366,11 +364,8 @@ recordings_list_row_selected (GtkListBox *box,
else
recording = NULL;
g_clear_object (&priv->render_node_model);
if (GTK_INSPECTOR_IS_RENDER_RECORDING (recording))
{
GListStore *root_model;
graphene_rect_t bounds;
GskRenderNode *node;
GdkPaintable *paintable;
@@ -380,14 +375,10 @@ recordings_list_row_selected (GtkListBox *box,
paintable = gtk_render_node_paintable_new (node, &bounds);
gtk_picture_set_paintable (GTK_PICTURE (priv->render_node_view), paintable);
root_model = g_list_store_new (GDK_TYPE_PAINTABLE);
g_list_store_append (root_model, paintable);
priv->render_node_model = gtk_tree_list_model_new (FALSE,
G_LIST_MODEL (root_model),
TRUE,
create_list_model_for_render_node_paintable,
NULL, NULL);
g_object_unref (root_model);
g_list_store_splice (priv->render_node_root_model,
0, g_list_model_get_n_items (G_LIST_MODEL (priv->render_node_root_model)),
(gpointer[1]) { paintable },
1);
g_object_unref (paintable);
g_print ("%u render nodes\n", g_list_model_get_n_items (G_LIST_MODEL (priv->render_node_model)));
@@ -395,14 +386,9 @@ recordings_list_row_selected (GtkListBox *box,
else
{
gtk_picture_set_paintable (GTK_PICTURE (priv->render_node_view), NULL);
g_list_store_remove_all (priv->render_node_root_model);
}
gtk_list_box_bind_model (GTK_LIST_BOX (priv->render_node_list),
G_LIST_MODEL (priv->render_node_model),
create_widget_for_render_node,
NULL, NULL);
if (recording)
g_object_unref (recording);
}
@@ -919,20 +905,16 @@ get_selected_node (GtkInspectorRecorder *recorder)
{
GtkInspectorRecorderPrivate *priv = gtk_inspector_recorder_get_instance_private (recorder);
GtkTreeListRow *row_item;
GtkListBoxRow *row;
GdkPaintable *paintable;
GskRenderNode *node;
row = gtk_list_box_get_selected_row (GTK_LIST_BOX (priv->render_node_list));
if (row == NULL)
row_item = gtk_single_selection_get_selected_item (priv->render_node_selection);
if (row_item == NULL)
return NULL;
row_item = g_list_model_get_item (G_LIST_MODEL (priv->render_node_model),
gtk_list_box_row_get_index (row));
paintable = gtk_tree_list_row_get_item (row_item);
node = gtk_render_node_paintable_get_render_node (GTK_RENDER_NODE_PAINTABLE (paintable));
g_object_unref (paintable);
g_object_unref (row_item);
return node;
}
@@ -947,14 +929,10 @@ render_node_list_selection_changed (GtkListBox *list,
GdkPaintable *paintable;
GtkTreeListRow *row_item;
if (row == NULL)
{
gtk_widget_set_sensitive (priv->render_node_save_button, FALSE);
return;
}
row_item = gtk_single_selection_get_selected_item (priv->render_node_selection);
if (row_item == NULL)
return;
row_item = g_list_model_get_item (G_LIST_MODEL (priv->render_node_model),
gtk_list_box_row_get_index (row));
paintable = gtk_tree_list_row_get_item (row_item);
gtk_widget_set_sensitive (priv->render_node_save_button, TRUE);
@@ -963,7 +941,6 @@ render_node_list_selection_changed (GtkListBox *list,
populate_render_node_properties (GTK_LIST_STORE (priv->render_node_properties), node);
g_object_unref (paintable);
g_object_unref (row_item);
}
static void
@@ -1233,6 +1210,8 @@ gtk_inspector_recorder_dispose (GObject *object)
GtkInspectorRecorderPrivate *priv = gtk_inspector_recorder_get_instance_private (recorder);
g_clear_object (&priv->render_node_model);
g_clear_object (&priv->render_node_root_model);
g_clear_object (&priv->render_node_selection);
G_OBJECT_CLASS (gtk_inspector_recorder_parent_class)->dispose (object);
}
@@ -1273,7 +1252,6 @@ gtk_inspector_recorder_class_init (GtkInspectorRecorderClass *klass)
gtk_widget_class_bind_template_callback (widget_class, recordings_clear_all);
gtk_widget_class_bind_template_callback (widget_class, recordings_list_row_selected);
gtk_widget_class_bind_template_callback (widget_class, render_node_list_selection_changed);
gtk_widget_class_bind_template_callback (widget_class, render_node_save);
gtk_widget_class_bind_template_callback (widget_class, node_property_activated);
}
@@ -1282,6 +1260,7 @@ static void
gtk_inspector_recorder_init (GtkInspectorRecorder *recorder)
{
GtkInspectorRecorderPrivate *priv = gtk_inspector_recorder_get_instance_private (recorder);
GtkListItemFactory *factory;
gtk_widget_init_template (GTK_WIDGET (recorder));
@@ -1291,6 +1270,23 @@ gtk_inspector_recorder_init (GtkInspectorRecorder *recorder)
recorder,
NULL);
priv->render_node_root_model = g_list_store_new (GDK_TYPE_PAINTABLE);
priv->render_node_model = gtk_tree_list_model_new (FALSE,
G_LIST_MODEL (priv->render_node_root_model),
TRUE,
create_list_model_for_render_node_paintable,
NULL, NULL);
priv->render_node_selection = gtk_single_selection_new (G_LIST_MODEL (priv->render_node_model));
g_signal_connect (priv->render_node_selection, "notify::selected-item", G_CALLBACK (render_node_list_selection_changed), recorder);
factory = gtk_functions_list_item_factory_new (setup_widget_for_render_node,
bind_widget_for_render_node,
NULL, NULL);
gtk_list_view_set_factory (GTK_LIST_VIEW (priv->render_node_list), factory);
g_object_unref (factory);
gtk_list_view_set_model (GTK_LIST_VIEW (priv->render_node_list),
G_LIST_MODEL (priv->render_node_selection));
priv->render_node_properties = GTK_TREE_MODEL (gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, GDK_TYPE_TEXTURE));
gtk_tree_view_set_model (GTK_TREE_VIEW (priv->node_property_tree), priv->render_node_properties);
g_object_unref (priv->render_node_properties);

View File

@@ -73,15 +73,14 @@
<object class="GtkFrame">
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="propagate-natural-width">1</property>
<style>
<class name="sidebar"/>
</style>
<child>
<object class="GtkListBox" id="render_node_list">
<object class="GtkListView" id="render_node_list">
<property name="vexpand">1</property>
<signal name="row-selected" handler="render_node_list_selection_changed"/>
<property name="hexpand">1</property>
</object>
</child>
</object>

View File

@@ -177,6 +177,7 @@ gtk_public_sources = files([
'gtkbuildable.c',
'gtkbuilder.c',
'gtkbuilderparser.c',
'gtkbuilderlistitemfactory.c',
'gtkbutton.c',
'gtkcalendar.c',
'gtkcellarea.c',
@@ -212,6 +213,7 @@ gtk_public_sources = files([
'gtkcontainer.c',
'gtkcssprovider.c',
'gtkdialog.c',
'gtkdirectorylist.c',
'gtkdnd.c',
'gtkdragdest.c',
'gtkdragsource.c',
@@ -245,6 +247,7 @@ gtk_public_sources = files([
'gtkfontchooserutils.c',
'gtkfontchooserwidget.c',
'gtkframe.c',
'gtkfunctionslistitemfactory.c',
'gtkgesture.c',
'gtkgesturedrag.c',
'gtkgesturelongpress.c',
@@ -258,6 +261,7 @@ gtk_public_sources = files([
'gtkglarea.c',
'gtkgrid.c',
'gtkgridlayout.c',
'gtkgridview.c',
'gtkheaderbar.c',
'gtkicontheme.c',
'gtkiconview.c',
@@ -274,8 +278,12 @@ gtk_public_sources = files([
'gtklevelbar.c',
'gtklinkbutton.c',
'gtklistbox.c',
'gtklistitem.c',
'gtklistitemfactory.c',
'gtklistitemmanager.c',
'gtklistlistmodel.c',
'gtkliststore.c',
'gtklistview.c',
'gtklockbutton.c',
'gtkmain.c',
'gtkmaplistmodel.c',
@@ -390,6 +398,7 @@ gtk_public_sources = files([
'gtktooltip.c',
'gtktooltipwindow.c',
'gtktreednd.c',
'gtktreeexpander.c',
'gtktreelistmodel.c',
'gtktreemenu.c',
'gtktreemodel.c',
@@ -442,6 +451,7 @@ gtk_public_headers = files([
'gtkboxlayout.h',
'gtkbuildable.h',
'gtkbuilder.h',
'gtkbuilderlistitemfactory.h',
'gtkbutton.h',
'gtkcalendar.h',
'gtkcenterbox.h',
@@ -478,6 +488,7 @@ gtk_public_headers = files([
'gtkcustomlayout.h',
'gtkdebug.h',
'gtkdialog.h',
'gtkdirectorylist.h',
'gtkdnd.h',
'gtkdragdest.h',
'gtkdragsource.h',
@@ -509,6 +520,7 @@ gtk_public_headers = files([
'gtkfontchooserdialog.h',
'gtkfontchooserwidget.h',
'gtkframe.h',
'gtkfunctionslistitemfactory.h',
'gtkgesture.h',
'gtkgesturedrag.h',
'gtkgesturelongpress.h',
@@ -522,6 +534,7 @@ gtk_public_headers = files([
'gtkglarea.h',
'gtkgrid.h',
'gtkgridlayout.h',
'gtkgridview.h',
'gtkheaderbar.h',
'gtkicontheme.h',
'gtkiconview.h',
@@ -537,7 +550,10 @@ gtk_public_headers = files([
'gtklevelbar.h',
'gtklinkbutton.h',
'gtklistbox.h',
'gtklistitem.h',
'gtklistitemfactory.h',
'gtkliststore.h',
'gtklistview.h',
'gtklockbutton.h',
'gtkmain.h',
'gtkmaplistmodel.h',
@@ -632,6 +648,7 @@ gtk_public_headers = files([
'gtktoolshell.h',
'gtktooltip.h',
'gtktreednd.h',
'gtktreeexpander.h',
'gtktreelistmodel.h',
'gtktreemodel.h',
'gtktreemodelfilter.h',

View File

@@ -3734,7 +3734,10 @@ list {
row.expander { padding: 0px; }
row.expander .row-header { padding: 2px; }
&.separators row:not(:first-child) {
&.separators.horizontal row:not(:first-child) {
border-left: 1px solid $borders_color;
}
&.separators:not(.horizontal) row:not(:first-child) {
border-top: 1px solid $borders_color;
}
}

View File

@@ -1627,7 +1627,9 @@ list row.expander { padding: 0px; }
list row.expander .row-header { padding: 2px; }
list.separators row:not(:first-child) { border-top: 1px solid #1b1b1b; }
list.separators.horizontal row:not(:first-child) { border-left: 1px solid #1b1b1b; }
list.separators:not(.horizontal) row:not(:first-child) { border-top: 1px solid #1b1b1b; }
row { transition: all 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94); }

View File

@@ -1643,7 +1643,9 @@ list row.expander { padding: 0px; }
list row.expander .row-header { padding: 2px; }
list.separators row:not(:first-child) { border-top: 1px solid #cdc7c2; }
list.separators.horizontal row:not(:first-child) { border-left: 1px solid #cdc7c2; }
list.separators:not(.horizontal) row:not(:first-child) { border-top: 1px solid #cdc7c2; }
row { transition: all 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94); }

View File

@@ -59,6 +59,8 @@ gtk_tests = [
['testlist2'],
['testlist3'],
['testlist4'],
['testlistview'],
['testlistview-animating'],
['testlevelbar'],
['testlockbutton'],
['testmenubutton'],

View File

@@ -0,0 +1,192 @@
#include <gtk/gtk.h>
#ifdef SMALL
#define AVERAGE 15
#define VARIANCE 10
#else
#define AVERAGE 300
#define VARIANCE 200
#endif
static void
update_label (GtkListItem *list_item,
GParamSpec *pspec,
GtkLabel *label)
{
gpointer item;
char *s;
item = gtk_list_item_get_item (list_item);
if (item)
s = g_strdup_printf ("%u: %s",
gtk_list_item_get_position (list_item),
(const char *) g_object_get_data (item, "message"));
else
s = NULL;
gtk_label_set_text (label, s);
g_free (s);
}
static void
setup_list_item (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *label = gtk_label_new ("");
g_signal_connect (list_item, "notify", G_CALLBACK (update_label), label);
gtk_container_add (GTK_CONTAINER (list_item), label);
}
static GtkWidget *
create_widget_for_listbox (gpointer item,
gpointer unused)
{
const char *message = g_object_get_data (item, "message");
GtkWidget *widget;
widget = gtk_label_new (message);
return widget;
}
static gboolean reverse_sort;
static int
compare (gconstpointer first,
gconstpointer second,
gpointer unused)
{
int diff = (GPOINTER_TO_UINT (g_object_get_data ((gpointer) first, "counter")) % 1000)
- (GPOINTER_TO_UINT (g_object_get_data ((gpointer) second, "counter")) % 1000);
if (reverse_sort)
return -diff;
else
return diff;
}
static void
add (GListStore *store)
{
static guint counter;
GObject *o;
char *message;
guint pos;
counter++;
o = g_object_new (G_TYPE_OBJECT, NULL);
g_object_set_data (o, "counter", GUINT_TO_POINTER (counter));
message = g_strdup_printf ("Item %u", counter);
g_object_set_data_full (o, "message", message, g_free);
pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
g_list_store_insert (store, pos, o);
g_object_unref (o);
}
static void
delete (GListStore *store)
{
guint pos;
pos = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)));
g_list_store_remove (store, pos);
}
static gboolean
do_stuff (gpointer store)
{
if (g_random_int_range (AVERAGE - VARIANCE, AVERAGE + VARIANCE) < g_list_model_get_n_items (store))
delete (store);
else
add (store);
return G_SOURCE_CONTINUE;
}
static gboolean
revert_sort (gpointer sort)
{
reverse_sort = !reverse_sort;
gtk_sort_list_model_resort (sort);
return G_SOURCE_CONTINUE;
}
int
main (int argc,
char *argv[])
{
GtkWidget *win, *hbox, *vbox, *sw, *listview, *listbox, *label;
GListStore *store;
GtkSortListModel *sort;
guint i;
gtk_init ();
store = g_list_store_new (G_TYPE_OBJECT);
for (i = 0; i < AVERAGE; i++)
add (store);
sort = gtk_sort_list_model_new (G_LIST_MODEL (store),
compare,
NULL, NULL);
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), win);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_container_add (GTK_CONTAINER (win), hbox);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new ("GtkListView");
gtk_container_add (GTK_CONTAINER (vbox), label);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_hexpand (sw, TRUE);
gtk_widget_set_vexpand (sw, TRUE);
gtk_container_add (GTK_CONTAINER (vbox), sw);
listview = gtk_list_view_new_with_factory (
gtk_functions_list_item_factory_new (setup_list_item,
NULL,
NULL, NULL));
gtk_container_add (GTK_CONTAINER (sw), listview);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
label = gtk_label_new ("GtkListBox");
gtk_container_add (GTK_CONTAINER (vbox), label);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_hexpand (sw, TRUE);
gtk_widget_set_vexpand (sw, TRUE);
gtk_container_add (GTK_CONTAINER (vbox), sw);
listbox = gtk_list_box_new ();
gtk_container_add (GTK_CONTAINER (sw), listbox);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (sort));
gtk_list_box_bind_model (GTK_LIST_BOX (listbox),
G_LIST_MODEL (sort),
create_widget_for_listbox,
NULL, NULL);
g_timeout_add (100, do_stuff, store);
g_timeout_add_seconds (3, revert_sort, sort);
gtk_widget_show (win);
gtk_main ();
g_object_unref (store);
return 0;
}

679
tests/testlistview.c Normal file
View File

@@ -0,0 +1,679 @@
#include <gtk/gtk.h>
#define FILE_INFO_TYPE_SELECTION (file_info_selection_get_type ())
G_DECLARE_FINAL_TYPE (FileInfoSelection, file_info_selection, FILE_INFO, SELECTION, GObject)
struct _FileInfoSelection
{
GObject parent_instance;
GListModel *model;
};
struct _FileInfoSelectionClass
{
GObjectClass parent_class;
};
static GType
file_info_selection_get_item_type (GListModel *list)
{
FileInfoSelection *self = FILE_INFO_SELECTION (list);
return g_list_model_get_item_type (self->model);
}
static guint
file_info_selection_get_n_items (GListModel *list)
{
FileInfoSelection *self = FILE_INFO_SELECTION (list);
return g_list_model_get_n_items (self->model);
}
static gpointer
file_info_selection_get_item (GListModel *list,
guint position)
{
FileInfoSelection *self = FILE_INFO_SELECTION (list);
return g_list_model_get_item (self->model, position);
}
static void
file_info_selection_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = file_info_selection_get_item_type;
iface->get_n_items = file_info_selection_get_n_items;
iface->get_item = file_info_selection_get_item;
}
static gboolean
file_info_selection_is_selected (GtkSelectionModel *model,
guint position)
{
FileInfoSelection *self = FILE_INFO_SELECTION (model);
gpointer item;
item = g_list_model_get_item (self->model, position);
if (item == NULL)
return FALSE;
if (GTK_IS_TREE_LIST_ROW (item))
{
GtkTreeListRow *row = item;
item = gtk_tree_list_row_get_item (row);
g_object_unref (row);
}
return g_file_info_get_attribute_boolean (item, "filechooser::selected");
}
static void
file_info_selection_set_selected (FileInfoSelection *self,
guint position,
gboolean selected)
{
gpointer item;
item = g_list_model_get_item (self->model, position);
if (item == NULL)
return;
if (GTK_IS_TREE_LIST_ROW (item))
{
GtkTreeListRow *row = item;
item = gtk_tree_list_row_get_item (row);
g_object_unref (row);
}
g_file_info_set_attribute_boolean (item, "filechooser::selected", selected);
}
static gboolean
file_info_selection_select_item (GtkSelectionModel *model,
guint position,
gboolean exclusive)
{
FileInfoSelection *self = FILE_INFO_SELECTION (model);
if (exclusive)
{
guint i;
for (i = 0; i < g_list_model_get_n_items (self->model); i++)
file_info_selection_set_selected (self, i, i == position);
gtk_selection_model_selection_changed (model, 0, g_list_model_get_n_items (self->model));
}
else
{
file_info_selection_set_selected (self, position, TRUE);
gtk_selection_model_selection_changed (model, position, 1);
}
return TRUE;
}
static gboolean
file_info_selection_unselect_item (GtkSelectionModel *model,
guint position)
{
FileInfoSelection *self = FILE_INFO_SELECTION (model);
file_info_selection_set_selected (self, position, FALSE);
gtk_selection_model_selection_changed (model, position, 1);
return TRUE;
}
static gboolean
file_info_selection_select_range (GtkSelectionModel *model,
guint position,
guint n_items,
gboolean exclusive)
{
FileInfoSelection *self = FILE_INFO_SELECTION (model);
guint i;
if (exclusive)
for (i = 0; i < position; i++)
file_info_selection_set_selected (self, i, FALSE);
for (i = position; i < position + n_items; i++)
file_info_selection_set_selected (self, i, TRUE);
if (exclusive)
for (i = position + n_items; i < g_list_model_get_n_items (self->model); i++)
file_info_selection_set_selected (self, i, FALSE);
if (exclusive)
gtk_selection_model_selection_changed (model, 0, g_list_model_get_n_items (self->model));
else
gtk_selection_model_selection_changed (model, position, n_items);
return TRUE;
}
static gboolean
file_info_selection_unselect_range (GtkSelectionModel *model,
guint position,
guint n_items)
{
FileInfoSelection *self = FILE_INFO_SELECTION (model);
guint i;
for (i = position; i < position + n_items; i++)
file_info_selection_set_selected (self, i, FALSE);
gtk_selection_model_selection_changed (model, position, n_items);
return TRUE;
}
static void
file_info_selection_selection_model_init (GtkSelectionModelInterface *iface)
{
iface->is_selected = file_info_selection_is_selected;
iface->select_item = file_info_selection_select_item;
iface->unselect_item = file_info_selection_unselect_item;
iface->select_range = file_info_selection_select_range;
iface->unselect_range = file_info_selection_unselect_range;
}
G_DEFINE_TYPE_EXTENDED (FileInfoSelection, file_info_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
file_info_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
file_info_selection_selection_model_init))
static void
file_info_selection_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
FileInfoSelection *self)
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
}
static void
file_info_selection_clear_model (FileInfoSelection *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model,
file_info_selection_items_changed_cb,
self);
g_clear_object (&self->model);
}
static void
file_info_selection_dispose (GObject *object)
{
FileInfoSelection *self = FILE_INFO_SELECTION (object);
file_info_selection_clear_model (self);
G_OBJECT_CLASS (file_info_selection_parent_class)->dispose (object);
}
static void
file_info_selection_class_init (FileInfoSelectionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = file_info_selection_dispose;
}
static void
file_info_selection_init (FileInfoSelection *self)
{
}
static FileInfoSelection *
file_info_selection_new (GListModel *model)
{
FileInfoSelection *result;
result = g_object_new (FILE_INFO_TYPE_SELECTION, NULL);
result->model = g_object_ref (model);
g_signal_connect (result->model, "items-changed",
G_CALLBACK (file_info_selection_items_changed_cb), result);
return result;
}
/*** ---------------------- ***/
GSList *pending = NULL;
guint active = 0;
static void
loading_cb (GtkDirectoryList *dir,
GParamSpec *pspec,
gpointer unused)
{
if (gtk_directory_list_is_loading (dir))
{
active++;
/* HACK: ensure loading finishes and the dir doesn't get destroyed */
g_object_ref (dir);
}
else
{
active--;
g_object_unref (dir);
while (active < 20 && pending)
{
GtkDirectoryList *dir2 = pending->data;
pending = g_slist_remove (pending, dir2);
gtk_directory_list_set_file (dir2, g_object_get_data (G_OBJECT (dir2), "file"));
g_object_unref (dir2);
}
}
}
static GtkDirectoryList *
create_directory_list (GFile *file)
{
GtkDirectoryList *dir;
dir = gtk_directory_list_new (G_FILE_ATTRIBUTE_STANDARD_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
NULL);
gtk_directory_list_set_io_priority (dir, G_PRIORITY_DEFAULT_IDLE);
g_signal_connect (dir, "notify::loading", G_CALLBACK (loading_cb), NULL);
g_assert (!gtk_directory_list_is_loading (dir));
if (active > 20)
{
g_object_set_data_full (G_OBJECT (dir), "file", g_object_ref (file), g_object_unref);
pending = g_slist_prepend (pending, g_object_ref (dir));
}
else
{
gtk_directory_list_set_file (dir, file);
}
return dir;
}
static int
compare_files (gconstpointer first,
gconstpointer second,
gpointer unused)
{
GFile *first_file, *second_file;
char *first_path, *second_path;
int result;
#if 0
GFileType first_type, second_type;
/* This is a bit slow, because each g_file_query_file_type() does a stat() */
first_type = g_file_query_file_type (G_FILE (first), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
second_type = g_file_query_file_type (G_FILE (second), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
if (first_type == G_FILE_TYPE_DIRECTORY && second_type != G_FILE_TYPE_DIRECTORY)
return -1;
if (first_type != G_FILE_TYPE_DIRECTORY && second_type == G_FILE_TYPE_DIRECTORY)
return 1;
#endif
first_file = G_FILE (g_file_info_get_attribute_object (G_FILE_INFO (first), "standard::file"));
second_file = G_FILE (g_file_info_get_attribute_object (G_FILE_INFO (second), "standard::file"));
first_path = g_file_get_path (first_file);
second_path = g_file_get_path (second_file);
result = strcasecmp (first_path, second_path);
g_free (first_path);
g_free (second_path);
return result;
}
static GListModel *
create_list_model_for_directory (gpointer file)
{
GtkSortListModel *sort;
GtkDirectoryList *dir;
if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
return NULL;
dir = create_directory_list(file);
sort = gtk_sort_list_model_new (G_LIST_MODEL (dir),
compare_files,
NULL, NULL);
g_object_unref (dir);
return G_LIST_MODEL (sort);
}
typedef struct _RowData RowData;
struct _RowData
{
GtkWidget *expander;
GtkWidget *icon;
GtkWidget *name;
GCancellable *cancellable;
GtkTreeListRow *current_item;
};
static void row_data_notify_item (GtkListItem *item,
GParamSpec *pspec,
RowData *data);
static void
row_data_unbind (RowData *data)
{
if (data->current_item == NULL)
return;
if (data->cancellable)
{
g_cancellable_cancel (data->cancellable);
g_clear_object (&data->cancellable);
}
g_clear_object (&data->current_item);
}
static void
row_data_update_info (RowData *data,
GFileInfo *info)
{
GIcon *icon;
const char *thumbnail_path;
thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
if (thumbnail_path)
{
/* XXX: not async */
GFile *thumbnail_file = g_file_new_for_path (thumbnail_path);
icon = g_file_icon_new (thumbnail_file);
g_object_unref (thumbnail_file);
}
else
{
icon = g_file_info_get_icon (info);
}
gtk_widget_set_visible (data->icon, icon != NULL);
gtk_image_set_from_gicon (GTK_IMAGE (data->icon), icon);
}
static void
copy_attribute (GFileInfo *to,
GFileInfo *from,
const gchar *attribute)
{
GFileAttributeType type;
gpointer value;
if (g_file_info_get_attribute_data (from, attribute, &type, &value, NULL))
g_file_info_set_attribute (to, attribute, type, value);
}
static void
row_data_got_thumbnail_info_cb (GObject *source,
GAsyncResult *res,
gpointer _data)
{
RowData *data = _data; /* invalid if operation was cancelled */
GFile *file = G_FILE (source);
GFileInfo *queried, *info;
queried = g_file_query_info_finish (file, res, NULL);
if (queried == NULL)
return;
/* now we know row is valid */
info = gtk_tree_list_row_get_item (data->current_item);
copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON);
g_object_unref (queried);
row_data_update_info (data, info);
g_clear_object (&data->cancellable);
}
static void
row_data_bind (RowData *data,
GtkTreeListRow *item)
{
GFileInfo *info;
row_data_unbind (data);
if (item == NULL)
return;
data->current_item = g_object_ref (item);
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (data->expander), item);
info = gtk_tree_list_row_get_item (item);
if (!g_file_info_has_attribute (info, "filechooser::queried"))
{
data->cancellable = g_cancellable_new ();
g_file_info_set_attribute_boolean (info, "filechooser::queried", TRUE);
g_file_query_info_async (G_FILE (g_file_info_get_attribute_object (info, "standard::file")),
G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
G_FILE_ATTRIBUTE_THUMBNAILING_FAILED ","
G_FILE_ATTRIBUTE_STANDARD_ICON,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
data->cancellable,
row_data_got_thumbnail_info_cb,
data);
}
row_data_update_info (data, info);
gtk_label_set_label (GTK_LABEL (data->name), g_file_info_get_display_name (info));
g_object_unref (info);
}
static void
row_data_notify_item (GtkListItem *item,
GParamSpec *pspec,
RowData *data)
{
row_data_bind (data, gtk_list_item_get_item (item));
}
static void
row_data_free (gpointer _data)
{
RowData *data = _data;
row_data_unbind (data);
g_slice_free (RowData, data);
}
static void
setup_widget (GtkListItem *list_item,
gpointer unused)
{
GtkWidget *box, *child;
RowData *data;
data = g_slice_new0 (RowData);
g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data);
g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_container_add (GTK_CONTAINER (list_item), box);
child = gtk_label_new (NULL);
gtk_label_set_width_chars (GTK_LABEL (child), 5);
gtk_label_set_xalign (GTK_LABEL (child), 1.0);
g_object_bind_property (list_item, "position", child, "label", G_BINDING_SYNC_CREATE);
gtk_container_add (GTK_CONTAINER (box), child);
data->expander = gtk_tree_expander_new ();
gtk_container_add (GTK_CONTAINER (box), data->expander);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (data->expander), box);
data->icon = gtk_image_new ();
gtk_container_add (GTK_CONTAINER (box), data->icon);
data->name = gtk_label_new (NULL);
gtk_container_add (GTK_CONTAINER (box), data->name);
}
static GListModel *
create_list_model_for_file_info (gpointer file_info,
gpointer unused)
{
GFile *file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file"));
if (file == NULL)
return NULL;
return create_list_model_for_directory (file);
}
static gboolean
update_statusbar (GtkStatusbar *statusbar)
{
GListModel *model = g_object_get_data (G_OBJECT (statusbar), "model");
GString *string = g_string_new (NULL);
guint n;
gboolean result = G_SOURCE_REMOVE;
gtk_statusbar_remove_all (statusbar, 0);
n = g_list_model_get_n_items (model);
g_string_append_printf (string, "%u", n);
if (GTK_IS_FILTER_LIST_MODEL (model))
{
guint n_unfiltered = g_list_model_get_n_items (gtk_filter_list_model_get_model (GTK_FILTER_LIST_MODEL (model)));
if (n != n_unfiltered)
g_string_append_printf (string, "/%u", n_unfiltered);
}
g_string_append (string, " items");
if (pending || active)
{
g_string_append_printf (string, " (%u directories remaining)", active + g_slist_length (pending));
result = G_SOURCE_CONTINUE;
}
result = G_SOURCE_CONTINUE;
gtk_statusbar_push (statusbar, 0, string->str);
g_free (string->str);
return result;
}
static gboolean
match_file (gpointer item, gpointer data)
{
GtkWidget *search_entry = data;
GFileInfo *info = gtk_tree_list_row_get_item (item);
GFile *file = G_FILE (g_file_info_get_attribute_object (info, "standard::file"));
char *path;
gboolean result;
path = g_file_get_path (file);
result = strstr (path, gtk_editable_get_text (GTK_EDITABLE (search_entry))) != NULL;
g_object_unref (info);
g_free (path);
return result;
}
int
main (int argc, char *argv[])
{
GtkWidget *win, *vbox, *sw, *listview, *search_entry, *statusbar;
GListModel *dirmodel;
GtkTreeListModel *tree;
GtkFilterListModel *filter;
FileInfoSelection *selectionmodel;
GFile *root;
gtk_init ();
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
g_signal_connect (win, "destroy", G_CALLBACK (gtk_main_quit), win);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (win), vbox);
search_entry = gtk_search_entry_new ();
gtk_container_add (GTK_CONTAINER (vbox), search_entry);
sw = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_vexpand (sw, TRUE);
gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), sw);
gtk_container_add (GTK_CONTAINER (vbox), sw);
listview = gtk_list_view_new_with_factory (
gtk_functions_list_item_factory_new (setup_widget,
NULL,
NULL, NULL));
gtk_container_add (GTK_CONTAINER (sw), listview);
if (argc > 1)
root = g_file_new_for_commandline_arg (argv[1]);
else
root = g_file_new_for_path (g_get_current_dir ());
dirmodel = create_list_model_for_directory (root);
tree = gtk_tree_list_model_new (FALSE,
dirmodel,
TRUE,
create_list_model_for_file_info,
NULL, NULL);
g_object_unref (dirmodel);
g_object_unref (root);
filter = gtk_filter_list_model_new (G_LIST_MODEL (tree),
match_file,
search_entry,
NULL);
g_signal_connect_swapped (search_entry, "search-changed", G_CALLBACK (gtk_filter_list_model_refilter), filter);
selectionmodel = file_info_selection_new (G_LIST_MODEL (filter));
g_object_unref (filter);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (selectionmodel));
statusbar = gtk_statusbar_new ();
gtk_widget_add_tick_callback (statusbar, (GtkTickCallback) update_statusbar, NULL, NULL);
g_object_set_data (G_OBJECT (statusbar), "model", filter);
g_signal_connect_swapped (filter, "items-changed", G_CALLBACK (update_statusbar), statusbar);
update_statusbar (GTK_STATUSBAR (statusbar));
gtk_container_add (GTK_CONTAINER (vbox), statusbar);
g_object_unref (tree);
g_object_unref (selectionmodel);
gtk_widget_show (win);
gtk_main ();
return 0;
}