from Cocoa import *
from Quartz import *
from LaunchServices import *

import os

#       openFiles
#
#       A simple C function that opens NSOpenPanel and returns an array of file paths.
#       It uses uniform type identifiers (UTIs) for proper filtering of image files.
# -------------------------------------------------------------------------
def openFiles():
    # Get a list of extensions to filter in our NSOpenPanel.
    panel = NSOpenPanel.openPanel()

    # The user can choose a folder; images in the folder are added recursively.
    panel.setCanChooseDirectories_(True)
    panel.setCanChooseFiles_(True)
    panel.setAllowsMultipleSelection_(True)

    if panel.runModalForTypes_(NSImage.imageUnfilteredTypes()) == NSOKButton:
        return panel.filenames()

    return []

#==============================================================================
# This is the data source object.
class myImageObject (NSObject):
    _path = objc.ivar()

    # -------------------------------------------------------------------------
    #   setPath:path
    #
    #   The data source object is just a file path representation
    # -------------------------------------------------------------------------
    def setPath_(self, inPath):
        self._path = inPath

    # The required methods of the IKImageBrowserItem protocol.

    # -------------------------------------------------------------------------
    #   imageRepresentationType:
    #
    #   Set up the image browser to use a path representation.
    # -------------------------------------------------------------------------
    def imageRepresentationType(self):
        return IKImageBrowserPathRepresentationType

    # -------------------------------------------------------------------------
    #   imageRepresentation:
    #
    #   Give the path representation to the image browser.
    # -------------------------------------------------------------------------
    def imageRepresentation(self):
        return self._path

    # -------------------------------------------------------------------------
    #   imageUID:
    #
    #   Use the absolute file path as the identifier.
    # -------------------------------------------------------------------------
    def imageUID(self):
        return self._path

