Friday, June 19, 2009

Rapid application development with Jython and JFace (part VI - First application reviewed)

New things to learn in this part: Tree, SashForm, DirectoryDialog, TreeViewer, ITreeContentProvider, LabelProvider, ISelectionChangedListener.

Tree control

Tree control is used to display a hierarchy of items and issues notification when an item in the hierarchy is selected. We'll use this control in our application to display hierarchy of directories and files. User will be able to select image file and then we'll display selected image.

SashFrom

Divides client are into independent areas. We'll use it to divide application screen into two peaces: tree of files and image view area.

DirectoryDialog

Directory selection dialog.

TreeViewer, ITreeContentProvider, LabelProvider, ISelectionChangedListener

These classes help to customize and simplify control of tree control. TreeViewer – sits on top of tree control and provides easy control of it, less of manual control is needed. ITreeContentProvider – provides items that should be displayed in tree control. LabelProvider – controls how items should apier in tree control. ISelectionChangedListener – tels what actions should be taken on item selection in tree control.

Explanation of source code

Create SashFrom as top window and divide client area of it into two peaces: treePanel (displays tree control) and imagePanel (displays selected image). Tree will get 30% of application window and image will get 70% of application window.

mainForm = SashForm(parent, SWT.HORIZONTAL | SWT.NULL)
treePanel = Composite(mainForm, SWT.NONE)
imagePanel = Composite(mainForm, SWT.NONE)
mainForm.setWeights([30, 70])
In treePanel we should put tree control.
self.treeViewer = TreeViewer(treePanel, SWT.BORDER | SWT.VIRTUAL)
We'll add ITreeContentProvider, LabelProvider, IselectionChangedListener to it.
self.treeViewer.setContentProvider(FileTreeContentProvider())
self.treeViewer.setLabelProvider(FileTreeLabelProvider())
self.treeViewer.addSelectionChangedListener(FileTreeSelectionChangedListener(self))
We'll add canvas to imagePanel.
self.canvas = Canvas(imagePanel, SWT.BORDER | SWT.NO_BACKGROUND|SWT.NO_REDRAW_RESIZE|SWT.V_SCROLL|SWT.H_SCROLL)

Class FileTreeContentProvider implements ITreeContentProvider

We search directory for directories and files and add those to tree.

Class FileTreeLabelProvider extends LabelProvider

We display short name of directory or file in tree control. If item is directory we are displaying directory image. If item is file we are displaying picture image.

Class FileTreeSelectionChangedListener implements ISelectionChangedListener

Then user selects item which is file we will display it in canvas.

Full Code

"""
Not so dumb image displaying application JPictureMaster II (Jython & JFace example)
Part VI - First application reviewed
GUID of this code snippet: 979fa00c-3001-4c24-b04d-f41d06f1d6d2
[ extends code snippet: eef8f89a-d1d6-4211-9063-ba4c736eb620 ]
Author: Darius Kucinskas (c) 2008-2009
Email: d[dot]kucinskas[eta]gmail[dot]com
Blog: http://blog-of-darius.blogspot.com/
License: GPL
"""

from org.eclipse.swt import *
from org.eclipse.swt.SWT import *
from org.eclipse.swt.widgets import *
from org.eclipse.swt.layout import *
from org.eclipse.jface.window import *
from org.eclipse.jface.action import *
from org.eclipse.jface.resource import *
from org.eclipse.swt.graphics import *
from org.eclipse.swt.custom import SashForm
from org.eclipse.jface.viewers import TreeViewer
from org.eclipse.jface.viewers import ITreeContentProvider
from org.eclipse.jface.viewers import LabelProvider
from org.eclipse.jface.viewers import ISelectionChangedListener
from java.lang import Math
from java.io import File
import jarray

