Make grid of buttons fixed width & height, and wrap text - c

We have a GTK3 user interface (coded in C) in an embedded system that must stay fixed width/height in order to fit the screen.
Basically it's a fixed grid of buttons on a touchscreen, where the buttons can contain a text label or a graphical image - and the grid MUST stay fixed, all buttons the same width & height, and fitting the screen 100%.
However, on updating button text/labels with longer strings the button expands to fit which is not what we want at all.
I can't post all the code but I believe this is the relevant button creation code that would affect what we're trying to do;
// Number buttons 1..n
buttonId = n;
bid = buttonId - 1; // Because zero-indexing, natch
dbp = &_context->buttons[bid]; // Data about button
// Create new button, attach to global buttons
dbp->btn = gtk_button_new();
// Button name != Button CSS#ID
snprintf(value, SHORT_STR, "BTN_%02d", buttonId );
gtk_widget_set_name(dbp->btn, value);
gtk_button_set_relief(GTK_BUTTON(dbp->btn), GTK_RELIEF_NORMAL/*GTK_RELIEF_NONE*/);
// Set text label
strncpy(dbp->text, value, MAX_BUTTON_TEXT);
gtk_button_set_label (GTK_BUTTON(dbp->btn), dbp->text);
// Attempt to make labels wrap - does nothing
label = gtk_bin_get_child(GTK_BIN(dbp->btn));
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
//gtk_label_set_max_width_chars(GTK_LABEL(label), 0);
// Create new blank (&transprent) image
dbp->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, _context->innerButton.width, _conte$
gdk_pixbuf_fill(dbp->pixbuf, 0x00000000);
// Image holds pixbuf
dbp->image = (GtkImage*)gtk_image_new_from_pixbuf(dbp->pixbuf);
dbp->image_modified = 0;
// Attach image to button
gtk_button_set_image(GTK_BUTTON(dbp->btn), GTK_WIDGET(dbp->image));
// Adds an individual CSS provider per button to allow style changes
GtkStyleContext *context = gtk_widget_get_style_context(dbp->btn);
dbp->bp = gtk_css_provider_new();
// Set default colour from table of default colours
dbp->bg = get_default_colour(bid);
dbp->fg = get_label_colour(dbp->bg);
BuildButtonCSS("btnid_", buttonId, dbp->bg, dbp->fg, tstr, NULL);
// Convert CSS to provider
gtk_css_provider_load_from_data(dbp->bp, tstr, -1, NULL);
// Add provider to button
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER(dbp->bp), GTK_STYLE_PROVIDER_PRIO$
// Give the button the CSS style/class corresponding to the one we just created for it
snprintf(value, SHORT_STR, "btnid_%02d", buttonId);
gtk_style_context_add_class(context, value);
g_object_unref(dbp->bp);
// Discourage expansion (again, doesn't appear to work when label gets too long)
gtk_widget_set_hexpand(dbp->btn, FALSE);
gtk_widget_set_vexpand(dbp->btn, FALSE);
// Attach button to grid
gtk_grid_attach(GTK_GRID(_grid), dbp->btn, c, r, 1, 1);
g_signal_connect(dbp->btn, "button_press_event", G_CALLBACK(button_press_callback), GINT_TO_P$
g_signal_connect(dbp->btn, "button_release_event", G_CALLBACK(button_release_callback), GINT_$
dbp->initialised = 1;
However when we update text within a button, it renders as a single (non-wrapped) line of text and causes the button to expand and totally bu&&er up the layout.
There's two problems here;
The text should wrap, the buttons have plenty of height but the text remains a single line
The buttons should be absoltuely FIXED width & height no matter what
This feels like it should be simple to do, but I'm not very familiar with GTK and all the examples out there seem to assume that you'd want everything to flex and resize itself all the time.
Edit: Tried Alexander Dmitriev's suggestion to use ellipsize - didn't seem to work although threw no errors;
// Experiment; ellipsize
label = gtk_bin_get_child(GTK_BIN(dbp->btn));
gtk_label_set_ellipsize(GTK_LABEL(label),PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars(GTK_LABEL(label), 14);
I also tried this exmaple by creating a GtkWidget label, applying the properties to it and then adding it to the button - but it seems gtk_button_set_label will only accept a string not a label.

Related

GTK+3 replacing/swapping pixbuf seems to break

I have a simple GTK3 grid layout using images as buttons and I want to replace the images when various things happen.
What's weird is that this works fine if I replace/remove the image from the same piece of code each time but not when I alternate between the two different sources despite (as far as I can see) everything being almost identical.
Anyway, here's my code...
Initial setup - create pixbuf, make it an image, attach it to the button - this works fine:
/*
* Initial setup
*
* dbp is a pointer to a struct that contains the button, the pixbuf, etc.
*/
// Create new blank (&transprent) image
dbp->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, innerButton.width, innerButton.height);
gdk_pixbuf_fill(dbp->pixbuf, 0x00000000);
// Image holds pixbuf
dbp->image = (GtkImage*)gtk_image_new_from_pixbuf(dbp->pixbuf);
// Attach image to button
gtk_button_set_image(GTK_BUTTON(dbp->btn), GTK_WIDGET(dbp->image));
// Attach button to grid
gtk_grid_attach(GTK_GRID(_grid), dbp->btn, c, r, 1, 1);
Clear image code - blanks the image (transparent):
gdk_pixbuf_fill(dbp->pixbuf, 0x00000000);
Button refresh callback (updates image from pixbuf):
if(dbp->image_modified != 0)
{
gtk_image_set_from_pixbuf(GTK_IMAGE(dbp->image), dbp->pixbuf);
dbp->image_modified = 0;
}
1st image replacement code - takes data input in our own format & creates new pixbuf:
GdkPixbuf *newpixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, BITMAP_WIDTH, BITMAP_HEIGHT);
g_object_unref(dbp->pixbuf);
// <Data unpacked to pixels here, code removed for clarity>
dbp->pixbuf = gdk_pixbuf_copy(newpixbuf);
gdk_pixbuf_copy_options(newpixbuf , dbp->pixbuf);
dbp->image_modified = 1; // Trigger refresh
2nd image replacement - loads image from file and creates new pixbuf:
GdkPixbuf *newpixbuf = gdk_pixbuf_new_from_file_at_scale (file_path, BITMAP_WIDTH, BITMAP_HEIGHT, FALSE, &err);
// [Error check code removed for clarity]
g_object_unref(dbp->pixbuf);
dbp->pixbuf = gdk_pixbuf_copy(newpixbuf );
gdk_pixbuf_copy_options(newpixbuf , dbp->pixbuf);
dbp->image_modified = 1; // Trigger refresh
Now, if I do repeated calls to these routines I get odd interactions;
If I do clear, image_replace_1, clear, image_replace_1, clear, image_replace_1 etc... it works absolutely perfectly.
If I do clear, image_replace_2, clear, image_replace_2, clear, image_replace_2 etc... it works absolutely perfectly.
However, when I do:
If I do clear, image_replace_1, clear, image_replace_2, clear, image_replace_1... it falls over with complaints that GDK_IS_PIXBUF(pixbuf) failed and I can't for the life of me work out how that happens?
I have also tried this alternative code for the button refresh callback;
dbp->image = (GtkImage*)gtk_image_new_from_pixbuf(dbp->pixbuf);
gtk_button_set_image(GTK_BUTTON(dbp->btn), GTK_WIDGET(dbp->image));
But the result is the same.

How to Scroll a ScrolledWindow with arrow keys in Gtk 3.24.5?

I have a gtk entry right below the scrolled window which has the default focus , left and right keys move the cursor in the entry ,I am able to catch the key press events for up and down arrow keys but don't know how to scroll the scrolled window, referred many websites none of them were clear or explained only in parts.
Below are some of the pages I went through:
https://mail.gnome.org/archives/gtk-devel-list/2002-February/msg00104.html
https://developer.gnome.org/gtkmm-tutorial/stable/sec-keyboardevents-overview.html.en
tried using gtk_scrolled_window_set_vadjustment() couldn't get it working.
The official page says GTK_SCROLL_STEP_UP is deprecated but doesn't say what should be used instead.
Every answer would be very appreciated.Thanks
bool Method::cb_MPWindow(GtkWidget *wgt, GdkEventKey *event, MethodSelect *ms)
{
if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down)
{
g_signal_emit_by_name(ms->ScrolledWindow, "scroll-child",(event->keyval == GDK_KEY_Up)?GTK_SCROLL_STEP_UP:GTK_SCROLL_STEP_DOWN);
//The above line works in gtk 3.14.5 but crashes the app in 3.24.5
return TRUE;
}
return FALSE;
}
In order to scroll the window with the keyboard, you need to:
Get the scrolled window's vertical or horizontal adjustment with gtk_scrolled_window_get_vadjustment() or gtk_scrolled_window_get_hadjustment().
From the adjustment object, get the following properties: value (the current scroll position), step-increment (how much to scroll by line), and page-increment (how much to scroll by page).
Then, according to the key that was pressed, you add or subtract the increment to value and then set the new value with gtk_adjustment_set_value().
The the window will scroll when change when you set the value. Typically the line increment is used when navigating with the arrow keys, while the page increment when using the Page Up/Down keys. You add them when scrolling down, and subtract while scrolling down. It is worth noting that the increments change dynamically based on the window size, so you do not need to set them manually.
Here is my code (in C). First setting up the callback:
// Create a scrolled window
GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
// Get the vertical adjustment object
GtkAdjustment *page_vertical_adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolled_window));
// Connect to the key press event
g_signal_connect(
GTK_SCROLLED_WINDOW(scrolled_window),
"key-press-event",
G_CALLBACK(keyboard_scrolling),
page_vertical_adjustment
);
And then the callback function:
void keyboard_scrolling(GtkScrolledWindow *widget, GdkEventKey event, GtkAdjustment *adjustment)
{
// Get the vertical position of the page
gdouble position = gtk_adjustment_get_value(adjustment);
// Get the scrolling increments
gdouble step = gtk_adjustment_get_step_increment(adjustment); // Amount to be scrolled by the arrows (roughly a line)
gdouble page = gtk_adjustment_get_page_increment(adjustment); // Amount to be scrolled by the Page keys (roughly the visible area)
// printf("step: %f, page: %f, key: %d\n", step, page, event.keyval);
// Check which key was pressed
switch (event.keyval)
{
case GDK_KEY_Page_Down:
gtk_adjustment_set_value(adjustment, position + page);
break;
case GDK_KEY_Page_Up:
gtk_adjustment_set_value(adjustment, position - page);
break;
case GDK_KEY_Down:
gtk_adjustment_set_value(adjustment, position + step);
break;
case GDK_KEY_Up:
gtk_adjustment_set_value(adjustment, position - step);
break;
default:
break;
}
}
For convenience, here is a list of keyboard macros that GTK accepts: https://github.com/GNOME/gtk/blob/main/gdk/gdkkeysyms.h

