-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
// Copyright 2025 Shota FUJI
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// SPDX-License-Identifier: MPL-2.0
using GLib;
namespace TimeTracker.Widget {
private class CalendarView : Gtk.Box {
public Model.Model model { get; construct; }
public DateTime month { get; set construct; }
private Gtk.Box weekdays = new Gtk.Box(HORIZONTAL, 6) {
homogeneous = true,
margin_top = 4,
margin_start = 8,
margin_end = 8,
valign = START,
vexpand_set = true,
};
private Gtk.Grid grid = new Gtk.Grid() {
column_homogeneous = true,
column_spacing = 6,
row_spacing = 6,
margin_top = 8,
margin_bottom = 8,
margin_start = 8,
margin_end = 8,
};
private Gtk.Label title = new Gtk.Label("") {
css_classes = { "numeric", "heading" },
hexpand = true,
hexpand_set = true,
};
private Array<CalendarCell> cells = new Array<CalendarCell>();
public CalendarView(Model.Model model) {
Object(
model: model,
month: new DateTime.now_local(),
orientation: Gtk.Orientation.VERTICAL,
spacing: 8
);
}
construct {
this.add_css_class("background");
var header = new Gtk.Box(HORIZONTAL, 4) {
hexpand = true,
margin_top = 4,
margin_bottom = 4,
margin_start = 8,
margin_end = 8,
};
var prev = new Gtk.Button() {
label = "Prev",
};
prev.clicked.connect(() => {
month = month.add_months(-1);
});
header.append(prev);
header.append(title);
var next = new Gtk.Button() {
label = "Next",
};
next.clicked.connect(() => {
month = month.add_months(1);
});
header.append(next);
this.append(header);
for (int i = 0; i < 7; i++) {
var cell = new Gtk.Box(VERTICAL, 0) {
hexpand = true,
vexpand = true,
};
var label = new Gtk.Label("") {
css_classes = { "caption" },
halign = CENTER,
margin_top = 4,
margin_bottom = 4,
margin_start = 4,
margin_end = 4,
};
switch (i) {
case 0:
label.label = "Mon";
break;
case 1:
label.label = "Tue";
break;
case 2:
label.label = "Wed";
break;
case 3:
label.label = "Thu";
break;
case 4:
label.label = "Fri";
break;
case 5:
label.label = "Sat";
label.add_css_class("error");
break;
case 6:
label.label = "Sun";
label.add_css_class("error");
break;
}
cell.append(label);
weekdays.append(cell);
}
this.append(weekdays);
var scrollable = new Gtk.ScrolledWindow() {
hscrollbar_policy = NEVER,
hexpand = true,
vexpand = true,
};
this.append(scrollable);
scrollable.child = grid;
this.notify["month"].connect(() => {
this.render();
});
this.render();
}
private void render() {
// Ideally, this should be binding but GObject binding not working for
// get-only property... what a half ass system.
title.label = month.format("%Y-%m");
foreach (var cell in cells.data) {
grid.remove(cell);
}
cells = new Array<CalendarCell>();
var first_cell = start_of_week(start_of_month(month));
var last_cell = end_of_week(end_of_month(month));
for (var d = first_cell; d.compare(last_cell) < 1; d = d.add_days(1)) {
var timers = new Array<Model.Timer>();
for (uint i = 0; i < model.stopped_timers.n_items; i++) {
var timer = (Model.Timer) model.stopped_timers.get_item(i);
if (timer == null) {
continue;
}
var end = new DateTime.from_unix_local(timer.stopped_at);
if (d.get_year() != end.get_year() || d.get_day_of_year() != end.get_day_of_year()) {
continue;
}
timers.append_val(timer);
}
var cell = new CalendarCell(d, timers) {
dimmed = d.get_month() != month.get_month(),
hexpand = true,
vexpand = true,
};
cells.append_val(cell);
grid.attach(
cell,
d.get_day_of_week() - 1,
(int) (d.difference(first_cell) / GLib.TimeSpan.DAY / 7),
1, 1
);
}
}
private static DateTime start_of_month(DateTime d) {
return new DateTime(
d.get_timezone(),
d.get_year(), d.get_month(), 1,
0, 0, 0
);
}
private static DateTime end_of_month(DateTime d) {
return start_of_month(d).add_months(1).add_seconds(-1);
}
private static DateTime start_of_day(DateTime d) {
return new DateTime(
d.get_timezone(),
d.get_year(), d.get_month(), d.get_day_of_month(),
0, 0, 0
);
}
private static DateTime start_of_week(DateTime d) {
var day_of_week = d.get_day_of_week();
return start_of_day(d).add_days(-(day_of_week - 1));
}
private static DateTime end_of_week(DateTime d) {
var day_of_week = d.get_day_of_week();
return start_of_day(d).add_days(7 - day_of_week);
}
}
}