# Skip this for now
# Draw glider emblem (I use it as icon for this example)
def drawIcon(display):
    image = Image(display, 16, 16)
    gc = GC(image)

    gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT))
    gc.fillRectangle(0, 0, 16, 16)

    gc.setBackground(Color(display, 193, 39, 45))
    gc.fillRectangle( 1, 11, 4, 4)
    gc.fillRectangle( 6, 11, 4, 4)
    gc.fillRectangle(11, 11, 4, 4)
    gc.fillRectangle(11,  6, 4, 4)
    gc.fillRectangle( 6,  1, 4, 4)

    gc.setBackground(Color(display, 253, 185, 19))
    gc.fillRectangle( 1,  1, 4, 4)
    gc.fillRectangle( 1,  6, 4, 4)
    gc.fillRectangle(11,  1, 4, 4)

    gc.setBackground(Color(display, 0, 106, 68))
    gc.fillRectangle( 6,  6, 4, 4)

    gc.dispose()
    return image

class HBarListener(Listener):
    """ Listener for horizontal scroll events """

    def __init__(self, app):
        self.app = app

    def handleEvent(self, e):        
        selection = self.app.canvas.getHorizontalBar().getSelection()
        x = -selection - self.app.origin.x
        rect = self.app.image.getBounds()
        self.app.canvas.scroll(x, 0, 0, 0, rect.width, rect.height, False)
        self.app.origin.x = -selection

class VBarListener(HBarListener):
    """ Listener for vertical scroll events """

    def handleEvent(self, e):        
        selection = self.app.canvas.getVerticalBar().getSelection()
        y = -selection - self.app.origin.y
        rect = self.app.image.getBounds()
        self.app.canvas.scroll(0, y, 0, 0, rect.width, rect.height, False)
        self.app.origin.y = -selection

class CanvasPaintListener(HBarListener):
    """ Listener for paint events """

    def handleEvent(self, e):
        gc = e.gc
        gc.drawImage(self.app.image, self.app.origin.x, self.app.origin.y)
        rect = self.app.image.getBounds()
        client = self.app.canvas.getClientArea()
        marginW = client.width - rect.width
        if (marginW > 0):
            gc.fillRectangle(rect.width, 0, marginW, client.height)

        marginH = client.height - rect.height
        if (marginH > 0):
            gc.fillRectangle(0, rect.height, client.width, marginH)

class CanvasResizeListener(HBarListener):
    """ Listener for resize events """

    def handleEvent(self, e):
        self.app.newOrResizeImage()
        self.app.canvas.redraw()

class ExitAction(Action):
    """ This is simple quit application action.

    All action classes should inherit from Action (org.eclipse.jface.action.Action) class.
    """

    def __init__(self, app):
        """ constructor of ExitAction

        arguments:
        app - in order to do anything useful I am passing 
        object of my application.    
        """

        self.app = app
        self.text = "E&xit@Ctrl+Q"
        self.toolTipText = "Exit the application"
        self.imageDescriptor = App.getImageRegistry().getDescriptor("file.exit")

    def run(self):
        """ run() method of action. Every action class should have one.

        This time it closes application.
        """

        if self.app != None:
            self.app.close()

class OpenDirectoryAction(Action):
    """ Simple open image action """

    def __init__(self, app):
        self.app = app
        self.text = "O&pen@Ctrl+O"
        self.toolTipText = "Open directiory and scan it for images"
        self.imageDescriptor = App.getImageRegistry().getDescriptor("file.open")

    def run(self):
        # this code shows DirectoryDialog usage
        dlg = DirectoryDialog(self.app.shell, SWT.OPEN)
        dlg.filterPath = "."
        dlg.text = "Select directory to scan for images"
        dir = dlg.open()     

        if (not dir):
            return

        self.app.treeViewer.setInput(File(dir))
        self.app.treeViewer.refresh()