class ImageBrowserController (NSWindowController):
    imageBrowser = objc.IBOutlet()

    images = objc.ivar()
    importedImages = objc.ivar()


    # -------------------------------------------------------------------------
    #   awakeFromNib:
    # -------------------------------------------------------------------------
    def awakeFromNib(self):
        # Create two arrays : The first is for the data source representation.
        # The second one contains temporary imported images  for thread safeness.
        self.images = NSMutableArray.alloc().init()
        self.importedImages = NSMutableArray.alloc().init()

        # Allow reordering, animations and set the dragging destination
        # delegate.
        self.imageBrowser.setAllowsReordering_(True)
        self.imageBrowser.setAnimates_(True)
        self.imageBrowser.setDraggingDestinationDelegate_(self)

    # -------------------------------------------------------------------------
    #   updateDatasource:
    #
    #   This is the entry point for reloading image browser data and
    #   triggering setNeedsDisplay.
    # -------------------------------------------------------------------------
    def updateDatasource(self):
        # Update the datasource, add recently imported items.
        self.images.extend(self.importedImages)

        # Empty the temporary array.
        del self.importedImages[:]

        # Reload the image browser, which triggers setNeedsDisplay.
        self.imageBrowser.reloadData()


    # -------------------------------------------------------------------------
    #   isImageFile:filePath
    #
    #   This utility method indicates if the file located at 'filePath' is
    #   an image file based on the UTI. It relies on the ImageIO framework for
    #   the supported type identifiers.
    #
    # -------------------------------------------------------------------------
    def isImageFile_(self, filePath):
        isImageFile = False
        uti = None

        url = CFURLCreateWithFileSystemPath(None, filePath, kCFURLPOSIXPathStyle, False)

        res, info =  LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, None)
        if res == 0:
            # Obtain the UTI using the file information.

            # If there is a file extension, get the UTI.
            if info[3] != None:
                uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info[3], kUTTypeData)

            # No UTI yet
            if uti is None:
                # If there is an OSType, get the UTI.
                typeString = UTCreateStringForOSType(info.filetype)
                if typeString != None:
                    uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData)

            # Verify that this is a file that the ImageIO framework supports.
            if uti is not None:
                supportedTypes = CGImageSourceCopyTypeIdentifiers()

                for item in supportedTypes:
                    if UTTypeConformsTo(uti, item):
                        isImageFile = True
                        break

        return isImageFile

    # -------------------------------------------------------------------------
    #   addAnImageWithPath:path
    # -------------------------------------------------------------------------
    def addAnImageWithPath_(self, path):
        addObject = False

        fileAttribs = NSFileManager.defaultManager().fileAttributesAtPath_traverseLink_(path, True)
        if fileAttribs is not None:
            # Check for packages.
            if NSFileTypeDirectory == fileAttribs[NSFileType]:
                if not NSWorkspace.sharedWorkspace().isFilePackageAtPath_(path):
                    addObject = True    # If it is a file, it's OK to add.

            else:
                addObject = True    # It is a file, so it's OK to add.

        if addObject and self.isImageFile_(path):
            # Add a path to the temporary images array.
            p = myImageObject.alloc().init()
            p.setPath_(path)
            self.importedImages.append(p)

    # -------------------------------------------------------------------------
    #   addImagesWithPath:path:recursive
    # -------------------------------------------------------------------------
    def addImagesWithPath_recursive_(self, path, recursive):
        dir = os.path.isdir(path)
        if dir:
            content = os.listdir(path)
            # Parse the directory content.
            for fn in content:
                if recursive:
                    self.addImagesWithPath_recursive_(
                            os.path.join(path, fn), True)
                else:
                    self.addAnImageWithPath_(os.path.join(path, fn))

        else:
            self.addAnImageWithPath_(path)

    # -------------------------------------------------------------------------
    #   addImagesWithPaths:paths
    #
    #   Performed in an independent thread, parse all paths in "paths" and
    #   add these paths in the temporary images array.
    # -------------------------------------------------------------------------
    def addImagesWithPaths_(self, paths):
        pool = NSAutoreleasePool.alloc().init()

        for path in paths:
            isdir = os.path.isdir(path)
            self.addImagesWithPath_recursive_(path, isdir)

        # Update the data source in the main thread.
        self.performSelectorOnMainThread_withObject_waitUntilDone_(
                'updateDatasource', None, True)

        del pool


    #pragma mark -
    #pragma mark actions

    # -------------------------------------------------------------------------
    #   addImageButtonClicked:sender
    #
    #   The user clicked the Add button.d
    # -------------------------------------------------------------------------
    @objc.IBAction
    def addImageButtonClicked_(self, sender):
        path = openFiles()
        if path:
            # launch import in an independent thread
            NSThread.detachNewThreadSelector_toTarget_withObject_(
                    'addImagesWithPaths:', self, path)

    # -------------------------------------------------------------------------
    #   addImageButtonClicked:sender
    #
    #   Action called when the zoom slider changes.
    # -------------------------------------------------------------------------
    @objc.IBAction
    def zoomSliderDidChange_(self, sender):
        # update the zoom value to scale images
        self.imageBrowser.setZoomValue_(sender.floatValue())

        # redisplay
        self.imageBrowser.setNeedsDisplay_(True)

    # Implement the image browser  data source protocol .
    # The data source representation is a simple mutable array.

    # -------------------------------------------------------------------------
    #   numberOfItemsInImageBrowser:view
    # -------------------------------------------------------------------------
    def numberOfItemsInImageBrowser_(self, view):
        # The item count to display is the datadsource item count.
        return len(self.images)

    # -------------------------------------------------------------------------
    #   imageBrowser:view:index:
    # -------------------------------------------------------------------------
    def imageBrowser_itemAtIndex_(self, view, index):
        return self.images[index]


    # Implement some optional methods of the image browser  datasource protocol to allow for removing and reodering items.

    # -------------------------------------------------------------------------
    #   removeItemsAtIndexes:
    #
    #   The user wants to delete images, so remove these entries from the data source.
    # -------------------------------------------------------------------------
    def imageBrowser_removeItemsAtIndexes_(self, view, indexes):
        self.images.removeObjectsAtIndexes_(indexes)

    # -------------------------------------------------------------------------
    #   moveItemsAtIndexes:
    #
    #   The user wants to reorder images, update the datadsource and the browser
    #   will reflect our changes.
    # -------------------------------------------------------------------------
    def imageBrowser_moveItemsAtIndexes_toIndex_(self, browser, indexes, destinationIndex):
        temporaryArray = []

        # First remove items from the data source and keep them in a
        # temporary array.
        for index in reversed(sorted(list(indexes))):
            if index < destinationIndex:
                destinationIndex -= 1

            obj = self.images[index]
            temporaryArray.append(obj)
            del images[index]

        # Then insert the removed items at the appropriate location.
        for item in temporaryArray:
            self.images.insertObject_atIndex_(item, destinationIndex)

        return True


    # -------------------------------------------------------------------------
    #   draggingEntered:sender
    # -------------------------------------------------------------------------
    def draggingEntered_(self, sender):
        return NSDragOperationCopy

    # -------------------------------------------------------------------------
    #   draggingUpdated:sender
    # -------------------------------------------------------------------------
    def draggingUpdated_(self, sender):
        return NSDragOperationCopy

    # -------------------------------------------------------------------------
    #   performDragOperation:sender
    # -------------------------------------------------------------------------
    def performDragOperation_(self, sender):
        pasteboard = sender.draggingPasteboard()

        # Look for paths on the pasteboard.
        data = None
        if NSFilenamesPboardType in pasteboard.types():
            data = pasteboard.dataForType_(NSFilenamesPboardType)

        if data is not None:
            # Retrieve  paths.
            filenames, format, errorDescription = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(
                    data , kCFPropertyListImmutable, None, None)

            # Add paths to the data source.
            for fn in filenames:
                self.addAnImageWithPath_(fn)

            # Make the image browser reload the data source.
            self.updateDatasource()

        # Accept the drag operation.
        return True
