Skip to content

Commit 7b16e05

Browse files
Merge pull request #1046 from theymightbetim/popup-cal
Popup calendar module
2 parents 993810c + 16413c1 commit 7b16e05

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# pylint: disable=C0111,R0903
2+
3+
"""Extended version of datetime module which displays a small popup calendar and can open google calendar in the browser
4+
Parameters:
5+
* calendar.format: strftime()-compatible formatting string
6+
* calendar.locale: locale to use rather than the system default
7+
* calendar.bg: background colors. default black.
8+
* calendar.fg: foreground colors. default white.
9+
* calendar.browserpath: path to broweser. default /usr/bin/firefox
10+
11+
"""
12+
13+
from __future__ import absolute_import
14+
from bumblebee_status.modules.core.datetime import Module
15+
import os
16+
17+
import core.module
18+
import core.widget
19+
import core.input
20+
import datetime
21+
import locale
22+
23+
def get_default_browser_linux():
24+
try:
25+
command = "xdg-settings get default-web-browser"
26+
process = os.popen(command)
27+
browser_id = process.read().strip()
28+
29+
if browser_id:
30+
# Convert .desktop ID to a more user-friendly name (basic handling)
31+
if ".desktop" in browser_id:
32+
browser_name = browser_id.replace(".desktop", "")
33+
return browser_name
34+
35+
# If it doesn't look like a .desktop id, return as is
36+
return browser_id
37+
else:
38+
return None
39+
except:
40+
return None
41+
42+
class Module(Module):
43+
def __init__(self, config, theme, dtlibrary=None):
44+
super().__init__(config, theme)
45+
46+
core.input.register(
47+
self, button=core.input.LEFT_MOUSE, cmd=self.display_calendar
48+
)
49+
50+
def display_calendar(self, widget):
51+
default_browser = get_default_browser_linux()
52+
import tkinter as tk
53+
from tkcalendar import Calendar
54+
55+
root = tk.Tk()
56+
window_width = 300
57+
window_height = 205
58+
59+
bg = self.parameter("bg", "black")
60+
fg = self.parameter("fg", "white")
61+
root.configure(bg=bg)
62+
63+
# Get screen width
64+
screen_width = root.winfo_screenwidth()
65+
x = screen_width - window_width
66+
y = 20
67+
68+
# Set the geometry of the window
69+
root.geometry(f"{window_width}x{window_height}+{x}+{y}")
70+
71+
month = datetime.datetime.now().month
72+
year = datetime.datetime.now().year
73+
root.title(f"{month} {year}")
74+
75+
def close_window():
76+
root.destroy()
77+
78+
close_button = tk.Button(
79+
root, text="X", bg="black", fg="white", command=close_window
80+
)
81+
close_button.place(x=290, y=0, width=10, height=10)
82+
83+
# Calendar widget
84+
85+
cal = Calendar(
86+
root,
87+
background=bg,
88+
foreground=fg,
89+
disabledbackground=bg,
90+
bordercolor=bg,
91+
headersbackground=bg,
92+
headersforeground=fg,
93+
normalbackground=bg,
94+
normalforeground=fg,
95+
weekendbackground=bg,
96+
weekendforeground=fg,
97+
othermonthforeground="grey",
98+
othermonthbackground=bg,
99+
othermonthwebackground=bg,
100+
othermonthweforground="grey",
101+
showweeknumbers=False,
102+
)
103+
cal.place(x=0, y=10, width=300, height=200)
104+
105+
def key_listener(event):
106+
if event.keysym == "Escape":
107+
close_window()
108+
if event.keysym == "Return":
109+
import subprocess
110+
111+
selected_date = cal.selection_get()
112+
browser_path = self.parameter("browserpath", "/usr/bin/firefox")
113+
url = f"https://calendar.google.com/calendar/u/0/r/day/{selected_date.strftime('%Y/%m/%d')}"
114+
subprocess.Popen([browser_path, url], stdout=subprocess.DEVNULL)
115+
close_window()
116+
return self.full_text
117+
118+
root.bind("<KeyRelease>", key_listener)
119+
root.mainloop()
120+
121+
122+
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

docs/modules.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,22 @@ contributed by `TheEdgeOfRage <https://github.com/TheEdgeOfRage>`_ - many thanks
627627