BoxLayout(X_AXIS_SCROLLING) layout manager for Codename One

Note for the readers: this question is specific for Codename One only.
My use case is to have one or more horizontally scrollable bars with Buttons.
I need a layout manager that:
puts all the Components on the X axis;
gives each Component its preferred size;
if there is enough horizontal space, it acts exactly as BoxLayout(X_AXIS_NO_GROW);
if there isn't enough horizontal space, it allows a horizontal scrolling to see all the Components;
in the last case, it adds a small arrow on the left and on the right to indicate that a scrolling is possible.
For example, the following code produces the following screenshot:
Form hi = new Form("BoxLayout X scrolling", BoxLayout.y());
Container exampleCnt = new Container();
exampleCnt.setLayout(new BoxLayout(BoxLayout.X_AXIS_NO_GROW));
for (int i=0; i<=10; i++) {
int j = i; // for lambda expression
Button button = new Button("Button " + i);
button.addActionListener(l -> {Log.p("Button " + j + " tapped");});
exampleCnt.add(button);
}
hi.add(exampleCnt);
hi.show();
Instead I need something like in the following screenshots. I suppose to horizontally scroll the buttons bar (the exampleCnt) with the finger. The app should be enough smart to don't confuse the tapping on a Button with the horizontal swiping to scroll the Buttons:
There are several approaches to doing this and they each have their advantages/disadvantages. Let's start by asking this:
Should the arrow buttons be clickable?
Should they draw as an overlay (you would see the button under them as they come into view) or should they act as separate components?
Based on your responses you can pick one of the following.
Builtin Scrolling Support
This would generate something that would render as an overlay and won't be clickable as scrolling assumes a scrollbar style UI but should work for this case too.
You might need to invoke setScrollVisible(true) on the Container.
Then override in the Container:
protected void paintScrollbarX(Graphics g) {
float scrollW = getScrollDimension().getWidth();
float block = ((float) getWidth()) / scrollW;
float offset;
if(getScrollX() + getWidth() == scrollW) {
// normalize the offset to avoid rounding errors to the bottom of the screen
offset = 1 - block;
} else {
offset = (((float) getScrollX() + getWidth()) / scrollW) - block;
}
// here paint the arrows to the left/right based on the values
}
Use a Wrapper
With a wrapper you can just use buttons to represent the arrows. You can place them either in a layered layout to create an overlay effect or in the sides to create separation e.g. this would result in separate buttons:
BorderLayout wrap = BorderLayout.centerEastWest(scrollableCnt, right, left);
This would result in overlay:
BorderLayout border = BorderLayout.centerEastWest(null, right, left);
LayeredLayout wrap = LayeredLayout(scrollableCnt, border);
To make this work we'd want to hide/show the components based on scroll. isScrollableX() will return false for a non-scrollable component so:
if(scrollableCnt.isScrollableX()) {
left.setVisible(false);
scrollableCnt.addScrollListener((scrollX, scrollY, oldscrollX, oldscrollY) -> {
left.setVisible(scrollX > 0);
// not 100% sure about this line but it should be something close to this
right.setVisible(scrollX < getScrollDimension().getWidth() - scrollableCnt.getWidth());
});
} else {
right.setVisible(false);
left.setVisible(false);
}

