GnuCash  5.6-150-g038405b370+
gnc-recurrence.c
1 /********************************************************************
2  * gnc-recurrence.c -- GncRecurrence is a minimal GUI for *
3  * specifying a Recurrence. *
4  * *
5  * You see, small is _nice_. :) *
6  * Copyright (C) 2005, Chris Shoemaker <c.shoemaker@cox.net> *
7  * Copyright (C) 2011, Robert Fewell *
8  * *
9  * This program is free software; you can redistribute it and/or *
10  * modify it under the terms of the GNU General Public License as *
11  * published by the Free Software Foundation; either version 2 of *
12  * the License, or (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License*
20  * along with this program; if not, contact: *
21  * *
22  * Free Software Foundation Voice: +1-617-542-5942 *
23  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
24  * Boston, MA 02110-1301, USA gnu@gnu.org *
25  *******************************************************************/
26 #include <config.h>
27 
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30 
31 #include "dialog-utils.h"
32 #include "gnc-date.h"
33 #include "gnc-recurrence.h"
34 #include "gnc-date-edit.h"
35 #include "Recurrence.h"
36 #include "gnc-engine.h"
37 
38 static QofLogModule log_module = GNC_MOD_GUI;
39 
41 {
42  GtkBox widget;
43 
44  GtkWidget *gde_start;
45  GtkComboBox *gcb_period;
46  GtkCheckButton *gcb_eom;
47  GtkSpinButton *gsb_mult;
48  GtkCheckButton *nth_weekday;
49 
50  Recurrence recurrence;
51 };
52 
53 typedef enum
54 {
55  GNCRECURRENCE_CHANGED,
56  LAST_SIGNAL
57 } GNCR_Signals;
58 
59 typedef enum
60 {
61  GNCR_DAY,
62  GNCR_WEEK,
63  GNCR_MONTH,
64  GNCR_YEAR,
65 } UIPeriodType;
66 
67 G_DEFINE_TYPE (GncRecurrence, gnc_recurrence, GTK_TYPE_BOX)
68 
69 static UIPeriodType get_pt_ui(GncRecurrence *gr)
70 {
71  return (gtk_combo_box_get_active(gr->gcb_period));
72 }
73 
74 
75 static void set_pt_ui(GncRecurrence *gr, PeriodType pt)
76 {
77  UIPeriodType idx;
78  switch (pt)
79  {
80  case PERIOD_DAY:
81  idx = 0;
82  break;
83  case PERIOD_WEEK:
84  idx = 1;
85  break;
86  case PERIOD_MONTH:
87  case PERIOD_END_OF_MONTH:
88  case PERIOD_NTH_WEEKDAY:
89  case PERIOD_LAST_WEEKDAY:
90  idx = 2;
91  break;
92  case PERIOD_YEAR:
93  idx = 3;
94  break;
95  default:
96  return;
97  }
98  gtk_combo_box_set_active(gr->gcb_period, idx);
99 
100  gtk_toggle_button_set_active(
101  GTK_TOGGLE_BUTTON(gr->nth_weekday),
102  (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY));
103 
104  gtk_toggle_button_set_active(
105  GTK_TOGGLE_BUTTON(gr->gcb_eom),
106  (pt == PERIOD_END_OF_MONTH || pt == PERIOD_LAST_WEEKDAY));
107 }
108 
109 
110 static gboolean
111 is_ambiguous_relative(const GDate *date)
112 {
113  GDateDay d;
114  guint8 dim;
115 
116  d = g_date_get_day(date);
117  dim = g_date_get_days_in_month(
118  g_date_get_month(date), g_date_get_year(date));
119  return ((d - 1) / 7 == 3) && (dim - d < 7);
120 }
121 
122 
123 static gboolean
124 is_ambiguous_absolute(const GDate *date)
125 {
126  return (g_date_is_last_of_month(date) &&
127  (g_date_get_day(date) < 31));
128 }
129 
130 
131 static void
132 something_changed( GtkWidget *wid, gpointer d )
133 {
134  UIPeriodType pt;
135  GDate start;
136  gboolean show_last, use_wd;
137  GncRecurrence *gr = GNC_RECURRENCE(d);
138 
139 
140  pt = get_pt_ui(gr);
141  gnc_date_edit_get_gdate(GNC_DATE_EDIT(gr->gde_start), &start);
142 
143  if (pt == GNCR_MONTH)
144  g_object_set(G_OBJECT(gr->nth_weekday), "visible", TRUE, NULL);
145  else
146  {
147  g_object_set(G_OBJECT(gr->nth_weekday), "visible", FALSE, NULL);
148  gtk_toggle_button_set_active(
149  GTK_TOGGLE_BUTTON(gr->nth_weekday), FALSE);
150  }
151  use_wd = gtk_toggle_button_get_active(
152  GTK_TOGGLE_BUTTON(gr->nth_weekday));
153  //TODO: change label
154 
155  /* The case under which we show the "end of month" flag is very
156  narrow, because we can almost always DTRT without it. */
157  if (pt == GNCR_MONTH)
158  {
159  if (use_wd)
160  show_last = is_ambiguous_relative(&start);
161  else
162  show_last = is_ambiguous_absolute(&start);
163  }
164  else
165  {
166  show_last = FALSE;
167  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gr->gcb_eom), FALSE);
168  }
169  g_object_set(G_OBJECT(gr->gcb_eom), "visible", show_last, NULL);
170 
171  g_signal_emit_by_name(d, "changed");
172 }
173 
174 
175 static void
176 gnc_recurrence_init( GncRecurrence *gr )
177 {
178  GtkBox *vb;
179  GtkBox *hb;
180  GtkWidget *w;
181  GtkBuilder *builder;
182 
183  recurrenceSet(&gr->recurrence, 1, PERIOD_MONTH, NULL, WEEKEND_ADJ_NONE);
184 
185  // Set the name for this widget so it can be easily manipulated with css
186  gtk_widget_set_name (GTK_WIDGET(gr), "gnc-id-recurrence");
187 
188  /* Open up the builder file */
189  builder = gtk_builder_new();
190  gnc_builder_add_from_file (builder, "gnc-recurrence.glade", "GCB_PeriodType_liststore");
191  gnc_builder_add_from_file (builder, "gnc-recurrence.glade", "GSB_Mult_Adj");
192  gnc_builder_add_from_file (builder, "gnc-recurrence.glade", "RecurrenceEntryVBox");
193 
194  vb = GTK_BOX(gtk_builder_get_object (builder, "RecurrenceEntryVBox"));
195  hb = GTK_BOX(gtk_builder_get_object (builder, "Startdate_hbox"));
196  w = gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE);
197  gr->gde_start = w;
198  gtk_box_pack_start (GTK_BOX (hb), w, TRUE, TRUE, 0);
199  gtk_widget_show (w);
200 
201  gtk_widget_set_no_show_all(GTK_WIDGET(gr->gde_start), TRUE);
202  gr->gcb_period = GTK_COMBO_BOX(gtk_builder_get_object (builder, "GCB_PeriodType"));
203  gr->gsb_mult = GTK_SPIN_BUTTON(gtk_builder_get_object (builder, "GSB_Mult"));
204  gr->gcb_eom = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "GCB_EndOfMonth"));
205  gr->nth_weekday = GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "GCB_NthWeekday"));
206  gtk_widget_set_no_show_all(GTK_WIDGET(gr->gcb_eom), TRUE);
207  gtk_widget_set_no_show_all(GTK_WIDGET(gr->nth_weekday), TRUE);
208 
209  gtk_container_add( GTK_CONTAINER(&gr->widget), GTK_WIDGET(vb) );
210 
211  gnc_recurrence_set(gr, &gr->recurrence);
212  something_changed( GTK_WIDGET(gr), gr);
213 
214  /* Setup the signals */
215  g_signal_connect( G_OBJECT(gr->gde_start), "date_changed",
216  G_CALLBACK(something_changed), gr );
217  g_signal_connect( G_OBJECT(gr->gcb_period), "changed",
218  G_CALLBACK(something_changed), gr );
219  g_signal_connect( G_OBJECT(gr->gsb_mult), "value-changed",
220  G_CALLBACK(something_changed), gr );
221  g_signal_connect( G_OBJECT(gr->gcb_eom), "toggled",
222  G_CALLBACK(something_changed), gr );
223  g_signal_connect( G_OBJECT(gr->nth_weekday), "toggled",
224  G_CALLBACK(something_changed), gr );
225 
226  gtk_widget_show_all( GTK_WIDGET(&gr->widget) );
227 
228  gtk_builder_connect_signals(builder, gr);
229  g_object_unref(G_OBJECT(builder));
230 }
231 
232 
233 void
234 gnc_recurrence_set(GncRecurrence *gr, const Recurrence *r)
235 {
236  PeriodType pt;
237  guint mult;
238  GDate start;
239 
240  g_return_if_fail(gr && r);
241  pt = recurrenceGetPeriodType(r);
242  mult = recurrenceGetMultiplier(r);
243  start = recurrenceGetDate(r);
244 
245  gtk_spin_button_set_value(gr->gsb_mult, (gdouble) mult);
246 
247  // is there some better way?
248  {
249  time64 t;
250  t = gnc_time64_get_day_start_gdate (&start);
251  gnc_date_edit_set_time (GNC_DATE_EDIT(gr->gde_start), t);
252  }
253 
254  set_pt_ui(gr, pt);
255 }
256 
257 
258 const Recurrence *
259 gnc_recurrence_get(GncRecurrence *gr)
260 {
261  guint mult;
262  UIPeriodType period;
263  PeriodType pt;
264  GDate start;
265  gboolean use_eom = FALSE, rel;
266 
267  mult = (guint) gtk_spin_button_get_value_as_int(gr->gsb_mult);
268  gnc_date_edit_get_gdate(GNC_DATE_EDIT(gr->gde_start), &start);
269  period = get_pt_ui(gr);
270 
271  switch (period)
272  {
273  case GNCR_DAY:
274  pt = PERIOD_DAY;
275  break;
276  case GNCR_WEEK:
277  pt = PERIOD_WEEK;
278  break;
279  case GNCR_MONTH:
280  rel = gtk_toggle_button_get_active(
281  GTK_TOGGLE_BUTTON(gr->nth_weekday));
282  if (rel)
283  {
284  if (is_ambiguous_relative(&start))
285  {
286  use_eom = gtk_toggle_button_get_active(
287  GTK_TOGGLE_BUTTON(gr->gcb_eom));
288  }
289  else
290  {
291  GDateDay d;
292  d = g_date_get_day(&start);
293 
294  use_eom = ((d - 1) / 7 == 4);
295  }
296  if (use_eom)
297  pt = PERIOD_LAST_WEEKDAY;
298  else pt = PERIOD_NTH_WEEKDAY;
299  }
300  else
301  {
302  if (g_date_is_last_of_month(&start) &&
303  (g_date_get_day(&start) < 31))
304  {
305  // ambiguous, need to examine the checkbox
306  use_eom = gtk_toggle_button_get_active(
307  GTK_TOGGLE_BUTTON(gr->gcb_eom));
308  }
309  else
310  {
311  // if it's the last dom, use eom anyway because it's the 31st.
312  use_eom = g_date_is_last_of_month(&start);
313  }
314  if (use_eom)
315  pt = PERIOD_END_OF_MONTH;
316  else pt = PERIOD_MONTH;
317  }
318  break;
319  case GNCR_YEAR:
320  pt = PERIOD_YEAR;
321  break;
322  default:
323  pt = PERIOD_INVALID;
324  }
325 
326  recurrenceSet(&gr->recurrence, mult, pt, &start, WEEKEND_ADJ_NONE);
327  return &gr->recurrence;
328 }
329 
330 
331 static void
332 gnc_recurrence_finalize(GObject *o)
333 {
334  GncRecurrence *gr = GNC_RECURRENCE(o);
335 
336  if (gr)
337  G_OBJECT_CLASS (gnc_recurrence_parent_class)->finalize (o);
338 }
339 
340 
341 static void
342 gnc_recurrence_class_init( GncRecurrenceClass *klass )
343 {
344  GObjectClass *object_class;
345 
346  object_class = G_OBJECT_CLASS (klass);
347  g_signal_new ("changed",
348  G_OBJECT_CLASS_TYPE (object_class),
349  G_SIGNAL_RUN_FIRST,
350  0,
351  NULL,
352  NULL,
353  g_cclosure_marshal_VOID__VOID,
354  G_TYPE_NONE,
355  0);
356 
357  object_class->finalize = gnc_recurrence_finalize;
358 }
359 
360 GtkWidget *
361 gnc_recurrence_new()
362 {
363  GncRecurrence *gr;
364 
365  ENTER(" ");
366  gr = g_object_new(gnc_recurrence_get_type(), NULL);
367  LEAVE(" ");
368  return GTK_WIDGET(gr);
369 }
370 
371 /* ========================= END OF FILE =========================== */
Date and Time handling routines.
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
All type declarations for the whole Gnucash engine.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
time64 gnc_time64_get_day_start_gdate(const GDate *date)
The gnc_time64_get_day_start() routine will take the given time in GLib GDate format and adjust it to...
Definition: gnc-date.cpp:1418