norcal 1.3.0 → 1.5.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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -12
  3. data/README.md +7 -3
  4. data/bin/norcal +132 -63
  5. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfb181fb12eccf2f5a2a573755d884474dc74eebddf4391fd6a88c5adc55ec7d
4
- data.tar.gz: a6fe09085766141d71509c87c8ff79aec265453c1456eac18646112a39fe599e
3
+ metadata.gz: 1608e3179b744d9c6ace3748742e20a83b0b2991bf859613e84541514565a9b5
4
+ data.tar.gz: 246f277da99b0498f00a4056849c06bcc0c9ac2d3395728051a56d351c81ec46
5
5
  SHA512:
6
- metadata.gz: de5b45e2e61b174ec3a3e7b1910f0f50eb063ebc3deeecbe74996750a942aa50ed8d4d46e60bea7be0a47a5cdcddbce9e8433ffd596d7e7eeee031a500b48178
7
- data.tar.gz: f832d1ff523264fdf967f3ce98cd00bb8f77ada069d3b5ec6df432bc36b08cebe90c851824c2df22380cd381842c7af9aca63c23945c8750711d921804de965a
6
+ metadata.gz: 6baf6625c75e4e7d170a577be6cfbd8ba25be9a9db1654b45d03eeb4249adac817a6572c591d4b1275504446d4fbc31c1fb92b2ec5c2ab9f7b4f60fcb46d5182
7
+ data.tar.gz: 35f41710cd20ccd926d854ea21f2a599fc3ba779f85f8bf6ad32015ef4e63f79bf23247b8966f357f92f3e71d5421b177172862f2fe78f6eec13421345d611be
data/LICENSE CHANGED
@@ -1,15 +1,24 @@
1
- ISC License
1
+ This is free and unencumbered software released into the public domain.
2
2
 
3
- Copyright (c) 2026 baosen <chibaosen@gmail.com>
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
4
7
 
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
8
15
 
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org/>
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # norcal
2
2
 
3
- A local Norwegian desktop calendar.
3
+ A local Norwegian calendar.
4
4
 
5
5
  ![norcal screenshot](screenshot.png)
6
6
 
