Skip to content

Commit 37035dd

Browse files
committed
Implement Dialog
Provides utilities to show info, error, dialog, question, custom dialogs. Compared to GTK: - Better alignment - Better button layout - Better DE integration
1 parent 6291e4e commit 37035dd

4 files changed

Lines changed: 425 additions & 1 deletion

File tree

‎tests/dialog.py‎

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#!/usr/bin/python3
2+
3+
import sys
4+
import os
5+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
6+
7+
import gi
8+
gi.require_version('Gtk', '3.0')
9+
from gi.repository import Gtk
10+
11+
from xapp.widgets import Dialog
12+
13+
LOREM = (
14+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
15+
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
16+
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
17+
"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
18+
"cillum dolore eu fugiat nulla pariatur."
19+
)
20+
21+
22+
class DemoWindow(Gtk.Window):
23+
24+
def __init__(self):
25+
super().__init__(title="XApp.widgets.Dialog() Demo")
26+
self.set_default_size(600, 500)
27+
self.set_border_width(12)
28+
29+
outer_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
30+
31+
grid = Gtk.Grid(column_spacing=12, row_spacing=8)
32+
33+
xapp_header = Gtk.Label()
34+
xapp_header.set_markup("<b>XApp</b>")
35+
grid.attach(xapp_header, 0, 0, 1, 1)
36+
37+
gtk_header = Gtk.Label()
38+
gtk_header.set_markup("<b>Gtk</b>")
39+
grid.attach(gtk_header, 1, 0, 1, 1)
40+
41+
rows = [
42+
("Info", self._xapp_info, self._gtk_info),
43+
("Warning", self._xapp_warning, self._gtk_warning),
44+
("Error", self._xapp_error, self._gtk_error),
45+
("Question", self._xapp_question, self._gtk_question),
46+
("Custom", self._xapp_custom, self._gtk_custom),
47+
("No title", self._xapp_no_title, self._gtk_no_title),
48+
("Large text", self._xapp_large_text, self._gtk_large_text),
49+
("Hint", self._xapp_hint, self._gtk_hint),
50+
("Dialog no icon", self._xapp_dialog_no_icon, self._gtk_dialog_no_icon),
51+
("Dialog icon", self._xapp_dialog_icon, self._gtk_dialog_icon),
52+
("Widget", self._xapp_widget, self._gtk_widget),
53+
]
54+
55+
for row, (label, xapp_cb, gtk_cb) in enumerate(rows, start=1):
56+
xapp_btn = Gtk.Button(label=f"Show {label} dialog")
57+
xapp_btn.connect("clicked", xapp_cb)
58+
grid.attach(xapp_btn, 0, row, 1, 1)
59+
60+
gtk_btn = Gtk.Button(label=f"Show {label} dialog")
61+
gtk_btn.connect("clicked", gtk_cb)
62+
grid.attach(gtk_btn, 1, row, 1, 1)
63+
64+
outer_box.pack_start(grid, True, True, 0)
65+
66+
self.result_label = Gtk.Label(label="Click a button to show a dialog.")
67+
outer_box.pack_start(self.result_label, False, False, 0)
68+
69+
self.add(outer_box)
70+
self.connect("destroy", Gtk.main_quit)
71+
self.show_all()
72+
73+
def _set_result(self, text):
74+
self.result_label.set_markup(f"<b>Result:</b> {text}")
75+
76+
# XApp variants
77+
78+
def _xapp_info(self, button):
79+
Dialog(self, "All done", "The operation completed successfully.").info()
80+
self._set_result("Info dialog closed")
81+
82+
def _xapp_warning(self, button):
83+
Dialog(self, "Disk space low", "You have less than 500 MB remaining.").warning()
84+
self._set_result("Warning dialog closed")
85+
86+
def _xapp_error(self, button):
87+
Dialog(self, "Could not save file", "Permission denied: /etc/hosts").error()
88+
self._set_result("Error dialog closed")
89+
90+
def _xapp_question(self, button):
91+
confirmed = Dialog(self, "Delete item?", "This action cannot be undone.").question()
92+
self._set_result(f"Question answered: {'Yes' if confirmed else 'No'}")
93+
94+
def _xapp_custom(self, button):
95+
dialog = Dialog(self, "Apply changes?", "Your settings will be saved and the service restarted.")
96+
dialog.add_button("Save", 1, "suggested-action")
97+
dialog.add_button("Discard", 2, "destructive-action")
98+
dialog.add_button("Cancel", 3)
99+
dialog.set_default_response(2)
100+
response = dialog.warning()
101+
self._set_result(f"Custom dialog response: {response}")
102+
103+
def _xapp_no_title(self, button):
104+
Dialog(self, message="This dialog has no title.").info()
105+
self._set_result("No-title dialog closed")
106+
107+
def _xapp_large_text(self, button):
108+
Dialog(self, "Lorem Ipsum", LOREM).info()
109+
self._set_result("Large text dialog closed")
110+
111+
def _xapp_hint(self, button):
112+
Dialog(self, "Reset settings?",
113+
"All preferences will be restored to their defaults.",
114+
hint="This only affects the current user and can be undone by restoring a backup.").warning()
115+
self._set_result("Hint dialog closed")
116+
117+
def _xapp_dialog_no_icon(self, button):
118+
Dialog(self, "Heads up", "This is a plain dialog with no icon.").dialog()
119+
self._set_result("Dialog (no icon) closed")
120+
121+
def _xapp_dialog_icon(self, button):
122+
Dialog(self, "Bluetooth", "A new device has been paired.").dialog("bluetooth-symbolic")
123+
self._set_result("Dialog (icon) closed")
124+
125+
def _xapp_widget(self, button):
126+
combo = Gtk.ComboBoxText()
127+
for opt in ("Option A", "Option B", "Option C"):
128+
combo.append_text(opt)
129+
combo.set_active(0)
130+
check = Gtk.CheckButton(label="Enable feature")
131+
dialog = Dialog(self, "Settings", "Configure the following options:")
132+
dialog.add_widget(combo)
133+
dialog.add_widget(check)
134+
response = dialog.info()
135+
if response == Gtk.ResponseType.OK:
136+
self._set_result(f"Widget dialog: {combo.get_active_text()!r}, enabled={check.get_active()}")
137+
else:
138+
self._set_result("Widget dialog cancelled")
139+
140+
# Gtk variants
141+
142+
def _gtk_info(self, button):
143+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
144+
message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK,
145+
text="All done")
146+
dialog.format_secondary_text("The operation completed successfully.")
147+
dialog.run()
148+
dialog.destroy()
149+
self._set_result("Info dialog closed")
150+
151+
def _gtk_warning(self, button):
152+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
153+
message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK,
154+
text="Disk space low")
155+
dialog.format_secondary_text("You have less than 500 MB remaining.")
156+
dialog.run()
157+
dialog.destroy()
158+
self._set_result("Warning dialog closed")
159+
160+
def _gtk_error(self, button):
161+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
162+
message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK,
163+
text="Could not save file")
164+
dialog.format_secondary_text("Permission denied: /etc/hosts")
165+
dialog.run()
166+
dialog.destroy()
167+
self._set_result("Error dialog closed")
168+
169+
def _gtk_question(self, button):
170+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
171+
message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO,
172+
text="Delete item?")
173+
dialog.format_secondary_text("This action cannot be undone.")
174+
response = dialog.run()
175+
dialog.destroy()
176+
confirmed = response == Gtk.ResponseType.YES
177+
self._set_result(f"Question answered: {'Yes' if confirmed else 'No'}")
178+
179+
def _gtk_custom(self, button):
180+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
181+
message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.NONE,
182+
text="Apply changes?")
183+
dialog.format_secondary_text("Your settings will be saved and the service restarted.")
184+
dialog.add_button("Save", 1)
185+
dialog.add_button("Discard", 2)
186+
dialog.add_button("Cancel", 3)
187+
dialog.get_widget_for_response(1).get_style_context().add_class("suggested-action")
188+
dialog.get_widget_for_response(2).get_style_context().add_class("destructive-action")
189+
response = dialog.run()
190+
dialog.destroy()
191+
self._set_result(f"Custom dialog response: {response}")
192+
193+
def _gtk_no_title(self, button):
194+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
195+
message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK)
196+
dialog.format_secondary_text("This dialog has no title.")
197+
dialog.run()
198+
dialog.destroy()
199+
self._set_result("No-title dialog closed")
200+
201+
def _gtk_large_text(self, button):
202+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
203+
message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK,
204+
text="Lorem Ipsum")
205+
dialog.format_secondary_text(LOREM)
206+
dialog.run()
207+
dialog.destroy()
208+
self._set_result("Large text dialog closed")
209+
210+
def _gtk_hint(self, button):
211+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
212+
message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK,
213+
text="Reset settings?")
214+
dialog.format_secondary_text("All preferences will be restored to their defaults.")
215+
hint_label = Gtk.Label()
216+
hint_label.set_markup('<small><i>This only affects the current user and can be undone by restoring a backup.</i></small>')
217+
hint_label.set_xalign(0)
218+
hint_label.set_line_wrap(True)
219+
dialog.get_message_area().pack_start(hint_label, False, False, 0)
220+
hint_label.show()
221+
dialog.run()
222+
dialog.destroy()
223+
self._set_result("Hint dialog closed")
224+
225+
def _gtk_dialog_no_icon(self, button):
226+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
227+
message_type=Gtk.MessageType.OTHER, buttons=Gtk.ButtonsType.OK,
228+
text="Heads up")
229+
dialog.format_secondary_text("This is a plain dialog with no icon.")
230+
dialog.run()
231+
dialog.destroy()
232+
self._set_result("Dialog (no icon) closed")
233+
234+
def _gtk_dialog_icon(self, button):
235+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
236+
message_type=Gtk.MessageType.OTHER, buttons=Gtk.ButtonsType.OK,
237+
text="Bluetooth")
238+
dialog.format_secondary_text("A new device has been paired.")
239+
image = Gtk.Image.new_from_icon_name("bluetooth", Gtk.IconSize.DIALOG)
240+
dialog.set_image(image)
241+
image.show()
242+
dialog.run()
243+
dialog.destroy()
244+
self._set_result("Dialog (icon) closed")
245+
246+
def _gtk_widget(self, button):
247+
dialog = Gtk.MessageDialog(transient_for=self, modal=True,
248+
message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK,
249+
text="Settings")
250+
dialog.format_secondary_text("Configure the following options:")
251+
combo = Gtk.ComboBoxText()
252+
for opt in ("Option A", "Option B", "Option C"):
253+
combo.append_text(opt)
254+
combo.set_active(0)
255+
check = Gtk.CheckButton(label="Enable feature")
256+
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
257+
vbox.pack_start(combo, False, False, 0)
258+
vbox.pack_start(check, False, False, 0)
259+
dialog.get_message_area().pack_start(vbox, False, False, 0)
260+
vbox.show_all()
261+
dialog.run()
262+
self._set_result(f"Widget dialog: {combo.get_active_text()!r}, enabled={check.get_active()}")
263+
dialog.destroy()
264+
265+
266+
if __name__ == "__main__":
267+
window = DemoWindow()
268+
Gtk.main()

‎xapp/meson.build‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ python3.install_sources(
1313
[
1414
'widgets/__init__.py',
1515
'widgets/ListEditor.py',
16+
'widgets/MessageDialog.py',
1617
],
1718
subdir: 'xapp/widgets'
1819
)

0 commit comments

Comments
 (0)