1/* 2 * Copyright 2017–2019 elementary, Inc. (https://elementary.io) 3 * SPDX-License-Identifier: GPL-2.0-or-later 4 */ 5 6/** 7 * MessageDialog is an elementary OS styled dialog used to display a message to the user. 8 * 9 * The class itself is similar to it's Gtk equivalent {@link Gtk.MessageDialog} 10 * but follows elementary OS design conventions. 11 * 12 * See [[https://elementary.io/docs/human-interface-guidelines#dialogs|The Human Interface Guidelines for dialogs]] 13 * for more detailed disscussion on the dialog wording and design. 14 * 15 * ''Example''<<BR>> 16 * {{{ 17 * var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( 18 * "This is a primary text", 19 * "This is a secondary, multiline, long text. This text usually extends the primary text and prints e.g: the details of an error.", 20 * "applications-development", 21 * Gtk.ButtonsType.CLOSE 22 * ); 23 * 24 * var custom_widget = new Gtk.CheckButton.with_label ("Custom widget"); 25 * custom_widget.show (); 26 * 27 * message_dialog.custom_bin.add (custom_widget); 28 * message_dialog.run (); 29 * message_dialog.destroy (); 30 * }}} 31 * 32 * {{../doc/images/MessageDialog.png}} 33 */ 34public class Granite.MessageDialog : Granite.Dialog { 35 /** 36 * The primary text, title of the dialog. 37 */ 38 public string primary_text { 39 get { 40 return primary_label.label; 41 } 42 43 set { 44 primary_label.label = value; 45 } 46 } 47 48 /** 49 * The secondary text, body of the dialog. 50 */ 51 public string secondary_text { 52 get { 53 return secondary_label.label; 54 } 55 56 set { 57 secondary_label.label = value; 58 } 59 } 60 61 /** 62 * The {@link GLib.Icon} that is used to display the image 63 * on the left side of the dialog. 64 */ 65 public GLib.Icon image_icon { 66 owned get { 67 return image.gicon; 68 } 69 70 set { 71 image.set_from_gicon (value, Gtk.IconSize.DIALOG); 72 } 73 } 74 75 /** 76 * The {@link GLib.Icon} that is used to display a badge, bottom-end aligned, 77 * over the image on the left side of the dialog. 78 */ 79 public GLib.Icon badge_icon { 80 owned get { 81 return badge.gicon; 82 } 83 84 set { 85 badge.set_from_gicon (value, Gtk.IconSize.LARGE_TOOLBAR); 86 } 87 } 88 89 /** 90 * The {@link Gtk.Label} that displays the {@link Granite.MessageDialog.primary_text}. 91 * 92 * Most of the times, you will only want to modify the {@link Granite.MessageDialog.primary_text} string, 93 * this is available to set additional properites like {@link Gtk.Label.use_markup} if you wish to do so. 94 */ 95 public Gtk.Label primary_label { get; construct; } 96 97 /** 98 * The {@link Gtk.Label} that displays the {@link Granite.MessageDialog.secondary_text}. 99 * 100 * Most of the times, you will only want to modify the {@link Granite.MessageDialog.secondary_text} string, 101 * this is available to set additional properites like {@link Gtk.Label.use_markup} if you wish to do so. 102 */ 103 public Gtk.Label secondary_label { get; construct; } 104 105 /** 106 * The {@link Gtk.ButtonsType} value to display a set of buttons 107 * in the dialog. 108 * 109 * By design, some actions are not acceptable and such action values will not be added to the dialog, these include: 110 * 111 * * {@link Gtk.ButtonsType.OK} 112 * * {@link Gtk.ButtonsType.YES_NO} 113 * * {@link Gtk.ButtonsType.OK_CANCEL} 114 * 115 * If you wish to provide more specific actions for your dialog 116 * pass a {@link Gtk.ButtonsType.NONE} to {@link Granite.MessageDialog.MessageDialog} and manually 117 * add those actions with {@link Gtk.Dialog.add_buttons} or {@link Gtk.Dialog.add_action_widget}. 118 */ 119 public Gtk.ButtonsType buttons { 120 construct { 121 switch (value) { 122 case Gtk.ButtonsType.NONE: 123 break; 124 case Gtk.ButtonsType.CLOSE: 125 add_button (_("_Close"), Gtk.ResponseType.CLOSE); 126 break; 127 case Gtk.ButtonsType.CANCEL: 128 add_button (_("_Cancel"), Gtk.ResponseType.CANCEL); 129 break; 130 case Gtk.ButtonsType.OK: 131 case Gtk.ButtonsType.YES_NO: 132 case Gtk.ButtonsType.OK_CANCEL: 133 warning ("Unsupported GtkButtonsType value"); 134 break; 135 default: 136 warning ("Unknown GtkButtonsType value"); 137 break; 138 } 139 } 140 } 141 142 /** 143 * The custom area to add custom widgets. 144 * 145 * This bin can be used to add any custom widget to the message area such as a {@link Gtk.ComboBox} or {@link Gtk.CheckButton}. 146 * Note that after adding such widget you will need to call {@link Gtk.Widget.show} or {@link Gtk.Widget.show_all} on the widget itself for it to appear in the dialog. 147 * 148 * If you want to add multiple widgets to this area, create a new container such as {@link Gtk.Grid} or {@link Gtk.Box} and then add it to the custom bin. 149 * 150 * When adding a custom widget to the custom bin, the {@link Granite.MessageDialog.secondary_label}'s bottom margin will be expanded automatically 151 * to compensate for the additional widget in the dialog. 152 * Removing the previously added widget will remove the bottom margin. 153 * 154 * If you don't want to have any margin between your custom widget and the {@link Granite.MessageDialog.secondary_label}, simply add your custom widget 155 * and then set the {@link Gtk.Label.margin_bottom} of {@link Granite.MessageDialog.secondary_label} to 0. 156 */ 157 public Gtk.Bin custom_bin { get; construct; } 158 159 /** 160 * The image that's displayed in the dialog. 161 */ 162 private Gtk.Image image; 163 164 /** 165 * The badge that's displayed in the dialog. 166 */ 167 private Gtk.Image badge; 168 169 /** 170 * The main grid that's used to contain all dialog widgets. 171 */ 172 private Gtk.Grid message_grid; 173 174 /** 175 * The {@link Gtk.TextView} used to display an additional error message. 176 */ 177 private Gtk.TextView? details_view; 178 179 /** 180 * The {@link Gtk.Expander} used to hold the error details view. 181 */ 182 private Gtk.Expander? expander; 183 184 /** 185 * SingleWidgetBin is only used within this class for creating a Bin that 186 * holds only one widget. 187 */ 188 private class SingleWidgetBin : Gtk.Bin {} 189 190 /** 191 * Constructs a new {@link Granite.MessageDialog}. 192 * See {@link Granite.Dialog} for more details. 193 * 194 * @param primary_text the title of the dialog 195 * @param secondary_text the body of the dialog 196 * @param image_icon the {@link GLib.Icon} that is displayed on the left side of the dialog 197 * @param buttons the {@link Gtk.ButtonsType} value that decides what buttons to use, defaults to {@link Gtk.ButtonsType.CLOSE}, 198 * see {@link Granite.MessageDialog.buttons} on details and what values are accepted 199 */ 200 public MessageDialog ( 201 string primary_text, 202 string secondary_text, 203 GLib.Icon image_icon, 204 Gtk.ButtonsType buttons = Gtk.ButtonsType.CLOSE 205 ) { 206 Object ( 207 primary_text: primary_text, 208 secondary_text: secondary_text, 209 image_icon: image_icon, 210 buttons: buttons 211 ); 212 } 213 214 /** 215 * Constructs a new {@link Granite.MessageDialog} with an icon name as it's icon displayed in the image. 216 * This constructor is same as the main one but with a difference that 217 * you can pass an icon name string instead of manually creating the {@link GLib.Icon}. 218 * 219 * The {@link Granite.MessageDialog.image_icon} will store the created icon 220 * so you can retrieve it later with {@link GLib.Icon.to_string}. 221 * 222 * See {@link Gtk.Dialog} for more details. 223 * 224 * @param primary_text the title of the dialog 225 * @param secondary_text the body of the dialog 226 * @param image_icon_name the icon name to create the dialog image with 227 * @param buttons the {@link Gtk.ButtonsType} value that decides what buttons to use, defaults to {@link Gtk.ButtonsType.CLOSE}, 228 * see {@link Granite.MessageDialog.buttons} on details and what values are accepted 229 */ 230 public MessageDialog.with_image_from_icon_name ( 231 string primary_text, 232 string secondary_text, 233 string image_icon_name = "dialog-information", 234 Gtk.ButtonsType buttons = Gtk.ButtonsType.CLOSE 235 ) { 236 Object ( 237 primary_text: primary_text, 238 secondary_text: secondary_text, 239 image_icon: new ThemedIcon (image_icon_name), 240 buttons: buttons 241 ); 242 } 243 244 static construct { 245 Granite.init (); 246 } 247 248 class construct { 249 set_css_name (Gtk.STYLE_CLASS_MESSAGE_DIALOG); 250 } 251 252 construct { 253 resizable = false; 254 deletable = false; 255 skip_taskbar_hint = true; 256 257 image = new Gtk.Image (); 258 259 badge = new Gtk.Image (); 260 badge.halign = badge.valign = Gtk.Align.END; 261 badge.pixel_size = 24; 262 263 var overlay = new Gtk.Overlay (); 264 overlay.valign = Gtk.Align.START; 265 overlay.add (image); 266 overlay.add_overlay (badge); 267 268 primary_label = new Gtk.Label (null); 269 primary_label.get_style_context ().add_class (Granite.STYLE_CLASS_PRIMARY_LABEL); 270 primary_label.selectable = true; 271 primary_label.max_width_chars = 50; 272 primary_label.wrap = true; 273 primary_label.xalign = 0; 274 275 secondary_label = new Gtk.Label (null); 276 secondary_label.use_markup = true; 277 secondary_label.selectable = true; 278 secondary_label.max_width_chars = 50; 279 secondary_label.wrap = true; 280 secondary_label.xalign = 0; 281 282 custom_bin = new SingleWidgetBin (); 283 custom_bin.add.connect (() => { 284 secondary_label.margin_bottom = 18; 285 if (expander != null) { 286 custom_bin.margin_top = 6; 287 } 288 }); 289 290 custom_bin.remove.connect (() => { 291 secondary_label.margin_bottom = 0; 292 293 if (expander != null) { 294 custom_bin.margin_top = 0; 295 } 296 }); 297 298 message_grid = new Gtk.Grid (); 299 message_grid.column_spacing = 12; 300 message_grid.row_spacing = 6; 301 message_grid.margin_start = message_grid.margin_end = 12; 302 message_grid.attach (overlay, 0, 0, 1, 2); 303 message_grid.attach (primary_label, 1, 0, 1, 1); 304 message_grid.attach (secondary_label, 1, 1, 1, 1); 305 message_grid.attach (custom_bin, 1, 3, 1, 1); 306 message_grid.show_all (); 307 308 get_content_area ().add (message_grid); 309 } 310 311 /** 312 * Shows a terminal-like widget for error details that can be expanded by the user. 313 * 314 * This method can be useful to provide the user extended error details in a 315 * terminal-like text view. Calling this method will not add any widgets to the 316 * {@link Granite.MessageDialog.custom_bin}. 317 * 318 * Subsequent calls to this method will change the error message to a new one. 319 * 320 * @param error_message the detailed error message to display 321 */ 322 public void show_error_details (string error_message) { 323 if (details_view == null) { 324 secondary_label.margin_bottom = 18; 325 326 details_view = new Gtk.TextView (); 327 details_view.border_width = 6; 328 details_view.editable = false; 329 details_view.pixels_below_lines = 3; 330 details_view.wrap_mode = Gtk.WrapMode.WORD; 331 details_view.get_style_context ().add_class (Granite.STYLE_CLASS_TERMINAL); 332 333 var scroll_box = new Gtk.ScrolledWindow (null, null); 334 scroll_box.margin_top = 12; 335 scroll_box.min_content_height = 70; 336 scroll_box.add (details_view); 337 338 expander = new Gtk.Expander (_("Details")); 339 expander.add (scroll_box); 340 341 message_grid.attach (expander, 1, 2, 1, 1); 342 message_grid.show_all (); 343 344 if (custom_bin.get_child () != null) { 345 custom_bin.margin_top = 12; 346 } 347 } 348 349 details_view.buffer.text = error_message; 350 } 351} 352