1""" 2Instances of WSTConnectionWindowController are the controlling object 3for the document windows for the Web Services Tool application. 4 5Implements a standard toolbar. 6""" 7 8from AppKit import * 9 10from twisted.internet import defer 11from twisted.web.xmlrpc import Proxy 12 13from RPCMethod import * 14 15#from twisted.python import log 16#import sys 17#log.startLogging(sys.stdout) 18 19# cheap dirty way to turn those messages off 20# from twisted.python import log 21# log.logerr = open('/dev/null','w') 22 23# Identifier for 'reload contents' toolbar item. 24kWSTReloadContentsToolbarItemIdentifier = u"WST: Reload Contents Toolbar Identifier" 25 26# Identifier for 'preferences' toolbar item. 27kWSTPreferencesToolbarItemIdentifier = u"WST: Preferences Toolbar Identifier" 28 29# Identifier for URL text field toolbar item. 30kWSTUrlTextFieldToolbarItemIdentifier = u"WST: URL Textfield Toolbar Identifier" 31 32def addToolbarItem(aController, anIdentifier, aLabel, aPaletteLabel, 33 aToolTip, aTarget, anAction, anItemContent, aMenu): 34 """ 35 Adds an freshly created item to the toolbar defined by 36 aController. Makes a number of assumptions about the 37 implementation of aController. It should be refactored into a 38 generically useful toolbar management untility. 39 """ 40 toolbarItem = NSToolbarItem.alloc().initWithItemIdentifier_(anIdentifier) 41 42 toolbarItem.setLabel_(aLabel) 43 toolbarItem.setPaletteLabel_(aPaletteLabel) 44 toolbarItem.setToolTip_(aToolTip) 45 toolbarItem.setTarget_(aTarget) 46 if anAction: 47 toolbarItem.setAction_(anAction) 48 49 if isinstance(anItemContent, NSImage): 50 toolbarItem.setImage_(anItemContent) 51 else: 52 toolbarItem.setView_(anItemContent) 53 bounds = anItemContent.bounds() 54 minSize = (100, bounds[1][1]) 55 maxSize = (1000, bounds[1][1]) 56 toolbarItem.setMinSize_( minSize ) 57 toolbarItem.setMaxSize_( maxSize ) 58 59 if aMenu: 60 menuItem = NSMenuItem.alloc().init() 61 menuItem.setSubmenu_(aMenu) 62 menuItem.setTitle_( aMenu.title() ) 63 toolbarItem.setMenuFormRepresentation_(menuItem) 64 65 aController.k_toolbarItems[anIdentifier] = toolbarItem 66 67class WSTConnectionWindowController (NSWindowController): 68 methodDescriptionTextView = objc.IBOutlet() 69 methodsTable = objc.IBOutlet() 70 progressIndicator = objc.IBOutlet() 71 statusTextField = objc.IBOutlet() 72 urlTextField = objc.IBOutlet() 73 74 @classmethod 75 def connectionWindowController(self): 76 """ 77 Create and return a default connection window instance. 78 """ 79 return WSTConnectionWindowController.alloc().init() 80 81 def init(self): 82 """ 83 Designated initializer. 84 85 Returns self (as per ObjC designated initializer definition, 86 unlike Python's __init__() method). 87 """ 88 self = self.initWithWindowNibName_(u"WSTConnection") 89 90 self.k_toolbarItems = {} 91 self.k_toolbarDefaultItemIdentifiers = [] 92 self.k_toolbarAllowedItemIdentifiers = [] 93 94 self.k_methods = {} 95 self.k_methodArray = [] 96 return self 97 98 def awakeFromNib(self): 99 """ 100 Invoked when the NIB file is loaded. Initializes the various 101 UI widgets. 102 """ 103 self.retain() # balanced by autorelease() in windowWillClose_ 104 105 self.statusTextField.setStringValue_(u"No host specified.") 106 self.progressIndicator.setStyle_(NSProgressIndicatorSpinningStyle) 107 self.progressIndicator.setDisplayedWhenStopped_(False) 108 109 self.createToolbar() 110 111 def windowWillClose_(self, aNotification): 112 """ 113 Clean up when the document window is closed. 114 """ 115 self.autorelease() 116 117 def createToolbar(self): 118 """ 119 Creates and configures the toolbar to be used by the window. 120 """ 121 toolbar = NSToolbar.alloc().initWithIdentifier_(u"WST Connection Window") 122 toolbar.setDelegate_(self) 123 toolbar.setAllowsUserCustomization_(True) 124 toolbar.setAutosavesConfiguration_(True) 125 126 self.createToolbarItems() 127 128 self.window().setToolbar_(toolbar) 129 130 lastURL = NSUserDefaults.standardUserDefaults().stringForKey_(u"LastURL") 131 if lastURL and len(lastURL): 132 self.urlTextField.setStringValue_(lastURL) 133 134 def createToolbarItems(self): 135 """ 136 Creates all of the toolbar items that can be made available in 137 the toolbar. The actual set of available toolbar items is 138 determined by other mechanisms (user defaults, for example). 139 """ 140 addToolbarItem( 141 self, kWSTReloadContentsToolbarItemIdentifier, 142 u"Reload", u"Reload", u"Reload Contents", None, 143 "reloadVisibleData:", NSImage.imageNamed_(u"Reload"), None) 144 addToolbarItem( 145 self, kWSTPreferencesToolbarItemIdentifier, 146 u"Preferences", u"Preferences", u"Show Preferences", None, 147 "orderFrontPreferences:", NSImage.imageNamed_(u"Preferences"), None) 148 addToolbarItem( 149 self, kWSTUrlTextFieldToolbarItemIdentifier, 150 u"URL", u"URL", u"Server URL", None, 151 None, self.urlTextField, None) 152 153 self.k_toolbarDefaultItemIdentifiers = [ 154 kWSTReloadContentsToolbarItemIdentifier, 155 kWSTUrlTextFieldToolbarItemIdentifier, 156 NSToolbarSeparatorItemIdentifier, 157 NSToolbarCustomizeToolbarItemIdentifier, 158 ] 159 160 self.k_toolbarAllowedItemIdentifiers = [ 161 kWSTReloadContentsToolbarItemIdentifier, 162 kWSTUrlTextFieldToolbarItemIdentifier, 163 NSToolbarSeparatorItemIdentifier, 164 NSToolbarSpaceItemIdentifier, 165 NSToolbarFlexibleSpaceItemIdentifier, 166 NSToolbarPrintItemIdentifier, 167 kWSTPreferencesToolbarItemIdentifier, 168 NSToolbarCustomizeToolbarItemIdentifier, 169 ] 170 171 def toolbarDefaultItemIdentifiers_(self, anIdentifier): 172 """ 173 Return an array of toolbar item identifiers that identify the 174 set, in order, of items that should be displayed on the 175 default toolbar. 176 """ 177 return self.k_toolbarDefaultItemIdentifiers 178 179 def toolbarAllowedItemIdentifiers_(self, anIdentifier): 180 """ 181 Return an array of toolbar items that may be used in the toolbar. 182 """ 183 return self.k_toolbarAllowedItemIdentifiers 184 185 def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(self, 186 toolbar, 187 itemIdentifier, flag): 188 """ 189 Delegate method fired when the toolbar is about to insert an 190 item into the toolbar. Item is identified by itemIdentifier. 191 192 Effectively makes a copy of the cached reference instance of 193 the toolbar item identified by itemIdentifier. 194 """ 195 newItem = NSToolbarItem.alloc().initWithItemIdentifier_(itemIdentifier) 196 item = self.k_toolbarItems[itemIdentifier] 197 198 newItem.setLabel_( item.label() ) 199 newItem.setPaletteLabel_( item.paletteLabel() ) 200 if item.view(): 201 newItem.setView_( item.view() ) 202 else: 203 newItem.setImage_( item.image() ) 204 205 newItem.setToolTip_( item.toolTip() ) 206 newItem.setTarget_( item.target() ) 207 newItem.setAction_( item.action() ) 208 newItem.setMenuFormRepresentation_( item.menuFormRepresentation() ) 209 210 if newItem.view(): 211 newItem.setMinSize_( item.minSize() ) 212 newItem.setMaxSize_( item.maxSize() ) 213 214 return newItem 215 216 def setStatusTextFieldMessage_(self, aMessage): 217 """ 218 Sets the contents of the statusTextField to aMessage and 219 forces the fileld's contents to be redisplayed. 220 """ 221 if not aMessage: 222 aMessage = u"Displaying information about %d methods." % (len(self.k_methods),) 223 self.statusTextField.setStringValue_(aMessage) 224 setStatusTextFieldMessage_ = objc.accessor(setStatusTextFieldMessage_) 225 226 def startWorking(self): 227 """Signal the UI there's work goin on.""" 228 self.progressIndicator.startAnimation_(self) 229 230 def stopWorking(self): 231 """Signal the UI that the work is done.""" 232 self.progressIndicator.stopAnimation_(self) 233 234 @objc.IBAction 235 def reloadVisibleData_(self, sender): 236 """ 237 Reloads the list of methods and their signatures from the 238 XML-RPC server specified in the urlTextField. Displays 239 appropriate error messages, if necessary. 240 """ 241 url = self.urlTextField.stringValue() 242 self.k_methods = {} 243 244 if not url: 245 self.window().setTitle_(u"Untitled.") 246 self.setStatusTextFieldMessage_(u"No URL specified.") 247 return 248 249 self.window().setTitle_(url) 250 NSUserDefaults.standardUserDefaults().setObject_forKey_(url, u"LastURL") 251 252 self.setStatusTextFieldMessage_(u"Retrieving method list...") 253 self.getMethods(url) 254 255 def getMethods(self, url): 256 _server = self.k_server = Proxy(url.encode('utf8')) 257 self.startWorking() 258 return _server.callRemote('listMethods').addCallback( 259 # call self.receivedMethods(result, _server, "") on success 260 self.receivedMethods, _server, "" 261 ).addErrback( 262 # on error, call this lambda 263 lambda e: _server.callRemote('system.listMethods').addCallback( 264 # call self.receievedMethods(result, _server, "system.") 265 self.receivedMethods, _server, 'system.' 266 ) 267 ).addErrback( 268 # log the failure instance, with a method 269 self.receivedMethodsFailure, 'listMethods()' 270 ).addBoth( 271 # stop working nomatter what trap all errors (returns None) 272 lambda n:self.stopWorking() 273 ) 274 275 def receivedMethodsFailure(self, why, method): 276 self.k_server = None 277 self.k_methodPrefix = None 278 self.setStatusTextFieldMessage_( 279 (u"Server failed to respond to %s. " 280 u"See below for more information." ) % (method,) 281 ) 282 #log.err(why) 283 self.methodDescriptionTextView.setString_(why.getTraceback()) 284 285 def receivedMethods(self, _methods, _server, _methodPrefix): 286 self.k_server = _server 287 self.k_methods = {} 288 self.k_methodPrefix = _methodPrefix 289 for aMethod in _methods: 290 self.k_methods[aMethod] = RPCMethod.alloc().initWithDocument_name_(self, aMethod) 291 self.setMethodArray_(self.k_methods.values()) 292 self.k_methodPrefix = _methodPrefix 293 294 self.setStatusTextFieldMessage_( 295 u"Retrieving information about %d methods." % (len(self.k_methods),) 296 ) 297 298 # we could make all the requests at once :) 299 # but the server might not like that so we will chain them 300 d = defer.succeed(None) 301 for index, aMethod in enumerate(self.k_methodArray): 302 d.addCallback( 303 self.fetchMethodSignature, index, aMethod 304 ).addCallbacks( 305 callback = self.processSignatureForMethod, 306 callbackArgs = (index, aMethod), 307 errback = self.couldntProcessSignatureForMethod, 308 errbackArgs = (index, aMethod), 309 ) 310 return d.addCallback( 311 lambda ig: self.setStatusTextFieldMessage_(None) 312 ) 313 314 def fetchMethodSignature(self, ignore, index, aMethod): 315 self.setStatusTextFieldMessage_( 316 u"Retrieving signature for method %s (%d of %d)." 317 % (aMethod.methodName() , index, len(self.k_methods)) 318 ) 319 return self.k_server.callRemote( 320 (self.k_methodPrefix + 'methodSignature').encode('utf-8'), 321 aMethod.methodName().encode('utf-8') 322 ) 323 324 def processSignatureForMethod(self, methodSignature, index, aMethod): 325 signatures = None 326 if not len(methodSignature): 327 return 328 for aSignature in methodSignature: 329 if isinstance(aSignature, list) and len(aSignature) > 0: 330 signature = u"%s %s(%s)" % (aSignature[0], aMethod.methodName(), u", ".join(aSignature[1:])) 331 else: 332 signature = aSignature 333 if signatures: 334 signatures = signatures + u", " + signature 335 else: 336 signatures = signature 337 338 aMethod.setMethodSignature_(signatures) 339 self.replaceObjectInMethodArrayAtIndex_withObject_(index, aMethod) 340 341 def couldntProcessSignatureForMethod(self, why, index, aMethod): 342 #log.err(why) 343 aMethod.setMethodSignature_(u"<error> %s %s" % (aMethod.methodName(), why.getBriefTraceback())) 344 self.replaceObjectInMethodArrayAtIndex_withObject_(index, aMethod) 345 346 def fetchMethodDescription_(self, aMethod): 347 def cacheDesc(v): 348 aMethod.setMethodDescription_(v or u'No description available.') 349 350 self.setStatusTextFieldMessage_(u"Retrieving documentation for method %s..." % (aMethod.methodName(),)) 351 self.startWorking() 352 self.k_server.callRemote((self.k_methodPrefix + u'methodHelp').encode('utf-8'), aMethod.methodName().encode('utf-8')).addCallback(cacheDesc) 353 354 def methodArray(self): 355 return self.k_methodArray 356 357 @objc.accessor 358 def countOfMethodArray(self): 359 if self.k_methodArray is None: 360 return 0 361 return self.k_methodArray 362 363 @objc.accessor 364 def objectInMethodArrayAtIndex_(self, anIndex): 365 return self.k_methodArray[anIndex] 366 367 @objc.accessor 368 def insertObject_inMethodArrayAtIndex_(self, anObject, anIndex): 369 self.k_methodArray.insert(anIndex, anObject) 370 371 @objc.accessor 372 def removeObjectFromMethodArrayAtIndex_(self, anIndex): 373 del self.k_methodArray[anIndex] 374 375 @objc.accessor 376 def replaceObjectInMethodArrayAtIndex_withObject_(self, anIndex, anObject): 377 self.k_methodArray[anIndex] = anObject 378 379 @objc.accessor 380 def setMethodArray_(self, anArray): 381 self.k_methodArray = anArray 382