628628
.. image:: ../screenshots/caffeine.png
629629

630+
calendar
631+
~~~~~~~~
632+
Extended version of datetime module which displays a small popup calendar and can open google calendar in the browser
633+
634+
Parameters:
635+
* popupcal.format: strftime()-compatible formatting string
636+
* popupcal.locale: locale to use rather than the system default
637+
* popupcal.bg: background colors. default black.
638+
* popupcal.fg: foreground colors. default white.
639+
* popupcal.browserpath: path to broweser. default /usr/bin/firefox
640+
641+
Requires:
642+
tkcalendar
643+
644+
.. image:: ../screenshots/calendar.png
645+
630646
cmus
631647
~~~~
632648

requirements/modules/calendar.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tkcalendar

screenshots/calendar.png

8.95 KB
Loading
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import pytest
2+
from bumblebee_status.modules.contrib.calendar import Module, get_default_browser_linux
3+
from unittest.mock import MagicMock, patch
4+
from unittest import mock, TestCase
5+
from freezegun import freeze_time
6+
7+
import core.config
8+
import core.input
9+
import core.widget
10+
import modules.core.datetime
11+
12+
pytest.importorskip("datetime")
13+
14+
def test_load_module():
15+
__import__("modules.contrib.calendar")
16+
17+
# Mocking os.popen
18+
@pytest.fixture
19+
def mock_popen(mocker):
20+
return mocker.patch('os.popen')
21+
22+
23+
def test_get_default_browser_linux_valid_browser(mock_popen):
24+
# Simulate a valid browser ID with ".desktop"
25+
mock_popen.return_value.read.return_value = 'firefox.desktop\n'
26+
27+
# Call the function
28+
result = get_default_browser_linux()
29+
30+
# Assert the result after stripping '.desktop'
31+
assert result == 'firefox'
32+
33+
34+
def test_get_default_browser_linux_no_browser(mock_popen):
35+
# Simulate the case where no browser is set (empty string)
36+
mock_popen.return_value.read.return_value = ''
37+
38+
# Call the function
39+
result = get_default_browser_linux()
40+
41+
# Assert the result is None
42+
assert result is None
43+
44+
45+
def test_get_default_browser_linux_invalid_browser(mock_popen):
46+
# Simulate a browser ID without ".desktop" (invalid case)
47+
mock_popen.return_value.read.return_value = 'firefox\n'
48+
49+
# Call the function
50+
result = get_default_browser_linux()
51+
52+
# Assert the result is the browser ID as it is
53+
assert result == 'firefox'
54+
55+
56+
def test_get_default_browser_linux_exception(mock_popen):
57+
# Simulate an exception during the command execution
58+
mock_popen.side_effect = Exception("Command failed")
59+
60+
# Call the function
61+
result = get_default_browser_linux()
62+
63+
# Assert the result is None due to exception
64+
assert result is None
65+
66+
@pytest.fixture
67+
def module():
68+
config = {}
69+
theme = {}
70+
mock_dtlibrary = MagicMock()
71+
return Module(config, theme, dtlibrary=mock_dtlibrary)
72+
73+
# Test for display_calendar method (opening browser)
74+
@patch('subprocess.Popen')
75+
@patch('tkinter.Tk')
76+
@patch('tkcalendar.Calendar')
77+
@patch('modules.contrib.calendar.get_default_browser_linux', return_value='/usr/bin/firefox')
78+
def test_display_calendar(mock_get_browser, mock_Calendar, mock_Tk, mock_popen, module):
79+
# Mock tkinter methods
80+
mock_root = MagicMock()
81+
mock_Tk.return_value = mock_root
82+
mock_cal = MagicMock()
83+
mock_Calendar.return_value = mock_cal
84+
# Mock parameter calls
85+
module.parameter = MagicMock(browserpath='/usr/bin/firefox')
86+
# Run display_calendar
87+
module.display_calendar(None)
88+
# Assertions for tkinter window and calendar creation
89+
mock_Tk.assert_called_once()
90+
mock_Calendar.assert_called_once()
91+
# Check if subprocess.Popen is called with correct parameters
92+
mock_popen.assert_called_once()

0 commit comments

Comments
 (0)