gambas-source-code/gb.gtk/src/gcombobox.cpp
gambas da4002dede ComboBox now correctly takes language direction into account.
[GB.GTK3]
* BUG: ComboBox now correctly takes language direction into account.
2022-02-11 01:29:47 +01:00

822 lines
16 KiB
C++

/***************************************************************************
gcombobox.cpp
(c) 2004-2006 - Daniel Campos Fernández <dcamposf@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
***************************************************************************/
#include "widgets.h"
#include "gmainwindow.h"
#include "gdesktop.h"
#include "gcombobox.h"
#ifdef GTK3
struct _GtkComboBoxPrivate
{
GtkTreeModel *model;
GtkCellArea *area;
gint col_column;
gint row_column;
gint wrap_width;
gint active; /* Only temporary */
GtkTreeRowReference *active_row;
GtkWidget *tree_view;
GtkWidget *cell_view;
GtkWidget *box;
GtkWidget *button;
GtkWidget *arrow;
GtkWidget *popup_widget;
GtkWidget *popup_window;
GtkWidget *scrolled_window;
//GtkCssGadget *gadget;
void *gadget;
guint popup_idle_id;
GdkEvent *trigger_event;
guint scroll_timer;
guint resize_idle_id;
/* For "has-entry" specific behavior we track
* an automated cell renderer and text column
*/
gint text_column;
GtkCellRenderer *text_renderer;
gint id_column;
guint popup_in_progress : 1;
guint popup_shown : 1;
guint add_tearoffs : 1;
guint has_frame : 1;
guint is_cell_renderer : 1;
guint editing_canceled : 1;
guint auto_scroll : 1;
guint button_sensitivity : 2;
guint has_entry : 1;
guint popup_fixed_width : 1;
GtkTreeViewRowSeparatorFunc row_separator_func;
gpointer row_separator_data;
GDestroyNotify row_separator_destroy;
GdkDevice *grab_pointer;
gchar *tearoff_title;
};
#endif
/**************************************************************************
gComboBox
**************************************************************************/
static void cb_click(GtkComboBox *widget,gComboBox *data)
{
if (data->locked())
return;
if (!data->isReadOnly() && data->count())
{
int index = data->index();
if (index >= 0)
{
const char *text = index >= 0 ? data->itemText(index) : NULL;
if (!text) text = "";
data->lock();
gtk_entry_set_text(GTK_ENTRY(data->entry), text);
data->setIndex(index);
data->unlock();
CB_textbox_change(data);
}
}
if (data->index() >= 0)
CB_combobox_click(data);
}
/*static int pathToIndex(GtkTreePath *path)
{
gint *indices;
if (!path)
return -1;
indices = gtk_tree_path_get_indices(path);
return indices[0];
}*/
static GtkTreePath *indexToPath(int index)
{
char buffer[16];
sprintf(buffer, "%d", index);
return gtk_tree_path_new_from_string(buffer);
}
static void combo_cell_text(GtkComboBox *combo, GtkCellRenderer *cell, GtkTreeModel *md, GtkTreeIter *iter, gTree *tr)
{
gTreeRow *row = NULL;
gTreeCell *data;
const char *buf = "";
char *key;
key = tr->iterToKey(iter);
if (key)
row = (gTreeRow*)g_hash_table_lookup(tr->datakey, (gpointer)key);
if (row)
{
data = row->get(0);
if (data)
{
if (data->text())
buf = data->text();
}
}
g_object_set(G_OBJECT(cell),
"text", buf,
(void *)NULL);
}
static GtkWidget *_find_button;
static void cb_find_button(GtkWidget *widget, gpointer data)
{
if (GTK_IS_TOGGLE_BUTTON(widget))
_find_button = widget;
else if (GTK_IS_CONTAINER(widget))
gtk_container_forall(GTK_CONTAINER(widget), cb_find_button, NULL);
}
static GtkWidget *find_button(GtkWidget *combo)
{
_find_button = NULL;
gtk_container_forall(GTK_CONTAINER(combo), cb_find_button, NULL);
return _find_button;
}
char *gComboBox::indexToKey(int index)
{
char *key;
GtkTreePath *path = indexToPath(index);
key = find(path);
gtk_tree_path_free(path);
return key;
}
void gComboBox::create(bool readOnly)
{
bool first = !border;
char *save = NULL;
GB_COLOR bg, fg;
GList *cells;
//fprintf(stderr, "create: %d hasFocus = %d\n", readOnly, focus );
lock();
if (first)
{
#if GTK3
border = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); //gtk_event_box_new();
#else
border = gtk_alignment_new(0, 0, 1, 1); //gtk_event_box_new();
#endif
}
else
{
save = g_strdup(text());
bg = background();
fg = foreground();
}
if (widget)
{
if (cell) g_object_unref(cell);
cell = NULL;
gtk_widget_destroy(widget);
_button = NULL;
createWidget();
}
if (readOnly)
{
widget = gtk_combo_box_new_with_model(GTK_TREE_MODEL(tree->store));
entry = NULL;
cell = gtk_cell_renderer_text_new ();
g_object_ref_sink(cell);
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), cell, true);
//gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(widget), cell, "text", 0, (void *)NULL);
g_object_set(cell, "ypad", 0, (void *)NULL);
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(widget), cell, (GtkCellLayoutDataFunc)combo_cell_text, (gpointer)tree, NULL);
#ifdef GTK3
gtk_widget_set_hexpand(widget, TRUE);
#endif
}
else
{
#if GTK_CHECK_VERSION(2, 24, 0)
widget = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(tree->store));
#else
widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(tree->store), 0);
#endif
entry = gtk_bin_get_child(GTK_BIN(widget));
#ifdef GTK3
//gtk_widget_set_hexpand(widget, TRUE);
gtk_widget_set_hexpand(entry, TRUE);
#endif
g_signal_handler_disconnect(widget, g_signal_handler_find(widget, G_SIGNAL_MATCH_ID, g_signal_lookup("changed", G_OBJECT_TYPE(widget)), 0, 0, 0, 0));
cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(widget));
cell = (GtkCellRenderer *)cells->data;
g_list_free(cells);
g_object_ref(cell);
//gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(widget), cell, "text", 0, (void *)NULL);
//g_object_set(cell, "ypad", 0, (void *)NULL);
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(widget), cell, (GtkCellLayoutDataFunc)combo_cell_text, (gpointer)tree, NULL);
}
#ifdef GTK3
gtk_combo_box_set_popup_fixed_width(GTK_COMBO_BOX(widget), TRUE);
#endif
if (first)
{
realize();
}
else
{
gtk_container_add(GTK_CONTAINER(border), widget);
gtk_widget_show(widget);
widgetSignals();
#ifdef GTK3
gt_patch_control(border);
gt_patch_control(widget);
#endif
}
#ifdef GTK3
if (entry)
gt_patch_control(entry);
#endif
g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(cb_click), (gpointer)this);
if (entry)
{
initEntry();
setColorBase();
//g_signal_connect(G_OBJECT(entry), "key-press-event", G_CALLBACK(gcb_keypress), (gpointer)this);
//g_signal_connect(G_OBJECT(entry), "key-release-event", G_CALLBACK(gcb_keyrelease), (gpointer)this);
g_signal_connect(G_OBJECT(entry), "focus-in-event", G_CALLBACK(gcb_focus_in), (gpointer)this);
g_signal_connect(G_OBJECT(entry), "focus-out-event", G_CALLBACK(gcb_focus_out), (gpointer)this);
}
else
{
_has_input_method = FALSE;
setColorButton();
}
updateFocusHandler();
//gtk_widget_reset_style(border);
if (!first)
{
setBackground(bg);
setForeground(fg);
updateFont();
setText(save);
g_free(save);
updateDesign();
updateDirection();
}
unlock();
}
gComboBox::gComboBox(gContainer *parent) : gTextBox(parent, true)
{
_no_background = TRUE;
_last_key = 0;
_model_dirty = false;
_model_dirty_timeout = 0;
_sort = false;
_has_border = true;
border = widget = NULL;
entry = NULL;
_button = NULL;
cell = NULL;
_use_wheel = true;
tree = new gTree();
tree->addColumn();
//tree->addColumn();
//tree->setHeaders(false);
create(false);
}
gComboBox::~gComboBox()
{
if (_model_dirty_timeout)
g_source_remove(_model_dirty_timeout);
gtk_combo_box_popdown(GTK_COMBO_BOX(widget));
if (cell) g_object_unref(cell);
delete tree;
}
void gComboBox::popup()
{
gtk_combo_box_popup(GTK_COMBO_BOX(widget));
}
void gComboBox::popdown()
{
gtk_combo_box_popdown(GTK_COMBO_BOX(widget));
}
#ifdef GTK3
GtkWidget *gComboBox::getStyleSheetWidget()
{
if (entry)
return entry;
return GTK_COMBO_BOX(widget)->priv->button;
}
const char *gComboBox::getStyleSheetColorNode()
{
return "";
}
const char *gComboBox::getStyleSheetFontNode()
{
return "";
}
void gComboBox::customStyleSheet(GString *css)
{
if (!_has_border)
{
setStyleSheetNode(css, "");
g_string_append_printf(css, "border:none;box-shadow:none;padding-top:0;padding-bottom:0;\n");
/*if (background() == COLOR_DEFAULT)
g_string_append_printf(css, "background:none;");*/
if (entry)
{
setStyleSheetNode(css, " + button");
g_string_append_printf(css, "border:none;box-shadow:none;");
/*if (background() == COLOR_DEFAULT)
g_string_append_printf(css, "background:none;");*/
}
}
}
void gComboBox::setForeground(gColor color)
{
GdkRGBA rgba;
gControl::setForeground(color);
gt_from_color(realForeground(true), &rgba);
g_object_set(G_OBJECT(cell), "foreground-rgba", &rgba, NULL);
}
#else
void gComboBox::setRealBackground(gColor color)
{
gControl::setRealBackground(color);
if (entry)
set_gdk_base_color(entry, color);
}
void gComboBox::setRealForeground(gColor color)
{
gControl::setRealForeground(color);
if (entry)
set_gdk_text_color(entry, color);
#ifdef GTK3
GdkRGBA rgba;
gt_from_color(color, &rgba);
g_object_set(G_OBJECT(cell), "foreground-rgba", &rgba, NULL);
#else
GdkColor col;
fill_gdk_color(&col, color);
g_object_set(G_OBJECT(cell), "foreground-gdk", &col, NULL);
#endif
}
#endif
int gComboBox::count() const
{
return tree->rowCount();
}
int gComboBox::index()
{
updateModel();
return gtk_combo_box_get_active (GTK_COMBO_BOX(widget));
}
char* gComboBox::itemText(int ind)
{
gTreeRow *row;
gTreeCell *cell;
if (ind < 0)
return NULL;
updateModel();
char *key = indexToKey(ind);
if (!key) return NULL;
row = tree->getRow(key);
if (!row) return NULL;
cell = row->get(0);
if (!cell) return NULL;
return cell->text();
}
int gComboBox::length()
{
gchar *buf;
if (!entry)
{
buf = itemText(index());
if (!buf)
return 0;
else
return g_utf8_strlen(buf, -1);
}
else
return gTextBox::length();
}
bool gComboBox::isSorted() const
{
return tree->isSorted();
}
char* gComboBox::text()
{
if (entry)
return gTextBox::text();
else
return itemText(index());
}
void gComboBox::setIndex(int vl)
{
//fprintf(stderr, "setIndex: %d\n", vl);
if (vl < 0)
vl = -1;
else if (vl >= count())
return;
if (vl == index() && vl >= 0)
{
CB_combobox_click(this);
return;
}
updateModel();
gtk_combo_box_set_active(GTK_COMBO_BOX(widget), vl);
if (entry)
gTextBox::setText(itemText(vl));
}
void gComboBox::checkIndex()
{
if (index() < 0)
{
lock();
setIndex(0);
unlock();
}
}
void gComboBox::setItemText(int ind, const char *text)
{
gTreeRow *row;
gTreeCell *cell;
char *key = indexToKey(ind);
if (!key) return;
row = tree->getRow(key);
if (!row) return;
cell = row->get(0);
if (!cell) return;
cell->setText(text);
updateSort();
}
void gComboBox::setReadOnly(bool vl)
{
if (isReadOnly() == vl)
return;
create(!isReadOnly());
}
void gComboBox::setSorted(bool vl)
{
tree->setSorted(vl);
}
void gComboBox::setText(const char *vl)
{
int index = find(vl);
if (!entry || index >= 0)
setIndex(index);
else if (entry)
gTextBox::setText(vl);
}
static gboolean combo_set_model_and_sort(gComboBox *combo)
{
//fprintf(stderr, "combo_set_model_and_sort\n");
gtk_combo_box_set_model(GTK_COMBO_BOX(combo->widget), GTK_TREE_MODEL(combo->tree->store));
if (combo->isSorted())
combo->tree->sort();
combo->_model_dirty = false;
combo->_model_dirty_timeout = 0;
if (combo->isReadOnly())
combo->checkIndex();
return FALSE;
}
void gComboBox::updateModel()
{
if (_model_dirty)
{
g_source_remove(_model_dirty_timeout);
combo_set_model_and_sort(this);
}
}
void gComboBox::updateSort()
{
if (_model_dirty)
return;
_model_dirty = true;
_model_dirty_timeout = g_timeout_add(0, (GSourceFunc)combo_set_model_and_sort, this);
gtk_combo_box_set_model(GTK_COMBO_BOX(widget), NULL);
}
void gComboBox::add(const char *text, int pos)
{
//GtkTreeModel *model;
gTreeRow *row;
gTreeCell *cell;
char key[16];
char *after;
_last_key++;
sprintf(key, "%d", _last_key);
if (pos < 0 || pos > count())
after = NULL;
else
after = indexToKey(pos);
//g_signal_lookup("rowGTK_TYPE_COMBO_BOX);
row = tree->addRow(key, after, true);
if (row)
{
cell = row->get(0);
if (cell)
{
cell->setText(text);
updateSort();
}
}
//gtk_combo_box_set_model(GTK_COMBO_BOX(widget), model);
}
void gComboBox::clear()
{
lock();
tree->clear();
unlock();
}
int gComboBox::find(const char *text)
{
int i;
const char *it;
if (!text)
text = "";
for (i = 0; i < count(); i++)
{
it = itemText(i);
if (!it)
it = "";
if (!strcmp(it, text))
return i;
}
return -1;
}
void gComboBox::remove(int pos)
{
updateModel();
tree->removeRow(indexToKey(pos));
updateSort();
}
/*void gComboBox::resize(int w, int h)
{
gControl::resize(w,h);
}*/
void gComboBox::updateFont()
{
gControl::updateFont();
if (cell)
g_object_set(G_OBJECT(cell), "font-desc", font()->desc(), (void *)NULL);
#ifndef GTK3
else
gtk_widget_modify_font(entry, font()->desc());
#endif
}
void gComboBox::setFocus()
{
if (entry && !proxy() && window()->isVisible())
{
gtk_editable_set_editable(GTK_EDITABLE(entry), false);
gtk_widget_grab_focus(entry);
gtk_editable_set_editable(GTK_EDITABLE(entry), true);
}
else
gControl::setFocus();
}
int gComboBox::minimumHeight()
{
int h;
#ifdef GTK3
gtk_widget_get_preferred_height(widget, &h, NULL);
#else
GtkRequisition req;
gtk_widget_size_request(widget, &req);
h = req.height;
#endif
if (entry)
return h - 4;
else
return h;
}
bool gComboBox::isReadOnly() const
{
return entry == NULL;
}
static gboolean button_focus_in(GtkWidget *widget, GdkEventFocus *event, gComboBox *control)
{
if (control->isReadOnly())
return gcb_focus_in(widget, event, control);
control->setFocus();
return false;
}
static gboolean button_focus_out(GtkWidget *widget, GdkEventFocus *event, gComboBox *control)
{
if (control->isReadOnly())
return gcb_focus_out(widget, event, control);
return false;
}
void gComboBox::updateFocusHandler()
{
GtkWidget *button = find_button(widget);
if (button == _button)
return;
_button = button;
//g_signal_connect(G_OBJECT(button), "key-press-event", G_CALLBACK(gcb_keypress), (gpointer)this);
//g_signal_connect(G_OBJECT(button), "key-release-event", G_CALLBACK(gcb_keyrelease), (gpointer)this);
g_signal_connect(G_OBJECT(button), "focus-in-event", G_CALLBACK(button_focus_in), (gpointer)this);
g_signal_connect(G_OBJECT(button), "focus-out-event", G_CALLBACK(button_focus_out), (gpointer)this);
}
void gComboBox::setBorder(bool v)
{
if (_has_border == v)
return;
_has_border = v;
updateBorder();
}
void gComboBox::updateBorder()
{
#ifdef GTK3
updateStyleSheet(true);
#else
g_object_set(G_OBJECT(widget), "has-frame", _has_border, NULL);
#endif
}
bool gComboBox::canFocus() const
{
return !_design;
}
void gComboBox::setDesign(bool ignore)
{
gControl::setDesign(ignore);
if (entry)
gtk_widget_set_can_focus(entry, false);
}
static void cb_update_direction(GtkWidget *widget, gpointer data)
{
gtk_widget_set_direction(widget, (GtkTextDirection)(uintptr_t)data);
if (GTK_IS_CONTAINER(widget))
gtk_container_forall(GTK_CONTAINER(widget), cb_update_direction, data);
}
void gComboBox::updateDirection()
{
GtkTextDirection dir;
gControl::updateDirection();
dir = gtk_widget_get_direction(widget);
cb_update_direction(widget, (gpointer)(uintptr_t)dir);
}