GnuCash  5.6-150-g038405b370+
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 
44 enum available_cols
45 {
46  AVAILABLE_COL_NAME = 0,
47  AVAILABLE_COL_GUID,
48  NUM_AVAILABLE_COLS
49 };
50 
51 enum 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 
60 using 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  */
87 extern "C"
88 {
89 void gnc_column_view_edit_add_cb(GtkButton * button, gpointer user_data);
90 void gnc_column_view_edit_remove_cb(GtkButton * button, gpointer user_data);
91 void gnc_edit_column_view_move_up_cb(GtkButton * button, gpointer user_data);
92 void gnc_edit_column_view_move_down_cb(GtkButton * button, gpointer user_data);
93 void gnc_column_view_edit_size_cb(GtkButton * button, gpointer user_data);
94 }
95 
96 static void
97 gnc_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 
103 static void
104 gnc_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 
111 static StrVec
112 get_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 
125 static void
126 update_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 
171 static void
172 update_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 
212 static void
213 gnc_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 
260 static void
261 gnc_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 
286 static void
287 gnc_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 
302 GtkWidget *
303 gnc_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 
421 void
422 gnc_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 
462 void
463 gnc_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 
477 static void
478 move_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 
497 void
498 gnc_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 
504 void
505 gnc_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 
511 void
512 gnc_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.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
void gnc_option_db_destroy(GncOptionDB *odb)
Destruct and release a GncOptionDB.
GList * gnc_option_db_commit(GncOptionDB *odb)
Write all changed ui_item values to their options.
Implementation details for GncOptionDB.