1 #!/usr/bin/env python 2 3 """ 4 Simple desktop dialogue box support for Python. 5 6 Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 22 -------- 23 24 Opening Dialogue Boxes (Dialogs) 25 -------------------------------- 26 27 To open a dialogue box (dialog) in the current desktop environment, relying on 28 the automatic detection of that environment, use the appropriate dialogue box 29 class: 30 31 question = desktop.dialog.Question("Are you sure?") 32 question.open() 33 34 To override the detected desktop, specify the desktop parameter to the open 35 function as follows: 36 37 question.open("KDE") # Insists on KDE 38 question.open("GNOME") # Insists on GNOME 39 40 The dialogue box options are documented in each class's docstring. 41 42 Available dialogue box classes are listed in the desktop.dialog.available 43 attribute. 44 """ 45 46 from desktop import use_desktop, _run, _readfrom, _status 47 48 class _wrapper: 49 def __init__(self, handler): 50 self.handler = handler 51 52 class _readvalue(_wrapper): 53 def __call__(self, cmd, shell): 54 return self.handler(cmd, shell).strip() 55 56 class _readinput(_wrapper): 57 def __call__(self, cmd, shell): 58 return self.handler(cmd, shell)[:-1] 59 60 class _readvalues_kdialog(_wrapper): 61 def __call__(self, cmd, shell): 62 result = self.handler(cmd, shell).strip().strip('"') 63 if result: 64 return result.split('" "') 65 else: 66 return [] 67 68 class _readvalues_zenity(_wrapper): 69 def __call__(self, cmd, shell): 70 result = self.handler(cmd, shell).strip() 71 if result: 72 return result.split("|") 73 else: 74 return [] 75 76 class _readvalues_Xdialog(_wrapper): 77 def __call__(self, cmd, shell): 78 result = self.handler(cmd, shell).strip() 79 if result: 80 return result.split("/") 81 else: 82 return [] 83 84 # Dialogue parameter classes. 85 86 class String: 87 88 "A generic parameter." 89 90 def __init__(self, name): 91 self.name = name 92 93 def convert(self, value, program): 94 return [value or ""] 95 96 class Strings(String): 97 98 "Multiple string parameters." 99 100 def convert(self, value, program): 101 return value or [] 102 103 class StringPairs(String): 104 105 "Multiple string parameters duplicated to make identifiers." 106 107 def convert(self, value, program): 108 l = [] 109 for v in value: 110 l.append(v) 111 l.append(v) 112 return l 113 114 class StringKeyword: 115 116 "A keyword parameter." 117 118 def __init__(self, keyword, name): 119 self.keyword = keyword 120 self.name = name 121 122 def convert(self, value, program): 123 return [self.keyword + "=" + (value or "")] 124 125 class StringKeywords: 126 127 "Multiple keyword parameters." 128 129 def __init__(self, keyword, name): 130 self.keyword = keyword 131 self.name = name 132 133 def convert(self, value, program): 134 l = [] 135 for v in value or []: 136 l.append(self.keyword + "=" + v) 137 return l 138 139 class Integer(String): 140 141 "An integer parameter." 142 143 defaults = { 144 "width" : 40, 145 "height" : 15, 146 "list_height" : 10 147 } 148 scale = 8 149 150 def __init__(self, name, pixels=0): 151 String.__init__(self, name) 152 if pixels: 153 self.factor = self.scale 154 else: 155 self.factor = 1 156 157 def convert(self, value, program): 158 if value is None: 159 value = self.defaults[self.name] 160 return [str(int(value) * self.factor)] 161 162 class IntegerKeyword(Integer): 163 164 "An integer keyword parameter." 165 166 def __init__(self, keyword, name, pixels=0): 167 Integer.__init__(self, name, pixels) 168 self.keyword = keyword 169 170 def convert(self, value, program): 171 if value is None: 172 value = self.defaults[self.name] 173 return [self.keyword + "=" + str(int(value) * self.factor)] 174 175 class Boolean(String): 176 177 "A boolean parameter." 178 179 values = { 180 "kdialog" : ["off", "on"], 181 "zenity" : ["FALSE", "TRUE"], 182 "Xdialog" : ["off", "on"] 183 } 184 185 def convert(self, value, program): 186 values = self.values[program] 187 if value: 188 return [values[1]] 189 else: 190 return [values[0]] 191 192 class MenuItemList(String): 193 194 "A menu item list parameter." 195 196 def convert(self, value, program): 197 l = [] 198 for v in value: 199 l.append(v.value) 200 l.append(v.text) 201 return l 202 203 class ListItemList(String): 204 205 "A radiolist/checklist item list parameter." 206 207 def __init__(self, name, status_first=0): 208 String.__init__(self, name) 209 self.status_first = status_first 210 211 def convert(self, value, program): 212 l = [] 213 for v in value: 214 boolean = Boolean(None) 215 status = boolean.convert(v.status, program) 216 if self.status_first: 217 l += status 218 l.append(v.value) 219 l.append(v.text) 220 if not self.status_first: 221 l += status 222 return l 223 224 # Dialogue argument values. 225 226 class MenuItem: 227 228 "A menu item which can also be used with radiolists and checklists." 229 230 def __init__(self, value, text, status=0): 231 self.value = value 232 self.text = text 233 self.status = status 234 235 # Dialogue classes. 236 237 class Dialogue: 238 239 commands = { 240 "KDE" : "kdialog", 241 "GNOME" : "zenity", 242 "X11" : "Xdialog" 243 } 244 245 def open(self, desktop=None): 246 247 """ 248 Open a dialogue box (dialog) using a program appropriate to the desktop 249 environment in use. 250 251 If the optional 'desktop' parameter is specified then attempt to use that 252 particular desktop environment's mechanisms to open the dialog instead of 253 guessing or detecting which environment is being used. 254 255 Suggested values for 'desktop' are "standard", "KDE", "GNOME", "Mac OS X", 256 "Windows". 257 258 The result of the dialogue interaction may be a string indicating user 259 input (for input, password, menu, radiolist, pulldown), a list of strings 260 indicating selections of one or more items (for checklist), or a value 261 indicating true or false (for question). 262 """ 263 264 # Decide on the desktop environment in use. 265 266 desktop_in_use = use_desktop(desktop) 267 268 # Get the program. 269 270 try: 271 program = self.commands[desktop_in_use] 272 except KeyError: 273 raise OSError, "Desktop '%s' not supported (no known dialogue box command could be suggested)" % desktop_in_use 274 275 handler, options = self.info[program] 276 277 cmd = [program] 278 for option in options: 279 if isinstance(option, str): 280 cmd.append(option) 281 else: 282 value = getattr(self, option.name, None) 283 cmd += option.convert(value, program) 284 285 return handler(cmd, 0) 286 287 class Simple(Dialogue): 288 def __init__(self, text, width=None, height=None): 289 self.text = text 290 self.width = width 291 self.height = height 292 293 class Question(Simple): 294 295 """ 296 A dialogue asking a question and showing response buttons. 297 Options: text, width (in characters), height (in characters) 298 """ 299 300 name = "question" 301 info = { 302 "kdialog" : (_status, ["--yesno", String("text")]), 303 "zenity" : (_status, ["--question", StringKeyword("--text", "text")]), 304 "Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]), 305 } 306 307 class Warning(Simple): 308 309 """ 310 A dialogue asking a question and showing response buttons. 311 Options: text, width (in characters), height (in characters) 312 """ 313 314 name = "warning" 315 info = { 316 "kdialog" : (_status, ["--warningyesno", String("text")]), 317 "zenity" : (_status, ["--warning", StringKeyword("--text", "text")]), 318 "Xdialog" : (_status, ["--stdout", "--yesno", String("text"), Integer("height"), Integer("width")]), 319 } 320 321 class Message(Simple): 322 323 """ 324 A message dialogue. 325 Options: text, width (in characters), height (in characters) 326 """ 327 328 name = "message" 329 info = { 330 "kdialog" : (_status, ["--msgbox", String("text")]), 331 "zenity" : (_status, ["--info", StringKeyword("--text", "text")]), 332 "Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]), 333 } 334 335 class Error(Simple): 336 337 """ 338 An error dialogue. 339 Options: text, width (in characters), height (in characters) 340 """ 341 342 name = "error" 343 info = { 344 "kdialog" : (_status, ["--error", String("text")]), 345 "zenity" : (_status, ["--error", StringKeyword("--text", "text")]), 346 "Xdialog" : (_status, ["--stdout", "--msgbox", String("text"), Integer("height"), Integer("width")]), 347 } 348 349 class Menu(Simple): 350 351 """ 352 A menu of options, one of which being selectable. 353 Options: text, width (in characters), height (in characters), 354 list_height (in items), items (MenuItem objects) 355 """ 356 357 name = "menu" 358 info = { 359 "kdialog" : (_readvalue(_readfrom), ["--menu", String("text"), MenuItemList("items")]), 360 "zenity" : (_readvalue(_readfrom), ["--list", StringKeyword("--text", "text"), StringKeywords("--column", "titles"), 361 MenuItemList("items")] 362 ), 363 "Xdialog" : (_readvalue(_readfrom), ["--stdout", "--menubox", 364 String("text"), Integer("height"), Integer("width"), Integer("list_height"), MenuItemList("items")] 365 ), 366 } 367 item = MenuItem 368 number_of_titles = 2 369 370 def __init__(self, text, titles, items=None, width=None, height=None, list_height=None): 371 372 """ 373 Initialise a menu with the given heading 'text', column 'titles', and 374 optional 'items' (which may be added later), 'width' (in characters), 375 'height' (in characters) and 'list_height' (in items). 376 """ 377 378 Simple.__init__(self, text, width, height) 379 self.titles = ([""] * self.number_of_titles + titles)[-self.number_of_titles:] 380 self.items = items or [] 381 self.list_height = list_height 382 383 def add(self, *args, **kw): 384 385 """ 386 Add an item, passing the given arguments to the appropriate item class. 387 """ 388 389 self.items.append(self.item(*args, **kw)) 390 391 class RadioList(Menu): 392 393 """ 394 A list of radio buttons, one of which being selectable. 395 Options: text, width (in characters), height (in characters), 396 list_height (in items), items (MenuItem objects), titles 397 """ 398 399 name = "radiolist" 400 info = { 401 "kdialog" : (_readvalues_kdialog(_readfrom), ["--radiolist", String("text"), ListItemList("items")]), 402 "zenity" : (_readvalues_zenity(_readfrom), 403 ["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"), 404 ListItemList("items", 1)] 405 ), 406 "Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--radiolist", 407 String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")] 408 ), 409 } 410 number_of_titles = 3 411 412 class CheckList(Menu): 413 414 """ 415 A list of checkboxes, many being selectable. 416 Options: text, width (in characters), height (in characters), 417 list_height (in items), items (MenuItem objects), titles 418 """ 419 420 name = "checklist" 421 info = { 422 "kdialog" : (_readvalues_kdialog(_readfrom), ["--checklist", String("text"), ListItemList("items")]), 423 "zenity" : (_readvalues_zenity(_readfrom), 424 ["--list", "--checklist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"), 425 ListItemList("items", 1)] 426 ), 427 "Xdialog" : (_readvalues_Xdialog(_readfrom), ["--stdout", "--checklist", 428 String("text"), Integer("height"), Integer("width"), Integer("list_height"), ListItemList("items")] 429 ), 430 } 431 number_of_titles = 3 432 433 class Pulldown(Menu): 434 435 """ 436 A pull-down menu of options, one of which being selectable. 437 Options: text, width (in characters), height (in characters), 438 entries (list of values) 439 """ 440 441 name = "pulldown" 442 info = { 443 "kdialog" : (_readvalue(_readfrom), ["--combobox", String("text"), Strings("items")]), 444 "zenity" : (_readvalue(_readfrom), 445 ["--list", "--radiolist", StringKeyword("--text", "text"), StringKeywords("--column", "titles"), 446 StringPairs("items")] 447 ), 448 "Xdialog" : (_readvalue(_readfrom), 449 ["--stdout", "--combobox", String("text"), Integer("height"), Integer("width"), Strings("items")]), 450 } 451 item = unicode 452 number_of_titles = 2 453 454 class Input(Simple): 455 456 """ 457 An input dialogue, consisting of an input field. 458 Options: text, input, width (in characters), height (in characters) 459 """ 460 461 name = "input" 462 info = { 463 "kdialog" : (_readinput(_readfrom), 464 ["--inputbox", String("text"), String("data")]), 465 "zenity" : (_readinput(_readfrom), 466 ["--entry", StringKeyword("--text", "text"), StringKeyword("--entry-text", "data")]), 467 "Xdialog" : (_readinput(_readfrom), 468 ["--stdout", "--inputbox", String("text"), Integer("height"), Integer("width"), String("data")]), 469 } 470 471 def __init__(self, text, data="", width=None, height=None): 472 Simple.__init__(self, text, width, height) 473 self.data = data 474 475 class Password(Input): 476 477 """ 478 A password dialogue, consisting of a password entry field. 479 Options: text, width (in characters), height (in characters) 480 """ 481 482 name = "password" 483 info = { 484 "kdialog" : (_readinput(_readfrom), 485 ["--password", String("text")]), 486 "zenity" : (_readinput(_readfrom), 487 ["--entry", StringKeyword("--text", "text"), "--hide-text"]), 488 "Xdialog" : (_readinput(_readfrom), 489 ["--stdout", "--password", "--inputbox", String("text"), Integer("height"), Integer("width")]), 490 } 491 492 class TextFile(Simple): 493 494 """ 495 A text file input box. 496 Options: filename, text, width (in characters), height (in characters) 497 """ 498 499 name = "textfile" 500 info = { 501 "kdialog" : (_readfrom, ["--textbox", String("filename"), Integer("width", pixels=1), Integer("height", pixels=1)]), 502 "zenity" : (_readfrom, ["--text-info", StringKeyword("--filename", "filename"), IntegerKeyword("--width", "width", pixels=1), 503 IntegerKeyword("--height", "height", pixels=1)] 504 ), 505 "Xdialog" : (_readfrom, ["--stdout", "--textbox", String("filename"), Integer("height"), Integer("width")]), 506 } 507 508 def __init__(self, filename, text="", width=None, height=None): 509 Simple.__init__(self, text, width, height) 510 self.filename = filename 511 512 # Available dialogues. 513 514 available = [Question, Warning, Message, Error, Menu, CheckList, RadioList, Input, Password, Pulldown, TextFile] 515 516 # vim: tabstop=4 expandtab shiftwidth=4