Package caldavclientlibrary :: Package ui :: Module WebDAVBrowser
[hide private]
[frames] | no frames]

Source Code for Module caldavclientlibrary.ui.WebDAVBrowser

  1  ## 
  2  # Copyright (c) 2007-2016 Apple Inc. All rights reserved. 
  3  # 
  4  # Licensed under the Apache License, Version 2.0 (the "License"); 
  5  # you may not use this file except in compliance with the License. 
  6  # You may obtain a copy of the License at 
  7  # 
  8  # http://www.apache.org/licenses/LICENSE-2.0 
  9  # 
 10  # Unless required by applicable law or agreed to in writing, software 
 11  # distributed under the License is distributed on an "AS IS" BASIS, 
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 13  # See the License for the specific language governing permissions and 
 14  # limitations under the License. 
 15  ## 
 16   
 17  """ 
 18  Code based on PyObjC examples included with Apple Developer tools. 
 19  """ 
 20   
 21  import Foundation, AppKit, WebKit #@UnusedImport 
 22   
 23  from AppKit import * #@UnusedWildImport 
 24  from AppKit import NSApplication #@UnresolvedImport 
 25  from AppKit import NSImage #@UnresolvedImport 
 26  from AppKit import NSMenuItem #@UnresolvedImport 
 27  from AppKit import NSObject #@UnresolvedImport 
 28  from AppKit import NSPlainFileType #@UnresolvedImport 
 29  from AppKit import NSSize #@UnresolvedImport 
 30  from AppKit import NSToolbar #@UnresolvedImport 
 31  from AppKit import NSToolbarCustomizeToolbarItemIdentifier #@UnresolvedImport 
 32  from AppKit import NSToolbarFlexibleSpaceItemIdentifier #@UnresolvedImport 
 33  from AppKit import NSToolbarItem #@UnresolvedImport 
 34  from AppKit import NSToolbarPrintItemIdentifier #@UnresolvedImport 
 35  from AppKit import NSToolbarSeparatorItemIdentifier #@UnresolvedImport 
 36  from AppKit import NSToolbarSpaceItemIdentifier #@UnresolvedImport 
 37  from AppKit import NSURL #@UnresolvedImport 
 38  from AppKit import NSUserDefaults #@UnresolvedImport 
 39  from AppKit import NSWorkspace #@UnresolvedImport 
 40   
 41  from Foundation import * #@UnusedWildImport 
 42   
 43  from PyObjCTools import NibClassBuilder, AppHelper 
 44   
 45  import objc 
 46   
 47  from caldavclientlibrary.protocol.utils import xmlhelpers 
 48  from caldavclientlibrary.ui.session import Session 
 49  from xml.etree.ElementTree import _ElementInterface 
 50  import types 
 51   
 52  NibClassBuilder.extractClasses("WebDAVBrowser") 
 53   
 54  kServerToolbarItemIdentifier = "WDB: Server Toolbar Identifier" 
 55  kRefreshToolbarItemIdentifier = "WDB: Refresh Toolbar Identifier" 
 56  kBrowserViewToolbarItemIdentifier = "WDB: Browser View Toolbar Identifier" 
 57  kDataViewToolbarItemIdentifier = "WDB: Data View Toolbar Identifier" 
