norcal 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -1
- data/bin/norcal +217 -123
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 628317b98eb4536e13c1199961ce51f89f246add68275897845fa08654060de1
|
|
4
|
+
data.tar.gz: 23ef85bb70d9342b372fb443b6ab26a3664c7b39b69ec479bbc54fe7a10c6423
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a23ac49843b37ed383b08d6215a5a9f4f24416471539654053abd2574c4d4f5aa46e714d56d6f78a50a14a9e43d17652d08173d2cb08ddb6a584e8d2a6515432
|
|
7
|
+
data.tar.gz: d4392d35c14eda23aebf58669e627c908c754cda781cdba6d61f72e4dc1a9b135ed56c8ff387683bf4f3e2c919c512ed44c839aae7d2c6901f773f611e5281f7
|
data/README.md
CHANGED
|
@@ -31,7 +31,7 @@ gem install norcal
|
|
|
31
31
|
git clone https://github.com/baosen/norcal.git
|
|
32
32
|
cd norcal
|
|
33
33
|
gem build norcal.gemspec
|
|
34
|
-
gem install norcal-1.
|
|
34
|
+
gem install norcal-1.2.0.gem
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## Usage
|
|
@@ -52,6 +52,9 @@ norcal --light 2026 # light mode, specific year
|
|
|
52
52
|
- Notable dates: royal birthdays, Samefolkets dag, Morsdag, Farsdag, solverv, sommertid, and more
|
|
53
53
|
- Today highlighted with yellow background
|
|
54
54
|
- Dark mode (default) and light mode, with toggle button
|
|
55
|
+
- Zoom in/out (`+`/`–` buttons, Ctrl+scroll, Ctrl++/Ctrl+-)
|
|
56
|
+
- Auto-fit to screen height on startup, with `fit` button
|
|
57
|
+
- Scrollbar for zoomed-in views
|
|
55
58
|
|
|
56
59
|
## License
|
|
57
60
|
|
data/bin/norcal
CHANGED
|
@@ -66,7 +66,7 @@ def red_days(year)
|
|
|
66
66
|
e - 2 => "Langfredag",
|
|
67
67
|
e => "1. påskedag",
|
|
68
68
|
e + 1 => "2. påskedag",
|
|
69
|
-
Date.new(year, 5, 1) => "
|
|
69
|
+
Date.new(year, 5, 1) => "Arbeidernes dag",
|
|
70
70
|
e + 39 => "Kr. Himmelfartsdag",
|
|
71
71
|
Date.new(year, 5, 17) => "Grunnlovsdag",
|
|
72
72
|
e + 49 => "1. pinsedag",
|
|
@@ -102,7 +102,7 @@ def notable_dates(year)
|
|
|
102
102
|
[e - 2, "Langfredag", true],
|
|
103
103
|
[e, "1. påskedag", true],
|
|
104
104
|
[e + 1, "2. påskedag", true],
|
|
105
|
-
[Date.new(year, 5, 1), "
|
|
105
|
+
[Date.new(year, 5, 1), "Arbeidernes dag", true],
|
|
106
106
|
[Date.new(year, 5, 8), "Frigjøringsdag 1945", false],
|
|
107
107
|
[e + 39, "Kr. Himmelfartsdag", true],
|
|
108
108
|
[Date.new(year, 5, 17), "Grunnlovsdag 1814", true],
|
|
@@ -148,72 +148,108 @@ THEME = {
|
|
|
148
148
|
# Parse arguments: optional --light flag and optional year (dark is default)
|
|
149
149
|
$dark = ARGV.delete('--light') ? false : true
|
|
150
150
|
$year = (ARGV[0] || Date.today.year).to_i
|
|
151
|
+
$zoom = 0
|
|
151
152
|
|
|
152
153
|
def theme
|
|
153
154
|
THEME[$dark ? :dark : :light]
|
|
154
155
|
end
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
FONT = 'Noto Serif'
|
|
158
|
+
|
|
159
|
+
def make_fonts
|
|
160
|
+
z = $zoom
|
|
161
|
+
{
|
|
162
|
+
title: TkFont.new(family: FONT, size: 22 + z, weight: 'bold'),
|
|
163
|
+
mheader: TkFont.new(family: FONT, size: 13 + z, weight: 'bold'),
|
|
164
|
+
cal: TkFont.new(family: FONT, size: 12 + z),
|
|
165
|
+
cal_bold: TkFont.new(family: FONT, size: 12 + z, weight: 'bold'),
|
|
166
|
+
note: TkFont.new(family: FONT, size: 11 + z),
|
|
167
|
+
note_b: TkFont.new(family: FONT, size: 11 + z, weight: 'bold'),
|
|
168
|
+
note_d: TkFont.new(family: FONT, size: 11 + z),
|
|
169
|
+
btn: TkFont.new(family: FONT, size: 9 + z),
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
$root = TkRoot.new do
|
|
157
174
|
title "norcal"
|
|
158
175
|
end
|
|
159
176
|
|
|
160
|
-
#
|
|
161
|
-
ft_title = TkFont.new(family: 'Noto Serif', size: 22, weight: 'bold')
|
|
162
|
-
ft_mheader = TkFont.new(family: 'Noto Serif', size: 13, weight: 'bold')
|
|
163
|
-
ft_cal = TkFont.new(family: 'Noto Serif', size: 12)
|
|
164
|
-
ft_cal_bold = TkFont.new(family: 'Noto Serif', size: 12, weight: 'bold')
|
|
165
|
-
ft_note = TkFont.new(family: 'Noto Serif', size: 11)
|
|
166
|
-
ft_note_b = TkFont.new(family: 'Noto Serif', size: 11, weight: 'bold')
|
|
167
|
-
ft_note_d = TkFont.new(family: 'Noto Serif', size: 11)
|
|
168
|
-
ft_toggle = TkFont.new(family: 'Noto Serif', size: 9)
|
|
177
|
+
# -- Widget tracking for in-place updates --------------------------------------
|
|
169
178
|
|
|
170
|
-
#
|
|
179
|
+
# All trackable widgets, grouped by update type.
|
|
180
|
+
# Labels store {w: widget, fg: theme_key, bg: theme_key, font: font_key}.
|
|
181
|
+
# Buttons store {w: widget, fg: theme_key, text_key: theme_key or nil, text: static}.
|
|
182
|
+
$w = { bg: [], border: [], sep: [], labels: [], texts: [], buttons: [] }
|
|
171
183
|
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
# Apply current theme + zoom to all tracked widgets (no widget recreation).
|
|
185
|
+
def restyle
|
|
174
186
|
t = theme
|
|
187
|
+
f = make_fonts
|
|
188
|
+
|
|
189
|
+
$root.configure(background: t[:bg])
|
|
190
|
+
$canvas.configure(background: t[:bg])
|
|
191
|
+
$scrollbar.configure(background: t[:bg], troughcolor: t[:bg])
|
|
192
|
+
$root.title = "Kalender #{$year}"
|
|
175
193
|
|
|
194
|
+
$w[:bg].each { |w| w.configure(background: t[:bg]) }
|
|
195
|
+
$w[:border].each { |w| w.configure(background: t[:bg], highlightbackground: t[:border]) }
|
|
196
|
+
$w[:sep].each { |w| w.configure(background: t[:sep]) }
|
|
197
|
+
|
|
198
|
+
$w[:labels].each do |e|
|
|
199
|
+
e[:w].configure(foreground: t[e[:fg]], background: t[e[:bg]], font: f[e[:font]])
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
$w[:texts].each do |tw|
|
|
203
|
+
tab = [65 + $zoom * 4, 40].max
|
|
204
|
+
tw.configure(background: t[:bg], font: f[:note], tabs: tab.to_s)
|
|
205
|
+
tw.tag_configure('dt', foreground: t[:fg], font: f[:note_b])
|
|
206
|
+
tw.tag_configure('dtr', foreground: t[:red], font: f[:note_b])
|
|
207
|
+
tw.tag_configure('desc', foreground: t[:desc_fg], font: f[:note_d])
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
$w[:buttons].each do |e|
|
|
211
|
+
text = e[:text_key] ? t[e[:text_key]] : e[:text]
|
|
212
|
+
e[:w].configure(text: text, font: f[:btn], foreground: t[e[:fg]],
|
|
213
|
+
background: t[:bg], activebackground: t[:bg], activeforeground: t[:fg])
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# -- Build calendar (one-time per year) ----------------------------------------
|
|
218
|
+
|
|
219
|
+
def build_month(parent, year, month, holidays)
|
|
176
220
|
first = Date.new(year, month, 1)
|
|
177
221
|
last_day = Date.new(year, month, -1)
|
|
178
222
|
monday = first - (first.cwday - 1)
|
|
179
223
|
today = Date.today
|
|
180
224
|
|
|
181
|
-
outer = TkFrame.new(parent) {
|
|
182
|
-
|
|
183
|
-
highlightbackground t[:border]; highlightthickness 0
|
|
184
|
-
}
|
|
225
|
+
outer = TkFrame.new(parent) { borderwidth 1; relief 'solid'; highlightthickness 0 }
|
|
226
|
+
$w[:border] << outer
|
|
185
227
|
|
|
186
|
-
grid = TkFrame.new(outer) {
|
|
228
|
+
grid = TkFrame.new(outer) { padx 6; pady 4 }
|
|
229
|
+
$w[:bg] << grid
|
|
187
230
|
grid.pack
|
|
188
231
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}.grid(row: 0, column: 0, columnspan: 8, pady: [0, 2])
|
|
232
|
+
hdr = TkLabel.new(grid) { text MONTHS_NO[month - 1] }
|
|
233
|
+
$w[:labels] << { w: hdr, fg: :fg, bg: :bg, font: :mheader }
|
|
234
|
+
hdr.grid(row: 0, column: 0, columnspan: 8, pady: [0, 2])
|
|
193
235
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}.grid(row: 1, column: 0, sticky: 'e', padx: [0, 4])
|
|
236
|
+
uke = TkLabel.new(grid) { text 'Uke' }
|
|
237
|
+
$w[:labels] << { w: uke, fg: :dkgray, bg: :bg, font: :cal }
|
|
238
|
+
uke.grid(row: 1, column: 0, sticky: 'e', padx: [0, 4])
|
|
198
239
|
|
|
199
240
|
DAYS_NO.each_with_index do |d, i|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
end
|
|
205
|
-
TkLabel.new(grid) {
|
|
206
|
-
text d; font ft_cal_bold; foreground fg; background t[:bg]
|
|
207
|
-
}.grid(row: 1, column: i + 1, sticky: 'e')
|
|
241
|
+
fg_key = case i when 5 then :gray when 6 then :red else :fg end
|
|
242
|
+
lbl = TkLabel.new(grid) { text d }
|
|
243
|
+
$w[:labels] << { w: lbl, fg: fg_key, bg: :bg, font: :cal_bold }
|
|
244
|
+
lbl.grid(row: 1, column: i + 1, sticky: 'e')
|
|
208
245
|
end
|
|
209
246
|
|
|
210
|
-
# Week rows
|
|
211
247
|
row = 2
|
|
212
248
|
cur = monday
|
|
213
249
|
while cur <= last_day
|
|
214
|
-
TkLabel.new(grid) {
|
|
215
|
-
|
|
216
|
-
|
|
250
|
+
wk = TkLabel.new(grid) { text cur.cweek.to_s }
|
|
251
|
+
$w[:labels] << { w: wk, fg: :dkgray, bg: :bg, font: :cal }
|
|
252
|
+
wk.grid(row: row, column: 0, sticky: 'e', padx: [0, 4])
|
|
217
253
|
|
|
218
254
|
7.times do |i|
|
|
219
255
|
day = cur + i
|
|
@@ -221,149 +257,207 @@ def render_month(parent, year, month, holidays, fonts)
|
|
|
221
257
|
is_red = day.cwday == 7 || holidays.key?(day)
|
|
222
258
|
is_sat = day.cwday == 6 && !holidays.key?(day)
|
|
223
259
|
is_today = day == today
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
TkLabel.new(grid) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
foreground fg
|
|
232
|
-
background(is_today ? t[:today_bg] : t[:bg])
|
|
233
|
-
}.grid(row: row, column: i + 1, sticky: 'e')
|
|
260
|
+
fg_key = is_red ? :red : (is_sat ? :gray : :fg)
|
|
261
|
+
bg_key = is_today ? :today_bg : :bg
|
|
262
|
+
font_key = is_today ? :cal_bold : :cal
|
|
263
|
+
|
|
264
|
+
lbl = TkLabel.new(grid) { text day.day.to_s }
|
|
265
|
+
$w[:labels] << { w: lbl, fg: fg_key, bg: bg_key, font: font_key }
|
|
266
|
+
lbl.grid(row: row, column: i + 1, sticky: 'e')
|
|
234
267
|
end
|
|
235
268
|
end
|
|
236
269
|
row += 1
|
|
237
270
|
cur += 7
|
|
238
271
|
end
|
|
239
272
|
|
|
240
|
-
# Pad empty rows so all months have the same height (max 6 week rows)
|
|
241
273
|
while row < 8
|
|
242
|
-
TkLabel.new(grid) { text ' '
|
|
243
|
-
|
|
274
|
+
pad = TkLabel.new(grid) { text ' ' }
|
|
275
|
+
$w[:labels] << { w: pad, fg: :fg, bg: :bg, font: :cal }
|
|
276
|
+
pad.grid(row: row, column: 0)
|
|
244
277
|
row += 1
|
|
245
278
|
end
|
|
246
279
|
|
|
247
|
-
# Uniform column widths
|
|
248
280
|
(1..7).each { |c| grid.grid_columnconfigure(c, uniform: 'day', minsize: 22) }
|
|
249
|
-
|
|
250
281
|
outer
|
|
251
282
|
end
|
|
252
283
|
|
|
253
|
-
def
|
|
254
|
-
ft_note, ft_note_b, ft_note_d = fonts
|
|
255
|
-
t = theme
|
|
284
|
+
def build_notable(parent, year)
|
|
256
285
|
dates = notable_dates(year)
|
|
257
|
-
|
|
258
|
-
# Split into 3 roughly-equal columns
|
|
259
286
|
third = (dates.size / 3.0).ceil
|
|
260
|
-
cols
|
|
287
|
+
cols = dates.each_slice(third).to_a
|
|
261
288
|
|
|
262
|
-
outer = TkFrame.new(parent) {
|
|
263
|
-
|
|
264
|
-
highlightbackground t[:border]; highlightthickness 0
|
|
265
|
-
}
|
|
289
|
+
outer = TkFrame.new(parent) { borderwidth 1; relief 'solid'; highlightthickness 0 }
|
|
290
|
+
$w[:border] << outer
|
|
266
291
|
|
|
267
|
-
inner = TkFrame.new(outer)
|
|
292
|
+
inner = TkFrame.new(outer)
|
|
293
|
+
$w[:bg] << inner
|
|
268
294
|
inner.pack(padx: 5, pady: 5, fill: 'both')
|
|
269
295
|
|
|
270
296
|
cols.each_with_index do |entries, ci|
|
|
271
297
|
tw = TkText.new(inner) {
|
|
272
298
|
width 1; height(entries.size + 4)
|
|
273
|
-
|
|
274
|
-
wrap 'word';
|
|
275
|
-
cursor ''; insertwidth 0
|
|
276
|
-
tabs '65'
|
|
299
|
+
borderwidth 0; highlightthickness 0
|
|
300
|
+
wrap 'word'; padx 3; pady 2; cursor ''; insertwidth 0
|
|
277
301
|
}
|
|
278
|
-
|
|
279
|
-
tw.tag_configure('dtr', foreground: t[:red], font: ft_note_b)
|
|
280
|
-
tw.tag_configure('desc', foreground: t[:desc_fg], font: ft_note_d)
|
|
302
|
+
$w[:texts] << tw
|
|
281
303
|
|
|
282
304
|
entries.each_with_index do |(date, desc, is_red), i|
|
|
283
305
|
dtag = is_red ? 'dtr' : 'dt'
|
|
284
|
-
|
|
285
|
-
tw.insert('end', date_str, dtag)
|
|
306
|
+
tw.insert('end', "#{date.day}. #{MABBR_NO[date.month - 1]}", dtag)
|
|
286
307
|
tw.insert('end', "\t")
|
|
287
308
|
tw.insert('end', desc, 'desc')
|
|
288
309
|
tw.insert('end', "\n") unless i == entries.size - 1
|
|
289
310
|
end
|
|
290
|
-
|
|
291
311
|
tw.state 'disabled'
|
|
292
312
|
tw.grid(row: 0, column: ci * 2, sticky: 'nsew', padx: [0, 2])
|
|
293
313
|
inner.grid_columnconfigure(ci * 2, weight: 1, uniform: 'notecol')
|
|
294
314
|
inner.grid_rowconfigure(0, weight: 1)
|
|
295
315
|
|
|
296
|
-
# vertical separator between columns (except after last)
|
|
297
316
|
if ci < cols.size - 1
|
|
298
|
-
sep = TkFrame.new(inner) {
|
|
317
|
+
sep = TkFrame.new(inner) { width 1 }
|
|
318
|
+
$w[:sep] << sep
|
|
299
319
|
sep.grid(row: 0, column: ci * 2 + 1, sticky: 'ns', padx: 4)
|
|
300
320
|
end
|
|
301
321
|
end
|
|
302
|
-
|
|
303
322
|
outer
|
|
304
323
|
end
|
|
305
324
|
|
|
306
|
-
def
|
|
307
|
-
|
|
308
|
-
|
|
325
|
+
def build_buttons(parent)
|
|
326
|
+
btn_bar = TkFrame.new(parent)
|
|
327
|
+
$w[:bg] << btn_bar
|
|
328
|
+
btn_bar.grid(row: 5, column: 0, columnspan: 3, pady: [4, 0])
|
|
329
|
+
|
|
330
|
+
b1 = TkButton.new(btn_bar) { relief 'flat'; borderwidth 0; command { $dark = !$dark; restyle } }
|
|
331
|
+
$w[:buttons] << { w: b1, fg: :dkgray, text_key: :toggle_label, text: nil }
|
|
332
|
+
b1.pack(side: 'left', padx: 4)
|
|
333
|
+
|
|
334
|
+
b2 = TkButton.new(btn_bar) { relief 'flat'; borderwidth 0; command { $zoom += 1; restyle } }
|
|
335
|
+
$w[:buttons] << { w: b2, fg: :dkgray, text_key: nil, text: '+' }
|
|
336
|
+
b2.pack(side: 'left', padx: 2)
|
|
337
|
+
|
|
338
|
+
b3 = TkButton.new(btn_bar) { relief 'flat'; borderwidth 0; command { $zoom -= 1 if $zoom > -8; restyle } }
|
|
339
|
+
$w[:buttons] << { w: b3, fg: :dkgray, text_key: nil, text: "\u2013" }
|
|
340
|
+
b3.pack(side: 'left', padx: 2)
|
|
341
|
+
|
|
342
|
+
b4 = TkButton.new(btn_bar) { relief 'flat'; borderwidth 0; command { $fit.call(false) } }
|
|
343
|
+
$w[:buttons] << { w: b4, fg: :dkgray, text_key: nil, text: 'fit' }
|
|
344
|
+
b4.pack(side: 'left', padx: 2)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def build_all(year)
|
|
348
|
+
$w.each_value(&:clear)
|
|
349
|
+
$inner_frame&.destroy
|
|
350
|
+
|
|
309
351
|
holidays = red_days(year)
|
|
310
352
|
|
|
311
|
-
|
|
353
|
+
$inner_frame = TkFrame.new($canvas)
|
|
354
|
+
$w[:bg] << $inner_frame
|
|
355
|
+
|
|
356
|
+
title = TkLabel.new($inner_frame) { text year.to_s }
|
|
357
|
+
$w[:labels] << { w: title, fg: :title_fg, bg: :bg, font: :title }
|
|
358
|
+
title.pack(pady: [8, 2])
|
|
312
359
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
360
|
+
frame = TkFrame.new($inner_frame)
|
|
361
|
+
$w[:bg] << frame
|
|
362
|
+
frame.pack(padx: 10, pady: [2, 8])
|
|
363
|
+
|
|
364
|
+
grid_f = TkFrame.new(frame)
|
|
365
|
+
$w[:bg] << grid_f
|
|
366
|
+
grid_f.pack
|
|
316
367
|
|
|
317
368
|
12.times do |idx|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
m_frame.grid(row: idx / 3, column: idx % 3, padx: 3, pady: 3, sticky: 'nsew')
|
|
369
|
+
build_month(grid_f, year, idx + 1, holidays)
|
|
370
|
+
.grid(row: idx / 3, column: idx % 3, padx: 3, pady: 3, sticky: 'nsew')
|
|
321
371
|
end
|
|
322
372
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
note_f.grid(row: 4, column: 0, columnspan: 3, padx: 3, pady: [4, 0], sticky: 'ew')
|
|
326
|
-
|
|
327
|
-
# Toggle button below notable dates
|
|
328
|
-
TkButton.new(grid_f) {
|
|
329
|
-
text t[:toggle_label]; font fonts[5] # ft_note_d
|
|
330
|
-
relief 'flat'; borderwidth 0
|
|
331
|
-
foreground t[:dkgray]; background t[:bg]
|
|
332
|
-
activebackground t[:bg]; activeforeground t[:fg]
|
|
333
|
-
command { $dark = !$dark; $rebuild&.call }
|
|
334
|
-
}.grid(row: 5, column: 0, columnspan: 3, pady: [4, 0])
|
|
373
|
+
build_notable(grid_f, year)
|
|
374
|
+
.grid(row: 4, column: 0, columnspan: 3, padx: 3, pady: [4, 0], sticky: 'ew')
|
|
335
375
|
|
|
376
|
+
build_buttons(grid_f)
|
|
336
377
|
3.times { |c| grid_f.grid_columnconfigure(c, weight: 1) }
|
|
337
378
|
|
|
338
|
-
|
|
339
|
-
|
|
379
|
+
# Embed in canvas
|
|
380
|
+
$canvas.delete('all')
|
|
381
|
+
$canvas_win = $canvas.create(:window, 0, 0, window: $inner_frame, anchor: 'nw')
|
|
340
382
|
|
|
341
|
-
|
|
383
|
+
center = proc {
|
|
384
|
+
cw = $canvas.winfo_width; ch = $canvas.winfo_height
|
|
385
|
+
fw = $inner_frame.winfo_reqwidth; fh = $inner_frame.winfo_reqheight
|
|
386
|
+
$canvas.coords($canvas_win, [(cw - fw) / 2, 0].max, [(ch - fh) / 2, 0].max)
|
|
387
|
+
$canvas.configure(scrollregion: "0 0 #{[cw, fw].max} #{[ch, fh].max}")
|
|
388
|
+
}
|
|
389
|
+
$inner_frame.bind('Configure', center)
|
|
390
|
+
$canvas.bind('Configure', center)
|
|
342
391
|
|
|
343
|
-
|
|
344
|
-
|
|
392
|
+
restyle
|
|
393
|
+
end
|
|
345
394
|
|
|
346
|
-
|
|
347
|
-
|
|
395
|
+
# -- Scrollable wrapper --------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
$scrollbar = TkScrollbar.new($root)
|
|
398
|
+
$canvas = TkCanvas.new($root) { highlightthickness 0; borderwidth 0 }
|
|
399
|
+
$canvas.configure(yscrollcommand: proc { |*a| $scrollbar.set(*a) })
|
|
400
|
+
$scrollbar.command(proc { |*a| $canvas.yview(*a) })
|
|
401
|
+
$scrollbar.pack(side: 'right', fill: 'y')
|
|
402
|
+
$canvas.pack(side: 'left', fill: 'both', expand: true)
|
|
403
|
+
|
|
404
|
+
# Scrolling
|
|
405
|
+
$root.bind('MouseWheel') { |e| $canvas.yview_scroll(-e.delta / 120, 'units') }
|
|
406
|
+
$root.bind('Button-4') { $canvas.yview_scroll(-3, 'units') }
|
|
407
|
+
$root.bind('Button-5') { $canvas.yview_scroll(3, 'units') }
|
|
408
|
+
|
|
409
|
+
# Keyboard zoom
|
|
410
|
+
$root.bind('Control-plus') { $zoom += 1; restyle }
|
|
411
|
+
$root.bind('Control-equal') { $zoom += 1; restyle }
|
|
412
|
+
$root.bind('Control-minus') { $zoom -= 1 if $zoom > -8; restyle }
|
|
413
|
+
|
|
414
|
+
# Ctrl+mouse wheel zoom
|
|
415
|
+
$root.bind('Control-MouseWheel') { |e|
|
|
416
|
+
if e.delta > 0 then $zoom += 1 else $zoom -= 1 if $zoom > -8 end
|
|
417
|
+
restyle
|
|
348
418
|
}
|
|
349
|
-
$
|
|
419
|
+
$root.bind('Control-Button-4') { $zoom += 1; restyle }
|
|
420
|
+
$root.bind('Control-Button-5') { $zoom -= 1 if $zoom > -8; restyle }
|
|
350
421
|
|
|
351
|
-
# --
|
|
422
|
+
# -- Fit to window -------------------------------------------------------------
|
|
352
423
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
424
|
+
$fit = proc { |resize_window|
|
|
425
|
+
target_h = resize_window ? $root.winfo_screenheight - 80 : $root.winfo_height
|
|
426
|
+
cur_h = $inner_frame.winfo_reqheight.to_f
|
|
427
|
+
base_avg = 13.0
|
|
356
428
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
429
|
+
if cur_h > 0 && target_h > 0
|
|
430
|
+
cur_font = base_avg + $zoom
|
|
431
|
+
target_font = cur_font * (target_h / cur_h)
|
|
432
|
+
$zoom = (target_font - base_avg).floor
|
|
433
|
+
$zoom = [$zoom, -8].max
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
restyle
|
|
437
|
+
Tk.update_idletasks
|
|
438
|
+
|
|
439
|
+
if $inner_frame.winfo_reqheight > target_h
|
|
440
|
+
$zoom -= 1
|
|
441
|
+
restyle
|
|
442
|
+
Tk.update_idletasks
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
if resize_window
|
|
446
|
+
cw = $inner_frame.winfo_reqwidth + 20
|
|
447
|
+
ch = $inner_frame.winfo_reqheight
|
|
448
|
+
sw = $root.winfo_screenwidth
|
|
449
|
+
mh = $root.winfo_screenheight - 50
|
|
450
|
+
w = [cw, sw].min; h = [ch, mh].min
|
|
451
|
+
$root.geometry("#{w}x#{h}+#{(sw - w) / 2}+0")
|
|
452
|
+
end
|
|
366
453
|
}
|
|
367
454
|
|
|
368
|
-
|
|
455
|
+
# -- Startup -------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
$root.withdraw
|
|
458
|
+
build_all($year)
|
|
459
|
+
Tk.update_idletasks
|
|
460
|
+
$fit.call(true)
|
|
461
|
+
$root.deiconify
|
|
462
|
+
|
|
369
463
|
Tk.mainloop
|