class MainMenuManager(MenuManager):
    """ MenuManager is responsible for constructing  menu """

    def __init__(self, app):
        """constructor of MainMenuManager

        arguments:
        app - in order to do anything useful I am passing 
        object of my application.    
        """
        # let parent class to do it's stuff first
        MenuManager.__init__(self)
        self.app = app

    def createMainMenu(self):
        """ creates menu """

        mainMenu = MenuManager("")
        fileMenu = MenuManager("&File")
        mainMenu.add(fileMenu)
        # add Exit action to file menu 
        fileMenu.add(ExitAction(self.app))
        fileMenu.add(OpenDirectoryAction(self.app))
        return mainMenu

class MainToolbarManager(ToolBarManager):
    """ """
    def __init__(self, app):
        """construcotr of MainToolbar

        arguments:
        app - in order to do anything useful I am passing 
        object of my application.    
        """
        # let parent class to do it's stuff first
        ToolBarManager.__init__(self)
        self.app = app        

    def createMainToolBar(self, style):
        """ add items to toolbar """

        mainToolBar = ToolBarManager(style)

        # add Exit action to toolbar 
        mainToolBar.add(ExitAction(self.app))
        mainToolBar.add(OpenDirectoryAction(self.app))
 return mainToolBar

class FileTreeContentProvider(ITreeContentProvider):
    def getChildren(self, element):
        if (not element):
            return None

        kids = element.listFiles()
        return kids

    def getElements(self, element):
        return self.getChildren(element)

    def hasChildren(self, element):
        if (not element):
            return False

        kids = self.getChildren(element)
        if (not kids):
            return False

        if (len(kids) < 1):
            return False

        return True

    def getParent(self, element):
        return element.getParent()

    def dispose(self):
        pass

    def inputChanged(self, viewer, old_input, new_input):
        pass

class FileTreeLabelProvider(LabelProvider):
    def getImage(self, element):
        if (element.isDirectory()):
            return App.getImageRegistry().getDescriptor("file.open").createImage()

        return App.getImageRegistry().getDescriptor("file.image").createImage()

    def getText(self, element):
        return element.getName()

class FileTreeSelectionChangedListener(ISelectionChangedListener):
    def __init__(self, app):
        self.app = app

    def selectionChanged(self, e):
        selection = e.getSelection()

        file = selection.getFirstElement()
        if (file.isDirectory()):
            return

        self.app.image = Image(Display.getCurrent(), file.getPath())
        self.app.setStatus("Open file: " + file.getPath())
        self.app.newOrResizeImage()

        gc = GC(self.app.canvas)
        rect = self.app.canvas.getClientArea()
        gc.drawImage(self.app.image, 0, 0)
        gc.dispose()

        self.app.canvas.redraw()

