Files
Flips/arlib/gui/gtk3-listbox.cpp
2016-07-13 18:44:41 +02:00

504 lines
16 KiB
C++

//#include<stdint.h>
//#include<time.h>
//static uint64_t nanotime(){
//struct timespec tv;
//clock_gettime(CLOCK_MONOTONIC, &tv);
//return tv.tv_sec*(uint64_t)1000000000 + tv.tv_nsec;}
#include "window.h"
#ifdef ARGUI_GTK3
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
//there is a gtk_tree_view_insert_column_with_data_func, but while it can get rid of
// virtual_list_get_value, it can not get rid of virtual_list_iter_n_children
//because GtkTreeView insists on iterating the entire list and building some silly tree out of it
//there's no need for anything like that; it's a list, you have O(1) to everything
//see gtk_tree_view_build_tree for details
#define MAX_ROWS 100000
//http://scentric.net/tutorial/
static GType M_VIRTUAL_TYPE=0;
#define M_VIRTUAL_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), M_VIRTUAL_TYPE, struct VirtualList))
#define M_IS_VIRTUAL_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), M_VIRTUAL_TYPE))
struct VirtualList
{
GObject g_parent;//leave this one on top
size_t rows;
unsigned int columns;
bool checkboxes;
struct widget_listbox_virtual * subject;
function<const char * (size_t row, int column)> get_cell;
};
struct VirtualListClass
{
GObjectClass parent_class;
};
static GtkTreeModelFlags virtual_list_get_flags(GtkTreeModel* tree_model)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), (GtkTreeModelFlags)0);
return (GtkTreeModelFlags)(GTK_TREE_MODEL_LIST_ONLY|GTK_TREE_MODEL_ITERS_PERSIST);
}
static gint virtual_list_get_n_columns(GtkTreeModel* tree_model)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), 0);
return M_VIRTUAL_LIST(tree_model)->columns;
}
static GType virtual_list_get_column_type(GtkTreeModel* tree_model, gint index)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), G_TYPE_INVALID);
g_return_val_if_fail(index>=0 && (unsigned int)index<M_VIRTUAL_LIST(tree_model)->columns, G_TYPE_INVALID);
return G_TYPE_STRING;
}
static gboolean virtual_list_get_iter(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreePath* path)
{
g_assert(M_IS_VIRTUAL_LIST(tree_model));
g_assert(path!=NULL);
g_assert(gtk_tree_path_get_depth(path)==1);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
uintptr_t n=gtk_tree_path_get_indices(path)[0];
if (n>=virtual_list->rows || n<0) return FALSE;
iter->stamp=0;
iter->user_data=(void*)n;
iter->user_data2=NULL;//we don't need those two
iter->user_data3=NULL;
return TRUE;
}
static GtkTreePath* virtual_list_get_path(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), NULL);
g_return_val_if_fail(iter!=NULL, NULL);
GtkTreePath* path=gtk_tree_path_new();
gtk_tree_path_append_index(path, (uintptr_t)iter->user_data);
return path;
}
static void virtual_list_get_value(GtkTreeModel* tree_model, GtkTreeIter* iter, gint column, GValue* value)
{
g_return_if_fail(M_IS_VIRTUAL_LIST(tree_model));
g_return_if_fail(iter!=NULL);
g_return_if_fail(column>=0);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
uintptr_t row=(uintptr_t)iter->user_data;
unsigned int ucolumn=column;
//if (row == MAX_ROWS)
//{
// if (ucolumn == virtual_list->columns)
// {
// g_value_init(value, G_TYPE_BOOLEAN);
// g_value_set_boolean(value, false);
// return;
// }
// else
// {
// g_value_init(value, G_TYPE_STRING);
// //if(0);
// //else if (virtual_list->columns==1) g_value_set_string(value, "(sorry, not supported)");
// //else if (virtual_list->columns==2 && column==0) g_value_set_string(value, "(sorry, not");
// //else if (virtual_list->columns==2 && column==1) g_value_set_string(value, "supported)");
// //else if (virtual_list->columns==3 && column==0) g_value_set_string(value, "(sorry,");
// //else if (virtual_list->columns==3 && column==1) g_value_set_string(value, "not");
// //else if (virtual_list->columns==3 && column==2) g_value_set_string(value, "supported)");
// //else g_value_set_string(value, "");
// g_value_set_string(value, "...");
// }
// return;
//}
if (ucolumn == virtual_list->columns)
{
g_value_init(value, G_TYPE_BOOLEAN);
g_value_set_boolean(value, virtual_list->get_cell(row, -1) ? true : false);
return;
}
g_return_if_fail(ucolumn<virtual_list->columns);
if (row >= virtual_list->rows) g_return_if_reached();
g_value_init(value, G_TYPE_STRING);
const char * ret=virtual_list->get_cell(row, ucolumn);
g_value_set_string(value, ret);
}
static gboolean virtual_list_iter_next(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), FALSE);
if (!iter) return FALSE;
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
uintptr_t id = (uintptr_t)iter->user_data;
if (id >= virtual_list->rows-1) return FALSE;
iter->stamp = 0;
iter->user_data = (void*)(id+1);
return TRUE;
}
static gboolean virtual_list_iter_children(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreeIter* parent)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), FALSE);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
if (parent || virtual_list->rows==0) return FALSE;
iter->stamp=0;
iter->user_data=(void*)(uintptr_t)0;
return TRUE;
}
static gboolean virtual_list_iter_has_child(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
return FALSE;
}
static gint virtual_list_iter_n_children(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), -1);
g_return_val_if_fail(iter==NULL || iter->user_data!=NULL, FALSE);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
if (iter) return 0;
return virtual_list->rows;
}
static gboolean virtual_list_iter_nth_child(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreeIter* parent, gint n)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), FALSE);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
if (parent) return FALSE;
if (n<0) return FALSE;
if ((unsigned int)n >= virtual_list->rows) return FALSE;
iter->stamp = 0;
iter->user_data = (void*)(uintptr_t)n;
return TRUE;
}
static gboolean virtual_list_iter_parent(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreeIter* child)
{
return FALSE;
}
static void virtual_list_tree_model_init (GtkTreeModelIface* iface)
{
iface->get_flags = virtual_list_get_flags;
iface->get_n_columns = virtual_list_get_n_columns;
iface->get_column_type = virtual_list_get_column_type;
iface->get_iter = virtual_list_get_iter;
iface->get_path = virtual_list_get_path;
iface->get_value = virtual_list_get_value;
iface->iter_next = virtual_list_iter_next;
iface->iter_children = virtual_list_iter_children;
iface->iter_has_child = virtual_list_iter_has_child;
iface->iter_n_children = virtual_list_iter_n_children;
iface->iter_nth_child = virtual_list_iter_nth_child;
iface->iter_parent = virtual_list_iter_parent;
}
static void init_widget()
{
if (M_VIRTUAL_TYPE != 0) return;
static const GTypeInfo virtual_list_info = {
sizeof(struct VirtualListClass),
NULL, NULL, NULL, NULL, NULL,
sizeof(struct VirtualList), 0, NULL };
static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc)virtual_list_tree_model_init, NULL, NULL };
M_VIRTUAL_TYPE = g_type_register_static(G_TYPE_OBJECT, "ArlibVirtualList", &virtual_list_info, (GTypeFlags)0);
g_type_add_interface_static(M_VIRTUAL_TYPE, GTK_TYPE_TREE_MODEL, &tree_model_info);
}
struct widget_listbox_virtual::impl {
GtkTreeView* tree;
gint borderheight;
gint checkwidth;
GtkCellRenderer* render;
struct VirtualList* vlist;
function<void(size_t row)> onfocus;
function<void(size_t row)> onactivate;
function<void(size_t row)> ontoggle;
};
void widget_listbox_virtual::construct(unsigned int numcolumns, const char * * columns)
{
init_widget();
m = new impl;
widget = gtk_scrolled_window_new(NULL, NULL);
widthprio = 3;
heightprio = 3;
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
m->tree = GTK_TREE_VIEW(gtk_tree_view_new());
gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(m->tree));
m->vlist = (VirtualList*)g_object_new(M_VIRTUAL_TYPE, NULL);
m->vlist->subject = this;
m->vlist->columns = numcolumns;
m->vlist->rows = 0;
m->vlist->checkboxes = false;
m->render = gtk_cell_renderer_text_new();
for (unsigned int i=0;i<numcolumns;i++)
{
gtk_tree_view_insert_column_with_attributes(m->tree, -1, columns[i], m->render, "text", i, NULL);
GtkTreeViewColumn* col = gtk_tree_view_get_column(m->tree, i);
gtk_tree_view_column_set_expand(col, true);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
}
gtk_widget_set_hexpand(GTK_WIDGET(this->widget), true);
gtk_widget_set_vexpand(GTK_WIDGET(this->widget), true);
gtk_tree_view_set_fixed_height_mode(m->tree, true);
gtk_widget_show_all(GTK_WIDGET(m->tree));
GtkRequisition size;
gtk_widget_get_preferred_size(GTK_WIDGET(m->tree), NULL, &size);
m->borderheight = size.height;
m->checkwidth = 0;
set_size(10, NULL, -1);
}
widget_listbox_virtual::~widget_listbox_virtual()
{
delete m;
}
//static void widget_listbox_virtual_refresh_row(widget_listbox_virtual* obj, size_t row)
//{
// GtkTreeIter iter;
// gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(obj->m->vlist), &iter, NULL, row);
// GtkTreePath* path=gtk_tree_path_new_from_indices(row, -1);
// gtk_tree_model_row_changed(GTK_TREE_MODEL(obj->m->vlist), path, &iter);
// gtk_tree_path_free(path);
//}
widget_listbox_virtual* widget_listbox_virtual::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(m->tree), enable);
return this;
}
widget_listbox_virtual* widget_listbox_virtual::set_contents(function<const char * (size_t row, int column)> get_cell,
function<size_t(const char * prefix, size_t start, bool up)> search)
{
//ignore the search function, there is no valid way for gtk+ to use it
m->vlist->get_cell=get_cell;
return this;
}
widget_listbox_virtual* widget_listbox_virtual::set_num_rows(size_t rows)
{
if (rows > MAX_ROWS) rows = MAX_ROWS;
GtkAdjustment* adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m->tree));
double scrollfrac = gtk_adjustment_get_value(adj) / m->vlist->rows;
//this is piss slow for some reason I can't figure out
gtk_tree_view_set_model(m->tree, GTK_TREE_MODEL(NULL));
m->vlist->rows = rows;
gtk_tree_view_set_model(m->tree, GTK_TREE_MODEL(m->vlist));
if (scrollfrac == scrollfrac)
{
gtk_adjustment_set_upper(adj, scrollfrac * m->vlist->rows + gtk_adjustment_get_page_size(adj));
gtk_adjustment_changed(adj);
gtk_adjustment_set_value(adj, scrollfrac * m->vlist->rows);
gtk_adjustment_value_changed(adj);//shouldn't it do this by itself?
}
return this;
}
size_t widget_listbox_virtual::get_max_rows()
{
return MAX_ROWS;
}
widget_listbox_virtual* widget_listbox_virtual::refresh()
{
gtk_widget_queue_draw(GTK_WIDGET(m->tree));
return this;
}
size_t widget_listbox_virtual::get_active_row()
{
GList* list = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m->tree), NULL);
if (!list) return (size_t)-1;
size_t ret = gtk_tree_path_get_indices((GtkTreePath*)list->data)[0];
//if (ret == MAX_ROWS) ret = (size_t)-1;
g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
return ret;
}
static void listbox_on_focus_change(GtkTreeView* tree_view, gpointer user_data)
{
widget_listbox_virtual* obj=(widget_listbox_virtual*)user_data;
GtkTreePath* path;
gtk_tree_view_get_cursor(obj->m->tree, &path, NULL);
size_t item = (size_t)-1;
if (path) item = gtk_tree_path_get_indices(path)[0];
//if (item == MAX_ROWS) item = (size_t)-1;
obj->m->onfocus(item);
if (path) gtk_tree_path_free(path);
}
widget_listbox_virtual* widget_listbox_virtual::set_on_focus_change(function<void(size_t row)> onchange)
{
m->onfocus = onchange;
g_signal_connect(m->tree, "cursor-changed", G_CALLBACK(listbox_on_focus_change), this);
return this;
}
static void listbox_onactivate(GtkTreeView* tree_view, GtkTreePath* path, GtkTreeViewColumn* column, gpointer user_data)
{
widget_listbox_virtual* obj = (widget_listbox_virtual*)user_data;
int item = gtk_tree_path_get_indices(path)[0];
if (item != MAX_ROWS)
{
obj->m->onactivate(item);
}
}
widget_listbox_virtual* widget_listbox_virtual::set_onactivate(function<void(size_t row)> onactivate)
{
m->onactivate=onactivate;
g_signal_connect(m->tree, "row-activated", G_CALLBACK(listbox_onactivate), this);
return this;
}
widget_listbox_virtual* widget_listbox_virtual::set_size(unsigned int height, const char * const * widths, int expand)
{
gint width_total = 0;
for (unsigned int i=0;i<m->vlist->columns;i++)
{
//figuring out this algorithm took about half an hour of digging in the GTK+ source code.
//it's fairly simple, but it's spread out all over the place so I can't find the parts I want.
GtkTreeViewColumn* col = gtk_tree_view_get_column(m->tree, (m->vlist->checkboxes ? 1 : 0) + i);
gint size1;
gtk_widget_get_preferred_width(gtk_tree_view_column_get_button(col), NULL, &size1);
GtkRequisition size2;
if (widths && widths[i])
{
g_object_set(m->render, "text", widths[i], NULL);
gtk_cell_renderer_get_preferred_size(m->render, GTK_WIDGET(widget), NULL, &size2);
gint horizontal_separator;
gtk_widget_style_get(GTK_WIDGET(m->tree), "horizontal-separator", &horizontal_separator, NULL);
size2.width += horizontal_separator;
}
else size2.width = 0;
//there's also a grid_line_width (gtktreeview.c:6110 or near _gtk_tree_view_column_push_padding),
//but I don't use grid lines, so I'll ignore that.
gint colwidth = (size1 > size2.width ? size1 : size2.width);
gtk_tree_view_column_set_fixed_width(col, colwidth);
width_total += colwidth;
}
gtk_widget_set_size_request(GTK_WIDGET(m->tree), width_total + m->checkwidth, -1);
if (height)
{
GtkRequisition size;
g_object_set(m->render, "text", "A", NULL);
gtk_cell_renderer_get_preferred_size(m->render, GTK_WIDGET(widget), NULL, &size);
gtk_widget_set_size_request(GTK_WIDGET(widget), -1, m->borderheight + (size.height+2) * height);
}
for (unsigned int i=0;i<m->vlist->columns;i++)
{
gtk_tree_view_column_set_expand(gtk_tree_view_get_column(m->tree, i), (expand == -1) || (i == (unsigned int)expand));
}
return this;
}
static void widget_listbox_virtual_checkbox_toggle(GtkCellRendererToggle* cell_renderer, gchar* path, gpointer user_data)
{
widget_listbox_virtual* obj = (widget_listbox_virtual*)user_data;
unsigned int row = atoi(path);
obj->m->ontoggle(row);
obj->refresh();
}
widget_listbox_virtual* widget_listbox_virtual::add_checkboxes(function<void(size_t row)> ontoggle)
{
m->vlist->checkboxes = true;
GtkCellRenderer* render = gtk_cell_renderer_toggle_new();
gtk_tree_view_insert_column_with_attributes(m->tree, 0, "", render, "active", m->vlist->columns, NULL);
//gint checkheight;
//g_object_get(render, "height", &checkheight, NULL);
//if (checkheight>m->cellheight) m->cellheight=checkheight;
m->ontoggle=ontoggle;
g_signal_connect(render, "toggled", G_CALLBACK(widget_listbox_virtual_checkbox_toggle), this);
GtkRequisition checksize;
gtk_cell_renderer_get_preferred_size(render, GTK_WIDGET(widget), NULL, &checksize);
gint horizontal_separator;
gtk_widget_style_get(GTK_WIDGET(m->tree), "horizontal-separator", &horizontal_separator, NULL);
GtkTreeViewColumn* col = gtk_tree_view_get_column(m->tree, 0);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_fixed_width(col, checksize.width + horizontal_separator);
m->checkwidth = checksize.width + horizontal_separator;
gint origwidth;
gtk_widget_get_size_request(GTK_WIDGET(m->tree), &origwidth, NULL);
printf("%i+%i+%i\n",origwidth,checksize.width, horizontal_separator);
gtk_widget_set_size_request(GTK_WIDGET(m->tree), m->checkwidth + origwidth, -1);
gtk_widget_show_all(GTK_WIDGET(widget));
return this;
}
#endif