@@ -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.3.0.gem
34
+ gem install norcal-1.5.0.gem
35
35
  ```
36
36
 
37
37
  ## Usage
@@ -57,6 +57,10 @@ norcal --light 2026 # light mode, specific year
57
57
  - Auto-fit to screen height on startup, with `fit` button
58
58
  - Scrollbar for zoomed-in views
59
59
 
60
+ ## References
61
+
62
+ - Djupvik, Guro: [røde dager](https://lille.snl.no/r%C3%B8de_dager) i Lille norske leksikon på snl.no. Hentet 7. april 2026.
63
+
60
64
  ## License
61
65
 
62
- [ISC](LICENSE)
66
+ [Unlicense](LICENSE)
data/bin/norcal CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # norcal - Norwegian desktop calendar
2
+ # norcal - Norwegian calendar
3
3
 
4
4
  require 'date'
5
5
  require 'tk'
@@ -23,8 +23,9 @@ MONTHS_NO = %w[
23
23
  Juli August September Oktober November Desember
24
24
  ].freeze
25
25
 
26
- DAYS_NO = %w[Ma Ti On To Fr Lø Sø].freeze
27
- MABBR_NO = %w[jan feb mar apr mai jun jul aug sep okt nov des].freeze
26
+ DAYS_NO = %w[Ma Ti On To Fr Lø Sø].freeze
27
+ MABBR_NO = %w[jan feb mar apr mai jun jul aug sep okt nov des].freeze
28
+ QUARTERS = ['1. kvartal', '2. kvartal', '3. kvartal', '4. kvartal'].freeze
28
29
 
29
30
  # -- Date helpers --------------------------------------------------------------
30
31
 
@@ -62,13 +63,14 @@ def red_days(year)
62
63
  e = easter(year)
63
64
  {
64
65
  Date.new(year, 1, 1) => "Første nyttårsdag",
66
+ e - 7 => "Palmesøndag",
65
67
  e - 3 => "Skjærtorsdag",
66
68
  e - 2 => "Langfredag",
67
69
  e => "1. påskedag",
68
70
  e + 1 => "2. påskedag",
69
71
  Date.new(year, 5, 1) => "Arbeidernes dag",
70
72
  e + 39 => "Kristi himmelfartsdag",
71
- Date.new(year, 5, 17) => "Grunnlovsdag",
73
+ Date.new(year, 5, 17) => "Grunnlovsdagen 1814",
72
74
  e + 49 => "1. pinsedag",
73
75
  e + 50 => "2. pinsedag",
74
76
  Date.new(year, 12, 25) => "1. juledag",
@@ -77,66 +79,62 @@ def red_days(year)
77
79
  end
78
80
 
79
81
  # Notable dates: [date, description, show_date_in_red?]
82
+ # Red days are auto-included from red_days(); some get extra context in the
83
+ # notable_names override below.
80
84
  def notable_dates(year)
81
85
  e = easter(year)
86
+ holidays = red_days(year)
87
+
82
88
  morsdag = nth_wday(year, 2, 7, 2)
83
89
  farsdag = nth_wday(year, 11, 7, 2)
84
90
  sommer_start = last_wday(year, 3, 7)
85
91
  sommer_slutt = last_wday(year, 10, 7)
86
92
  fastelavn = e - 49
93
+ botsdag = last_wday(year, 10, 7)
87
94
  nov27 = Date.new(year, 11, 27)
88
95
  advent1 = nov27 + ((7 - nov27.cwday) % 7)
89
96
 
90
- botsdag = last_wday(year, 10, 7) # last Sunday in October
91
-
92
- [
93
- [Date.new(year, 1, 1), "Første nyttårsdag", true],
94
- [Date.new(year, 1, 21), "Prinsesse Ingrid Alexandra, #{year - 2004} år", false],
95
- [Date.new(year, 2, 6), "Samefolkets dag", false],
96
- [morsdag, "Morsdag", false],
97
- [Date.new(year, 2, 14), "Valentinsdag", false],
98
- [fastelavn, "Fastelavn", false],
99
- [Date.new(year, 2, 21), "Kong Harald, #{year - 1937} år", false],
100
- [Date.new(year, 3, 8), "Kvinnedagen", false],
101
- [Date.new(year, 3, 20), "Vårjevndøgn", false],
102
- [sommer_start, "Sommertid start", false],
103
- [e - 7, "Palmesøndag", true],
104
- [e - 3, "Skjærtorsdag", true],
105
- [e - 2, "Langfredag", true],
106
- [e - 1, "Påskeaften", false],
107
- [e, "1. påskedag", true],
108
- [e + 1, "2. påskedag", true],
109
- [Date.new(year, 5, 1), "Arbeidernes dag", true],
110
- [Date.new(year, 5, 8), "Frigjøringsdag 1945", false],
111
- [e + 39, "Kristi himmelfartsdag", true],
112
- [Date.new(year, 5, 17), "Grunnlovsdag 1814", true],
113
- [e + 48, "Pinseaften", false],
114
- [e + 49, "1. pinsedag", true],
115
- [e + 50, "2. pinsedag", true],
116
- [Date.new(year, 6, 7), "Unionsoppløsning 1905", false],
117
- [Date.new(year, 6, 21), "Sommersolverv", false],
118
- [Date.new(year, 6, 23), "Sankthansaften", false],
119
- [Date.new(year, 7, 4), "Dronning Sonja, #{year - 1937} år", false],
120
- [Date.new(year, 7, 20), "Kronprins Haakon, #{year - 1973} år", false],
121
- [Date.new(year, 7, 29), "Olsokdagen", false],
122
- [Date.new(year, 8, 19), "Kronprinsesse Mette-Marit, #{year - 1973} år", false],
123
- [Date.new(year, 9, 23), "Høstjevndøgn", false],
124
- [botsdag, "Bots- og bededag", false],
125
- [sommer_slutt, "Sommertid slutt", false],
126
- [Date.new(year, 10, 31), "Halloween", false],
127
- [Date.new(year, 11, 1), "Allehelgensdag", false],
128
- [farsdag, "Farsdag", false],
129
- [advent1, "1. søndag i advent", false],
130
- [advent1 + 7, "2. søndag i advent", false],
131
- [Date.new(year, 12, 13), "Luciadagen", false],
132
- [advent1 + 14, "3. søndag i advent", false],
133
- [advent1 + 21, "4. søndag i advent", false],
134
- [Date.new(year, 12, 21), "Vintersolverv", false],
135
- [Date.new(year, 12, 24), "Julaften", false],
136
- [Date.new(year, 12, 25), "1. juledag", true],
137
- [Date.new(year, 12, 26), "2. juledag", true],
138
- [Date.new(year, 12, 31), "Nyttårsaften", false],
139
- ].sort_by { |d, _, _| d }
97
+ # Start with all red days
98
+ entries = holidays.map { |date, name| [date, name, true] }
99
+
100
+ # Non-red notable dates
101
+ entries += [
102
+ [Date.new(year, 1, 21), "Prinsesse Ingrid Alexandra, #{year - 2004} år"],
103
+ [Date.new(year, 2, 6), "Samefolkets dag"],
104
+ [morsdag, "Morsdag"],
105
+ [Date.new(year, 2, 14), "Valentinsdag"],
106
+ [fastelavn, "Fastelavn"],
107
+ [Date.new(year, 2, 21), "Kong Harald, #{year - 1937} år"],
108
+ [Date.new(year, 3, 8), "Kvinnedagen"],
109
+ [Date.new(year, 3, 20), "Vårjevndøgn"],
110
+ [sommer_start, "Sommertid start"],
111
+ [e - 1, "Påskeaften"],
112
+ [Date.new(year, 5, 8), "Frigjøringsdagen 1945"],
113
+ [e + 48, "Pinseaften"],
114
+ [Date.new(year, 6, 7), "Unionsoppløsning 1905"],
115
+ [Date.new(year, 6, 21), "Sommersolverv"],
116
+ [Date.new(year, 6, 23), "Sankthansaften"],
117
+ [Date.new(year, 7, 4), "Dronning Sonja, #{year - 1937} år"],
118
+ [Date.new(year, 7, 20), "Kronprins Haakon, #{year - 1973} år"],
119
+ [Date.new(year, 7, 29), "Olsokdagen"],
120
+ [Date.new(year, 8, 19), "Kronprinsesse Mette-Marit, #{year - 1973} år"],
121
+ [Date.new(year, 9, 23), "Høstjevndøgn"],
122
+ [botsdag, "Bots- og bededag"],
123
+ [sommer_slutt, "Sommertid slutt"],
124
+ [Date.new(year, 10, 31), "Halloween"],
125
+ [Date.new(year, 11, 1), "Allehelgensdag"],
126
+ [farsdag, "Farsdag"],
127
+ [advent1, "1. søndag i advent"],
128
+ [advent1 + 7, "2. søndag i advent"],
129
+ [Date.new(year, 12, 13), "Luciadagen"],
130
+ [advent1 + 14, "3. søndag i advent"],
131
+ [advent1 + 21, "4. søndag i advent"],
132
+ [Date.new(year, 12, 21), "Vintersolverv"],
133
+ [Date.new(year, 12, 24), "Julaften"],
134
+ [Date.new(year, 12, 31), "Nyttårsaften"],
135
+ ].map { |date, name| [date, name, false] }
136
+
137
+ entries.sort_by { |d, _, _| d }
140
138
  end
141
139
 
142
140
  # -- GUI -----------------------------------------------------------------------
@@ -186,6 +184,66 @@ $root = TkRoot.new do
186
184
  title "norcal"
187
185
  end
188
186
 
187
+ # -- Tooltip -------------------------------------------------------------------
188
+
189
+ $tooltip = TkToplevel.new($root) { withdraw; overrideredirect true }
190
+ $tooltip_label = TkLabel.new($tooltip) {
191
+ justify 'left'; padx 6; pady 3; borderwidth 1; relief 'solid'; wraplength 0
192
+ }
193
+ $tooltip_label.pack
194
+
195
+ # Make tooltip transparent to mouse events (X11 input passthrough)
196
+ begin
197
+ Tk.tk_call('wm', 'attributes', $tooltip, '-type', 'tooltip')
198
+ rescue; end
199
+
200
+ def tooltip_show(widget, text)
201
+ t = theme
202
+ f = make_fonts
203
+ $tooltip_label.configure(
204
+ text: text, font: f[:btn], background: t[:bg], foreground: t[:fg],
205
+ highlightbackground: t[:border]
206
+ )
207
+ $tooltip.configure(background: t[:border])
208
+
209
+ # Force geometry recalculation so tooltip is never cropped
210
+ $tooltip.geometry('')
211
+ Tk.update_idletasks
212
+
213
+ # Position below the widget
214
+ x = widget.winfo_rootx
215
+ y = widget.winfo_rooty + widget.winfo_height + 2
216
+ # Keep on screen
217
+ screen_w = $root.winfo_screenwidth
218
+ screen_h = $root.winfo_screenheight
219
+ tip_w = $tooltip.winfo_reqwidth
220
+ tip_h = $tooltip.winfo_reqheight
221
+ x = screen_w - tip_w if x + tip_w > screen_w
222
+ y = widget.winfo_rooty - tip_h - 2 if y + tip_h > screen_h
223
+ $tooltip.geometry("+#{x}+#{y}")
224
+ $tooltip.deiconify
225
+ $tooltip.raise
226
+ end
227
+
228
+ def tooltip_hide
229
+ $tooltip.withdraw
230
+ end
231
+
232
+ def bind_tooltip(widget, text)
233
+ widget.bind('Enter') { tooltip_show(widget, text) }
234
+ widget.bind('Leave') { tooltip_hide }
235
+ end
236
+
237
+ # Build a lookup: Date -> [name, ...] for tooltips.
238
+ # Uses red_days names directly (no overrides), plus non-red notable names.
239
+ def date_names(year)
240
+ names = Hash.new { |h, k| h[k] = [] }
241
+ notable_dates(year).each do |d, n, _|
242
+ names[d] << n unless names[d].include?(n)
243
+ end
244
+ names
245
+ end
246
+
189
247
  # -- Widget tracking for in-place updates --------------------------------------
190
248
 
191
249
  # All trackable widgets, grouped by update type.
@@ -229,7 +287,7 @@ end
229
287
 
230
288
  # -- Build calendar (one-time per year) ----------------------------------------
231
289
 
232
- def build_month(parent, year, month, holidays)
290
+ def build_month(parent, year, month, holidays, names)
233
291
  first = Date.new(year, month, 1)
234
292
  last_day = Date.new(year, month, -1)
235
293
  monday = first - (first.cwday - 1)
@@ -274,9 +332,10 @@ def build_month(parent, year, month, holidays)
274
332
  bg_key = is_today ? :today_bg : :bg
275
333
  font_key = is_today ? :cal_bold : :cal
276
334
 
277
- lbl = TkLabel.new(grid) { text day.day.to_s }
335
+ lbl = TkLabel.new(grid) { text day.day.to_s; anchor 'e' }
278
336
  $w[:labels] << { w: lbl, fg: fg_key, bg: bg_key, font: font_key }
279
- lbl.grid(row: row, column: i + 1, sticky: 'e')
337
+ lbl.grid(row: row, column: i + 1, sticky: 'nsew')
338
+ bind_tooltip(lbl, names[day].join("\n")) if names.key?(day)
280
339
  end
281
340
  end
282
341
  row += 1
@@ -341,14 +400,14 @@ end
341
400
  def rebuild_notable
342
401
  $notable_frame&.destroy
343
402
  $notable_frame = build_notable($notable_parent, $year)
344
- $notable_frame.grid(row: 4, column: 0, columnspan: 3, padx: 3, pady: [4, 0], sticky: 'ew')
403
+ $notable_frame.grid(row: 8, column: 0, columnspan: 3, padx: 3, pady: [4, 0], sticky: 'ew')
345
404
  restyle
346
405
  end
347
406
 
348
407
  def build_buttons(parent)
349
408
  btn_bar = TkFrame.new(parent)
350
409
  $w[:bg] << btn_bar
351
- btn_bar.grid(row: 5, column: 0, columnspan: 3, pady: [4, 0])
410
+ btn_bar.grid(row: 9, column: 0, columnspan: 3, pady: [4, 0])
352
411
 
353
412
  b1 = TkButton.new(btn_bar) { relief 'flat'; borderwidth 0; command { $dark = !$dark; restyle } }
354
413
  $w[:buttons] << { w: b1, fg: :dkgray, text_key: :toggle_label, text: nil }
@@ -383,6 +442,7 @@ def build_all(year)
383
442
  $inner_frame&.destroy
384
443
 
385
444
  holidays = red_days(year)
445
+ names = date_names(year)
386
446
 
387
447
  $inner_frame = TkFrame.new($canvas)
388
448
  $w[:bg] << $inner_frame
@@ -399,14 +459,23 @@ def build_all(year)
399
459
  $w[:bg] << grid_f
400
460
  grid_f.pack
401
461
 
402
- 12.times do |idx|
403
- build_month(grid_f, year, idx + 1, holidays)
404
- .grid(row: idx / 3, column: idx % 3, padx: 3, pady: 3, sticky: 'nsew')
462
+ 4.times do |q|
463
+ label_row = q * 2
464
+ month_row = q * 2 + 1
465
+
466
+ qlbl = TkLabel.new(grid_f) { text QUARTERS[q] }
467
+ $w[:labels] << { w: qlbl, fg: :dkgray, bg: :bg, font: :cal }
468
+ qlbl.grid(row: label_row, column: 0, columnspan: 3, pady: [4, 0], sticky: 'w')
469
+
470
+ 3.times do |col|
471
+ build_month(grid_f, year, q * 3 + col + 1, holidays, names)
472
+ .grid(row: month_row, column: col, padx: 3, pady: 3, sticky: 'nsew')
473
+ end
405
474
  end
406
475
 
407
476
  $notable_parent = grid_f
408
477
  $notable_frame = build_notable(grid_f, year)
409
- $notable_frame.grid(row: 4, column: 0, columnspan: 3, padx: 3, pady: [4, 0], sticky: 'ew')
478
+ $notable_frame.grid(row: 8, column: 0, columnspan: 3, padx: 3, pady: [4, 0], sticky: 'ew')
410
479
 
411
480
  build_buttons(grid_f)
412
481
  3.times { |c| grid_f.grid_columnconfigure(c, weight: 1) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: norcal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - baosen
@@ -23,7 +23,7 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.6'
26
- description: A local Norwegian desktop calendar with Tk GUI
26
+ description: A local Norwegian calendar with Tk GUI
27
27
  email: chibaosen@gmail.com
28
28
  executables:
29
29
  - norcal
@@ -35,7 +35,7 @@ files:
35
35
  - bin/norcal
36
36
  homepage: https://github.com/baosen/norcal
37
37
  licenses:
38
- - ISC
38
+ - Unlicense
39
39
  metadata: {}
40
40
  rdoc_options: []
41
41
  require_paths:
@@ -53,5 +53,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
53
  requirements: []
54
54
  rubygems_version: 4.0.3
55
55
  specification_version: 4
56
- summary: Norwegian desktop calendar
56
+ summary: Norwegian calendar
57
57
  test_files: []