GnuCash  5.6-150-g038405b370+
gnc-frequency.c
1 /********************************************************************\
2  * gnc-frequency.c -- GnuCash widget for frequency editing. *
3  * Copyright (C) 2001,2002,2007 Joshua Sled <jsled@asynchronous.org>*
4  * Copyright (C) 2003 Linas Vepstas <linas@linas.org> *
5  * Copyright (C) 2006 David Hampton <hampton@employees.org> *
6  * Copyright (C) 2011 Robert Fewell *
7  * *
8  * This program is free software; you can redistribute it and/or *
9  * modify it under the terms of the GNU General Public License as *
10  * published by the Free Software Foundation; either version 2 of *
11  * the License, or (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License*
19  * along with this program; if not, contact: *
20  * *
21  * Free Software Foundation Voice: +1-617-542-5942 *
22  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23  * Boston, MA 02110-1301, USA gnu@gnu.org *
24  * *
25 \********************************************************************/
26 
27 #include <config.h>
28 
29 #include <glib.h>
30 #include <gtk/gtk.h>
31 #include <math.h>
32 #include <time.h>
33 
34 #include "dialog-utils.h"
35 #include "gnc-component-manager.h"
36 #include "gnc-engine.h"
37 #include "gnc-frequency.h"
38 #include "FreqSpec.h"
39 #include "gnc-ui-util.h"
40 
41 #undef G_LOG_DOMAIN
42 #define G_LOG_DOMAIN "gnc.gui.frequency"
43 
44 static QofLogModule log_module = GNC_MOD_GUI;
45 
46 #define LAST_DAY_OF_MONTH_OPTION_INDEX 31
47 
50 typedef enum
51 {
52  GNCFREQ_CHANGED,
53  LAST_SIGNAL
54 } GNCF_Signals;
55 
56 static guint gnc_frequency_signals[LAST_SIGNAL] = { 0 };
57 
60 static void gnc_frequency_destroy( GtkWidget *widget );
61 
62 static void freq_combo_changed( GtkComboBox *b, gpointer d );
63 static void start_date_changed( GNCDateEdit *gde, gpointer d );
64 static void spin_changed_helper( GtkAdjustment *adj, gpointer d );
65 
66 static void weekly_days_changed( GtkButton *b, gpointer d );
67 
68 static void monthly_sel_changed( GtkButton *b, gpointer d );
69 static void semimonthly_sel_changed( GtkButton *b, gpointer d );
70 
73 enum
74 {
75  PAGE_NONE = 0,
76  PAGE_ONCE,
77  PAGE_DAILY,
78  PAGE_WEEKLY,
79  PAGE_SEMI_MONTHLY,
80  PAGE_MONTHLY
81 };
82 
83 static const char *CHECKBOX_NAMES[] =
84 {
85  "wd_check_sun",
86  "wd_check_mon",
87  "wd_check_tue",
88  "wd_check_wed",
89  "wd_check_thu",
90  "wd_check_fri",
91  "wd_check_sat",
92  NULL
93 };
94 
103 typedef struct _GncFrequency
104 {
105  GtkBox widget;
106  GtkBox *vb;
107  GtkNotebook *nb;
108  GtkComboBox *freqComboBox;
109  GNCDateEdit *startDate;
110  GtkBuilder *builder;
111 } GncFrequency;
112 
113 G_DEFINE_TYPE (GncFrequency, gnc_frequency, GTK_TYPE_BOX)
114 
115 static void
116 gnc_frequency_class_init( GncFrequencyClass *klass )
117 {
118  GObjectClass *object_class;
119  GtkWidgetClass *gtkwidget_class;
120 
121  object_class = G_OBJECT_CLASS (klass);
122  gtkwidget_class = GTK_WIDGET_CLASS (klass);
123 
124  gnc_frequency_signals[GNCFREQ_CHANGED] =
125  g_signal_new ("changed",
126  G_OBJECT_CLASS_TYPE (object_class),
127  G_SIGNAL_RUN_FIRST,
128  0,
129  NULL,
130  NULL,
131  g_cclosure_marshal_VOID__VOID,
132  G_TYPE_NONE,
133  0);
134 
135  /* GtkWidget signals */
136  gtkwidget_class->destroy = gnc_frequency_destroy;
137 }
138 
139 
140 static void
141 gnc_frequency_init(GncFrequency *gf)
142 {
143  int i;
144  GtkBox* vb;
145  GtkWidget* o;
146  GtkAdjustment* adj;
147  GtkBuilder *builder;
148 
149  static const struct comboBoxTuple
150  {
151  char *name;
152  void (*fn)();
153  } comboBoxes[] =
154  {
155  { "freq_combobox", freq_combo_changed },
156  { "semimonthly_first", semimonthly_sel_changed },
157  { "semimonthly_first_weekend", semimonthly_sel_changed },
158  { "semimonthly_second", semimonthly_sel_changed },
159  { "semimonthly_second_weekend", semimonthly_sel_changed },
160  { "monthly_day", monthly_sel_changed },
161  { "monthly_weekend", monthly_sel_changed },
162  { NULL, NULL }
163  };
164 
165  static const struct spinvalTuple
166  {
167  char *name;
168  void (*fn)();
169  } spinVals[] =
170  {
171  { "daily_spin", spin_changed_helper },
172  { "weekly_spin", spin_changed_helper },
173  { "semimonthly_spin", spin_changed_helper },
174  { "monthly_spin", spin_changed_helper },
175  { NULL, NULL }
176  };
177 
178  gtk_orientable_set_orientation (GTK_ORIENTABLE(gf), GTK_ORIENTATION_VERTICAL);
179 
180  // Set the name for this widget so it can be easily manipulated with css
181  gtk_widget_set_name (GTK_WIDGET(gf), "gnc-id-frequency");
182 
183  builder = gtk_builder_new();
184  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment1");
185  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment2");
186  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment3");
187  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "adjustment4");
188  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore1");
189  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore2");
190  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore3");
191  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore4");
192  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore5");
193  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore6");
194  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "liststore7");
195  gnc_builder_add_from_file (builder , "gnc-frequency.glade", "gncfreq_vbox");
196 
197  gf->builder = builder;
198  o = GTK_WIDGET(gtk_builder_get_object (builder, "gncfreq_nb"));
199  gf->nb = GTK_NOTEBOOK(o);
200  o = GTK_WIDGET(gtk_builder_get_object (builder, "freq_combobox"));
201  gf->freqComboBox = GTK_COMBO_BOX(o);
202  gf->startDate = GNC_DATE_EDIT(gnc_date_edit_new(time(NULL), FALSE, FALSE));
203  /* Add the new widget to the table. */
204  {
205  GtkWidget *table = GTK_WIDGET(gtk_builder_get_object (builder, "gncfreq_table"));
206  gtk_grid_attach(GTK_GRID(table), GTK_WIDGET(gf->startDate), 4, 0, 1, 1);
207  gtk_widget_set_vexpand (GTK_WIDGET(gf->startDate), FALSE);
208  gtk_widget_set_hexpand (GTK_WIDGET(gf->startDate), FALSE);
209  gtk_widget_set_valign (GTK_WIDGET(gf->startDate), GTK_ALIGN_CENTER);
210  gtk_widget_set_halign (GTK_WIDGET(gf->startDate), GTK_ALIGN_CENTER);
211  g_object_set (GTK_WIDGET(gf->startDate), "margin", 0, NULL);
212  }
213  vb = GTK_BOX(gtk_builder_get_object (builder, "gncfreq_vbox"));
214  gf->vb = vb;
215  gtk_container_add(GTK_CONTAINER(&gf->widget), GTK_WIDGET(gf->vb));
216 
217  /* initialize the combo boxes */
218  for (i = 0; comboBoxes[i].name != NULL; i++)
219  {
220  o = GTK_WIDGET(gtk_builder_get_object (builder, comboBoxes[i].name));
221  gtk_combo_box_set_active(GTK_COMBO_BOX(o), 0);
222  if (comboBoxes[i].fn != NULL)
223  {
224  g_signal_connect(o, "changed", G_CALLBACK(comboBoxes[i].fn), gf);
225  }
226  }
227 
228  /* initialize the spin buttons */
229  for (i = 0; spinVals[i].name != NULL; i++)
230  {
231  if (spinVals[i].fn != NULL)
232  {
233  o = GTK_WIDGET(gtk_builder_get_object (builder, spinVals[i].name));
234  adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(o));
235  g_signal_connect(adj, "value_changed", G_CALLBACK(spinVals[i].fn), gf);
236  }
237  }
238 
239  /* initialize the weekly::day-of-week checkbox-selection hooks */
240  for (i = 0; i < 7; i++)
241  {
242  o = GTK_WIDGET(gtk_builder_get_object (builder, CHECKBOX_NAMES[i]));
243  g_signal_connect(o, "clicked",
244  G_CALLBACK(weekly_days_changed), gf);
245  }
246 
247  gtk_widget_show_all(GTK_WIDGET(&gf->widget));
248 
249  /* respond to start date changes */
250  g_signal_connect(gf->startDate, "date_changed", G_CALLBACK(start_date_changed), gf);
251 
252  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, gf);
253 
254 }
255 
256 
264 static void
265 gnc_frequency_destroy (GtkWidget *widget)
266 {
267  GncFrequency *gf;
268 
269  ENTER("frequency %p", widget);
270  g_return_if_fail (widget != NULL);
271  g_return_if_fail (GNC_IS_FREQUENCY (widget));
272 
273  gf = GNC_FREQUENCY (widget);
274 
275  if (gf->builder)
276  {
277  DEBUG("removing builder");
278  g_object_unref(G_OBJECT(gf->builder));
279  gf->builder = NULL;
280  }
281 
282  GTK_WIDGET_CLASS (gnc_frequency_parent_class)->destroy (widget);
283  LEAVE(" ");
284 }
285 
286 
287 static void
288 spin_changed_helper( GtkAdjustment *adj, gpointer d )
289 {
290  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
291 }
292 
293 
294 static void
295 weekly_days_changed( GtkButton *b, gpointer d )
296 {
297  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
298 }
299 
300 
301 static void
302 monthly_sel_changed( GtkButton *b, gpointer d )
303 {
304  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
305 }
306 
307 
308 static void
309 semimonthly_sel_changed( GtkButton *b, gpointer d )
310 {
311  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
312 }
313 
314 
315 static void
316 freq_combo_changed(GtkComboBox *b, gpointer d)
317 {
318  GncFrequency *gf = GNC_FREQUENCY(d);
319  int option_index;
320 
321  /* Set the new page. */
322  option_index = gtk_combo_box_get_active(GTK_COMBO_BOX(gf->freqComboBox));
323  gtk_notebook_set_current_page(gf->nb, option_index);
324  g_signal_emit_by_name(gf, "changed");
325 }
326 
327 
328 static void
329 start_date_changed( GNCDateEdit *gde, gpointer d )
330 {
331  g_signal_emit_by_name(GNC_FREQUENCY(d), "changed");
332 }
333 
334 
335 /**************************************
336  * Relabel some of the labels *
337  *************************************/
338 void
339 gnc_frequency_set_frequency_label_text(GncFrequency *gf, const gchar *txt)
340 {
341  GtkLabel *lbl;
342  if (!gf || !txt) return;
343  lbl = GTK_LABEL (gtk_builder_get_object (gf->builder, "freq_label"));
344  gtk_label_set_text (lbl, txt);
345 }
346 
347 
348 void
349 gnc_frequency_set_date_label_text(GncFrequency *gf, const gchar *txt)
350 {
351  GtkLabel *lbl;
352  if (!gf || !txt) return;
353  lbl = GTK_LABEL (gtk_builder_get_object (gf->builder, "startdate_label"));
354  gtk_label_set_text (lbl, txt);
355 }
356 
357 
358 GtkWidget*
359 gnc_frequency_new_from_recurrence(GList *recurrences, const GDate *start_date)
360 {
361  return gnc_frequency_new(recurrences, start_date);
362 }
363 
364 
365 GtkWidget*
366 gnc_frequency_new(GList *recurrences, const GDate *start_date)
367 {
368  GncFrequency *toRet;
369  toRet = g_object_new(gnc_frequency_get_type(), NULL);
370  gnc_frequency_setup_recurrence(toRet, recurrences, start_date);
371  return GTK_WIDGET(toRet);
372 }
373 
374 
375 static void
376 _setup_weekly_recurrence(GncFrequency *gf, Recurrence *r)
377 {
378  GDate recurrence_date;
379  GDateWeekday day_of_week;
380  guint multiplier = recurrenceGetMultiplier(r);
381  const char *checkbox_widget_name;
382  GtkWidget *weekday_checkbox;
383 
384  GtkWidget *multipler_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, "weekly_spin"));
385  gtk_spin_button_set_value(GTK_SPIN_BUTTON(multipler_spin), multiplier);
386 
387  recurrence_date = recurrenceGetDate(r);
388  day_of_week = g_date_get_weekday(&recurrence_date);
389  g_assert(day_of_week >= G_DATE_MONDAY && day_of_week <= G_DATE_SUNDAY);
390  // this `mod 7' is explicit knowledge of the values of (monday=1)-based
391  // GDateWeekday, vs. our (sunday=0)-based checkbox names array.
392  checkbox_widget_name = CHECKBOX_NAMES[day_of_week % 7];
393  weekday_checkbox = GTK_WIDGET(gtk_builder_get_object (gf->builder, checkbox_widget_name));
394  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(weekday_checkbox), TRUE);
395 }
396 
397 
398 static int
399 _get_monthly_combobox_index(Recurrence *r)
400 {
401  GDate recurrence_date = recurrenceGetDate(r);
402  int week = 0;
403  int day_of_month_index = g_date_get_day(&recurrence_date) - 1;
404  if (recurrenceGetPeriodType(r) == PERIOD_END_OF_MONTH)
405  {
406  day_of_month_index = LAST_DAY_OF_MONTH_OPTION_INDEX;
407  }
408  else if (recurrenceGetPeriodType(r) == PERIOD_LAST_WEEKDAY)
409  {
410  day_of_month_index
411  = LAST_DAY_OF_MONTH_OPTION_INDEX
412  + g_date_get_weekday(&recurrence_date);
413  }
414  else if (recurrenceGetPeriodType(r) == PERIOD_NTH_WEEKDAY)
415  {
416  week = day_of_month_index / 7 > 3 ? 3 : day_of_month_index / 7;
417  day_of_month_index = LAST_DAY_OF_MONTH_OPTION_INDEX + 7 +
418  g_date_get_weekday(&recurrence_date) + 7 * week;
419 
420 
421  }
422  /* else { default value } */
423  return day_of_month_index;
424 }
425 
426 
427 void
428 gnc_frequency_setup_recurrence(GncFrequency *gf, GList *recurrences, const GDate *start_date)
429 {
430  gnc_frequency_setup(gf, recurrences, start_date);
431 }
432 
433 
434 void
435 gnc_frequency_setup(GncFrequency *gf, GList *recurrences, const GDate *start_date)
436 {
437  gboolean made_changes = FALSE;
438 
439  // setup start-date, if present
440  if (start_date != NULL
441  && g_date_valid(start_date))
442  {
443  gnc_date_edit_set_gdate(gf->startDate, start_date);
444  made_changes = TRUE;
445  }
446 
447  if (recurrences == NULL)
448  {
449  goto maybe_signal;
450  // return...
451  }
452 
453  if (g_list_length(recurrences) > 1)
454  {
455  if (recurrenceListIsWeeklyMultiple(recurrences))
456  {
457  for (; recurrences != NULL; recurrences = recurrences->next)
458  {
459  _setup_weekly_recurrence(gf, (Recurrence*)recurrences->data);
460  }
461 
462  gtk_notebook_set_current_page(gf->nb, PAGE_WEEKLY);
463  gtk_combo_box_set_active(gf->freqComboBox, PAGE_WEEKLY);
464  }
465  else if (recurrenceListIsSemiMonthly(recurrences))
466  {
467  Recurrence *first, *second;
468  GtkWidget *multiplier_spin;
469  GtkWidget *dom_combobox;
470 
471  first = (Recurrence*)g_list_nth_data(recurrences, 0);
472  second = (Recurrence*)g_list_nth_data(recurrences, 1);
473 
474  multiplier_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_spin"));
475  gtk_spin_button_set_value(GTK_SPIN_BUTTON(multiplier_spin), recurrenceGetMultiplier(first));
476  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_first"));
477  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), _get_monthly_combobox_index(first));
478  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_first_weekend"));
479  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), recurrenceGetWeekendAdjust(first));
480  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_second"));
481  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), _get_monthly_combobox_index(second));
482  dom_combobox = GTK_WIDGET(gtk_builder_get_object (gf->builder, "semimonthly_second_weekend"));
483  gtk_combo_box_set_active(GTK_COMBO_BOX(dom_combobox), recurrenceGetWeekendAdjust(second));
484 
485  gtk_notebook_set_current_page(gf->nb, PAGE_SEMI_MONTHLY);
486  gtk_combo_box_set_active(gf->freqComboBox, PAGE_SEMI_MONTHLY);
487  }
488  else
489  {
490  g_error("unknown composite recurrence with [%d] entries", g_list_length(recurrences));
491  }
492  }
493  else
494  {
495  Recurrence *r = (Recurrence*)recurrences->data;
496  DEBUG("recurrence period [%d]", recurrenceGetPeriodType(r));
497  switch (recurrenceGetPeriodType(r))
498  {
499  case PERIOD_ONCE:
500  {
501  GDate recurrence_date = recurrenceGetDate(r);
502  if (g_date_compare(start_date, &recurrence_date) != 0)
503  {
504  char start_date_str[128], recur_date_str[128];
505  g_date_strftime(start_date_str, 127, "%x", start_date);
506  g_date_strftime(recur_date_str, 127, "%x", &recurrence_date);
507  g_critical("start_date [%s] != recurrence_date [%s]", start_date_str, recur_date_str);
508  }
509 
510  gtk_notebook_set_current_page(gf->nb, PAGE_ONCE);
511  gtk_combo_box_set_active(gf->freqComboBox, PAGE_ONCE);
512  }
513  break;
514  case PERIOD_DAY:
515  {
516  guint multiplier;
517  GtkWidget *spin_button;
518 
519  multiplier = recurrenceGetMultiplier(r);
520  spin_button = GTK_WIDGET(gtk_builder_get_object (gf->builder, "daily_spin"));
521  gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin_button), multiplier);
522  made_changes = TRUE;
523 
524  gtk_notebook_set_current_page(gf->nb, PAGE_DAILY);
525  gtk_combo_box_set_active(gf->freqComboBox, PAGE_DAILY);
526  }
527  break;
528  case PERIOD_WEEK:
529  {
530  _setup_weekly_recurrence(gf, r);
531  gtk_notebook_set_current_page(gf->nb, PAGE_WEEKLY);
532  gtk_combo_box_set_active(gf->freqComboBox, PAGE_WEEKLY);
533  }
534  break;
535  case PERIOD_END_OF_MONTH:
536  case PERIOD_MONTH:
537  case PERIOD_YEAR:
538  case PERIOD_LAST_WEEKDAY:
539  case PERIOD_NTH_WEEKDAY:
540  {
541  guint multiplier;
542  GtkWidget *multipler_spin, *day_of_month, *weekend_mode;
543 
544  multipler_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, "monthly_spin"));
545  multiplier = recurrenceGetMultiplier(r);
546  if (recurrenceGetPeriodType(r) == PERIOD_YEAR)
547  multiplier *= 12;
548  gtk_spin_button_set_value(GTK_SPIN_BUTTON(multipler_spin), multiplier);
549 
550  day_of_month = GTK_WIDGET(gtk_builder_get_object (gf->builder, "monthly_day"));
551  gtk_combo_box_set_active(GTK_COMBO_BOX(day_of_month), _get_monthly_combobox_index(r));
552  weekend_mode = GTK_WIDGET(gtk_builder_get_object (gf->builder, "monthly_weekend"));
553  gtk_combo_box_set_active(GTK_COMBO_BOX(weekend_mode), recurrenceGetWeekendAdjust(r));
554 
555  gtk_notebook_set_current_page(gf->nb, PAGE_MONTHLY);
556  gtk_combo_box_set_active(gf->freqComboBox, PAGE_MONTHLY);
557  }
558  break;
559  default:
560  g_error("unknown recurrence period type [%d]", recurrenceGetPeriodType(r));
561  break;
562  }
563  }
564 
565 maybe_signal:
566  if (made_changes)
567  g_signal_emit_by_name(gf, "changed");
568 }
569 
570 
571 static gint
572 _get_multiplier_from_widget(GncFrequency *gf, char *widget_name)
573 {
574  GtkWidget *multiplier_spin;
575  multiplier_spin = GTK_WIDGET(gtk_builder_get_object (gf->builder, widget_name));
576  return gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(multiplier_spin));
577 }
578 
579 
580 static Recurrence*
581 _get_day_of_month_recurrence(GncFrequency *gf, GDate *start_date, int multiplier, char *combo_name, char *combo_weekend_name)
582 {
583  Recurrence *r;
584  GtkWidget *day_of_month_combo = GTK_WIDGET(gtk_builder_get_object (gf->builder, combo_name));
585  int day_of_month_index = gtk_combo_box_get_active(GTK_COMBO_BOX(day_of_month_combo));
586  GtkWidget *weekend_adjust_combo = GTK_WIDGET(gtk_builder_get_object (gf->builder, combo_weekend_name));
587  int weekend_adjust = gtk_combo_box_get_active(GTK_COMBO_BOX(weekend_adjust_combo));
588  GDateWeekday selected_day_of_week;
589  GDate *day_of_week_date;
590  int selected_index, selected_week;
591  r = g_new0(Recurrence, 1);
592  if (day_of_month_index > LAST_DAY_OF_MONTH_OPTION_INDEX + 7)
593  {
594  selected_index = day_of_month_index - LAST_DAY_OF_MONTH_OPTION_INDEX - 7;
595  day_of_week_date = g_date_new_julian(g_date_get_julian(start_date));
596  selected_week = (selected_index - 1) / 7 == 4 ? 3 : (selected_index - 1) / 7;
597  selected_day_of_week = selected_index - 7 * selected_week;
598  g_date_set_day(day_of_week_date, 1);
599  while (g_date_get_weekday(day_of_week_date) != selected_day_of_week)
600  g_date_add_days(day_of_week_date, 1);
601  g_date_add_days(day_of_week_date, 7 * selected_week);
602  recurrenceSet(r, multiplier, PERIOD_NTH_WEEKDAY, day_of_week_date, WEEKEND_ADJ_NONE);
603  }
604  else if (day_of_month_index > LAST_DAY_OF_MONTH_OPTION_INDEX)
605  {
606  day_of_week_date = g_date_new_julian(g_date_get_julian(start_date));
607  selected_day_of_week = day_of_month_index - LAST_DAY_OF_MONTH_OPTION_INDEX;
608  // increment until we align on the DOW, but stay inside the month
609  g_date_set_day(day_of_week_date, 1);
610  while (g_date_get_weekday(day_of_week_date) != selected_day_of_week)
611  g_date_add_days(day_of_week_date, 1);
612  recurrenceSet(r, multiplier, PERIOD_LAST_WEEKDAY, day_of_week_date, weekend_adjust);
613  }
614  else if (day_of_month_index == LAST_DAY_OF_MONTH_OPTION_INDEX)
615  {
616  GDate *day_of_month = g_date_new_julian(g_date_get_julian(start_date));
617  recurrenceSet(r, multiplier, PERIOD_END_OF_MONTH, day_of_month, weekend_adjust);
618  }
619  else
620  {
621  int allowable_date = -1;
622  GDate *day_of_month = g_date_new_julian(g_date_get_julian(start_date));
623  allowable_date = MIN(day_of_month_index + 1,
624  g_date_get_days_in_month(g_date_get_month(day_of_month),
625  g_date_get_year(day_of_month)));
626  g_date_set_day(day_of_month, allowable_date);
627  recurrenceSet(r, multiplier, PERIOD_MONTH, day_of_month, weekend_adjust);
628  }
629  return r;
630 }
631 
632 
633 void
634 gnc_frequency_save_to_recurrence(GncFrequency *gf, GList **recurrences, GDate *out_start_date)
635 {
636  GDate start_date;
637  gint page_index;
638 
639  gnc_date_edit_get_gdate(gf->startDate, &start_date);
640  if (out_start_date != NULL)
641  *out_start_date = start_date;
642 
643  if (recurrences == NULL)
644  return;
645 
646  page_index = gtk_notebook_get_current_page(gf->nb);
647 
648  switch (page_index)
649  {
650  case PAGE_NONE:
651  {
652  // empty-recurrence list ~~ none.
653  } break;
654  case PAGE_ONCE:
655  {
656  Recurrence *r = g_new0(Recurrence, 1);
657  recurrenceSet(r, 1, PERIOD_ONCE, &start_date, WEEKEND_ADJ_NONE);
658  *recurrences = g_list_append(*recurrences, r);
659  }
660  break;
661  case PAGE_DAILY:
662  {
663  gint multiplier = _get_multiplier_from_widget(gf, "daily_spin");
664  Recurrence *r = g_new0(Recurrence, 1);
665  recurrenceSet(r, multiplier, PERIOD_DAY, &start_date, WEEKEND_ADJ_NONE);
666  *recurrences = g_list_append(*recurrences, r);
667  }
668  break;
669  case PAGE_WEEKLY:
670  {
671  int multiplier = _get_multiplier_from_widget(gf, "weekly_spin");
672  int checkbox_idx;
673  for (checkbox_idx = 0; CHECKBOX_NAMES[checkbox_idx] != NULL; checkbox_idx++)
674  {
675  GDate *day_of_week_aligned_date;
676  Recurrence *r;
677  const char *day_widget_name = CHECKBOX_NAMES[checkbox_idx];
678  GtkWidget *weekday_checkbox = GTK_WIDGET(gtk_builder_get_object (gf->builder, day_widget_name));
679 
680  if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(weekday_checkbox)))
681  continue;
682 
683  day_of_week_aligned_date = g_date_new_julian(g_date_get_julian(&start_date));
684  // increment until we align on the DOW.
685  while ((g_date_get_weekday(day_of_week_aligned_date) % 7) != checkbox_idx)
686  g_date_add_days(day_of_week_aligned_date, 1);
687 
688  r = g_new0(Recurrence, 1);
689  recurrenceSet(r, multiplier, PERIOD_WEEK, day_of_week_aligned_date, WEEKEND_ADJ_NONE);
690 
691  *recurrences = g_list_append(*recurrences, r);
692  }
693  }
694  break;
695  case PAGE_SEMI_MONTHLY:
696  {
697  int multiplier = _get_multiplier_from_widget(gf, "semimonthly_spin");
698  *recurrences = g_list_append(*recurrences, _get_day_of_month_recurrence(gf, &start_date, multiplier, "semimonthly_first", "semimonthly_first_weekend"));
699  *recurrences = g_list_append(*recurrences, _get_day_of_month_recurrence(gf, &start_date, multiplier, "semimonthly_second", "semimonthly_second_weekend"));
700  }
701  break;
702  case PAGE_MONTHLY:
703  {
704  int multiplier = _get_multiplier_from_widget(gf, "monthly_spin");
705  Recurrence *r = _get_day_of_month_recurrence(gf, &start_date, multiplier, "monthly_day", "monthly_weekend");
706  *recurrences = g_list_append(*recurrences, r);
707  }
708  break;
709  default:
710  g_error("unknown page index [%d]", page_index);
711  break;
712  }
713 }
utility functions for the GnuCash UI
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
Period / Date Frequency Specification.
All type declarations for the whole Gnucash engine.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
Implementations.