1from Cocoa import * 2from Quartz import * 3from LaunchServices import * 4 5import os 6 7# openFiles 8# 9# A simple C function that opens NSOpenPanel and returns an array of file paths. 10# It uses uniform type identifiers (UTIs) for proper filtering of image files. 11# ------------------------------------------------------------------------- 12def openFiles(): 13 # Get a list of extensions to filter in our NSOpenPanel. 14 panel = NSOpenPanel.openPanel() 15 16 # The user can choose a folder; images in the folder are added recursively. 17 panel.setCanChooseDirectories_(True) 18 panel.setCanChooseFiles_(True) 19 panel.setAllowsMultipleSelection_(True) 20 21 if panel.runModalForTypes_(NSImage.imageUnfilteredTypes()) == NSOKButton: 22 return panel.filenames() 23 24 return [] 25 26#============================================================================== 27# This is the data source object. 28class myImageObject (NSObject): 29 _path = objc.ivar() 30 31 # ------------------------------------------------------------------------- 32 # setPath:path 33 # 34 # The data source object is just a file path representation 35 # ------------------------------------------------------------------------- 36 def setPath_(self, inPath): 37 self._path = inPath 38 39 # The required methods of the IKImageBrowserItem protocol. 40 41 # ------------------------------------------------------------------------- 42 # imageRepresentationType: 43 # 44 # Set up the image browser to use a path representation. 45 # ------------------------------------------------------------------------- 46 def imageRepresentationType(self): 47 return IKImageBrowserPathRepresentationType 48 49 # ------------------------------------------------------------------------- 50 # imageRepresentation: 51 # 52 # Give the path representation to the image browser. 53 # ------------------------------------------------------------------------- 54 def imageRepresentation(self): 55 return self._path 56 57 # ------------------------------------------------------------------------- 58 # imageUID: 59 # 60 # Use the absolute file path as the identifier. 61 # ------------------------------------------------------------------------- 62 def imageUID(self): 63 return self._path 64 65class ImageBrowserController (NSWindowController): 66 imageBrowser = objc.IBOutlet() 67 68 images = objc.ivar() 69 importedImages = objc.ivar() 70 71 72 # ------------------------------------------------------------------------- 73 # awakeFromNib: 74 # ------------------------------------------------------------------------- 75 def awakeFromNib(self): 76 # Create two arrays : The first is for the data source representation. 77 # The second one contains temporary imported images for thread safeness. 78 self.images = NSMutableArray.alloc().init() 79 self.importedImages = NSMutableArray.alloc().init() 80 81 # Allow reordering, animations and set the dragging destination 82 # delegate. 83 self.imageBrowser.setAllowsReordering_(True) 84 self.imageBrowser.setAnimates_(True) 85 self.imageBrowser.setDraggingDestinationDelegate_(self) 86 87 # ------------------------------------------------------------------------- 88 # updateDatasource: 89 # 90 # This is the entry point for reloading image browser data and 91 # triggering setNeedsDisplay. 92 # ------------------------------------------------------------------------- 93 def updateDatasource(self): 94 # Update the datasource, add recently imported items. 95 self.images.extend(self.importedImages) 96 97 # Empty the temporary array. 98 del self.importedImages[:] 99 100 # Reload the image browser, which triggers setNeedsDisplay. 101 self.imageBrowser.reloadData() 102 103 104 # ------------------------------------------------------------------------- 105 # isImageFile:filePath 106 # 107 # This utility method indicates if the file located at 'filePath' is 108 # an image file based on the UTI. It relies on the ImageIO framework for 109 # the supported type identifiers. 110 # 111 # ------------------------------------------------------------------------- 112 def isImageFile_(self, filePath): 113 isImageFile = False 114 uti = None 115 116 url = CFURLCreateWithFileSystemPath(None, filePath, kCFURLPOSIXPathStyle, False) 117 118 res, info = LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, None) 119 if res == 0: 120 # Obtain the UTI using the file information. 121 122 # If there is a file extension, get the UTI. 123 if info[3] != None: 124 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info[3], kUTTypeData) 125 126 # No UTI yet 127 if uti is None: 128 # If there is an OSType, get the UTI. 129 typeString = UTCreateStringForOSType(info.filetype) 130 if typeString != None: 131 uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData) 132 133 # Verify that this is a file that the ImageIO framework supports. 134 if uti is not None: 135 supportedTypes = CGImageSourceCopyTypeIdentifiers() 136 137 for item in supportedTypes: 138 if UTTypeConformsTo(uti, item): 139 isImageFile = True 140 break 141 142 return isImageFile 143 144 # ------------------------------------------------------------------------- 145 # addAnImageWithPath:path 146 # ------------------------------------------------------------------------- 147 def addAnImageWithPath_(self, path): 148 addObject = False 149 150 fileAttribs = NSFileManager.defaultManager().fileAttributesAtPath_traverseLink_(path, True) 151 if fileAttribs is not None: 152 # Check for packages. 153 if NSFileTypeDirectory == fileAttribs[NSFileType]: 154 if not NSWorkspace.sharedWorkspace().isFilePackageAtPath_(path): 155 addObject = True # If it is a file, it's OK to add. 156 157 else: 158 addObject = True # It is a file, so it's OK to add. 159 160 if addObject and self.isImageFile_(path): 161 # Add a path to the temporary images array. 162 p = myImageObject.alloc().init() 163 p.setPath_(path) 164 self.importedImages.append(p) 165 166 # ------------------------------------------------------------------------- 167 # addImagesWithPath:path:recursive 168 # ------------------------------------------------------------------------- 169 def addImagesWithPath_recursive_(self, path, recursive): 170 dir = os.path.isdir(path) 171 if dir: 172 content = os.listdir(path) 173 # Parse the directory content. 174 for fn in content: 175 if recursive: 176 self.addImagesWithPath_recursive_( 177 os.path.join(path, fn), True) 178 else: 179 self.addAnImageWithPath_(os.path.join(path, fn)) 180 181 else: 182 self.addAnImageWithPath_(path) 183 184 # ------------------------------------------------------------------------- 185 # addImagesWithPaths:paths 186 # 187 # Performed in an independent thread, parse all paths in "paths" and 188 # add these paths in the temporary images array. 189 # ------------------------------------------------------------------------- 190 def addImagesWithPaths_(self, paths): 191 pool = NSAutoreleasePool.alloc().init() 192 193 for path in paths: 194 isdir = os.path.isdir(path) 195 self.addImagesWithPath_recursive_(path, isdir) 196 197 # Update the data source in the main thread. 198 self.performSelectorOnMainThread_withObject_waitUntilDone_( 199 'updateDatasource', None, True) 200 201 del pool 202 203 204 #pragma mark - 205 #pragma mark actions 206 207 # ------------------------------------------------------------------------- 208 # addImageButtonClicked:sender 209 # 210 # The user clicked the Add button.d 211 # ------------------------------------------------------------------------- 212 @objc.IBAction 213 def addImageButtonClicked_(self, sender): 214 path = openFiles() 215 if path: 216 # launch import in an independent thread 217 NSThread.detachNewThreadSelector_toTarget_withObject_( 218 'addImagesWithPaths:', self, path) 219 220 # ------------------------------------------------------------------------- 221 # addImageButtonClicked:sender 222 # 223 # Action called when the zoom slider changes. 224 # ------------------------------------------------------------------------- 225 @objc.IBAction 226 def zoomSliderDidChange_(self, sender): 227 # update the zoom value to scale images 228 self.imageBrowser.setZoomValue_(sender.floatValue()) 229 230 # redisplay 231 self.imageBrowser.setNeedsDisplay_(True) 232 233 # Implement the image browser data source protocol . 234 # The data source representation is a simple mutable array. 235 236 # ------------------------------------------------------------------------- 237 # numberOfItemsInImageBrowser:view 238 # ------------------------------------------------------------------------- 239 def numberOfItemsInImageBrowser_(self, view): 240 # The item count to display is the datadsource item count. 241 return len(self.images) 242 243 # ------------------------------------------------------------------------- 244 # imageBrowser:view:index: 245 # ------------------------------------------------------------------------- 246 def imageBrowser_itemAtIndex_(self, view, index): 247 return self.images[index] 248 249 250 # Implement some optional methods of the image browser datasource protocol to allow for removing and reodering items. 251 252 # ------------------------------------------------------------------------- 253 # removeItemsAtIndexes: 254 # 255 # The user wants to delete images, so remove these entries from the data source. 256 # ------------------------------------------------------------------------- 257 def imageBrowser_removeItemsAtIndexes_(self, view, indexes): 258 self.images.removeObjectsAtIndexes_(indexes) 259 260 # ------------------------------------------------------------------------- 261 # moveItemsAtIndexes: 262 # 263 # The user wants to reorder images, update the datadsource and the browser 264 # will reflect our changes. 265 # ------------------------------------------------------------------------- 266 def imageBrowser_moveItemsAtIndexes_toIndex_(self, browser, indexes, destinationIndex): 267 temporaryArray = [] 268 269 # First remove items from the data source and keep them in a 270 # temporary array. 271 for index in reversed(sorted(list(indexes))): 272 if index < destinationIndex: 273 destinationIndex -= 1 274 275 obj = self.images[index] 276 temporaryArray.append(obj) 277 del images[index] 278 279 # Then insert the removed items at the appropriate location. 280 for item in temporaryArray: 281 self.images.insertObject_atIndex_(item, destinationIndex) 282 283 return True 284 285 286 # ------------------------------------------------------------------------- 287 # draggingEntered:sender 288 # ------------------------------------------------------------------------- 289 def draggingEntered_(self, sender): 290 return NSDragOperationCopy 291 292 # ------------------------------------------------------------------------- 293 # draggingUpdated:sender 294 # ------------------------------------------------------------------------- 295 def draggingUpdated_(self, sender): 296 return NSDragOperationCopy 297 298 # ------------------------------------------------------------------------- 299 # performDragOperation:sender 300 # ------------------------------------------------------------------------- 301 def performDragOperation_(self, sender): 302 pasteboard = sender.draggingPasteboard() 303 304 # Look for paths on the pasteboard. 305 data = None 306 if NSFilenamesPboardType in pasteboard.types(): 307 data = pasteboard.dataForType_(NSFilenamesPboardType) 308 309 if data is not None: 310 # Retrieve paths. 311 filenames, format, errorDescription = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_( 312 data , kCFPropertyListImmutable, None, None) 313 314 # Add paths to the data source. 315 for fn in filenames: 316 self.addAnImageWithPath_(fn) 317 318 # Make the image browser reload the data source. 319 self.updateDatasource() 320 321 # Accept the drag operation. 322 return True 323