# -*- coding: utf-8 -*-

# Copyright (c) 2004 - 2006 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog showing an imports diagram of the application.
"""

import os

from qt import *
from qtcanvas import *

from KdeQt.KQProgressDialog import KQProgressDialog

from UMLCanvasView import UMLCanvasView
from Utilities.ModuleParser import readModule
from PackageWidget import PackageWidget, PackageModel
from AssociationWidget import AssociationWidget, Imports
import GraphicsUtilities
import Utilities

class ApplicationDiagram(QDialog):
    """
    Class implementing a dialog showing an imports diagram of the application.
    """
    allClasses = {}
    
    def __init__(self,project,parent = None,name = None,modal = 0,fl = 0, noModules=0):
        """
        Constructor
        
        @param project reference to the project object
        @param parent parent widget of the view (QWidget)
        @param name name of the view widget (QString or string)
        @param flags the window flags to be passed to the view widget
        @keyparam noModules flag indicating, that no module names should be shown (boolean)
        """
        QDialog.__init__(self,parent,name,modal,fl)

        if not name:
            self.setName("ApplicationDiagram")

        UMLFormLayout = QVBoxLayout(self,6,6,"UMLFormLayout")

        self.canvas = QCanvas(800, 600)
        self.umlCanvas = UMLCanvasView(self.canvas,self,"umlCanvas")
        self.umlCanvas.setGeometry(QRect(6,6,788,555))
        UMLFormLayout.addWidget(self.umlCanvas)

        layout1 = QHBoxLayout(None,0,6,"layout1")
        spacer = QSpacerItem(40,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
        layout1.addItem(spacer)

        self.closeButton = QPushButton(self,"closeButton")
        layout1.addWidget(self.closeButton)
        spacer_2 = QSpacerItem(40,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
        layout1.addItem(spacer_2)
        UMLFormLayout.addLayout(layout1)

        self.languageChange()

        self.resize(QSize(800,604).expandedTo(self.minimumSizeHint()))
        self.clearWState(Qt.WState_Polished)

        self.connect(self.closeButton,SIGNAL("clicked()"),self,SLOT("close()"))
        
        self.project = project
        self.noModules = noModules

    def languageChange(self):
        """
        Private method used to show the localized strings for this dialog.
        """
        self.setCaption(self.__tr("Application-Diagram"))
        self.closeButton.setText(self.__tr("&Close"))
        self.closeButton.setAccel(self.__tr("Alt+C"))


    def __tr(self,s,c = None):
        """
        Private method to translate the display strings.
        """
        return qApp.translate("ApplicationDiagram",s,c)


    def getDiagramName(self):
        """
        Method to retrieve a name for the diagram.
        
        @return name for the diagram
        """
        return self.project.ppath
        
    def buildModulesDict(self):
        """
        Private method to build a dictionary of modules contained in the application.
        
        @return dictionary of modules contained in the application.
        """
        moduleDict = {}
        mods = self.project.pdata["SOURCES"]
        modules = []
        for module in mods:
            modules.append(Utilities.normabsjoinpath(self.project.ppath, module))
        tot = len(modules)
        try:
            prog = 0
            progress = KQProgressDialog(self.trUtf8("Parsing modules..."),
                QString.null, tot, None, None, 1)
            progress.show()
            qApp.processEvents()
            for module in modules:
                progress.setProgress(prog)
                qApp.processEvents()
                prog = prog + 1
                if module.endswith("__init__.py"):
                    continue
                try: 
                    mod = readModule(module)
                except ImportError:
                    continue
                else:
                    name = mod.name
                    moduleDict[name] = mod
        finally:
            progress.setProgress(tot)
        return moduleDict
        
    def buildPackages(self):
        """
        Private method to build the modules shapes of the diagram.
        """
        project = os.path.splitdrive(self.project.ppath)[1].replace(os.sep, '.')[1:]
        packages = {}
        shapes = {}
        p = 10
        y = 10
        maxHeight = 0
        
        modules = self.buildModulesDict()
        sortedkeys = modules.keys()
        sortedkeys.sort()
        
        # step 1: build a dictionary of packages
        for module in sortedkeys:
            l = module.split('.')
            package = '.'.join(l[:-1])
            if packages.has_key(package):
                packages[package][0].append(l[-1])
            else:
                packages[package] = ([l[-1]], [])
                
        # step 2: assign modules to dictionaries and update import relationship
        for module in sortedkeys:
            l = module.split('.')
            package = '.'.join(l[:-1])
            impLst = []
            for i in modules[module].imports:
                if modules.has_key(i):
                    impLst.append(i)
                else:
                    if i.find('.') == -1:
                        n = "%s.%s" % (modules[module].package, i)
                        if modules.has_key(n):
                            impLst.append(n)
                        else:
                            n = "%s.%s" % (project, i)
                            if modules.has_key(n):
                                impLst.append(n)
                            elif packages.has_key(n):
                                n = "%s.<<Dummy>>" % n
                                impLst.append(n)
                    else:
                        n = "%s.%s" % (project, i)
                        if modules.has_key(n):
                            impLst.append(n)
            for i in modules[module].from_imports.keys():
                if modules.has_key(i):
                    impLst.append(i)
                else:
                    if i.find('.') == -1:
                        n = "%s.%s" % (modules[module].package, i)
                        if modules.has_key(n):
                            impLst.append(n)
                        else:
                            n = "%s.%s" % (project, i)
                            if modules.has_key(n):
                                impLst.append(n)
                            elif packages.has_key(n):
                                n = "%s.<<Dummy>>" % n
                                impLst.append(n)
                    else:
                        n = "%s.%s" % (project, i)
                        if modules.has_key(n):
                            impLst.append(n)
            for imp in impLst:
                impPackage = '.'.join(imp.split('.')[:-1])
                if not impPackage in packages[package][1] and \
                   not impPackage == package:
                    packages[package][1].append(impPackage)
                    
        sortedkeys = packages.keys()
        sortedkeys.sort()
        for package in sortedkeys:
            if package:
                relPackage = package.replace(project, '')
                if relPackage and relPackage[0] == '.':
                    relPackage = relPackage[1:]
                else:
                    relPackage = unicode(self.trUtf8("<<Application>>"))
            else:
                relPackage = unicode(self.trUtf8("<<Others>>"))
            shape = self.addPackage(relPackage, packages[package][0], p, y)
            shapes[package] = (shape, packages[package][1])
            p += shape.width() + 10
            maxHeight = max(maxHeight, shape.height())
            if p > self.canvas.width():
                p = 10
                y += maxHeight + 10
                maxHeight = 0
                
        self.createAssociations(shapes)
        
        # resize the canvas to accomodate the widgets
        rect = self.umlCanvas.getDiagramRect(10) # 10 pixel border
        newSize = self.canvas.size()
        if rect.width() > newSize.width():
            newSize.setWidth(rect.width())
        if rect.height() > newSize.height():
            newSize.setHeight(rect.height())
        self.canvas.resize(newSize.width(), newSize.height())
        
    def addPackage(self, name, modules, x, y):
        """
        Private method to add a package to the diagram.
        
        @param name package name to be shown (string)
        @param modules list of module names contained in the package
            (list of strings)
        @param x x-coordinate (integer)
        @param y y-coordinate (integer)
        """
        modules.sort()
        pm = PackageModel(name, modules)
        pw = PackageWidget(self.canvas, pm, x, y, noModules=self.noModules)
        pw.show()
        rect = pw.rect()
        newSize = self.canvas.size()
        if rect.bottom() > newSize.height():
            newSize.setHeight(rect.bottom()+10)
        self.canvas.resize(newSize.width(), newSize.height())
        return pw
        
    def createAssociations(self, shapes):
        """
        Private method to generate the associations between the module shapes.
        
        @param shapes list of shapes
        """
        for module in shapes.keys():
            for rel in shapes[module][1]:
                assoc = AssociationWidget(self.canvas, 
                        shapes[module][0], shapes[rel][0],
                        Imports)
                assoc.show()
                
    def show(self):
        """
        Overriden method to show the dialog.
        """
        self.buildPackages()
        QDialog.show(self)
        self.allClasses.clear()
        
    def relayout(self):
        """
        Method to relayout the diagram.
        """
        self.buildPackages()
