• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /macosx-10.10/pyobjc-45/pyobjc/pyobjc-framework-Cocoa-2.5.1/Examples/AppKit/WebServicesTool/
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
8# Note about multi-threading.
9# Although WST does its network stuff in a background thread, with Python 2.2
10# there are still moments where the app appears to hang briefly. This should
11# only be noticable when your DNS is slow-ish. The hang is caused by the
12# socket.getaddrinfo() function, which is used (indirectly) when connecting
13# to a server, which is a frequent operation when using xmlrpclib (it makes
14# a new connection for each request). Up to (and including) version 2.3b1,
15# Python would not grant time to other threads while blocking inside
16# getaddrinfo(). This has been fixed *after* 2.3b1 was released. (jvr)
17
18from Cocoa import *
19
20from threading import Thread
21from Queue import Queue
22
23import xmlrpclib
24import sys
25import types
26import string
27import traceback
28
29kWSTReloadContentsToolbarItemIdentifier = "WST: Reload Contents Toolbar Identifier"
30"""Identifier for 'reload contents' toolbar item."""
31
32kWSTPreferencesToolbarItemIdentifier = "WST: Preferences Toolbar Identifier"
33"""Identifier for 'preferences' toolbar item."""
34
35kWSTUrlTextFieldToolbarItemIdentifier = "WST: URL Textfield Toolbar Identifier"
36"""Idnetifier for URL text field toolbar item."""
37
38def addToolbarItem(aController, anIdentifier, aLabel, aPaletteLabel,
39                   aToolTip, aTarget, anAction, anItemContent, aMenu):
40    """
41    Adds an freshly created item to the toolbar defined by
42    aController.  Makes a number of assumptions about the
43    implementation of aController.  It should be refactored into a
44    generically useful toolbar management untility.
45    """
46    toolbarItem = NSToolbarItem.alloc().initWithItemIdentifier_(anIdentifier)
47
48    toolbarItem.setLabel_(aLabel)
49    toolbarItem.setPaletteLabel_(aPaletteLabel)
50    toolbarItem.setToolTip_(aToolTip)
51    toolbarItem.setTarget_(aTarget)
52    if anAction:
53        toolbarItem.setAction_(anAction)
54
55    if type(anItemContent) == NSImage:
56        toolbarItem.setImage_(anItemContent)
57    else:
58        toolbarItem.setView_(anItemContent)
59        bounds = anItemContent.bounds()
60        minSize = (100, bounds[1][1])
61        maxSize = (1000, bounds[1][1])
62        toolbarItem.setMinSize_( minSize )
63        toolbarItem.setMaxSize_( maxSize )
64
65    if aMenu:
66        menuItem = NSMenuItem.alloc().init()
67        menuItem.setSubmenu_(aMenu)
68        menuItem.setTitle_( aMenu.title() )
69        toolbarItem.setMenuFormRepresentation_(menuItem)
70
71    aController._toolbarItems[anIdentifier] = toolbarItem
72
73
74class WorkerThread(Thread):
75
76    def __init__(self):
77        """Create a worker thread. Start it by calling the start() method."""
78        self.queue = Queue()
79        Thread.__init__(self)
80
81    def stop(self):
82        """Stop the thread a.s.a.p., meaning whenever the currently running
83        job is finished."""
84        self.working = 0
85        self.queue.put(None)
86
87    def scheduleWork(self, func, *args, **kwargs):
88        """Schedule some work to be done in the worker thread."""
89        self.queue.put((func, args, kwargs))
90
91    def run(self):
92        """Fetch work from a queue, block when there's nothing to do.
93        This method is called by Thread, don't call it yourself."""
94        self.working = 1
95        while self.working:
96            work = self.queue.get()
97            if work is None or not self.working:
98                break
99            func, args, kwargs = work
100            pool = NSAutoreleasePool.alloc().init()
101            try:
102                func(*args, **kwargs)
103            finally:
104                # delete all local references; if they are the last refs they
105                # may invoke autoreleases, which should then end up in our pool
106                del func, args, kwargs, work
107                del pool
108
109
110class WSTConnectionWindowController(NSWindowController):
111    methodDescriptionTextView = objc.IBOutlet()
112    methodsTable = objc.IBOutlet()
113    progressIndicator = objc.IBOutlet()
114    statusTextField = objc.IBOutlet()
115    urlTextField = objc.IBOutlet()
116
117    __slots__ = ('_toolbarItems',
118        '_toolbarDefaultItemIdentifiers',
119        '_toolbarAllowedItemIdentifiers',
120        '_methods',
121        '_methodSignatures',
122        '_methodDescriptions',
123        '_server',
124        '_methodPrefix',
125        '_workQueue',
126        '_working',
127        '_workerThread',
128        '_windowIsClosing')
129
130    @classmethod
131    def connectionWindowController(self):
132        """
133        Create and return a default connection window instance.
134        """
135        return WSTConnectionWindowController.alloc().init()
136
137    def init(self):
138        """
139        Designated initializer.
140
141        Returns self (as per ObjC designated initializer definition,
142        unlike Python's __init__() method).
143        """
144        self = self.initWithWindowNibName_("WSTConnection")
145
146        self._toolbarItems = {}
147        self._toolbarDefaultItemIdentifiers = []
148        self._toolbarAllowedItemIdentifiers = []
149
150        self._methods = []
151        self._working = 0
152        self._windowIsClosing = 0
153        self._workerThread = WorkerThread()
154        self._workerThread.start()
155        return self
156
157    def awakeFromNib(self):
158        """
159        Invoked when the NIB file is loaded.  Initializes the various
160        UI widgets.
161        """
162        self.retain() # balanced by autorelease() in windowWillClose_
163
164        self.statusTextField.setStringValue_("No host specified.")
165        self.progressIndicator.setStyle_(NSProgressIndicatorSpinningStyle)
166        self.progressIndicator.setDisplayedWhenStopped_(False)
167
168        self.createToolbar()
169
170    def windowWillClose_(self, aNotification):
171        """
172        Clean up when the document window is closed.
173        """
174        # We must stop the worker thread and wait until it actually finishes before
175        # we can allow the window to close. Weird stuff happens if we simply let the
176        # thread run. When this thread is idle (blocking in queue.get()) there is
177        # no problem and we can almost instantly close the window. If it's actually
178        # in the middle of working it may take a couple of seconds, as we can't
179        # _force_ the thread to stop: we have to ask it to to stop itself.
180        self._windowIsClosing = 1  # try to stop the thread a.s.a.p.
181        self._workerThread.stop()  # signal the thread that there is no more work to do
182        self._workerThread.join()  # wait until it finishes
183        self.autorelease()
184
185    def createToolbar(self):
186        """
187        Creates and configures the toolbar to be used by the window.
188        """
189        toolbar = NSToolbar.alloc().initWithIdentifier_("WST Connection Window")
190        toolbar.setDelegate_(self)
191        toolbar.setAllowsUserCustomization_(True)
192        toolbar.setAutosavesConfiguration_(True)
193
194        self.createToolbarItems()
195
196        self.window().setToolbar_(toolbar)
197
198        lastURL = NSUserDefaults.standardUserDefaults().stringForKey_("LastURL")
199        if lastURL and len(lastURL):
200            self.urlTextField.setStringValue_(lastURL)
201
202    def createToolbarItems(self):
203        """
204        Creates all of the toolbar items that can be made available in
205        the toolbar.  The actual set of available toolbar items is
206        determined by other mechanisms (user defaults, for example).
207        """
208        addToolbarItem(self, kWSTReloadContentsToolbarItemIdentifier,
209                       "Reload", "Reload", "Reload Contents", None,
210                       "reloadVisibleData:", NSImage.imageNamed_("Reload"), None)
211        addToolbarItem(self, kWSTPreferencesToolbarItemIdentifier,
212                       "Preferences", "Preferences", "Show Preferences", None,
213                       "orderFrontPreferences:", NSImage.imageNamed_("Preferences"), None)
214        addToolbarItem(self, kWSTUrlTextFieldToolbarItemIdentifier,
215                       "URL", "URL", "Server URL", None, None, self.urlTextField, None)
216
217        self._toolbarDefaultItemIdentifiers = [
218            kWSTReloadContentsToolbarItemIdentifier,
219            kWSTUrlTextFieldToolbarItemIdentifier,
220            NSToolbarSeparatorItemIdentifier,
221            NSToolbarCustomizeToolbarItemIdentifier,
222        ]
223
224        self._toolbarAllowedItemIdentifiers = [
225            kWSTReloadContentsToolbarItemIdentifier,
226            kWSTUrlTextFieldToolbarItemIdentifier,
227            NSToolbarSeparatorItemIdentifier,
228            NSToolbarSpaceItemIdentifier,
229            NSToolbarFlexibleSpaceItemIdentifier,
230            NSToolbarPrintItemIdentifier,
231            kWSTPreferencesToolbarItemIdentifier,
232            NSToolbarCustomizeToolbarItemIdentifier,
233        ]
234
235    def toolbarDefaultItemIdentifiers_(self, anIdentifier):
236        """
237        Return an array of toolbar item identifiers that identify the
238        set, in order, of items that should be displayed on the
239        default toolbar.
240        """
241        return self._toolbarDefaultItemIdentifiers
242
243    def toolbarAllowedItemIdentifiers_(self, anIdentifier):
244        """
245        Return an array of toolbar items that may be used in the toolbar.
246        """
247        return self._toolbarAllowedItemIdentifiers
248
249    def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(self,
250                                                                 toolbar,
251                                                                 itemIdentifier, flag):
252        """
253        Delegate method fired when the toolbar is about to insert an
254        item into the toolbar.  Item is identified by itemIdentifier.
255
256        Effectively makes a copy of the cached reference instance of
257        the toolbar item identified by itemIdentifier.
258        """
259        newItem = NSToolbarItem.alloc().initWithItemIdentifier_(itemIdentifier)
260        item = self._toolbarItems[itemIdentifier]
261
262        newItem.setLabel_( item.label() )
263        newItem.setPaletteLabel_( item.paletteLabel() )
264        if item.view():
265            newItem.setView_( item.view() )
266        else:
267            newItem.setImage_( item.image() )
268
269        newItem.setToolTip_( item.toolTip() )
270        newItem.setTarget_( item.target() )
271        newItem.setAction_( item.action() )
272        newItem.setMenuFormRepresentation_( item.menuFormRepresentation() )
273
274        if newItem.view():
275            newItem.setMinSize_( item.minSize() )
276            newItem.setMaxSize_( item.maxSize() )
277
278        return newItem
279
280    def setStatusTextFieldMessage_(self, aMessage):
281        """
282        Sets the contents of the statusTextField to aMessage and
283        forces the fileld's contents to be redisplayed.
284        """
285        if not aMessage:
286            aMessage = "Displaying information about %d methods." % len(self._methods)
287        # All UI calls should be directed to the main thread
288        self.statusTextField.performSelectorOnMainThread_withObject_waitUntilDone_(
289            "setStringValue:", aMessage, 0)
290
291    def reloadData(self):
292        """Tell the main thread to update the table view."""
293        self.methodsTable.performSelectorOnMainThread_withObject_waitUntilDone_(
294            "reloadData", None, 0)
295
296    def startWorking(self):
297        """Signal the UI there's work goin on."""
298        if not self._working:
299            self.progressIndicator.startAnimation_(self)
300        self._working += 1
301
302    def stopWorking(self):
303        """Signal the UI that the work is done."""
304        self._working -= 1
305        if not self._working:
306            self.progressIndicator.performSelectorOnMainThread_withObject_waitUntilDone_(
307                "stopAnimation:", self, 0)
308
309    @objc.IBAction
310    def reloadVisibleData_(self, sender):
311        """
312        Reloads the list of methods and their signatures from the
313        XML-RPC server specified in the urlTextField.  Displays
314        appropriate error messages, if necessary.
315        """
316        if self._working:
317            # don't start a new job while there's an unfinished one
318            return
319        url = self.urlTextField.stringValue()
320        self._methods = []
321        self._methodSignatures = {}
322        self._methodDescriptions = {}
323
324        if not url:
325            self.window().setTitle_("Untitled.")
326            self.setStatusTextFieldMessage_("No URL specified.")
327            return
328
329        self.window().setTitle_(url)
330        NSUserDefaults.standardUserDefaults().setObject_forKey_(url, "LastURL")
331
332        self.setStatusTextFieldMessage_("Retrieving method list...")
333        self.startWorking()
334        self._workerThread.scheduleWork(self.getMethods, url)
335
336    def getMethods(self, url):
337        self._server = xmlrpclib.ServerProxy(url)
338        pool = NSAutoreleasePool.alloc().init()  # use an extra pool to get rid of intermediates
339        try:
340            self._methods = self._server.listMethods()
341            self._methodPrefix = ""
342        except:
343            try:
344                self._methods = self._server.system.listMethods()
345                self._methodPrefix = "system."
346            except:
347                self._server = None
348                self._methodPrefix = None
349                self.setStatusTextFieldMessage_("Server failed to respond to listMethods query.  "
350                                                "See below for more information.")
351
352                exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
353                self.methodDescriptionTextView.performSelectorOnMainThread_withObject_waitUntilDone_(
354                    "setString:",
355                    "Exception information\n\nType: %s\n\nValue: %s\n\nTraceback:\n\n %s\n" %
356                    (exceptionType, exceptionValue, "\n".join(traceback.format_tb(exceptionTraceback))),
357                    0)
358                self.stopWorking()
359                return
360
361        del pool
362        if self._windowIsClosing:
363            return
364
365        self._methods.sort(lambda x, y: cmp(x, y))
366        self.reloadData()
367        self.setStatusTextFieldMessage_("Retrieving information about %d methods." % len(self._methods))
368
369        index = 0
370        for aMethod in self._methods:
371            if self._windowIsClosing:
372                return
373            pool = NSAutoreleasePool.alloc().init()  # use an extra pool to get rid of intermediates
374            index = index + 1
375            if not (index % 5):
376                self.reloadData()
377            self.setStatusTextFieldMessage_("Retrieving signature for method %s (%d of %d)." % (aMethod , index, len(self._methods)))
378            del pool
379            methodSignature = getattr(self._server, self._methodPrefix + "methodSignature")(aMethod)
380            signatures = None
381            if isinstance(methodSignature, str): continue
382            if not len(methodSignature):
383                continue
384
385            for aSignature in methodSignature:
386                if (type(aSignature) == types.ListType) and (len(aSignature) > 0):
387                    signature = "%s %s(%s)" % (aSignature[0], aMethod, string.join(aSignature[1:], ", "))
388                else:
389                    signature = aSignature
390            if signatures:
391                signatures = signatures + ", " + signature
392            else:
393                signatures = signature
394            self._methodSignatures[aMethod] = signatures
395        self.setStatusTextFieldMessage_(None)
396        self.reloadData()
397        self.stopWorking()
398
399    def tableViewSelectionDidChange_(self, sender):
400        """
401        When the user selects a remote method, this method displays
402        the documentation for that method as returned by the XML-RPC
403        server.  If the method's documentation has been previously
404        queried, the documentation will be retrieved from a cache.
405        """
406        selectedRow = self.methodsTable.selectedRow()
407        selectedMethod = self._methods[selectedRow]
408
409        if not self._methodDescriptions.has_key(selectedMethod):
410            self._methodDescriptions[selectedMethod] = "<description is being retrieved>"
411            self.startWorking()
412            def work():
413                self.setStatusTextFieldMessage_("Retrieving signature for method %s..." % selectedMethod)
414                methodDescription = getattr(self._server, self._methodPrefix + "methodHelp")(selectedMethod)
415                if not methodDescription:
416                    methodDescription = "No description available."
417                self._methodDescriptions[selectedMethod] = methodDescription
418                if selectedRow == self.methodsTable.selectedRow():
419                    self.setStatusTextFieldMessage_(None)
420                    self.methodDescriptionTextView.setString_(methodDescription)
421                self.stopWorking()
422            self._workerThread.scheduleWork(work)
423        else:
424            self.setStatusTextFieldMessage_(None)
425        methodDescription = self._methodDescriptions[selectedMethod]
426        self.methodDescriptionTextView.setString_(methodDescription)
427
428    def numberOfRowsInTableView_(self, aTableView):
429        """
430        Returns the number of methods found on the server.
431        """
432        return len(self._methods)
433
434    def tableView_objectValueForTableColumn_row_(self, aTableView, aTableColumn, rowIndex):
435        """
436        Returns either the raw method name or the method signature,
437        depending on if a signature had been found on the server.
438        """
439        aMethod = self._methods[rowIndex]
440        if self._methodSignatures.has_key(aMethod):
441            return self._methodSignatures[aMethod]
442        else:
443            return aMethod
444
445    def tableView_shouldEditTableColumn_row_(self, aTableView, aTableColumn, rowIndex):
446        # don't allow editing of any cells
447        return 0
448