GnuCash c935c2f+
Loading...
Searching...
No Matches
dialog-report-column-view.cpp
1/********************************************************************
2 * dialog-report-column-view.c -- editor for column view of reports *
3 * Copyright (C) 2001 Bill Gribble <grib@billgribble.com> *
4 * Copyright (c) 2006 David Hampton <hampton@employees.org> *
5 * *
6 * This program is free software; you can redistribute it and/or *
7 * modify it under the terms of the GNU General Public License as *
8 * published by the Free Software Foundation; either version 2 of *
9 * the License, or (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License*
17 * along with this program; if not, contact: *
18 * *
19 * Free Software Foundation Voice: +1-617-542-5942 *
20 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 * Boston, MA 02110-1301, USA gnu@gnu.org *
22 ********************************************************************/
23
24#include <glib/gi18n.h>
25#include <gtk/gtk.h>
26#include <algorithm>
27#include <dialog-options.hpp>
28#include <gnc-optiondb-impl.hpp>
29#include <libguile.h>
30
31#include <config.h>
32
33#include "swig-runtime.h"
34
35#include "dialog-utils.h"
36#include "window-report.h"
37#include "guile-mappings.h"
38#include "gnc-guile-utils.h"
39#include "gnc-ui.h"
40
41#include "dialog-report-column-view.hpp"
42#include <gnc-report.h>
43
44enum available_cols
45{
46 AVAILABLE_COL_NAME = 0,
47 AVAILABLE_COL_GUID,
48 NUM_AVAILABLE_COLS
49};
50
51enum contents_cols
52{
53 CONTENTS_COL_NAME = 0,
54 CONTENTS_COL_ROW,
55 CONTENTS_COL_REPORT_ROWS,
56 CONTENTS_COL_REPORT_COLS,
57 NUM_CONTENTS_COLS
58};
59
60using StrVec = std::vector<std::string>;
61
63{
64 std::unique_ptr<GncOptionsDialog> optwin;
65 GtkTreeView * available;
66 GtkTreeView * contents;
67
68 SCM view;
69 GncOptionDB * odb;
70
71 StrVec available_list;
72 GncOptionReportPlacementVec contents_list;
73 int contents_selected;
74
75 GtkWidget *add_button;
76 GtkWidget *remove_button;
77 GtkWidget *up_button;
78 GtkWidget *down_button;
79 GtkWidget *size_button;
80};
81
82/* Even though these aren't external nor used outside this file they must be
83 * declared this way to ensure that they're in the library's symbol table and
84 * aren't mangled. That's so that dlsym is able to find them when GtkBuilder
85 * needs to connect the signals to them.
86 */
87extern "C"
88{
89void gnc_column_view_edit_add_cb(GtkButton * button, gpointer user_data);
90void gnc_column_view_edit_remove_cb(GtkButton * button, gpointer user_data);
91void gnc_edit_column_view_move_up_cb(GtkButton * button, gpointer user_data);
92void gnc_edit_column_view_move_down_cb(GtkButton * button, gpointer user_data);
93void gnc_column_view_edit_size_cb(GtkButton * button, gpointer user_data);
94}
95
96static void
97gnc_column_view_set_option(GncOptionDB* odb, const char* section,
98 const char* name, const GncOptionReportPlacementVec& new_value)
99{
100 odb->find_option(section, name)->set_value(new_value);
101}
102
103static void
104gnc_column_view_edit_destroy(gnc_column_view_edit * view)
105{
106 scm_gc_unprotect_object(view->view);
107 gnc_option_db_destroy(view->odb);
108 delete view;
109}
110
111static StrVec
112get_available_reports ()
113{
114 StrVec sv;
115 auto scm_list{scm_call_0(scm_c_eval_string("gnc:all-report-template-guids"))};
116 for (auto next{scm_list}; !scm_is_null(next); next = scm_cdr(next))
117 {
118 auto guid{scm_to_utf8_string(scm_car(next))};
119 sv.emplace_back(guid);
120 g_free (guid);
121 }
122 return sv;
123}
124
125static void
126update_available_lists(gnc_column_view_edit * view)
127{
128 SCM template_menu_name = scm_c_eval_string("gnc:report-template-menu-name/report-guid");
129 std::string selection;
130
131
132 GtkTreeIter iter;
133
134 /* Update the list of available reports (left selection box). */
135 auto tree_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view->available));
136 auto model = gtk_tree_view_get_model (GTK_TREE_VIEW(view->available));
137
138 if (gtk_tree_selection_get_selected(tree_selection, &model, &iter))
139 {
140 gchar *guid_str;
141 gtk_tree_model_get(model, &iter,
142 AVAILABLE_COL_GUID, &guid_str,
143 -1);
144 selection = std::string(guid_str);
145 g_free (guid_str);
146 }
147 view->available_list = get_available_reports();
148
149 auto store = GTK_LIST_STORE(model);
150 gtk_list_store_clear(store);
151
152 for (const auto& guid : view->available_list)
153 {
154 auto rpt_guid{scm_from_utf8_string(guid.c_str())};
155 auto name =
156 gnc_scm_to_utf8_string (scm_call_2(template_menu_name, rpt_guid, SCM_BOOL_F));
157
158 gtk_list_store_append(store, &iter);
159 gtk_list_store_set(store, &iter,
160 AVAILABLE_COL_NAME, _(name),
161 AVAILABLE_COL_GUID, guid.c_str(),
162 -1);
163
164 if (guid == selection)
165 gtk_tree_selection_select_iter (tree_selection, &iter);
166
167 g_free (name);
168 }
169}
170
171static void
172update_contents_lists(gnc_column_view_edit * view)
173{
174 SCM report_menu_name = scm_c_eval_string("gnc:report-menu-name");
175 auto contents{view->odb->find_option("__general", "report-list")->get_value<GncOptionReportPlacementVec>()};
176 GtkTreeIter iter;
177 GncOptionReportPlacement selection{0, 0, 0};
178
179 /* Update the list of selected reports (right selection box). */
180 auto tree_selection = gtk_tree_view_get_selection(view->contents);
181
182 view->contents_list = contents;
183
184 if (!contents.empty() && static_cast<size_t>(view->contents_selected) < contents.size())
185 selection = contents[view->contents_selected];
186
187 auto store = GTK_LIST_STORE(gtk_tree_view_get_model(view->contents));
188 gtk_list_store_clear(store);
189
190 for (size_t i = 0; i < contents.size(); ++i)
191 {
192 auto [id, wide, high] = contents[i];
193 auto this_report = gnc_report_find(id);
194 auto name = gnc_scm_to_utf8_string (scm_call_1(report_menu_name, this_report));
195
196 gtk_list_store_append(store, &iter);
197 gtk_list_store_set
198 (store, &iter,
199 CONTENTS_COL_NAME, _(name),
200 CONTENTS_COL_ROW, i,
201 CONTENTS_COL_REPORT_COLS, wide,
202 CONTENTS_COL_REPORT_ROWS, high,
203 -1);
204
205 if (id == std::get<0>(selection))
206 gtk_tree_selection_select_iter (tree_selection, &iter);
207
208 g_free (name);
209 }
210}
211
212static void
213gnc_column_view_update_buttons_cb (GtkTreeSelection *selection,
214 gnc_column_view_edit *r)
215{
216 GtkTreeModel *model;
217 GtkTreeIter iter;
218 gboolean is_selected;
219
220 /* compare treeviews to establish which selected treeview */
221 if (gtk_tree_selection_get_tree_view (selection) == r->available)
222 {
223 /* available treeview */
224 is_selected = gtk_tree_selection_get_selected (selection, &model, &iter);
225 gtk_widget_set_sensitive (r->add_button, is_selected);
226 return;
227 }
228
229 /* contents treeview */
230 is_selected = gtk_tree_selection_get_selected (selection, &model, &iter);
231 gtk_widget_set_sensitive (r->size_button, is_selected);
232 gtk_widget_set_sensitive (r->remove_button, is_selected);
233
234 if (is_selected)
235 {
236 int len = r->contents_list.size();
237
238 gtk_tree_model_get (model, &iter,
239 CONTENTS_COL_ROW, &r->contents_selected, -1);
240
241 if (len > 1)
242 {
243 gtk_widget_set_sensitive (r->up_button, TRUE);
244 gtk_widget_set_sensitive (r->down_button, TRUE);
245
246 if (r->contents_selected == len -1)
247 gtk_widget_set_sensitive (r->down_button, FALSE);
248
249 if (r->contents_selected == 0)
250 gtk_widget_set_sensitive (r->up_button, FALSE);
251 }
252 }
253 else
254 {
255 gtk_widget_set_sensitive (r->up_button, FALSE);
256 gtk_widget_set_sensitive (r->down_button, FALSE);
257 }
258}
259
260static void
261gnc_column_view_edit_apply_cb(GncOptionsDialog *dlg, gpointer user_data)
262{
263 SCM dirty_report = scm_c_eval_string("gnc:report-set-dirty?!");
264 auto win{static_cast<gnc_column_view_edit*>(user_data)};
265
266 if (!win) return;
267 auto results = gnc_option_db_commit (dlg->get_option_db());
268 for (auto iter = results; iter; iter = iter->next)
269 {
270 GtkWidget *dialog =
271 gtk_message_dialog_new(GTK_WINDOW(dlg->get_widget()),
272 GTK_DIALOG_MODAL,
273 GTK_MESSAGE_ERROR,
274 GTK_BUTTONS_OK,
275 "%s",
276 (char*)iter->data);
277 gtk_dialog_run(GTK_DIALOG(dialog));
278 gtk_widget_destroy(dialog);
279 g_free (iter->data);
280 }
281 g_list_free (results);
282
283 scm_call_2(dirty_report, win->view, SCM_BOOL_T);
284}
285
286static void
287gnc_column_view_edit_close_cb(GncOptionsDialog *win, gpointer user_data)
288{
289 auto r{static_cast<gnc_column_view_edit*>(user_data)};
290 SCM set_editor = scm_c_eval_string("gnc:report-set-editor-widget!");
291
292 scm_call_2(set_editor, r->view, SCM_BOOL_F);
293 gnc_column_view_edit_destroy(r);
294}
295
296
297/********************************************************************
298 * gnc_column_view_edit_options
299 * create the editor.
300 ********************************************************************/
301
302GtkWidget *
303gnc_column_view_edit_options(GncOptionDB* odb, SCM view)
304{
305 SCM get_editor = scm_c_eval_string("gnc:report-editor-widget");
306 SCM ptr;
307 GtkWidget * editor;
308 GtkListStore *store;
309 GtkCellRenderer *renderer;
310 GtkTreeViewColumn *column;
311 GtkTreeSelection *selection;
312
313 ptr = scm_call_1(get_editor, view);
314 if (ptr != SCM_BOOL_F)
315 {
316#define FUNC_NAME "gtk_window_present"
317 auto w{static_cast<GtkWindow*>(SWIG_MustGetPtr(ptr, SWIG_TypeQuery("_p_GtkWidget"), 1, 0))};
318 gtk_window_present(w);
319#undef FUNC_NAME
320 return nullptr;
321 }
322 else
323 {
324 auto r = new gnc_column_view_edit;
325 GtkBuilder *builder;
326
327 r->optwin = std::make_unique<GncOptionsDialog>(nullptr, GTK_WINDOW(gnc_ui_get_main_window (nullptr)));
328
329 /* Hide the generic dialog page list. */
330 gtk_widget_hide(r->optwin->get_page_list());
331
332 builder = gtk_builder_new();
333 gnc_builder_add_from_file (builder, "dialog-report.glade", "view_contents_table");
334
335 editor = GTK_WIDGET(gtk_builder_get_object (builder, "view_contents_table"));
336 r->available = GTK_TREE_VIEW (gtk_builder_get_object (builder, "available_view"));
337 r->contents = GTK_TREE_VIEW (gtk_builder_get_object (builder, "contents_view"));
338
339 r->add_button = GTK_WIDGET(gtk_builder_get_object (builder, "add_button1"));
340 r->remove_button = GTK_WIDGET(gtk_builder_get_object (builder, "remove_button1"));
341 r->up_button = GTK_WIDGET(gtk_builder_get_object (builder, "up_button1"));
342 r->down_button = GTK_WIDGET(gtk_builder_get_object (builder, "down_button1"));
343 r->size_button = GTK_WIDGET(gtk_builder_get_object (builder, "size_button1"));
344
345 r->view = view;
346 r->available_list.clear();
347 r->contents_selected = 0;
348 r->contents_list.clear();
349 r->odb = odb;
350
351 r->optwin->build_contents(r->odb);
352
353 gtk_notebook_append_page(GTK_NOTEBOOK(r->optwin->get_notebook()),
354 editor,
355 gtk_label_new(_("Contents")));
356
357 scm_gc_protect_object(r->view);
358
359 /* Build the 'available' view */
360 store = gtk_list_store_new (NUM_AVAILABLE_COLS, G_TYPE_STRING, G_TYPE_STRING);
361 gtk_tree_view_set_model(r->available, GTK_TREE_MODEL(store));
362 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), AVAILABLE_COL_NAME, GTK_SORT_ASCENDING);
363 g_object_unref(store);
364
365 renderer = gtk_cell_renderer_text_new();
366 column = gtk_tree_view_column_new_with_attributes("", renderer,
367 "text", AVAILABLE_COL_NAME,
368 nullptr);
369 gtk_tree_view_append_column(r->available, column);
370
371 /* use the selection cb to update buttons */
372 selection = gtk_tree_view_get_selection(r->available);
373 g_signal_connect(selection, "changed",
374 G_CALLBACK(gnc_column_view_update_buttons_cb), r);
375
376 /* Build the 'contents' view */
377 store = gtk_list_store_new (NUM_CONTENTS_COLS, G_TYPE_STRING, G_TYPE_INT,
378 G_TYPE_INT, G_TYPE_INT);
379 gtk_tree_view_set_model(r->contents, GTK_TREE_MODEL(store));
380 g_object_unref(store);
381
382 renderer = gtk_cell_renderer_text_new();
383 column = gtk_tree_view_column_new_with_attributes(_("Report"), renderer,
384 "text", CONTENTS_COL_NAME,
385 nullptr);
386 gtk_tree_view_append_column(r->contents, column);
387
388 renderer = gtk_cell_renderer_text_new();
389 column = gtk_tree_view_column_new_with_attributes(_("Rows"), renderer,
390 "text", CONTENTS_COL_REPORT_ROWS,
391 nullptr);
392 gtk_tree_view_append_column(r->contents, column);
393
394 renderer = gtk_cell_renderer_text_new();
395 column = gtk_tree_view_column_new_with_attributes(_("Cols"), renderer,
396 "text", CONTENTS_COL_REPORT_COLS,
397 nullptr);
398 gtk_tree_view_append_column(r->contents, column);
399
400 /* use the selection cb to update buttons */
401 selection = gtk_tree_view_get_selection(r->contents);
402 g_signal_connect(selection, "changed",
403 G_CALLBACK(gnc_column_view_update_buttons_cb), r);
404
405 update_available_lists(r);
406 update_contents_lists(r);
407
408 r->optwin->set_apply_cb(gnc_column_view_edit_apply_cb, r);
409 r->optwin->set_close_cb(gnc_column_view_edit_close_cb, r);
410
411 gtk_widget_show(r->optwin->get_widget());
412
413 gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, r);
414
415 g_object_unref(G_OBJECT(builder));
416
417 return r->optwin->get_widget();
418 }
419}
420
421void
422gnc_column_view_edit_add_cb(GtkButton * button, gpointer user_data)
423{
424 auto r = static_cast<gnc_column_view_edit *>(user_data);
425 SCM make_report = scm_c_eval_string("gnc:make-report");
426 SCM mark_report = scm_c_eval_string("gnc:report-set-needs-save?!");
427 gchar *guid_str;
428 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(r->available));
429 GtkTreeModel *model;
430 GtkTreeIter iter;
431
432 /* make sure there is a selected entry */
433 if (gtk_tree_selection_get_selected(selection, &model, &iter))
434 gtk_tree_model_get(model, &iter,
435 AVAILABLE_COL_GUID, &guid_str, -1);
436 else
437 return;
438
439 auto template_name = scm_from_utf8_string(guid_str);
440
441 auto new_report = scm_call_1(make_report, template_name);
442 auto id = scm_to_int(new_report);
443 scm_call_2(mark_report, gnc_report_find(id), SCM_BOOL_T);
444 auto oldlength = r->contents_list.size();
445 GncOptionReportPlacement new_rpt_placement{id, 1, 1};
446
447 if (oldlength > static_cast<size_t>(r->contents_selected))
448 r->contents_list.emplace(r->contents_list.begin() + r->contents_selected + 1, id, 1, 1);
449 else
450 {
451 r->contents_list.emplace_back(id, 1, 1);
452 r->contents_selected = oldlength;
453 }
454
455 gnc_column_view_set_option(r->odb, "__general", "report-list",
456 r->contents_list);
457 g_free (guid_str);
458 r->optwin->changed();
459 update_contents_lists(r);
460}
461
462void
463gnc_column_view_edit_remove_cb(GtkButton * button, gpointer user_data)
464{
465 auto r = static_cast<gnc_column_view_edit *>(user_data);
466
467 r->contents_list.erase(r->contents_list.begin() + r->contents_selected);
468 if (r->contents_selected)
469 --r->contents_selected;
470 gnc_column_view_set_option(r->odb, "__general", "report-list",
471 r->contents_list);
472
473 r->optwin->changed();
474 update_contents_lists(r);
475}
476
477static void
478move_selected_item(gnc_column_view_edit* r, int increment)
479{
480 if (!r || !increment)
481 return;
482
483 auto cur_sel{r->contents_list.begin() + r->contents_selected};
484 auto move_to{cur_sel + increment};
485 if (increment > 0)
486 std::reverse(cur_sel, move_to + 1);
487 else
488 std::reverse(move_to, cur_sel + 1);
489 r->contents_selected += increment;
490
491 gnc_column_view_set_option(r->odb, "__general", "report-list",
492 r->contents_list);
493 r->optwin->changed();
494 update_contents_lists(r);
495}
496
497void
498gnc_edit_column_view_move_up_cb(GtkButton * button, gpointer user_data)
499{
500 auto r = static_cast<gnc_column_view_edit *>(user_data);
501 move_selected_item(r, -1);
502}
503
504void
505gnc_edit_column_view_move_down_cb(GtkButton * button, gpointer user_data)
506{
507 auto r = static_cast<gnc_column_view_edit *>(user_data);
508 move_selected_item(r, 1);
509}
510
511void
512gnc_column_view_edit_size_cb(GtkButton * button, gpointer user_data)
513{
514 auto r = static_cast<gnc_column_view_edit *>(user_data);
515 GtkWidget * rowspin;
516 GtkWidget * colspin;
517 GtkWidget * dlg;
518 GtkBuilder *builder;
519 int dlg_ret;
520
521 builder = gtk_builder_new();
522 gnc_builder_add_from_file (builder, "dialog-report.glade", "col_adjustment");
523 gnc_builder_add_from_file (builder, "dialog-report.glade", "row_adjustment");
524 gnc_builder_add_from_file (builder, "dialog-report.glade", "edit_report_size");
525 dlg = GTK_WIDGET(gtk_builder_get_object (builder, "edit_report_size"));
526
527 gtk_window_set_transient_for (GTK_WINDOW(dlg),
528 GTK_WINDOW(gtk_widget_get_toplevel (GTK_WIDGET(button))));
529
530 /* get the spinner widgets */
531 rowspin = GTK_WIDGET(gtk_builder_get_object (builder, "row_spin"));
532 colspin = GTK_WIDGET(gtk_builder_get_object (builder, "col_spin"));
533
534 if (r->contents_list.size() > static_cast<size_t>(r->contents_selected))
535 {
536 auto [id, wide, high] = r->contents_list[r->contents_selected];
537
538 gtk_spin_button_set_value(GTK_SPIN_BUTTON(colspin),
539 static_cast<float>(wide));
540 gtk_spin_button_set_value(GTK_SPIN_BUTTON(rowspin),
541 static_cast<float>(high));
542
543 dlg_ret = gtk_dialog_run(GTK_DIALOG(dlg));
544 gtk_widget_hide(dlg);
545
546 if (dlg_ret == GTK_RESPONSE_OK)
547 {
548 std::get<1>(r->contents_list[r->contents_selected]) =
549 gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(colspin));
550 std::get<2>(r->contents_list[r->contents_selected]) =
551 gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(rowspin));
552
553 gnc_column_view_set_option(r->odb, "__general", "report-list",
554 r->contents_list);
555 r->optwin->changed();
556 update_contents_lists(r);
557 }
558
559 g_object_unref(G_OBJECT(builder));
560
561 gtk_widget_destroy(dlg);
562 }
563}
Holds all of the options for a book, report, or stylesheet, organized by GncOptionSections.
Implementation details for GncOptionDB.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
GList * gnc_option_db_commit(GncOptionDB *odb)
Write all changed ui_item values to their options.
void gnc_option_db_destroy(GncOptionDB *odb)
Destruct and release a GncOptionDB.