Theme change for Button + Codenameone

is there any thing got changed for images. Circular button shows different with projections. Button use to display proper circle before. Please see the screenshots for reference. Please advise.
Button b;
Button finished = new Button("");
FontImage.setMaterialIcon(finished, FontImage.MATERIAL_CHECK, 8.0f);
Test.makeBorderRound(finished);
Style bg = finished.getDisabledStyle();
finished.setDisabledStyle(bg);
b = finished;
b.setDisabledStyle(bg);
public static void makeBorderRound(Component cmp) {
makeBorderRound(cmp.getUnselectedStyle());
makeBorderRound(cmp.getSelectedStyle());
makeBorderRound(cmp.getPressedStyle());
makeBorderRound(cmp.getDisabledStyle());
}
public static void makeBorderRound(Style stl) {
stl.setBorder(RoundBorder.create().
rectangle(true).
color(stl.getBgColor()).
opacity(stl.getBgTransparency() & 0xff));
stl.setPaddingUnit(Style.UNIT_TYPE_DIPS);
stl.setPaddingLeft(1);
stl.setPaddingRight(1);
}
That's the icon peaking out from the edges of the round border. We fixed a bug where the background wasn't painted for some cases.
A simple workaround would be:
Button finished = new Button("");
finished.getAllStyles().setBgTransparency(0);
FontImage.setMaterialIcon(finished, FontImage.MATERIAL_CHECK, 8.0f);
finished.getAllStyles().setBgTransparency(255);
Test.makeBorderRound(finished);
By setting the bg transparency to 0 the icon will have transparency, this will then get overwritten by the second line after the icons are created.