class App(ApplicationWindow):
    """ Main class for JFace application drived from ApplicationWindow class """

    # add static image registry
    imageRegistry = None

    # add class method for image registry
    @classmethod
    def getImageRegistry(cls):
        if (not cls.imageRegistry):
            cls.imageRegistry = ImageRegistry()
            cls.imageRegistry.put("app.icon", ImageDescriptor.createFromImage(drawIcon(Display.getCurrent())))

            # load icon image from file
            cls.imageRegistry.put("file.exit", ImageDescriptor.createFromImage(Image(Display.getCurrent(), "img/system-log-out.png")))
            cls.imageRegistry.put("file.open", ImageDescriptor.createFromImage(Image(Display.getCurrent(), "img/document-open.png")))
            cls.imageRegistry.put("file.image", ImageDescriptor.createFromImage(Image(Display.getCurrent(), "img/image-x-generic.png")))

        return cls.imageRegistry

    def __init__(self, shell):
        """ application constructor """
        # let parent class to do it's stuff first
        ApplicationWindow.__init__(self, shell)

        self.canvas = None
        self.image = None
        self.origin = Point(0, 0)

        self.treeViewer = None

        # lets add menu
        self.addMenuBar()

        # lets add toolbar
        self.addToolBar(SWT.FLAT | SWT.WRAP)

        # lets add status line
        self.addStatusLine()

    def dispose(self):
        """ dispose resources here """
        if (self.image != None):
            self.image.dispose()

    def createContents(self, parent):
        """Creates the main window's contents
        parent - the main window
        return - control
        """
        self.shell.setImage(drawIcon(Display.getCurrent())) 
        self.shell.text = 'JPictureMaster II (Jython & JFace example)'

        # create split form
        mainForm = SashForm(parent, SWT.HORIZONTAL | SWT.NULL)
        mainForm.setLayout(FillLayout())

        # create tree
        treePanel = Composite(mainForm, SWT.NONE)
        treePanel.setLayout(GridLayout(1, True))

        self.treeViewer = TreeViewer(treePanel, SWT.BORDER | SWT.VIRTUAL)
        self.treeViewer.getControl().setLayoutData(GridData(SWT.FILL, SWT.FILL, True, True, 3, 3))
        self.treeViewer.setContentProvider(FileTreeContentProvider())
        self.treeViewer.setLabelProvider(FileTreeLabelProvider())
        self.treeViewer.setInput(File("."))
        self.treeViewer.addSelectionChangedListener(FileTreeSelectionChangedListener(self))
        self.treeViewer.refresh()

        imagePanel = Composite(mainForm, SWT.NONE)
        # set layout as one column grid
        imagePanel.setLayout(GridLayout(1, True))

        # create canvas for image displaying
        self.canvas = Canvas(imagePanel, SWT.BORDER | SWT.NO_BACKGROUND|SWT.NO_REDRAW_RESIZE|SWT.V_SCROLL|SWT.H_SCROLL)
        self.canvas.setLayoutData(GridData(SWT.FILL, SWT.FILL, True, True, 1, 1))
        # add listeners to canvas, so we could react to events
        self.canvas.getHorizontalBar().addListener(SWT.Selection, HBarListener(self)) 
        self.canvas.getVerticalBar().addListener(SWT.Selection, VBarListener(self))
        self.canvas.addListener(SWT.Paint, CanvasPaintListener(self))
        self.canvas.addListener(SWT.Resize, CanvasResizeListener(self))

        mainForm.setWeights([30, 70])
        return mainForm

    def createMenuManager(self):
        """ create main menu """
        return MainMenuManager(self).createMainMenu()

    def createToolBarManager(self, style):
        """ create main toolbar """
        return MainToolbarManager(self).createMainToolBar(style)

    def getStatusLineManagerForUpdate(self):
        """ get status line manager """
        return self.getStatusLineManager()

    def newOrResizeImage(self):
        if (not self.canvas):
            return

        client = self.canvas.getClientArea()

        if (not self.image):
            self.image = Image(self.shell.getDisplay(), client.width, client.height)

            gc = GC(self.image)
            gc.fillRectangle(0, 0, client.width, client.height)
            gc.drawLine (0, 0, client.width, client.height)
            gc.drawLine (0, client.height, client.width, 0)
            gc.drawText ("No Image", 10, 10)
            gc.dispose ()

        rect = self.image.getBounds()
        self.canvas.getHorizontalBar().setMaximum(rect.width)
        self.canvas.getVerticalBar().setMaximum(rect.height)
        self.canvas.getHorizontalBar().setThumb(Math.min (rect.width, client.width))
        self.canvas.getVerticalBar().setThumb(Math.min (rect.height, client.height))

        hPage = rect.width - client.width
        vPage = rect.height - client.height
        hSelection = self.canvas.getHorizontalBar().getSelection()
        vSelection = self.canvas.getVerticalBar().getSelection()

        if (hSelection >= hPage):
            if (hPage <= 0):
                hSelection = 0
                self.origin.x = -hSelection

        if (vSelection >= vPage):
            if (vPage <= 0):
                vSelection = 0
                self.origin.y = -vSelection

if __name__ == "__main__":
    """ The entry point for our application """
    display = Display()
    shell = Shell(display)

    app = App(shell)
    # Voodoo of SWT message loop
    try:
        app.setBlockOnOpen(True)
        app.open()
    finally:
        if app != None:
            app.dispose()

    display.dispose()