Wednesday, June 17, 2009

Rapid application development with Jython and JFace (part V – First application)

Jython 2.5.0 final is out! Big congrats to the Jython team! Go and download it now! Today my friends we will glue all components together and will make our first application. This application will be dump image displaying utility. I’ll call it “JPictureMaster”. I am using some free icons from Tango Desktop Project so you’ll need to download this archive in order to be able to run application. Then done downloading copy two icons: tango-icon-theme-0.8.90\16x16\actions\system-log-out.png and tango-icon-theme-0.8.90\16x16\actions\document-open.png into "img" folder there your jython script is located. In this part I will add FileDialog and StatusLine. FileDialog This is simple single file selection dialog. By using it I’ll show basics of SWT\JFace dialog usage. StatusLine Status line is used for displaying short status information in your application. Search script code for self.app.setStatus("Open file: " + filename) and you will get idea how to use it. Code:
"""
Dump image displaying application JPictureMaster (Jython & JFace example)
Part V - First application
GUID of this 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 java.lang import *

# Skip this for now
# Draw glider emblem (I use it as icon for this example)
from org.eclipse.swt.graphics import *
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):
        rect = self.app.image.getBounds()
        client = self.app.canvas.getClientArea()
        self.app.canvas.getHorizontalBar().setMaximum(rect.width)
        self.app.canvas.getVerticalBar().setMaximum(rect.height)
        self.app.canvas.getHorizontalBar().setThumb(Math.min (rect.width, client.width))
        self.app.canvas.getVerticalBar().setThumb(Math.min (rect.height, client.height))
        hPage = rect.width - client.width
        vPage = rect.height - client.height
        hSelection = self.app.canvas.getHorizontalBar().getSelection()
 vSelection = self.app.canvas.getVerticalBar().getSelection()
 if (hSelection >= hPage):
            if (hPage <= 0):
                hSelection = 0
  self.app.origin.x = -hSelection
  
 if (vSelection >= vPage):
            if (vPage <= 0):
                vSelection = 0
                self.app.origin.y = -vSelection

        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 OpenImageAction(Action):
    """ Simple open image action """

    FILTER_NAMES = ["All Files (*.*)"]
    FILTER_EXTS = ["*.*"]

    def __init__(self, app):
        self.app = app
        self.text = "O&pen@Ctrl+O"
        self.toolTipText = "Open and display image"
        self.imageDescriptor = App.getImageRegistry().getDescriptor("file.open")

    def run(self):
        # this code shows FileDialog usage
        dlg = FileDialog(app.getShell(), SWT.OPEN)
        dlg.filterNames = OpenImageAction.FILTER_NAMES
        dlg.filterExtensions = OpenImageAction.FILTER_EXTS
        dlg.text = "Open an image file or cancel"
        filename = dlg.open()
        
        if (filename != ""):
            # now we'll open image and draw it on canvas
            self.app.setStatus("Open file: " + filename)
            self.app.image = Image(Display.getCurrent(), filename)
            if (self.app.image != None):
                gc = GC(self.app.canvas)
                rect = self.app.canvas.getClientArea()
                gc.fillRectangle(0, 0, rect.width, rect.height)
                gc.drawImage(self.app.image, 0, 0)                
                gc.dispose()

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(OpenImageAction(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(OpenImageAction(self.app))
        
 return mainToolBar

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 (cls.imageRegistry == None):
            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")))

        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

        #we will draw default image
 width = 150
 height = 200
 self.image = Image(shell.getDisplay(), width, height)
 gc = GC(self.image)
 gc.fillRectangle (0, 0, width, height)
 gc.drawLine (0, 0, width, height)
 gc.drawLine (0, height, width, 0)
 gc.drawText ("Default Image", 10, 10)
 gc.dispose ()

        self.origin = Point(0, 0)
        
        # 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.getShell().setImage(drawIcon(Display.getCurrent())) 
        self.getShell().text = 'JPictureMaster (Jython & JFace example)'
        
        panel = Composite(parent, SWT.BORDER)
        # set layout as one column grid
        panel.setLayout(GridLayout(1, True))
        
        # create canvas for image displaying
        self.canvas = Canvas(panel, 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))
        
        return panel
    
    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()

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()
P.S. If you think I am some kind of Jython & JFace guru you are wrong my friend. So if you will spot any kind of bug in this example, please let me know about it!