How to add a hover effect to a GtkTreeView cell

I have a GtkTreeView with two columns of type text (e.g. G_TYPE_STRING) and I am using GtkCellRendererText to render the column.
Is there any why that I can react when the mouse enters and leaves a certain cell and then hover or highlight the cell.
For example I would like to underline the text in the cell renderer, when the mouse enters it, in order to give a visual clue that the cell can be clicked to perform an action.
There are quite a few ways to accomplish the task. A few examples follow, separated by horizontal lines.
The simplest, and in some ways the best, option is to simply change the mouse cursor to a finger (same as the mouse cursor you see in a browser when pointing to a link) within the GtkTreeView.
Let's say GtkTreeView *view is the view we're interested in. After you have called gtk_widget_show_all(window);, you can call
GdkWindow *win = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(view));
GdkDisplay *disp = gdk_window_get_display(win);
gdk_window_set_cursor(win, gdk_cursor_new_from_name(disp, "pointer"));
With this, the mouse pointer will be a finger for all rows in the view, excluding the header row.
The set of cursor names you can use (instead of "pointer" above) is listed here in the GDK3 documentation.
The downside is that if you have empty rows in the tree view, or separator rows, the mouse pointer will still look like a finger for those.
You can override the hover color by applying an application-specific CSS hover color, say
GtkTreeView:hover {
color: #ff0000;
}
with e.g.
GtkCssProvider *css_provider;
css_provider = gtk_css_provider_new();
if (gtk_css_provider_load_from_data(css_provider,
"GtkTreeView:hover {\n"
" color: #ff0000;\n"
"}\n"
"", -1, NULL))
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
GTK_STYLE_PROVIDER(css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref(css_provider);
after creating your main window. (Of course, you can include all application-specific CSS overrides in the CSS snippet.)
However, on gtk+ 3.18.9 at least, the renderer does not seem to support text-decoration or font CSS attributes here. (The GtkCssProvider does not seem to support cursor attribute at all, in case you're wondering.)
Also, since this essentially overrides the user theme, and there are a number of possible themes a Gtk+ user might be using, changing the color this way is unlikely to look good in all themes.
If your GtkTreeView *view; consists of clickable rows, and you don't need the selection for anything (that is, you don't have a separate button or something that causes an action to be applied to all the selected rows in the GtkTreeView), then you can simply set the selection to follow the mouse cursor:
gtk_tree_view_set_hover_selection(view, TRUE);
this causes the row you hover over to be selected, and therefore also highlighted. You probably also want to limit the selection to one row at a time:
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view),
GTK_SELECTION_SINGLE);
As Herbalist already mentioned, you can create a custom GtkCellRendererText derivative, which changes the PangoUnderline underline property depending on whether the text cell being rendered is also prelit (hovered over) or not:
#include <gtk/gtk.h>
#define CUSTOM_TYPE_CELL_RENDERER_TEXT (custom_cell_renderer_text_get_type())
#define CUSTOM_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_TEXT, CustomCellRendererText))
#define CUSTOM_CELL_RENDERER_TEXT_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), CUSTOM_TYPE_CELL_RENDERER_TEXT, CustomCellRendererTextClass))
#define CUSTOM_IS_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), CUSTOM_TYPE_CELL_RENDERER_TEXT))
#define CUSTOM_IS_CELL_RENDERER_TEXT_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), CUSTOM_TYPE_CELL_RENDERER_TEXT))
#define CUSTOM_CELL_RENDERER_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), CUSTOM_TYPE_CELL_RENDERER_TEXT, CustomCellRendererTextClass))
GType custom_cell_renderer_text_get_type(void) G_GNUC_CONST;
typedef struct {
GtkCellRendererText renderer;
} CustomCellRendererText;
typedef struct {
GtkCellRendererTextClass parent_class;
} CustomCellRendererTextClass;
G_DEFINE_TYPE(CustomCellRendererText, custom_cell_renderer_text, GTK_TYPE_CELL_RENDERER_TEXT);
void custom_cell_renderer_text_render(GtkCellRenderer *cell,
cairo_t *cr,
GtkWidget *widget,
const GdkRectangle *backarea,
const GdkRectangle *cellarea,
GtkCellRendererState state)
{
if (state & GTK_CELL_RENDERER_PRELIT) {
const PangoUnderline prelit = PANGO_UNDERLINE_SINGLE;
PangoUnderline curr;
g_object_get(cell, "underline", &curr, NULL);
g_object_set(cell, "underline", prelit, NULL);
((GtkCellRendererClass *)custom_cell_renderer_text_parent_class)->render(cell, cr, widget, backarea, cellarea, state);
g_object_set(cell, "underline", curr, NULL);
} else
((GtkCellRendererClass *)custom_cell_renderer_text_parent_class)->render(cell, cr, widget, backarea, cellarea, state);
}
void custom_cell_renderer_text_class_init(CustomCellRendererTextClass *cls)
{
((GtkCellRendererClass *)cls)->render = custom_cell_renderer_text_render;
return;
}
void custom_cell_renderer_text_init(CustomCellRendererText *renderer)
{
return;
}
GtkCellRenderer *custom_cell_renderer_text_new(void)
{
return g_object_new(CUSTOM_TYPE_CELL_RENDERER_TEXT, NULL);
}
If you then replace your gtk_cell_render_text_new() with custom_cell_render_text_new() (and change GtkCellRenderText * to CustomCellRenderText *) in your GtkTreeView code, the row you hover on in the GtkTreeView will become underlined.
Note that all cells in the row you hover over will be highlighted, not just the cell/column you hover over. This is because in a GtkTreeView, the row (or index) is the unit being manipulated (rather than a specific cell on a specific column).
If you use the above renderer only for some columns in your GtkTreeView, the text in those columns will get underlined even when the mouse hovers over some other column on that row.
Also note that if you don't want all rows to indicate clickability, you can add a third column in your data model -- name "sensitive", type G_TYPE_BOOLEAN for gboolean type; value TRUE if the row is clickable, FALSE if the row is non-clickable -- and change the if clause in custom_cell_renderer_text_render() to
const GtkStateFlags flags = gtk_cell_renderer_get_state(cell, widget, state);
if ((state & GTK_CELL_RENDERER_PRELIT) &&
!(flags & GTK_STATE_FLAG_INSENSITIVE)) {
Gtk+ will render rows with TRUE normally, and rows with FALSE as separators or disabled text, because the column names in the model are automatically associated with the GtkCellRenderer properties of the same name.
You could make a custom CellRendererText and set the underline property if the PRELIT flag is present.
i only know in Python:
class My_CellRendererText( Gtk.CellRendererText ):
def __init__( self ):
super().__init__()
def do_render( self, cr, widget, background_area, cell_area, flags ):
self.props.underline = ( ( flags & Gtk.CellRendererState.PRELIT ) != 0 )
return Gtk.CellRendererText.do_render( self, cr, widget, background_area, cell_area, flags )
GObject.type_register( My_CellRendererText )

Resources