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