Package pyraf :: Module tpar
[hide private]
[frames] | no frames]

Source Code for Module pyraf.tpar

   1  """module 'tpar.py' -- main module for generating the tpar task editor 
   2   
   3  tpar is curses based parameter editing similar to epar.  Tpar has the 
   4  primary goal of simplicity similar to IRAF's CL epar and as such is 
   5  missing many PyRAF epar features.  The primary advantage of tpar is 
   6  that it works in a simple terminal window (rather than requiring full 
   7  X-11 and Tk); this is an improvement for low bandwidth network 
   8  contexts or for people who prefer text interfaces to GUIs. 
   9   
  10  $Id: tpar.py 1194 2010-05-14 17:57:37Z sontag $ 
  11   
  12  Todd Miller, 2006 May 30  derived from epar.py and IRAF CL epar. 
  13  """ 
  14  from __future__ import division # confidence high 
  15   
  16  # XXXX Debugging tip:  uncomment self.inform() in the debug() method below 
  17   
  18  import os, sys, string, commands, re 
  19   
  20  # Fake out import of urwid if it fails to keep tpar from bringing down 
  21  # all of PyRAF. 
22 -class FakeModule:
23 - def __new__(*args, **keys):
24 pass
25 - def __init__(*args, **keys):
26 pass
27
28 -class FakeClass:
29 - def __new__(*args, **keys):
30 pass
31 - def __init__(*args, **keys):
32 pass
33 34 35 URWID_PRE_9P9 = False 36 37 try: 38 import urwid.curses_display 39 import urwid.raw_display 40 import urwid 41 import urwutil 42 import urwfiledlg 43 urwid.set_encoding("ascii") # gives better performance than 'utf8' 44 if 0==urwid.__version__.find('0.9.8') or 0==urwid.__version__.find('0.9.7'): 45 URWID_PRE_9P9 = True 46 except Exception, e: 47 urwid = FakeModule() 48 urwid.Edit = FakeClass() 49 urwid.Columns = FakeClass() 50 urwid.AttrWrap = FakeClass() 51 urwid.Pile = FakeClass() 52 urwid.the_error = str(e) 53 54 # PyRAF modules 55 import iraf, irafpar, irafhelp, iraftask, cStringIO, iraffunctions 56 57 TPAR_HELP_EMACS = """ EDIT COMMANDS (emacs) 58 59 DEL_CHAR = DEL MOVE_RIGHT = RIGHT_ARROW 60 DEL_LEFT = ^H_or_BS MOVE_RIGHT = ^F 61 DEL_LINE = ^K MOVE_START = ESC-< 62 DEL_WORD = ESC-D MOVE_UP = UP_ARROW 63 DEL_WORD = ESC-d MOVE_UP = ^P 64 EXIT_NOUPD = ^C NEXT_PAGE = ^V 65 EXIT_UPDAT = ^D NEXT_WORD = ESC-F 66 NEXT_WORD = ESC-f 67 GET_HELP = ESC-? PREV_PAGE = ESC-V 68 MOVE_BOL = ^A PREV_PAGE = ESC-v 69 MOVE_DOWN = DOWN_ARROW PREV_WORD = ESC-B 70 MOVE_DOWN = ^N PREV_WORD = ESC-b 71 MOVE_END = ESC-> REPAINT = ^L 72 MOVE_EOL = ^E UNDEL_CHAR = ESC-^D 73 MOVE_LEFT = LEFT_ARROW UNDEL_LINE = ESC-^K 74 MOVE_LEFT = ^B UNDEL_WORD = ESC-^W 75 76 X-11 Paste: hold down shift and click middle mouse button 77 78 :e[!] [pset] edit pset "!" == no update 79 :q[!] exit tpar "!" == no update 80 :r! unlearn 81 :w[!] [pset] unsupported 82 :g[!] run task 83 """ 84 85 TPAR_BINDINGS_EMACS = { 86 "ctrl c" : "quit", 87 "ctrl C" : "quit", 88 "ctrl d" : "exit", 89 "ctrl D" : "exit", 90 "ctrl z" : "exit", 91 "ctrl Z" : "exit", 92 93 "ctrl p" : "up", 94 "ctrl P" : "up", 95 "shift tab": "up", 96 97 "ctrl n" : "down", 98 "ctrl N" : "down", 99 100 "esc v" : "page down", 101 "esc V" : "page down", 102 103 "esc p" : "page up", 104 "esc P" : "page up", 105 106 # "ctrl l" : "redraw", # re-draw... just ignore 107 # "ctrl L" : "redraw", 108 109 "ctrl K" : "del_line", 110 "ctrl k" : "del_line", 111 112 "esc d": "del_word", 113 "esc D": "del_word", 114 115 "esc f": "next_word", 116 "esc F": "next_word", 117 118 "esc b": "prev_word", 119 "esc B": "prev_word", 120 121 "ctrl a": "move_bol", 122 "ctrl A": "move_bol", 123 124 "ctrl e": "move_eol", 125 "ctrl E": "move_eol", 126 127 "esc >": "end", 128 "esc <": "home", 129 130 "ctrl f": "right", 131 "ctrl F": "right", 132 133 "ctrl b": "left", 134 "ctrl B": "left", 135 136 "esc ctrl d": "undel_char", 137 "esc ctrl k": "undel_line", 138 "ctrl y": "undel_line", 139 "esc ctrl w": "undel_word", 140 141 "esc ?": "help" 142 } 143 144 145 TPAR_HELP_VI = """ EDIT COMMANDS (vi) 146 147 DEL_CHAR = BACKSPACE MOVE_LEFT = ^H 148 DEL_LEFT = DEL MOVE_RIGHT = RIGHT_ARROW 149 DEL_LINE = ^I^D MOVE_RIGHT = ^L 150 DEL_WORD = ^I^W MOVE_START = ^T^S 151 EXIT_NOUPD = ^C MOVE_UP = UP_ARROW 152 EXIT_UPDAT = ^D MOVE_UP = ^K 153 EXIT_UPDAT = ^Z NEXT_PAGE = ^N 154 GET_HELP = ESC-? NEXT_WORD = ^W 155 MOVE_BOL = ^A PREV_PAGE = ^P 156 MOVE_DOWN = DOWN_ARROW PREV_WORD = ^B 157 MOVE_DOWN = ^J REPAINT = ^R 158 MOVE_END = ^T^E UNDEL_CHAR = ^U^C 159 MOVE_EOL = ^E UNDEL_LINE = ^U^L 160 MOVE_LEFT = LEFT_ARROW UNDEL_WORD = ^U^W 161 162 X-11 Paste: hold down shift and click middle mouse button 163 164 :e[!] [pset] edit pset "!" == no update 165 :q[!] exit tpar "!" == no update 166 :r! unlearn 167 :w[!] [pset] unsupported 168 :g[!] run task 169 """ 170 171 TPAR_BINDINGS_VI = { 172 "ctrl c" : "quit", 173 "ctrl d" : "exit", 174 "ctrl C" : "quit", 175 "ctrl D" : "exit", 176 177 "ctrl K" : "up", 178 "ctrl k" : "up", 179 180 "ctrl j" : "down", 181 "ctrl J" : "down", 182 183 "ctrl n" : "page down", 184 "ctrl N" : "page down", 185 186 "ctrl p" : "page up", 187 "ctrl P" : "page up", 188 189 # "ctrl r" : "redraw", # re-draw... just ignore 190 # "ctrl R" : "redraw", 191 192 "tab ctrl D" : "del_line", 193 "tab ctrl d" : "del_line", 194 195 "tab ctrl W": "del_word", 196 "tab ctrl w": "del_word", 197 198 "ctrl w": "next_word", 199 "ctrl W": "next_word", 200 201 "ctrl b": "prev_word", 202 "ctrl B": "prev_word", 203 204 "ctrl a": "move_bol", 205 "ctrl A": "move_bol", 206 207 "ctrl e": "move_eol", 208 "ctrl E": "move_eol", 209 210 "ctrl T ctrl E": "end", 211 "ctrl t ctrl e": "end", 212 "ctrl T ctrl S": "home", 213 "ctrl t ctrl s": "home", 214 215 "ctrl L": "right", 216 "ctrl l": "right", 217 218 "ctrl H": "left", 219 "ctrl h": "left", 220 221 "ctrl U ctrl C": "undel_char", 222 "ctrl u ctrl c": "undel_char", 223 224 "ctrl U ctrl L": "undel_line", 225 "ctrl u ctrl l": "undel_line", 226 227 "ctrl U ctrl W": "undel_word", 228 "ctrl u ctrl w": "undel_word", 229 230 "esc ?": "help" 231 } 232
233 -class Binder(object):
234 """The Binder class manages keypresses for urwid and adds the 235 ability to bind specific inputs to actions. 236 """
237 - def __init__(self, bindings, inform, mode_keys=[]):
238 self.bindings = bindings 239 self.inform = inform 240 self.mode_keys = mode_keys 241 self.chord = []
242
243 - def bind(self, k, f):
244 self.bindings[k] = f
245
246 - def keypress(self, pos, key):
247 if key is None: 248 return 249 if key is "ready": # Handle the "ready" binding specially to keep the rest simple. 250 if self.bindings.has_key("ready"): 251 return self.bindings["ready"]() 252 else: 253 return "ready" 254 self.debug("pos: %s key: %s" % (pos, key)) 255 if key in self.mode_keys: 256 self.chord.append(key) 257 return None 258 elif not urwid.is_mouse_event(key): 259 key = " ".join(self.chord + [key]) 260 self.chord = [] 261 visited = [] 262 while self.bindings.has_key(key) and key not in visited: 263 visited.append(key) 264 f = self.bindings[key] 265 if f is None: 266 key = None 267 elif isinstance(f, str): # str & unicode? 268 key = f 269 else: 270 key = f() 271 self.debug("pos: %s visited: %s key: %s mapping: %s" % \ 272 (pos, " --> ".join(visited), key, f)) 273 return key
274
275 - def debug(self, s):
276 # return self.inform(s) 277 return None
278
279 -class PyrafEdit(urwid.Edit):
280 """PyrafEdit is a text entry widget which has keybindings similar 281 to IRAF's CL epar command. 282 """
283 - def __init__(self, *args, **keys):
284 inform = keys["inform"] 285 del keys["inform"] 286 self.reset_del_buffers() 287 urwid.Edit.__init__(self, *args, **keys) 288 EDIT_BINDINGS = { # single field bindings 289 "delete" : self.DEL_CHAR, 290 "del_line": self.DEL_LINE, 291 "del_word": self.DEL_WORD, 292 293 "undel_char": self.UNDEL_CHAR, 294 "undel_word": self.UNDEL_WORD, 295 "undel_line": self.UNDEL_LINE, 296 297 "next_word": self.NEXT_WORD, 298 "prev_word": self.PREV_WORD, 299 300 "move_bol": self.MOVE_BOL, 301 "move_eol": self.MOVE_EOL, 302 303 "right": self.MOVE_RIGHT, 304 "left": self.MOVE_LEFT, 305 } 306 self._binder = Binder(EDIT_BINDINGS, inform)
307
308 - def reset_del_buffers(self):
309 self._del_words = [] 310 self._del_lines = [] 311 self._del_chars = []
312
313 - def DEL_CHAR(self):
314 s = self.get_edit_text() 315 if len(s): 316 n = self.edit_pos 317 if n >= len(s): 318 n -= 1 319 c = s[n] 320 self.set_edit_text(s[:n] + s[n+1:]) 321 self._del_chars.append(c)
322
323 - def DEL_WORD(self):
324 s = self.get_edit_text() 325 i = self.edit_pos 326 while i > 0 and not s[i].isspace(): 327 i -= 1 328 if s[i].isspace(): 329 i += 1 330 word = "" 331 while i < len(s) and not s[i].isspace(): 332 word += s[i] 333 i += 1 334 s = s[:i-len(word)] + s[i:] 335 self._del_words.append(word) 336 self.edit_pos = i 337 self.set_edit_text(s)
338
339 - def DEL_LINE(self):
340 s = self.get_edit_text() 341 line = s[self.edit_pos:] 342 self.set_edit_text(s[:self.edit_pos]) 343 self.set_edit_pos(len(self.get_edit_text())) 344 self._del_lines.append(line)
345
346 - def NEXT_WORD(self):
347 s = self.get_edit_text() 348 i = self.edit_pos 349 while s and i < len(s)-1 and not s[i].isspace(): 350 i += 1 351 while s and i < len(s)-1 and s[i].isspace(): 352 i += 1 353 self.edit_pos = i
354
355 - def PREV_WORD(self):
356 s = self.get_edit_text() 357 i = self.edit_pos 358 while s and i > 0 and s[i].isspace(): 359 i -= 1 360 while s and i > 0 and not s[i].isspace(): 361 i -= 1 362 self.edit_pos = i
363
364 - def MOVE_BOL(self):
365 self.edit_pos = 0
366
367 - def MOVE_EOL(self):
368 self.edit_pos = len(self.get_edit_text())
369
370 - def MOVE_RIGHT(self):
371 if self.edit_pos < len(self.get_edit_text()): 372 self.edit_pos += 1
373
374 - def MOVE_LEFT(self):
375 if self.edit_pos > 0: 376 self.edit_pos -= 1
377
378 - def UNDEL_CHAR(self):
379 try: 380 char = self._del_chars.pop() 381 except: 382 return 383 self.insert_text(char) 384 self.edit_pos -= 1
385
386 - def UNDEL_WORD(self):
387 try: 388 word = self._del_words.pop() 389 except: 390 return 391 self.insert_text(word)
392
393 - def UNDEL_LINE(self):
394 try: 395 if len(self._del_lines) > 1: 396 line = self._del_lines.pop() 397 else: 398 line = self._del_lines[0] 399 except: 400 return 401 self.insert_text(line)
402
403 - def keypress(self, pos, key):
404 key = Binder.keypress(self._binder, pos, key) 405 if key is not None and not urwid.is_mouse_event(key): 406 key = urwid.Edit.keypress(self, pos, key) 407 return key
408
409 - def get_result(self):
410 return self.get_edit_text().strip()
411
412 - def verify(self):
413 return True
414
415 -class StringTparOption(urwid.Columns):
416 - def __init__(self, paramInfo, defaultParamInfo, inform):
417 418 MODE_KEYS = [] 419 420 BINDINGS = { 421 "enter" : self.ENTER, 422 "up" : self.MOVE_UP, 423 "down" : self.MOVE_DOWN, 424 "page up" : self.PAGE_UP, 425 "page down" : self.PAGE_DOWN, 426 "undel_line": self.UNDEL_LINE, 427 "ready" : self.READY_LINE, 428 "end" : self.MOVE_END, 429 "home": self.MOVE_START 430 } 431 432 self._binder = Binder(BINDINGS, inform, MODE_KEYS) 433 434 self._mode = "clear" 435 self._newline = True 436 self.inform = inform 437 self.paramInfo = paramInfo 438 self.defaultParamInfo = defaultParamInfo 439 440 name = self.paramInfo.name 441 value = self.paramInfo.get(field = "p_filename", native = 0, 442 prompt = 0) 443 self._previousValue = value 444 445 # Generate the input label 446 if (self.paramInfo.get(field = "p_mode") == "h"): 447 required=False 448 else: 449 required=True 450 451 help = self.paramInfo.get(field = "p_prompt", native = 0, prompt = 0) 452 self._args = (name, value, help, required) 453 if not required: 454 name = "(" + name 455 help = ") " + help 456 else: 457 help = " " + help 458 self._name = urwid.Text( "%-10s=" % name ) 459 self._edit = PyrafEdit("", "", wrap="clip", align="right", inform=inform) 460 self._edit.verify = self.verify 461 self._value = urwid.Text( "%10s" % value, align="right" ) 462 self._help = urwid.Text( "%-30s" % help ) 463 urwid.Columns.__init__( self, [('weight',0.20, self._name), 464 ('weight',0.20, self._edit), 465 ('weight',0.20, self._value), 466 ('weight',0.40, self._help)], 467 0, 1, 1)
468
469 - def keypress(self, pos, key):
470 key = Binder.keypress(self._binder, pos, key) 471 if key: 472 key = self._edit.keypress(pos, key) 473 return key
474
475 - def get_name(self):
476 return self._args[0]
477
478 - def get_candidate(self):
479 return self._edit.get_edit_text()
480
481 - def set_candidate(self, s):
482 self._edit.set_edit_text(s) 483 self._edit.edit_pos = len(s)
484
485 - def normalize(self, v):
486 """abstract method called to standardize equivalent values 487 when the 'result' is set.""" 488 return v
489
490 - def get_result(self):
491 return self._value.get_text()[0].strip()
492
493 - def set_result(self, r):
494 self._value.set_text( self.normalize(str(r)) )
495
496 - def unlearn_value(self):
497 self.set_result(self._previousValue)
498
499 - def verify(self, v):
500 self.inform("") 501 return True
502
503 - def UNDEL_LINE(self): # a little iffy. handle first copy from value field to edit field here. defer subsequent calls.
504 v = self.get_result() 505 if v: 506 self.set_candidate( self.get_candidate() + v) 507 self.set_result("") 508 else: 509 return "undel_line"
510
511 - def ENTER(self):
512 return self.linechange("down")
513
514 - def MOVE_UP(self):
515 return self.linechange("up")
516
517 - def MOVE_DOWN(self):
518 return self.linechange("down")
519
520 - def PAGE_UP(self):
521 return self.linechange("page up")
522
523 - def PAGE_DOWN(self):
524 return self.linechange("page down")
525
526 - def MOVE_START(self):
527 return self.linechange("home")
528
529 - def MOVE_END(self):
530 return self.linechange("end")
531
532 - def linechange(self, rval):
533 """Updates this field when changing the field focus, 534 i.e. switching lines.""" 535 s = self.get_candidate() 536 if s != "": 537 if self.verify(s): 538 self.set_result(s) 539 self.set_candidate("") 540 else: 541 return None 542 else: # clear old error messages 543 self.inform("") 544 self._edit.set_edit_pos(0) 545 self._edit.reset_del_buffers() 546 self._newline = True 547 return rval
548
549 - def READY_LINE(self):
550 """Prepares this field for editing in the current 551 mode: default clear or default edit.""" 552 if not self._newline: 553 return 554 self._newline = False 555 if self._mode == "clear": 556 self.set_candidate("") 557 else: 558 s = self.get_result() 559 self.set_candidate( s ) 560 self._edit.set_edit_pos( len(s) )
561
562 - def klass(self):
563 return "string"
564 565
566 -class NumberTparOption(StringTparOption):
567 - def normalize(self, v):
568 if v in ["INDEF","Indef","indef"]: 569 return "INDEF" 570 else: 571 return v
572
573 - def verify(self, v):
574 try: 575 if v != self._previousValue: 576 self.paramInfo.set(v) 577 self.paramInfo.set(self._previousValue) 578 return True 579 except ValueError, e: 580 self.set_candidate("") 581 self.inform(str(e)) 582 return False
583
584 - def klass(self):
585 return "number"
586
587 -class BooleanTparOption(StringTparOption):
588 - def __init__(self, *args, **keys):
589 StringTparOption.__init__(self, *args, **keys) 590 self._binder.bind(" ","space") 591 self._binder.bind("space", self.TOGGLE) 592 self._binder.bind("right", self.TOGGLE) 593 self._binder.bind("left", self.TOGGLE)
594
595 - def TOGGLE(self):
596 if self.get_result() == "yes": 597 self.set_result("no") 598 else: 599 self.set_result("yes")
600
601 - def normalize(self, v):
602 if v in ["n","N"]: 603 return "no" 604 elif v in ["y","Y"]: 605 return "yes" 606 else: 607 return v
608
609 - def verify(self, v):
610 v = self.normalize(v) 611 if v in ["yes","no"]: 612 self.inform("") 613 return True 614 else: 615 self.set_candidate("") 616 self.inform("Not a valid boolean value.") 617 return False
618 - def klass(self):
619 return "boolean"
620
621 -class EnumTparOption(StringTparOption):
622 - def __init__(self, *args, **keys):
623 StringTparOption.__init__(self, *args, **keys) 624 self._binder.bind(" ","space") 625 self._binder.bind("space", self.SPACE) 626 self._binder.bind("right", self.SPACE) 627 self._binder.bind("left", self.LEFT)
628
629 - def adjust(self, delta, wrap):
630 choices = self.paramInfo.choice 631 try: 632 v = choices[ choices.index(self.get_result()) + delta ] 633 except IndexError: 634 v = choices[ wrap ] 635 self.set_result(v)
636
637 - def SPACE(self):
638 return self.adjust(1, 0)
639
640 - def LEFT(self):
641 return self.adjust(-1, -1)
642 643
644 - def klass(self):
645 return "enumeration"
646
647 - def verify(self, v):
648 if v not in self.paramInfo.choice: 649 self.inform("What? choose: " + "|".join(self.paramInfo.choice)) 650 self.set_candidate("") 651 return False 652 return True
653
654 -class PsetTparOption(StringTparOption):
655 - def klass(self):
656 return "pset"
657
658 -class TparHeader(urwid.Pile):
659 banner = """ I R A F 660 Image Reduction and Analysis Facility 661 """
662 - def __init__(self, package, task=None):
663 top = urwid.Text(("header", self.banner)) 664 s = "%8s= %-10s\n" % ("PACKAGE", package) 665 if task is not None: 666 s += "%8s= %-10s" % ("TASK", task) 667 info = urwid.Text(("body", s)) 668 urwid.Pile.__init__(self, [top, info])
669 670
671 -class TparDisplay(Binder):
672 palette = [ 673 ('body','default','default', 'standout'), 674 ('header', 'default', 'default', ('standout', 'underline')), 675 ('help','black','light gray'), 676 ('reverse','light gray','black'), 677 ('important','dark blue','light gray',('standout','underline')), 678 ('editfc','white', 'dark blue', 'bold'), 679 ('editbx','light gray', 'dark blue'), 680 ('editcp','black','light gray', 'standout'), 681 ('bright','dark gray','light gray', ('bold','standout')), 682 ('buttn','black','dark cyan'), 683 ('buttnf','white','dark blue','bold'), 684 ] 685
686 - def __init__(self, taskName):
687 688 MODE_KEYS_EMACS = [ "esc"] 689 690 MODE_KEYS_VI = ["esc", "tab", 691 "ctrl u", "ctrl U", 692 "ctrl t", "ctrl T"] 693 694 TPAR_BINDINGS = { # Page level bindings 695 "quit" : self.QUIT, 696 "exit " : self.EXIT, 697 "help" : self.HELP, 698 "end" : self.MOVE_END, 699 "home" : self.MOVE_START, 700 } 701 702 # Get the Iraftask object 703 if isinstance(taskName, irafpar.IrafParList): 704 # IrafParList acts as an IrafTask for our purposes 705 self.taskObject = taskName 706 else: 707 # taskName must be a string or an IrafTask object 708 self.taskObject = iraf.getTask(taskName) 709 710 # Now go back and ensure we have the full taskname 711 self.taskName = self.taskObject.getName() 712 self.pkgName = self.taskObject.getPkgname() 713 self.paramList = self.taskObject.getParList(docopy=1) 714 715 # See if there exist any special versions on disk to load 716 self.__areAnyToLoad = irafpar.haveSpecialVersions(self.taskName, 717 self.pkgName) # irafpar caches them 718 719 # Ignore the last parameter which is $nargs 720 self.numParams = len(self.paramList) - 1 721 722 # Get default parameter values for unlearn 723 self.get_default_param_list() 724 self.make_entries() 725 726 self.escape = False 727 728 if URWID_PRE_9P9: 729 self._createButtonsOld() 730 else: 731 self._createButtons() 732 733 734 self.colon_edit = PyrafEdit("", "", wrap="clip", align="left", inform=self.inform) 735 self.listitems = [urwid.Divider(" ")] + self.entryNo + \ 736 [urwid.Divider(" "), self.colon_edit, 737 self.buttons] 738 self.listbox = urwid.ListBox( self.listitems ) 739 740 self.listbox.set_focus(1) 741 self.footer = urwid.Text("") 742 self.header = TparHeader(self.pkgName, self.taskName) 743 744 self.view = urwid.Frame( 745 self.listbox, 746 header=self.header, 747 footer=self.footer) 748 749 self._editor = iraf.envget("editor") 750 BINDINGS = {} 751 BINDINGS.update(TPAR_BINDINGS) 752 if self._editor == "vi": 753 BINDINGS.update(TPAR_BINDINGS_VI) 754 MODE_KEYS = MODE_KEYS_VI 755 else: 756 BINDINGS.update(TPAR_BINDINGS_EMACS) 757 MODE_KEYS = MODE_KEYS_EMACS 758 Binder.__init__(self, BINDINGS, self.inform, MODE_KEYS)
759 760
761 - def _createButtonsOld(self):
762 """ Set up all the bottom row buttons and their spacings """ 763 764 isPset = isinstance(self.taskObject, iraftask.IrafPset) 765 766 self.help_button = urwid.Padding( 767 urwid.Button("Help",self.HELP), 768 align="center", width=('fixed', 8)) 769 self.cancel_button = urwid.Padding( 770 urwid.Button("Cancel",self.QUIT), 771 align="center", width=('fixed', 10)) 772 if not isPset: 773 self.save_as_button = urwid.Padding( 774 urwid.Button("Save As",self.SAVEAS), 775 align="center", width=('fixed', 11)) 776 self.save_button = urwid.Padding( 777 urwid.Button("Save",self.EXIT), 778 align="center", width=('fixed', 8)) 779 self.exec_button = urwid.Padding( 780 urwid.Button("Exec",self.go), 781 align="center", width=('fixed', 8)) 782 if self.__areAnyToLoad: 783 self.open_button = urwid.Padding( 784 urwid.Button("Open",self.PFOPEN), 785 align="center", width=('fixed', 8)) 786 787 # GUI button layout - weightings 788 if isPset: # show no Open nor Save As buttons 789 self.buttons = urwid.Columns([ 790 ('weight', 0.2, self.exec_button), 791 ('weight', 0.2, self.save_button), 792 ('weight', 0.2, self.cancel_button), 793 ('weight', 0.4, self.help_button)]) 794 else: 795 if not self.__areAnyToLoad: # show Save As but not Open 796 self.buttons = urwid.Columns([ 797 ('weight', 0.175, self.exec_button), 798 ('weight', 0.175, self.save_button), 799 ('weight', 0.175, self.save_as_button), 800 ('weight', 0.175, self.cancel_button), 801 ('weight', 0.3, self.help_button)]) 802 else: # show all possible buttons (iterated on this spacing) 803 self.buttons = urwid.Columns([ 804 ('weight', 0.20, self.open_button), 805 ('weight', 0.15, self.exec_button), 806 ('weight', 0.15, self.save_button), 807 ('weight', 0.15, self.save_as_button), 808 ('weight', 0.18, self.cancel_button), 809 ('weight', 0.20, self.help_button)])
810 811
812 - def _createButtons(self):
813 """ Set up all the bottom row buttons and their spacings """ 814 815 isPset = isinstance(self.taskObject, iraftask.IrafPset) 816 817 self.help_button = urwid.Padding( 818 urwid.Button("Help",self.HELP), align="center", width=8, right=4, 819 left=5) 820 self.cancel_button = urwid.Padding( 821 urwid.Button("Cancel",self.QUIT), align="center", width=10) 822 if not isPset: 823 self.save_as_button = urwid.Padding( 824 urwid.Button("Save As",self.SAVEAS), align="center", width=11) 825 self.save_button = urwid.Padding( 826 urwid.Button("Save",self.EXIT), align="center", width=8) 827 self.exec_button = urwid.Padding( 828 urwid.Button("Exec",self.go), align="center", width=8) 829 if self.__areAnyToLoad: 830 self.open_button = urwid.Padding( 831 urwid.Button("Open",self.PFOPEN), align="center", width=8) 832 833 # GUI button layout - weightings 834 if isPset: # show no Open nor Save As buttons 835 self.buttons = urwid.Columns([ 836 ('weight', 0.20, self.exec_button), 837 ('weight', 0.23, self.save_button), 838 ('weight', 0.23, self.cancel_button), 839 ('weight', 0.20, self.help_button)]) 840 else: 841 if not self.__areAnyToLoad: # show Save As but not Open 842 self.buttons = urwid.Columns([ 843 ('weight', 0.15, self.exec_button), 844 ('weight', 0.15, self.save_button), 845 ('weight', 0.18, self.save_as_button), 846 ('weight', 0.18, self.cancel_button), 847 ('weight', 0.15, self.help_button)]) 848 else: # show all possible buttons (iterated on this spacing) 849 self.buttons = urwid.Columns([ 850 ('weight', 0.10, self.open_button), 851 ('weight', 0.10, self.exec_button), 852 ('weight', 0.10, self.save_button), 853 ('weight', 0.12, self.save_as_button), 854 ('weight', 0.12, self.cancel_button), 855 ('weight', 0.10, self.help_button)])
856 857
858 - def get_default_param_list(self):
859 # Obtain the default parameter list 860 dlist = self.taskObject.getDefaultParList() 861 if len(dlist) != len(self.paramList): 862 # whoops, lengths don't match 863 raise ValueError("Mismatch between default, current par lists" 864 " for task %s (try unlearn)" % self.taskName) 865 dict = {} 866 for par in dlist: 867 dict[par.name] = par 868 869 # Build default list sorted into same order as current list 870 try: 871 dsort = [] 872 for par in self.paramList: 873 dsort.append(dict[par.name]) 874 except KeyError: 875 raise ValueError("Mismatch between default, current par lists" 876 " for task %s (try unlearn)" % self.taskName) 877 self.defaultParamList = dsort
878 879 # Method to create the parameter entries
880 - def make_entries(self):
881 # Loop over the parameters to create the entries 882 self.entryNo = [None] * self.numParams 883 for i in range(self.numParams): 884 self.entryNo[i] = self.tpar_option_factory( 885 self.paramList[i], self.defaultParamList[i])
886
887 - def main(self):
888 # Create the Screen using curses_display. 889 # On OSX in py2.6 and greater, this causes issues (see #117), 890 # where raw_display seems to work just as well so use it. 891 if sys.platform == 'darwin' and sys.version_info[0] == 2 and \ 892 sys.version_info[1] > 5: 893 self.ui = urwid.raw_display.Screen() 894 else: 895 self.ui = urwid.curses_display.Screen() 896 self.ui.register_palette( self.palette ) 897 self.ui.run_wrapper(self.run) # raw_display has alternate_buffer=True 898 self.done()
899
900 - def get_keys(self):
901 keys = [] 902 while not keys: 903 try: 904 keys = self.ui.get_input() 905 except KeyboardInterrupt: 906 keys = ["ctrl c"] 907 return keys
908
909 - def run(self):
910 self.ui.set_mouse_tracking() 911 size = self.ui.get_cols_rows() 912 self.done = False 913 self._newline = True 914 while not self.done: 915 self.view.keypress(size, "ready") 916 canvas = self.view.render( size, focus=1 ) 917 self.ui.draw_screen( size, canvas ) 918 for k in self.get_keys(): 919 if k == ":": 920 self.colon_escape() 921 break 922 elif urwid.is_mouse_event(k): 923 event, button, col, row = k 924 self.view.mouse_event( 925 size, event, 926 button, col, row, focus=True ) 927 elif k == 'window resize': 928 size = self.ui.get_cols_rows() 929 self.inform("resize %s" % (str(size))) 930 k = self.keypress(size, k) 931 self.view.keypress( size, k )
932
933 - def colon_escape(self):
934 """colon_escape switches the focus to the 'mini-buffer' and 935 accepts and executes a one line colon command.""" 936 w, pos0 = self.listbox.get_focus() 937 try: 938 default_file = w.get_result() 939 except: 940 default_file = "" 941 self.listbox.set_focus(len(self.listitems)-2) 942 size = self.ui.get_cols_rows() 943 self.colon_edit.set_edit_text("") 944 self.colon_edit.set_edit_pos(0) 945 self.view.keypress(size, ":") 946 done = False 947 while not done: 948 canvas = self.view.render( size, focus=1 ) 949 self.ui.draw_screen( size, canvas ) 950 for k in self.get_keys(): 951 if urwid.is_mouse_event(k) or \ 952 k == "ctrl c" or k == "ctrl g": 953 self.colon_edit.set_edit_text("") 954 return 955 elif k == 'window resize': 956 size = self.ui.get_cols_rows() 957 elif k == 'enter': 958 done = True 959 break 960 k = self.keypress(size, k) 961 self.view.keypress( size, k ) 962 cmd = self.colon_edit.get_edit_text() 963 self.listbox.set_focus(pos0) 964 self.colon_edit.set_edit_text("") 965 self.process_colon(cmd)
966
967 - def process_colon(self, cmd):
968 # : <cmd_letter> [!] [<filename>] 969 groups = re.match("^:(?P<cmd>[a-z])\s*" 970 "(?P<emph>!?)\s*" 971 "(?P<file>\w*)", cmd) 972 if not groups: 973 self.inform("bad command: " + cmd) 974 else: 975 letter = groups.group("cmd") 976 emph = groups.group("emph") == "!" 977 file = groups.group("file") 978 try: 979 f = { "q" : self.QUIT, 980 "g" : self.go, 981 "r" : self.read_pset, 982 "w" : self.write_pset, 983 "e" : self.edit_pset 984 }[letter] 985 except KeyError: 986 self.inform("unknown command: " + cmd) 987 return 988 try: 989 f(file, emph) 990 except Exception, e: 991 self.inform("command '%s' failed with exception '%s'" % (cmd, e))
992
993 - def save_as(self):
994 """ Save the parameter settings to a user-specified file. Any 995 changes here must be coordinated with the corresponding epar saveAs 996 function. """ 997 998 # The user wishes to save to a different name. 999 fname = self.select_file( 1000 "Save parameter values to which file?", overwriteCheck=True) 1001 1002 # Now save the parameters 1003 if fname == None: 1004 msg = "Parameters NOT saved to a file." 1005 okdlg = urwutil.DialogDisplay(msg, 8, 0) 1006 okdlg.add_buttons([ ("OK",0) ]) 1007 okdlg.main() 1008 return 1009 1010 # Tpar apparently does nothing with children (PSETs), so skip the 1011 # check or set or save of them 1012 1013 # Notify them that pset children will not be saved as part of 1014 # their special version 1015 pars = [] 1016 for par in self.paramList: 1017 if par.type == "pset": pars.append(par.name) 1018 if len(pars): 1019 msg = "If you have made any changes to the PSET "+ \ 1020 "values for:\n\n" 1021 for p in pars: msg += " "+p+"\n" 1022 msg = msg+"\nthose changes will NOT be explicitly saved to:"+ \ 1023 '\n\n"'+fname+'"' 1024 # title='PSET Save-As Not Yet Supported 1025 okdlg = urwutil.DialogDisplay(msg, 0, 0) 1026 okdlg.add_buttons([ ("OK",0) ]) 1027 okdlg.main() 1028 1029 # Verify all the entries (without save), keeping track of the invalid 1030 # entries which have been reset to their original input values 1031 self.badEntriesList = self.check_set_save_entries(False) 1032 1033 # If there were invalid entries, prepare the message dialog 1034 ansOKCANCEL = True 1035 if self.badEntriesList: 1036 ansOKCANCEL = self.process_bad_entries(self.badEntriesList, 1037 self.taskName) 1038 if not ansOKCANCEL: 1039 return # should we tell them we are not saving ? 1040 1041 # If there were no invalid entries or the user said OK, finally 1042 # save to their stated file. Since we have already processed the 1043 # bad entries, there should be none returned. 1044 mstr = "TASKMETA: task="+self.taskName+" package="+self.pkgName 1045 if self.check_set_save_entries(doSave=True, filename=fname, \ 1046 comment=mstr): 1047 raise Exception("Unexpected bad entries for: "+self.taskName) 1048 1049 # Let them know what they just did 1050 msg = 'Saved to: "'+fname+'"' 1051 okdlg = urwutil.DialogDisplay(msg, 8, 0) 1052 okdlg.add_buttons([ ("OK",0) ]) 1053 okdlg.main() 1054 1055 # Notify irafpar that there is a new special-purpose file on the scene 1056 irafpar.newSpecialParFile(self.taskName, self.pkgName, fname)
1057 1058
1059 - def pfopen(self):
1060 """ Load the parameter settings from a user-specified file. Any 1061 changes here must be coordinated with the corresponding epar pfopen 1062 function. """ 1063 1064 flist = irafpar.getSpecialVersionFiles(self.taskName, self.pkgName) 1065 if len(flist) <= 0: 1066 msg = "No special-purpose parameter files found for "+self.taskName 1067 okdlg = urwutil.DialogDisplay(msg, 8, 0) 1068 okdlg.add_buttons([ ("OK",0) ]) 1069 okdlg.main() 1070 return 1071 1072 fname = None 1073 if len(flist) == 1: 1074 msg = "One special-purpose parameter file found.\n"+ \ 1075 "Load file?\n\n"+flist[0] 1076 yesnodlg = urwutil.DialogDisplay(msg, 12, 0) 1077 yesnodlg.add_buttons([ ("OK", 0), ("Cancel", 1) ]) 1078 rv, junk = yesnodlg.main() 1079 if rv == 0: fname = flist[0] # if not, fname is still None 1080 else: # >1 file, need a select dialog 1081 flist.sort() 1082 chcs = [] # ListDialogDisplay takes a 2-column tuple 1083 for i in range(len(flist)): 1084 chcs.append(str(i)) # need index as tag - it is the return val 1085 chcs.append(flist[i]) 1086 def menuItemConstr(tag, state): 1087 return urwutil.MenuItem(tag)
1088 selectdlg = urwutil.ListDialogDisplay("Select from these:", 1089 len(flist)+7, 75, 1090 menuItemConstr, tuple(chcs), False) 1091 selectdlg.add_buttons([ ("Cancel",1), ]) 1092 rv, ans = selectdlg.main() 1093 if rv == 0: fname = flist[int(ans)] 1094 1095 # check-point: if fname is not None, we load a file 1096 msg = "\n\nPress any key to continue..." 1097 1098 if fname != None: 1099 newParList = irafpar.IrafParList(self.taskName, fname) # load it 1100 self.set_all_entries_from_par_list(newParList) # set GUI entries 1101 msg = "\n\nLoaded:\n\n "+fname+msg 1102 1103 # Notify them (also forces a screen redraw, which we need) 1104 try: 1105 self.ui.clear() # fixes clear when next line calls draw_screen 1106 except AttributeError: 1107 self.ui._clear() # older urwid vers use different method name 1108 self.info(msg, None)
1109 1110
1111 - def save(self, emph):
1112 # Save all the entries and verify them, keeping track of the invalid 1113 # entries which have been reset to their original input values 1114 if emph: 1115 return 1116 self.badEntriesList = self.check_set_save_entries(True) 1117 1118 # If there were invalid entries, prepare the message dialog 1119 ansOKCANCEL = True 1120 if (self.badEntriesList): 1121 ansOKCANCEL = self.process_bad_entries( 1122 self.badEntriesList, 1123 self.taskName) 1124 return ansOKCANCEL
1125
1126 - def MOVE_START(self):
1127 self.listbox.set_focus(1) 1128 return "home"
1129
1130 - def MOVE_END(self):
1131 self.listbox.set_focus(len(self.entryNo)) 1132 return "end"
1133 1134 # For the following routines, event is either a urwid event *or* 1135 # a Pset filename
1136 - def QUIT(self, event=None, emph=True): # maybe save
1137 self.save(emph) 1138 def quit_continue(): 1139 pass 1140 self.done = quit_continue 1141 1142
1143 - def PFOPEN(self, event=None):
1144 1145 """ Open button - load parameters from a user specified file""" 1146 self.pfopen() 1147 self.done = None # simply continue
1148 1149
1150 - def SAVEAS(self, event=None):
1151 1152 """ SaveAs button - save parameters to a user specified file""" 1153 self.save_as() 1154 def save_as_continue(): # get back to editing 1155 iraffunctions.tparam(self.taskObject)
1156 self.done = save_as_continue # self.done = None # will also continue 1157 1158
1159 - def EXIT(self, event=None): # always save
1160 self.QUIT(event, False) 1161 1162 1163 # EXECUTE: save the parameter settings and run the task
1164 - def go(self, event=None, emph=False):
1165 """Executes the task.""" 1166 self.save(emph) 1167 def go_continue(): 1168 print "\nTask %s is running...\n" % self.taskName 1169 self.run_task()
1170 self.done = go_continue 1171
1172 - def edit_pset(self, file, emph):
1173 """Edits the pset referred to by the specifiefd file or the current field.""" 1174 self.save(emph) 1175 w, pos0 = self.listbox.get_focus() 1176 try: 1177 default_file = w.get_result() 1178 except: 1179 default_file = "" 1180 if file == "": 1181 iraffunctions.tparam(default_file) 1182 else: 1183 def edit_pset_continue(): 1184 iraffunctions.tparam(file)
1185 self.done = edit_pset_continue 1186
1187 - def read_pset(self, file, emph):
1188 """Unlearns the current changes *or* reads in the specified file.""" 1189 if file == "": 1190 self.unlearn_all_entries() 1191 else: 1192 def new_pset(): 1193 self.__init__(file)
1194 self.done = new_pset 1195
1196 - def write_pset(self, file, overwrite):
1197 if os.path.exists(file) and not overwrite: 1198 self.inform("File '%s' exists and overwrite (!) not used." % (file,)) 1199 # XXXX write out parameters to file 1200 self.inform("write pset: %s" % (file,))
1201
1202 - def set_all_entries_from_par_list(self, aParList):
1203 """ Set all the parameter entry values in the GUI to the values 1204 in the given par list. Note corresponding EditParDialog method. """ 1205 for i in range(self.numParams): 1206 par = self.paramList[i] 1207 if par.type == "pset": 1208 continue # skip PSET's for now 1209 gui_entry = self.entryNo[i] 1210 par.set(aParList.getValue(par.name, native=1, prompt=0)) 1211 # gui holds a str, but par.value is native; conversion occurs 1212 gui_entry.set_result(par.value)
1213
1214 - def unlearn_all_entries(self):
1215 """ Method to "unlearn" all the parameter entry values in the GUI 1216 and set the parameter back to the default value """ 1217 for entry in self.entryNo: 1218 entry.unlearn_value()
1219 1220 # Read, save, and verify the entries
1221 - def check_set_save_entries(self, doSave, filename=None, comment=None):
1222 1223 self.badEntries = [] 1224 1225 # Loop over the parameters to obtain the modified information 1226 for i in range(self.numParams): 1227 1228 par = self.paramList[i] 1229 entry = self.entryNo[i] 1230 # Cannot change an entry if it is a PSET, just skip 1231 if par.type == "pset": 1232 continue 1233 1234 value = entry.get_result() 1235 1236 # Set new values for changed parameters - a bit tricky, 1237 # since changes that weren't followed by a return or 1238 # tab have not yet been checked. If we eventually 1239 # use a widget that can check all changes, we will 1240 # only need to check the isChanged flag. 1241 if par.isChanged() or value != entry._previousValue: 1242 # Verify the value is valid. If it is invalid, 1243 # the value will be converted to its original valid value. 1244 # Maintain a list of the reset values for user notification. 1245 if not entry.verify(value): 1246 self.badEntries.append([ 1247 entry.paramInfo.name, value, 1248 entry._previousValue]) 1249 else: 1250 self.taskObject.setParam(par.name, value) 1251 1252 # Save results to the uparm directory 1253 # Skip the save if the thing being edited is an IrafParList without 1254 # an associated file (in which case the changes are just being 1255 # made in memory.) 1256 1257 if doSave and ((not isinstance(self.taskObject, irafpar.IrafParList)) \ 1258 or self.taskObject.getFilename()): 1259 self.taskObject.saveParList(filename=filename, comment=comment) 1260 1261 return self.badEntries
1262 1263 # Run the task
1264 - def run_task(self):
1265 1266 # Use the run method of the IrafTask class 1267 # Set mode='h' so it does not prompt for parameters (like IRAF epar) 1268 # Also turn on parameter saving 1269 self.taskObject.run(mode='h', _save=1)
1270
1271 - def get_results(self):
1272 results = {} 1273 for i in self.items: 1274 results[i.get_name()] = i.get_result() 1275 return results
1276
1277 - def draw_screen(self, size):
1278 canvas = self.view.render( size, focus=True ) 1279 self.ui.draw_screen( size, canvas )
1280
1281 - def inform(self, s):
1282 """output any message to status bar""" 1283 self.footer.set_text(s)
1284
1285 - def info(self, msg, b):
1286 self.exit_flag = False 1287 size = self.ui.get_cols_rows() 1288 exit_button = urwid.Padding( 1289 urwid.Button("Exit", self.exit_info), 1290 align="center", width=8) 1291 frame = urwid.Frame(urwid.Filler( 1292 urwid.AttrWrap(urwid.Text(msg), "help"), 1293 valign="top"), 1294 header=self.header, 1295 footer=exit_button) 1296 canvas = frame.render( size ) 1297 self.ui.draw_screen( size, canvas ) 1298 self.get_keys() # wait for keypress
1299
1300 - def exit_info(self, ehb):
1301 self.exit_flag = True
1302
1303 - def HELP(self, event=None):
1304 if self._editor == "vi": 1305 self.info(TPAR_HELP_VI, self.help_button) 1306 else: 1307 self.info(TPAR_HELP_EMACS, self.help_button)
1308
1309 - def select_file(self, prompt, overwriteCheck=False):
1310 """ Allow user to input a file - handle whether it is expected 1311 to be new or existing. Returns file name on success, None on error. """ 1312 1313 # Allow the user to select a specific file. Note that urwid's 1314 # browser example (browse.py) doesn't work with 0.9.7. 1315 while True: 1316 try: 1317 fname = urwfiledlg.main() 1318 except: 1319 prompt="(File chooser error, enter choice manually.)\n"+prompt 1320 inputdlg = urwutil.InputDialogDisplay(prompt, 9, 0) 1321 inputdlg.add_buttons([ ("OK", 0), ("Cancel", 1) ]) 1322 rv, fname = inputdlg.main() 1323 if rv > 0: fname = None 1324 1325 if fname == None: return None # they canceled 1326 fname = fname.strip() 1327 if len(fname) == 0: return None 1328 1329 # See if the file exists (if we care) 1330 if overwriteCheck and os.path.exists(fname): 1331 yesnodlg = urwutil.DialogDisplay( 1332 "File exists! Overwrite?\n\n "+fname, 9, 0) 1333 yesnodlg.add_buttons([ ("Yes", 0), ("No", 1) ]) 1334 rv, junk = yesnodlg.main() 1335 if rv == 0: return fname 1336 # if no, then go thru selection again 1337 else: 1338 return fname
1339 1340
1341 - def askokcancel(self, title, msg):
1342 self.info(msg, None) 1343 return False
1344 1345 # Process invalid input values and invoke a query dialog
1346 - def process_bad_entries(self, badEntriesList, taskname):
1347 1348 format = "%20s %20s %20s\n" 1349 badEntriesString = "\nTask " + taskname.upper() + \ 1350 " -- Invalid values have been entered.\n\n" 1351 badEntriesString += format % \ 1352 ("Parameter", "Bad Value", "Reset Value") 1353 for i in range (len(badEntriesList)): 1354 badEntriesString += format % \ 1355 (badEntriesList[i][0].strip(), \ 1356 badEntriesList[i][1].strip(), \ 1357 badEntriesList[i][2].strip()) 1358 1359 badEntriesString += "\nOK to continue using"\ 1360 " the reset\nvalues or cancel to re-enter\nvalues?\n" 1361 1362 # Invoke the modal message dialog 1363 return (self.askokcancel("Notice", badEntriesString))
1364 1365 # TparOption values for non-string types 1366 _tparOptionDict = { "b": BooleanTparOption, 1367 "r": NumberTparOption, 1368 "d": NumberTparOption, 1369 "i": NumberTparOption, 1370 "pset": PsetTparOption, 1371 "ar": NumberTparOption, 1372 "ai": NumberTparOption, 1373 } 1374
1375 - def tpar_option_factory(self, param, defaultParam):
1376 """Return TparOption item of appropriate type for the parameter param""" 1377 # If there is an enumerated list, regardless of datatype, use 1378 # the EnumTparOption 1379 if (param.choice != None): 1380 tparOption = EnumTparOption 1381 else: 1382 # Use String for types not in the dictionary 1383 tparOption = self._tparOptionDict.get(param.type, StringTparOption) 1384 return tparOption(param, defaultParam, self.inform)
1385 1386
1387 -def tpar(taskName):
1388 if isinstance(urwid, FakeModule): 1389 print >>sys.stderr, "The urwid package isn't found on your Python system so tpar can't be used." 1390 print >>sys.stderr, ' (the error given: "'+urwid.the_error+'")' 1391 print >>sys.stderr, "Please install urwid version >= 0.9.7 or use epar instead." 1392 return 1393 TparDisplay(taskName).main()
1394 1395 if __name__ == "__main__": 1396 main() 1397