58 59 -def addToolbarItem(aController, anIdentifier, aLabel, aPaletteLabel, 60 aToolTip, aTarget, anAction, anItemContent, aMenu):
61 """ 62 Add a toolbar button of some kind. 63 """ 64 toolbarItem = NSToolbarItem.alloc().initWithItemIdentifier_(anIdentifier) 65 66 toolbarItem.setLabel_(aLabel) 67 toolbarItem.setPaletteLabel_(aPaletteLabel) 68 toolbarItem.setToolTip_(aToolTip) 69 toolbarItem.setTarget_(aTarget) 70 if anAction: 71 toolbarItem.setAction_(anAction) 72 73 if type(anItemContent) == NSImage: 74 toolbarItem.setImage_(anItemContent) 75 else: 76 toolbarItem.setView_(anItemContent) 77 bounds = anItemContent.bounds() 78 minSize = (bounds[1][0], bounds[1][1]) 79 maxSize = (bounds[1][0], bounds[1][1]) 80 toolbarItem.setMinSize_( minSize ) 81 toolbarItem.setMaxSize_( maxSize ) 82 83 if aMenu: 84 menuItem = NSMenuItem.alloc().init() 85 menuItem.setSubmenu_(aMenu) 86 menuItem.setTitle_( aMenu.title() ) 87 toolbarItem.setMenuFormRepresentation_(menuItem) 88 89 aController._toolbarItems[anIdentifier] = toolbarItem
90 91 WRAPPED={}
92 -class Wrapper(NSObject):
93 """ 94 NSOutlineView doesn't retain values, which means we cannot use normal 95 python values as values in an outline view. 96 """
97 - def init_(self, value):
98 self.value = value 99 return self
100
101 - def __str__(self):
102 return '<Wrapper for %s>' % self.value
103
104 - def description(self):
105 return str(self)
106
107 -def wrap_object(obj):
108 if WRAPPED.has_key(obj): 109 return WRAPPED[obj] 110 else: 111 WRAPPED[obj] = Wrapper.alloc().init_(obj) 112 return WRAPPED[obj]
113
114 -def unwrap_object(obj):
115 if obj is None: 116 return obj 117 return obj.value
118
119 -class WebDAVBrowserDelegate(NibClassBuilder.AutoBaseClass):
120 """ 121 Class defined in NIB file. This acts as the delegate and responder 122 for the various NSViews and toolbar items. It basically our controller. 123 """ 124 125 list = objc.IBOutlet() 126 127 listView = objc.IBOutlet() 128 129 mainSplitterView = objc.IBOutlet() 130 131 passwordText = objc.IBOutlet() 132 133 pathLabel = objc.IBOutlet() 134 135 pathText = objc.IBOutlet() 136 137 progress = objc.IBOutlet() 138 139 propertiesView = objc.IBOutlet() 140 141 serverText = objc.IBOutlet() 142 143 startupSheet = objc.IBOutlet() 144 145 table = objc.IBOutlet() 146 147 text = objc.IBOutlet() 148 149 toolbarBrowserViewButton = objc.IBOutlet() 150 151 toolbarDataViewButton = objc.IBOutlet() 152 153 userText = objc.IBOutlet() 154 155 webView = objc.IBOutlet() 156 157 window = objc.IBOutlet() 158 159 browser = objc.IBOutlet() 160 161 columnView = objc.IBOutlet() 162 163 dataView = objc.IBOutlet() 164 165 BROWSERVIEW_COLUMNS = 0 166 BROWSERVIEW_LIST = 1 167 168 DATAVIEW_PROPERTIES = 0 169 DATAVIEW_DATA = 1 170 #DATAVIEW_DELEGATES = 2 171 #DATAVIEW_ACLS = 3 172 173 selectedDetails = None 174
175 - def awakeFromNib(self):
176 # Initialise our session and selected state parameters 177 self.session = None 178 self.columns = [[]] 179 self.selectedResource = None 180 self.selectedDetails = None 181 self.selectedData = None 182 183 # Cache some useful icons 184 self.fileImage = NSWorkspace.sharedWorkspace().iconForFileType_(NSPlainFileType) 185 self.fileImage.setSize_(NSSize(16, 16)) 186 self.directoryImage = NSWorkspace.sharedWorkspace().iconForFile_("/usr/") 187 self.directoryImage.setSize_(NSSize(16, 16)) 188 189 # Initialise the toolbar 190 self._toolbarItems = {} 191 self._toolbarDefaultItemIdentifiers = [] 192 self._toolbarAllowedItemIdentifiers = [] 193 self.createToolbar() 194 195 # Set up browser view 196 container = self.mainSplitterView.subviews()[0] 197 container.addSubview_(self.columnView) 198 container.addSubview_(self.listView) 199 self.currentBrowserView = None 200 self.setBrowserView(self.BROWSERVIEW_COLUMNS) 201 self.browser.setMaxVisibleColumns_(7) 202 self.browser.setMinColumnWidth_(150) 203 204 # Set up data view 205 container = self.mainSplitterView.subviews()[1] 206 container.addSubview_(self.propertiesView) 207 container.addSubview_(self.dataView) 208 self.currentDataView = None 209 self.setDataView(self.DATAVIEW_PROPERTIES) 210 self.text.setString_("") 211 212 self.pathLabel.setStringValue_("No server specified") 213 214 # Get preferences 215 lastServer = NSUserDefaults.standardUserDefaults().stringForKey_("LastServer") 216 if lastServer and len(lastServer): 217 self.serverText.setStringValue_(lastServer) 218 lastPath = NSUserDefaults.standardUserDefaults().stringForKey_("LastPath") 219 if lastPath and len(lastPath): 220 self.pathText.setStringValue_(lastPath) 221 else: 222 self.pathText.setStringValue_("/") 223 lastUser = NSUserDefaults.standardUserDefaults().stringForKey_("LastUser") 224 if lastUser and len(lastUser): 225 self.userText.setStringValue_(lastUser)
226
227 - def createToolbar(self):
228 """ 229 Create the toolbar for our app. 230 """ 231 toolbar = NSToolbar.alloc().initWithIdentifier_("Toolbar") 232 toolbar.setDelegate_(self) 233 toolbar.setAllowsUserCustomization_(YES) 234 toolbar.setAutosavesConfiguration_(YES) 235 236 self.createToolbarItems() 237 238 self.window.setToolbar_(toolbar)
239
240 - def createToolbarItems(self):
241 """ 242 Create the toolbar item and define the default and allowed set. 243 """ 244 addToolbarItem(self, kServerToolbarItemIdentifier, 245 "Server", "Server", "Reset Server", self, 246 "resetServer:", NSImage.imageNamed_("NSNetwork"), None,) 247 248 addToolbarItem(self, kRefreshToolbarItemIdentifier, 249 "Refresh", "Refresh", "Refresh Display", self, 250 "refreshData:", NSImage.imageNamed_("NSRefresh"), None,) 251 252 addToolbarItem(self, kBrowserViewToolbarItemIdentifier, 253 "Browser", "Browser", "Browser View", self, 254 "changeBrowserView:", self.toolbarBrowserViewButton, None,) 255 256 addToolbarItem(self, kDataViewToolbarItemIdentifier, 257 "View", "View", "Data View", self, 258 "changeDataView:", self.toolbarDataViewButton, None,) 259 260 self._toolbarDefaultItemIdentifiers = [ 261 kServerToolbarItemIdentifier, 262 kBrowserViewToolbarItemIdentifier, 263 kDataViewToolbarItemIdentifier, 264 NSToolbarFlexibleSpaceItemIdentifier, 265 kRefreshToolbarItemIdentifier, 266 ] 267 268 self._toolbarAllowedItemIdentifiers = [ 269 kServerToolbarItemIdentifier, 270 kBrowserViewToolbarItemIdentifier, 271 kDataViewToolbarItemIdentifier, 272 kRefreshToolbarItemIdentifier, 273 NSToolbarSeparatorItemIdentifier, 274 NSToolbarSpaceItemIdentifier, 275 NSToolbarFlexibleSpaceItemIdentifier, 276 NSToolbarPrintItemIdentifier, 277 NSToolbarCustomizeToolbarItemIdentifier, 278 ]
279
280 - def toolbarDefaultItemIdentifiers_(self, anIdentifier):
281 """ 282 Return the default set of toolbar items. 283 """ 284 return self._toolbarDefaultItemIdentifiers
285
286 - def toolbarAllowedItemIdentifiers_(self, anIdentifier):
287 """ 288 Return the allowed set of toolbar items. 289 """ 290 return self._toolbarAllowedItemIdentifiers
291
292 - def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(self, 293 toolbar, 294 itemIdentifier, flag):
295 """ 296 Delegate method fired when the toolbar is about to insert an 297 item into the toolbar. Item is identified by itemIdentifier. 298 299 Effectively makes a copy of the cached reference instance of 300 the toolbar item identified by itemIdentifier. 301 """ 302 newItem = NSToolbarItem.alloc().initWithItemIdentifier_(itemIdentifier) 303 item = self._toolbarItems[itemIdentifier] 304 305 newItem.setLabel_( item.label() ) 306 newItem.setPaletteLabel_( item.paletteLabel() ) 307 if item.view(): 308 newItem.setView_( item.view() ) 309 else: 310 newItem.setImage_( item.image() ) 311 312 newItem.setToolTip_( item.toolTip() ) 313 newItem.setTarget_( item.target() ) 314 newItem.setAction_( item.action() ) 315 newItem.setMenuFormRepresentation_( item.menuFormRepresentation() ) 316 317 if newItem.view(): 318 newItem.setMinSize_( item.minSize() ) 319 newItem.setMaxSize_( item.maxSize() ) 320 321 return newItem
322
323 - def setBrowserView(self, view):
324 """ 325 Change the browser view pane to the specified list type. 326 """ 327 newView = { 328 self.BROWSERVIEW_COLUMNS: self.columnView, 329 self.BROWSERVIEW_LIST : self.listView, 330 }.get(view, None) 331 if self.currentBrowserView != newView: 332 if self.currentBrowserView: 333 self.currentBrowserView.setHidden_(YES) 334 self.currentBrowserView = newView 335 if self.currentBrowserView: 336 self.currentBrowserView.setHidden_(NO) 337 self.browserview = view 338 self.refreshView()
339 340 @objc.IBAction
341 - def changeBrowserView_(self, sender):
342 """ 343 User clicked a browser toolbar button. 344 """ 345 self.setBrowserView(sender.selectedSegment())
346
347 - def setDataView(self, view):
348 """ 349 Change the data view pane to the specified type. 350 """ 351 newView = { 352 self.DATAVIEW_PROPERTIES: self.propertiesView, 353 self.DATAVIEW_DATA : self.dataView, 354 }.get(view, None) 355 if self.currentDataView != newView: 356 if self.currentDataView: 357 self.currentDataView.setHidden_(YES) 358 self.currentDataView = newView 359 if self.currentDataView: 360 self.currentDataView.setHidden_(NO) 361 self.dataview = view 362 self.refreshView()
363 364 @objc.IBAction
365 - def changeDataView_(self, sender):
366 """ 367 User clicked a view toolbar button. 368 """ 369 self.setDataView(sender.selectedSegment())
370 371 @objc.IBAction
372 - def resetServer_(self, sender):
373 """ 374 Display the sheet asking for server details. 375 """ 376 NSApplication.sharedApplication().beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_( 377 self.startupSheet, 378 self.window, 379 None, None, 0 380 )
381
382 - def refreshData_(self, sender):
383 """ 384 Force a refresh of the data for the current selected resource. 385 """ 386 if self.selectedResource: 387 self.selectedResource.clear() 388 389 self.progress.startAnimation_(self) 390 resources = self.selectedResource.listChildren() 391 self.columns[-1] = resources 392 self.progress.stopAnimation_(self) 393 394 self.refreshView()
395
396 - def refreshView(self):
397 """ 398 Refresh the actual data view for the selected resource. 399 """ 400 if self.selectedResource: 401 self.progress.startAnimation_(self) 402 if self.dataview == self.DATAVIEW_PROPERTIES: 403 self.selectedDetails = self.selectedResource.getAllDetails() 404 self.table.reloadData() 405 self.table.deselectAll_(self) 406 self.text.setString_("") 407 elif self.dataview == self.DATAVIEW_DATA: 408 self.selectedData = self.selectedResource.getDataAsHTML() 409 url = NSURL.alloc().initWithString_(self.serverText.stringValue()) 410 self.webView.mainFrame().loadHTMLString_baseURL_(self.selectedData, url) 411 self.progress.stopAnimation_(self)
412 413 @objc.IBAction
414 - def startupOKAction_(self, btn):
415 """ 416 User clicked OK in the server setup sheet. 417 """ 418 419 # Create the actual session. 420 server = self.serverText.stringValue() 421 path = self.pathText.stringValue() 422 user = self.userText.stringValue() 423 pswd = self.passwordText.stringValue() 424 self.session = Session(server, path, user, pswd, logging=False) 425 self.window.setTitle_(self.serverText.stringValue()) 426 self.pathLabel.setStringValue_(self.session.path) 427 NSUserDefaults.standardUserDefaults().setObject_forKey_(server, "LastServer") 428 NSUserDefaults.standardUserDefaults().setObject_forKey_(path, "LastPath") 429 NSUserDefaults.standardUserDefaults().setObject_forKey_(user, "LastUser") 430 431 # List the root resource. 432 self.progress.startAnimation_(self) 433 resources = self.session.getRoot().listChildren() 434 self.progress.stopAnimation_(self) 435 self.columns = [resources] 436 437 # Done with the sheet. 438 self.startupSheet.close() 439 NSApplication.sharedApplication().endSheet_(self.startupSheet) 440 441 # Force reload of browser pane views. 442 self.browser.loadColumnZero() 443 self.list.reloadItem_(None)
444 445 @objc.IBAction
446 - def startupCancelAction_(self, btn):
447 """ 448 User clicked the cancel button in the server sheet. 449 """ 450 self.startupSheet.close() 451 NSApplication.sharedApplication().endSheet_(self.startupSheet)
452 453 @objc.IBAction
454 - def browserAction_(self, browser):
455 """ 456 Something changed in the column browser. 457 """ 458 459 # Update current path. 460 self.pathLabel.setStringValue_((self.session.path if len(self.session.path) > 1 else "") + browser.path()) 461 462 # Get new selected resource and refresh the data view. 463 self.selectedResource = None 464 self.selectedDetails = None 465 col = len(self.columns) 466 row = -1 467 while row == -1: 468 col -= 1 469 if col < 0: 470 break 471 row = self.browser.selectedRowInColumn_(col) 472 if row >= 0: 473 self.selectedResource = self.columns[col][row] 474 475 self.refreshView()
476
477 - def browser_willDisplayCell_atRow_column_(self, browser, cell, row, col):
478 """ 479 Delegate method to set the actual stuff displayed in a column view row. 480 """ 481 isLeaf = not self.columns[col][row].isCollection() 482 cell.setLeaf_(isLeaf) 483 cell.setStringValue_(self.columns[col][row].getName()) 484 cell.setImage_(self.fileImage if isLeaf else self.directoryImage)
485
486 - def browser_numberOfRowsInColumn_(self, browser, col):
487 """ 488 Delegate method that returns the number of rows in a column view column. 489 """ 490 if col == 0: 491 return len(self.columns[0]) 492 del self.columns[col:] 493 resource = self.columns[col - 1][browser.selectedRowInColumn_(col - 1)] 494 self.progress.startAnimation_(self) 495 resources = resource.listChildren() 496 self.progress.stopAnimation_(self) 497 self.columns.append(resources) 498 return len(resources)
499
500 - def outlineViewSelectionDidChange_(self, notification):
501 """ 502 Delegate method called when the selection in the outline view changes. 503 """ 504 505 # Get the new selected resource and refresh the data view. 506 row = self.list.selectedRow() 507 if row == -1: 508 self.selectedResource = None 509 self.pathLabel.setStringValue_("Nothing Selected") 510 else: 511 self.selectedResource = unwrap_object(self.list.itemAtRow_(row)) 512 self.pathLabel.setStringValue_(self.selectedResource.getPath()) 513 514 self.refreshView()
515
516 - def outlineView_numberOfChildrenOfItem_(self, outlineView, item):
517 """ 518 Delegate method to return the number of children of an item in the outline view. 519 """ 520 if self.session is None: 521 return 0 522 if item is None: 523 resource = self.session.getRoot() 524 else: 525 resource = unwrap_object(item) 526 self.progress.startAnimation_(self) 527 resources = resource.listChildren() 528 self.progress.stopAnimation_(self) 529 return len(resources)
530
531 - def outlineView_isItemExpandable_(self, outlineView, item):
532 """ 533 Delegate method to return the whether an item in the outline view is expandable. 534 """ 535 if item is None: 536 return YES 537 else: 538 resource = unwrap_object(item) 539 return YES if resource.isCollection() else NO
540
541 - def outlineView_child_ofItem_(self, outlineView, index, item):
542 """ 543 Delegate method to return the item associated with a row in the outline view. 544 """ 545 if item is None: 546 resource = self.session.getRoot() 547 else: 548 resource = unwrap_object(item) 549 self.progress.startAnimation_(self) 550 resources = resource.listChildren() 551 self.progress.stopAnimation_(self) 552 return wrap_object(resources[index])
553
554 - def outlineView_objectValueForTableColumn_byItem_(self, outlineView, tableColumn, item):
555 """ 556 Delegate method to return the data displayed in the outline view. 557 """ 558 if item is None: 559 resource = self.session.getRoot() 560 else: 561 resource = unwrap_object(item) 562 return { 563 "Name" : resource.getName(), 564 "Size" : resource.getSize(), 565 "Modified": resource.getLastMod(), 566 }[tableColumn.identifier()]
567 568 @objc.IBAction
569 - def tableAction_(self, table):
570 """ 571 Called when the selection in the properties list changes. 572 """ 573 574 # Get the selected property and display its value. 575 row = self.table.selectedRow() 576 if row >= 0: 577 value = self.selectedDetails[row][1] 578 if type(value) in (types.ListType, types.TupleType,): 579 if len(value) == 1: 580 text = self.propValueToText(value[0]) 581 else: 582 sorted = [self.propValueToText(v) for v in value] 583 sorted.sort() 584 text = "\r".join(sorted) 585 else: 586 text = self.propValueToText(value) 587 else: 588 text = "" 589 self.text.setString_(text)
590
591 - def numberOfRowsInTableView_(self, tableView):
592 """ 593 Delegate method to return the number of rows in the list. 594 @param tableView: 595 @type tableView: 596 """ 597 if self.selectedDetails is None: 598 return 0 599 return len(self.selectedDetails)
600
601 - def tableView_objectValueForTableColumn_row_(self, tableView, col, row):
602 """ 603 Delegate method to return the text for a list cell. 604 """ 605 return str(self.selectedDetails[row][0])
606
607 - def tableView_shouldEditTableColumn_row_(self, tableView, col, row):
608 """ 609 Delegate method to indicate whether a cell can be edited. 610 """ 611 return 0
612
613 - def propValueToText(self, value):
614 """ 615 Do a sensible print of a property value taking type into account. 616 """ 617 if type(value) in (types.StringType, types.UnicodeType, types.IntType): 618 text = str(value) 619 elif isinstance(value, _ElementInterface): 620 text = xmlhelpers.elementToString(value).replace("\n", "") 621 else: 622 text = str(value) 623 return text
624 625 if __name__ == "__main__": 626 AppHelper.runEventLoop() 627