GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-cell-renderer-label.c
1/********************************************************************
2 * gnc-cell-renderer-label.c -- A GtkCellRendererText subclass that
3 * shows a selectable (but not editable) GtkEntry when activated,
4 * allowing the user to select and copy cell text via Ctrl+C.
5 * The entry has no frame and zero minimum width so it fits exactly
6 * within the cell area without overflowing into adjacent columns.
7 *
8 * Copyright (C) 2026 GnuCash contributors
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of
13 * the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23 * MA 02110-1301, USA.
24 *******************************************************************/
25
26#include <config.h>
27
28#include <gtk/gtk.h>
29#include <gdk/gdkkeysyms.h>
30
31#include "gnc-cell-renderer-label.h"
32#include "gnc-ui-util.h"
33
34/* ================================================================
35 * Selectable entry helpers
36 *
37 * GtkEntry already implements GtkCellEditable. We use a plain
38 * non-editable GtkEntry (no wrapper widget) so its size exactly
39 * matches the cell area allocated by the tree view. Callbacks
40 * always set editing-canceled so nothing is written back to the
41 * model.
42 * ================================================================ */
43
44static void
45gsl_dismiss (GtkEntry *entry)
46{
47 g_object_set (entry, "editing-canceled", TRUE, NULL);
48 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (entry));
49 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (entry));
50}
51
52static gboolean
53gsl_key_press_cb (GtkWidget *widget,
54 GdkEventKey *event,
55 gpointer user_data)
56{
57 if (event->keyval == GDK_KEY_Escape)
58 {
59 gsl_dismiss (GTK_ENTRY (widget));
60 return TRUE;
61 }
62 /* GtkEntry handles Ctrl+C natively (copies to GDK_SELECTION_CLIPBOARD). */
63 return FALSE;
64}
65
66static gboolean
67gsl_focus_in_cb (GtkWidget *widget,
68 GdkEventFocus *event,
69 gpointer user_data)
70{
71 gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
72 return FALSE;
73}
74
75static gboolean
76gsl_focus_out_cb (GtkWidget *widget,
77 GdkEventFocus *event,
78 gpointer user_data)
79{
80 gsl_dismiss (GTK_ENTRY (widget));
81 return FALSE;
82}
83
84static GtkWidget *
85gnc_selectable_entry_new (const gchar *text, gfloat xalign)
86{
87 GtkEntry *entry = GTK_ENTRY (gtk_entry_new ());
88
89 /* Non-editable so the user can select but not modify. */
90 gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
91 /* No frame: matches the cell visual and avoids extra padding. */
92 gtk_entry_set_has_frame (entry, FALSE);
93 /* Allow the entry to shrink to the cell width. */
94 gtk_entry_set_width_chars (entry, 0);
95 gtk_entry_set_alignment (entry, xalign);
96
97 gchar *clean = gnc_filter_text_for_bidi_marks (text ? text : "");
98 gtk_entry_set_text (entry, clean ? clean : "");
99 g_free (clean);
100
101 g_signal_connect (entry, "focus-in-event",
102 G_CALLBACK (gsl_focus_in_cb), NULL);
103 g_signal_connect (entry, "key-press-event",
104 G_CALLBACK (gsl_key_press_cb), NULL);
105 g_signal_connect (entry, "focus-out-event",
106 G_CALLBACK (gsl_focus_out_cb), NULL);
107
108 gtk_widget_show (GTK_WIDGET (entry));
109 return GTK_WIDGET (entry);
110}
111
112/* ================================================================
113 * GncCellRendererLabel
114 *
115 * A GtkCellRendererText subclass whose start_editing returns a
116 * non-editable GtkEntry (no frame, zero minimum width) so clicking
117 * a cell shows the value in a selectable widget that fits exactly
118 * within the cell area.
119 * ================================================================ */
120
122{
123 GtkCellRendererText parent;
124};
125
126G_DEFINE_TYPE (GncCellRendererLabel, gnc_cell_renderer_label, GTK_TYPE_CELL_RENDERER_TEXT)
127
128static GtkCellEditable *
129gnc_cell_renderer_label_start_editing (GtkCellRenderer *cell,
130 GdkEvent *event,
131 GtkWidget *widget,
132 const gchar *path,
133 const GdkRectangle *background_area,
134 const GdkRectangle *cell_area,
135 GtkCellRendererState flags)
136{
137 gchar *text = NULL;
138 gfloat xalign = 0.0;
139 GtkWidget *editable;
140
141 g_object_get (cell, "text", &text, "xalign", &xalign, NULL);
142
143 editable = gnc_selectable_entry_new (text, xalign);
144 g_free (text);
145
146 return GTK_CELL_EDITABLE (editable);
147}
148
149static void
150gnc_cell_renderer_label_class_init (GncCellRendererLabelClass *klass)
151{
152 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
153
154 cell_class->start_editing = gnc_cell_renderer_label_start_editing;
155}
156
157static void
158gnc_cell_renderer_label_init (GncCellRendererLabel *self)
159{
160 /* GTK_CELL_RENDERER_MODE_EDITABLE causes start_editing() to be
161 * called when the user activates the cell. */
162 g_object_set (self, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
163}
164
165GtkCellRenderer *
166gnc_cell_renderer_label_new (void)
167{
168 return g_object_new (GNC_TYPE_CELL_RENDERER_LABEL, NULL);
169}
utility functions for the GnuCash UI
char * gnc_filter_text_for_bidi_marks(const char *text)
Returns the incoming text with Unicode bidi control characters removed.