view src/translator/translator.py @ 806:bcb74c9b895c

Moved out files in the trunk folder to the root.
author Aziz K?ksal <aziz.koeksal@gmail.com>
date Sun, 09 Mar 2008 00:12:19 +0100
parents trunk/src/translator/translator.py@312da78ab301
children
line wrap: on
line source

#! /usr/bin/python
# -*- coding: utf-8 -*-
# Author: Aziz Köksal
# License: GPL2
import sys, os
import yaml

from PyQt4 import QtCore, QtGui
# User interface modules
from ui_translator import Ui_MainWindow
from ui_about import Ui_AboutDialog
from ui_new_project import Ui_NewProjectDialog
from ui_project_properties import Ui_ProjectPropertiesDialog
from ui_msg_form import Ui_MsgForm
from ui_closing_project import Ui_ClosingProjectDialog

from project import Project, newProjectData

g_scriptDir = sys.path[0]
g_CWD = os.getcwd()
g_projectExt = ".tproj"
g_catExt = ".cat"
g_settingsFile = os.path.join(g_scriptDir, "settings.yaml")
g_settings = {}

Qt = QtCore.Qt
Qt.connect = QtCore.QObject.connect
Qt.disconnect = QtCore.QObject.disconnect
Qt.SIGNAL = QtCore.SIGNAL
Qt.SLOT = QtCore.SLOT

def QTabWidgetCloseAll(self):
 for i in range(self.count()-1,-1,-1):
   widget = self.widget(i)
   self.removeTab(i)
   widget.close()
QtGui.QTabWidget.closeAll = QTabWidgetCloseAll

class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
  def __init__(self):
    QtGui.QMainWindow.__init__(self)
    self.setupUi(self)

    self.project = None
    # Modifications
    self.pages = QtGui.QTabWidget()
    self.setCentralWidget(self.pages)
    self.disableMenuItems()
    self.projectDock = QtGui.QDockWidget("Project", self)
    self.projectTree = ProjectTree(self.projectDock, self)
    self.projectDock.setWidget(self.projectTree)
    self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.projectDock)
    # Custom connections
    triggered = Qt.SIGNAL("triggered()")
    Qt.connect(self.action_About, triggered, self.showAboutDialog)
    Qt.connect(self.action_New_Project, triggered, self.createNewProject)
    Qt.connect(self.action_Open_Project, triggered, self.openProjectAction)
    Qt.connect(self.action_Close_Project, triggered, self.closeProjectAction)
    Qt.connect(self.action_Save, triggered, self.saveForm)
    Qt.connect(self.action_Save_All, triggered, self.saveAllForms)
    Qt.connect(self.action_Close, triggered, self.closeForm)
    Qt.connect(self.action_Close_All, triggered, self.closeAllForms)
    Qt.connect(self.action_Properties, triggered, self.showProjectProperties)
    Qt.connect(self.action_Add_Catalogue, triggered, self.addCatalogue)
    Qt.connect(self.action_Add_New_Catalogue, triggered, self.addNewCatalogue)
    Qt.connect(self.projectTree, Qt.SIGNAL("itemDoubleClicked(QTreeWidgetItem*,int)"), self.projectTreeItemDblClicked)
    Qt.connect(self.projectTree, Qt.SIGNAL("onKeyEnter"), self.projectTreeItemActivated)
    Qt.connect(self.projectTree, Qt.SIGNAL("onKeyDelete"), self.projectTreeItemDeleted)

    shortcut = QtGui.QShortcut(QtGui.QKeySequence(Qt.CTRL+Qt.Key_Tab), self)
    Qt.connect(shortcut, Qt.SIGNAL("activated()"), self.nextDocument)
    shortcut = QtGui.QShortcut(QtGui.QKeySequence(Qt.CTRL+Qt.SHIFT+Qt.Key_Tab), self)
    Qt.connect(shortcut, Qt.SIGNAL("activated()"), self.prevDocument)

    self.readSettings()

  def nextDocument(self):
    count = self.pages.count()
    if count < 1: return
    index = self.pages.currentIndex()+1
    if index == count: index = 0
    self.pages.setCurrentIndex(index)

  def prevDocument(self):
    count = self.pages.count()
    if count < 1: return
    index = self.pages.currentIndex()-1
    if index == -1: index = count-1
    self.pages.setCurrentIndex(index)

  def showAboutDialog(self):
    about = QtGui.QDialog()
    Ui_AboutDialog().setupUi(about)
    about.exec_()

  def showProjectProperties(self):
    dialog = ProjectPropertiesDialog(self.project)
    dialog.exec_()
    self.projectTree.updateProjectName()

  def createNewProject(self):
    if self.rejectClosingProject():
      return
    dialog = NewProjectDialog()
    code = dialog.exec_()
    if code == QtGui.QDialog.Accepted:
      self.closeProject()
      self.openProject(str(dialog.projectFilePath.text()))

  def openProjectAction(self):
    if self.rejectClosingProject():
      return
    filePath = QtGui.QFileDialog.getOpenFileName(self, "Select Project File", g_CWD, "Translator Project (*%s)" % g_projectExt);
    filePath = str(filePath)
    if filePath:
      self.closeProject()
      self.openProject(filePath)

  def openProject(self, filePath):
    from errors import LoadingError
    try:
      self.project = Project(filePath)
    except LoadingError, e:
      QtGui.QMessageBox.critical(self, "Error", u"Couldn't load project file:\n\n"+str(e))
      return
    self.enableMenuItems()
    self.projectTree.setProject(self.project)

  def closeProjectAction(self):
    if not self.rejectClosingProject():
      self.closeProject()

  def addCatalogue(self):
    filePath = QtGui.QFileDialog.getOpenFileName(self, "Select Project File", g_CWD, "Catalogue (*%s)" % g_catExt);
    filePath = str(filePath)
    # TODO:
    #self.project.addLangFile(filePath)

  def addNewCatalogue(self):
    pass

  def rejectClosingProject(self):
    if self.project == None:
      return False

    modifiedDocs = []
    # Check if any open document is modified.
    for i in range(0, self.pages.count()):
      if self.pages.widget(i).isModified:
        modifiedDocs += [self.pages.widget(i)]
    # Display dialog if so.
    if len(modifiedDocs):
      dialog = ClosingProjectDialog(modifiedDocs)
      code = dialog.exec_()
      if code == dialog.Accepted:
        for doc in dialog.getSelectedDocs():
          self.saveDocument(doc)
      elif code == dialog.Rejected:
        return True
      elif code == dialog.DiscardAll:
        pass

    return False

  def closeProject(self):
    if self.project == None:
      return
    self.project.save()
    del self.project
    self.project = None
    self.disableMenuItems()
    self.projectTree.clear()
    self.pages.closeAll()

  def enableMenuItems(self):
    #self.action_Close_Project.setEnabled(True)
    for action in [ self.action_Save,
                    self.action_Save_All,
                    self.action_Close,
                    self.action_Close_All ]:
      action.setEnabled(True)
    self.menubar.insertMenu(self.menu_Help.menuAction(), self.menu_Project)

  def disableMenuItems(self):
    #self.action_Close_Project.setEnabled(False)
    for action in [ self.action_Save,
                    self.action_Save_All,
                    self.action_Close,
                    self.action_Close_All ]:
      action.setEnabled(False)
    self.menubar.removeAction(self.menu_Project.menuAction())

  def projectTreeItemDblClicked(self, item, int):
    self.projectTreeItemActivated(item)

  def projectTreeItemActivated(self, item):
    if item == None:
      return

    if isinstance(item, LangFileItem):
      msgForm = None
      if not item.isDocOpen():
        msgForm = item.openDoc()
        msgForm.setModifiedCallback(self.formModified)
      else:
        msgForm = item.openDoc()
      index = self.pages.indexOf(msgForm)
      if index == -1:
        index = self.pages.addTab(msgForm, msgForm.getDocumentTitle())
      self.pages.setCurrentIndex(index)
      msgForm.updateData()

  def projectTreeItemDeleted(self, item):
    pass

  def formModified(self, form):
    # Append an asterisk to the tab label
    index = self.pages.indexOf(form)
    text = form.getDocumentTitle() + "*"
    self.pages.setTabText(index, text)

  def saveForm(self):
    self.saveDocument(self.pages.currentWidget())

  def saveAllForms(self):
    for i in range(0, self.pages.count()):
      self.saveDocument(self.pages.widget(i))

  def saveDocument(self, form):
    if form.isModified:
      # Reset tab text.
      index = self.pages.indexOf(form)
      text = form.getDocumentTitle()
      self.pages.setTabText(index, text)

      form.save()

  def closeForm(self):
    if self.pages.currentWidget():
      self.closeDocument(self.pages.currentWidget())

  def closeAllForms(self):
    for i in range(self.pages.count()-1, -1, -1):
      self.closeDocument(self.pages.widget(i))

  def closeDocument(self, form):
    if form.isModified:
      MB = QtGui.QMessageBox
      button = MB.question(self, "Closing Document", "The document '%s' has been modified.\nDo you want to save the changes?" % form.getDocumentFullPath(), MB.Save | MB.Discard | MB.Cancel, MB.Cancel)
      if button == MB.Cancel:
        return False
      if button == MB.Save:
        self.saveDocument(form)
    index = self.pages.indexOf(form)
    self.pages.removeTab(index)
    form.close()
    return True

  def closeEvent(self, event):
    if self.rejectClosingProject():
      event.ignore()
      return
    self.closeProject()
    self.writeSettings()
    # Closing application

  def moveToCenterOfDesktop(self):
    rect = QtGui.QApplication.desktop().geometry()
    self.move(rect.center() - self.rect().center())

  def readSettings(self):
    # Set default size
    self.resize(QtCore.QSize(500, 400))
    doc = {}
    try:
      doc = yaml.load(open(g_settingsFile, "r"))
    except:
      self.moveToCenterOfDesktop()
      return

    g_settings = doc
    if not isinstance(doc, dict):
      g_settings = {}

    try:
      coord = doc["Window"]
      size = QtCore.QSize(coord["Size"][0], coord["Size"][1])
      point = QtCore.QPoint(coord["Pos"][0], coord["Pos"][1])
      self.resize(size)
      self.move(point)
    except:
      self.moveToCenterOfDesktop()

  def writeSettings(self):
    # Save window coordinates
    g_settings["Window"] = {
      "Pos" : [self.pos().x(), self.pos().y()],
      "Size" : [self.size().width(), self.size().height()]
    }
    yaml.dump(g_settings, open(g_settingsFile, "w")) #default_flow_style=False


class Document(QtGui.QWidget):
  def __init__(self):
    QtGui.QWidget.__init__(self)
    self.isModified = False
    self.modifiedCallback = None
    self.documentTitle = ""
    self.documentFullPath = ""

  def modified(self):
    if not self.isModified:
      self.isModified = True
      self.modifiedCallback(self)

  def setModifiedCallback(self, func):
    self.modifiedCallback = func

  def save(self):
    self.isModified = False

  def close(self):
    self.emit(Qt.SIGNAL("closed()"))
    QtGui.QWidget.close(self)

  def getDocumentTitle(self):
    return self.documentTitle

  def getDocumentFullPath(self):
    return self.documentFullPath


class MessageItem(QtGui.QTreeWidgetItem):
  def __init__(self, msg):
    QtGui.QTreeWidgetItem.__init__(self, [str(msg["ID"]), msg["Text"], "Done"])
    self.msg = msg

  def getID(self):
    return self.msg["ID"]

  def setMsgText(self, text):
    self.msg["Text"] = text

  def setMsgAnnot(self, text):
    self.msg["Annot"] = text


class MsgForm(Document, Ui_MsgForm):
  def __init__(self, langFile):
    Document.__init__(self)
    self.documentTitle = langFile.getFileName()
    self.documentFullPath = langFile.getFilePath()
    self.setupUi(self)
    self.vboxlayout.setMargin(0)

    self.langFile = langFile
    self.currentItem = None
    self.colID = 0
    self.colText = 1
    #self.colStat = 2
    #self.treeWidget.setColumnCount(3)
    self.treeWidget.setColumnCount(2)
    self.treeWidget.setHeaderLabels(["ID", "Text"]) #, "Status"
    self.msgItemDict = {} # Maps msg IDs to msg items.
    for msg in self.langFile.messages:
      item = MessageItem(msg)
      self.msgItemDict[msg["ID"]] = item
      self.treeWidget.addTopLevelItem(item)

    Qt.connect(self.treeWidget, Qt.SIGNAL("currentItemChanged (QTreeWidgetItem *,QTreeWidgetItem *)"), self.treeItemChanged)
    Qt.connect(self.translEdit, Qt.SIGNAL("textChanged()"), self.translEditTextChanged)
    Qt.connect(self.translAnnotEdit, Qt.SIGNAL("textChanged()"), self.translAnnotEditTextChanged)

    #self.translEdit.focusOutEvent = self.translEditFocusOut

  def sourceMsgChanged(self, msg):
    # TODO:
    pass

  def treeItemChanged(self, current, previous):
    if current == None:
      self.setTranslMsg("")
      self.setSourceMsg("")
      return
    ID = current.getID()
    # Set the text controls.
    # The slots receiving text changed signals do nothing if self.currentItem is None.
    self.currentItem = None
    self.setTranslMsg(self.langFile.getMsg(ID))
    self.setSourceMsg(self.langFile.source.getMsg(ID))
    self.currentItem = current

  def setTranslMsg(self, msg):
    self.translEdit.setText(msg["Text"])
    self.translAnnotEdit.setText(msg["Annot"])

  def setSourceMsg(self, msg):
    self.sourceEdit.setText(msg["Text"])
    self.sourceAnnotEdit.setText(msg["Annot"])

  #def translEditFocusOut(self, event):
    #if self.currentItem:
      #print self.currentItem.text(self.colText)
      #if self.translEdit.document().isModified():
        #self.translEdit.document().setModified(False)
        #print "translEdit was modified"
    #QtGui.QTextEdit.focusOutEvent(self.translEdit, event)

  def translEditTextChanged(self):
    if self.currentItem:
      text = unicode(self.translEdit.toPlainText())
      self.currentItem.setText(self.colText, text)
      self.currentItem.setMsgText(text)
      self.modified()

  def translAnnotEditTextChanged(self):
    if self.currentItem:
      text = unicode(self.translAnnotEdit.toPlainText())
      self.currentItem.setMsgAnnot(text)
      self.modified()

  def updateData(self):
    if self.currentItem == None:
      return
    ID = self.currentItem.getID()
    msg = self.langFile.source.getMsg(ID)
    text = self.sourceEdit.toPlainText()
    if text != msg["Text"]:
      self.sourceEdit.setText(msg["Text"])
    text = self.sourceAnnotEdit.toPlainText()
    if text != msg["Annot"]:
      self.sourceAnnotEdit.setText(msg["Annot"])

  def save(self):
    Document.save(self)
    self.langFile.save()


class MsgFormSource(MsgForm):
  def __init__(self, langFile):
    MsgForm.__init__(self, langFile)

    for x in [self.translEdit,
              self.translAnnotEdit,
              self.label_4,
              self.label_5]:
      x.close()

    Qt.connect(self.sourceEdit, Qt.SIGNAL("textChanged()"), self.sourceEditTextChanged)
    Qt.connect(self.sourceAnnotEdit, Qt.SIGNAL("textChanged()"), self.sourceAnnotEditTextChanged)

  def treeItemChanged(self, current, previous):
    if current == None:
      self.setSourceMsg("")
      return
    ID = current.getID()
    self.currentItem = None
    self.setSourceMsg(self.langFile.getMsg(ID))
    self.currentItem = current

  def sourceEditTextChanged(self):
    if self.currentItem:
      text = unicode(self.sourceEdit.toPlainText())
      self.currentItem.setText(self.colText, text)
      self.currentItem.setMsgText(text)
      self.modified()

  def sourceAnnotEditTextChanged(self):
    if self.currentItem:
      text = unicode(self.sourceAnnotEdit.toPlainText())
      #self.currentItem.setText(self.colAnnot, text)
      self.currentItem.setMsgAnnot(text)
      self.modified()


class MsgIDItem(QtGui.QTreeWidgetItem):
  def __init__(self, parent, text):
    QtGui.QTreeWidgetItem.__init__(self, parent, [text])
    self.setFlags(self.flags()|Qt.ItemIsEditable);

class LangFileItem(QtGui.QTreeWidgetItem):
  def __init__(self, parent, langFile):
    QtGui.QTreeWidgetItem.__init__(self, parent, [langFile.langCode])
    self.langFile = langFile
    self.msgForm = None

  def isDocOpen(self):
    return self.msgForm != None

  def openDoc(self):
    if self.msgForm == None:
      if self.langFile.isSource:
        self.msgForm = MsgFormSource(self.langFile)
      else:
        self.msgForm = MsgForm(self.langFile)
      Qt.connect(self.msgForm, Qt.SIGNAL("closed()"), self.docClosed)
    return self.msgForm

  def docClosed(self):
    self.msgForm = None

class ProjectItem(QtGui.QTreeWidgetItem):
  def __init__(self, text):
    QtGui.QTreeWidgetItem.__init__(self, [text])
    self.setFlags(self.flags()|Qt.ItemIsEditable);

class ProjectTree(QtGui.QTreeWidget):
  def __init__(self, parent, mainWindow):
    QtGui.QTreeWidget.__init__(self, parent)
    self.mainWindow = mainWindow
    self.project = None
    self.topItem = None
    self.msgIDsItem = None
    self.headerItem().setHidden(True)
    self.ignoreItemChanged = False

  def itemChanged(self, item, column):
    if self.ignoreItemChanged:
      return
    text = unicode(item.text(0))
    #if hasattr(item, "textChanged"):
      #item.textChanged(text)
    if isinstance(item, ProjectItem):
      self.project.setName(text)
      print text

  def keyReleaseEvent(self, event):
    Qt = QtCore.Qt
    key = event.key()
    if key in [Qt.Key_Enter, Qt.Key_Return]:
      self.emit(Qt.SIGNAL("onKeyEnter"), self.currentItem())
    elif key == Qt.Key_Delete:
      self.emit(Qt.SIGNAL("onKeyDelete"), self.currentItem())

  def setProject(self, project):
    self.project = project

    self.topItem = ProjectItem(self.project.name)
    self.addTopLevelItem(self.topItem)

    for langFile in self.project.langFiles:
      langFileItem = LangFileItem(self.topItem, langFile)

    self.msgIDsItem = QtGui.QTreeWidgetItem(self.topItem, ["Message IDs"])
    for msgID in self.project.msgIDs:
      MsgIDItem(self.msgIDsItem, msgID["Name"])

    for x in [self.topItem, self.msgIDsItem]:
      x.setExpanded(True)

    Qt.connect(self, Qt.SIGNAL("itemChanged(QTreeWidgetItem*,int)"), self.itemChanged)

  def contextMenuEvent(self, event):
    item = self.itemAt(event.pos())
    func_map = {
      None : lambda item: None,
      QtGui.QTreeWidgetItem : lambda item: None,
      ProjectItem : self.showMenuProjectItem,
      LangFileItem : self.showMenuLangFileItem,
      MsgIDItem : self.showMenuMsgIDItem
    }
    func_map[type(item)](item)

  def showMenuProjectItem(self, item):
    mousePos = QtGui.QCursor.pos()
    menu = QtGui.QMenu()
    actions = {}
    actions[menu.addAction("Build")] = lambda: None
    actions[menu.addAction("Properties")] = lambda: self.mainWindow.showProjectProperties()
    actions[menu.exec_(mousePos)]()

  def showMenuLangFileItem(self, item):
    print "LangFileItem"

  def showMenuMsgIDItem(self, item):
    print "MsgIDItem"

  def updateProjectName(self):
    self.ignoreItemChanged = True
    self.topItem.setText(0, self.project.name)
    self.ignoreItemChanged = False

  def clear(self):
    self.project = None
    self.topItem = None
    self.msgIDsItem = None
    Qt.disconnect(self, Qt.SIGNAL("itemChanged(QTreeWidgetItem*,int)"), self.itemChanged)
    QtGui.QTreeWidget.clear(self)


class ClosingProjectDialog(QtGui.QDialog, Ui_ClosingProjectDialog):
  DiscardAll = 2
  def __init__(self, docs):
    QtGui.QDialog.__init__(self)
    self.setupUi(self)
    Qt.connect(self.button_Discard_All, Qt.SIGNAL("clicked()"),   self.discardAll)

    self.items = []
    for doc in docs:
      title = doc.getDocumentTitle()
      path = doc.getDocumentFullPath()
      item = QtGui.QTreeWidgetItem([title, path])
      item.doc = doc
      item.setFlags(item.flags()|Qt.ItemIsUserCheckable);
      item.setCheckState(0, Qt.Checked)
      self.items += [item]
    self.treeWidget.addTopLevelItems(self.items)

    self.button_Cancel.setFocus()

  def getSelectedDocs(self):
    return [item.doc for item in self.items if item.checkState(0)]

  def discardAll(self):
    self.done(self.DiscardAll)


class ProjectPropertiesDialog(QtGui.QDialog, Ui_ProjectPropertiesDialog):
  def __init__(self, project):
    QtGui.QDialog.__init__(self)
    self.setupUi(self)
    Qt.connect(self.pickFileButton, Qt.SIGNAL("clicked()"), self.pickFilePath)

    self.project = project
    self.projectNameField.setText(self.project.name)
    self.buildScriptField.setText(self.project.buildScript)
    self.creationDateField.setText(self.project.creationDate)

  def pickFilePath(self):
    filePath = QtGui.QFileDialog.getOpenFileName(self, "Select Build Script File", g_CWD, "Python Script (*.py)");
    if filePath:
      self.buildScriptField.setText(str(filePath))

  def accept(self):
    self.project.setName(unicode(self.projectNameField.text()))
    self.project.setBuildScript(unicode(self.buildScriptField.text()))
    self.project.setCreationDate(str(self.creationDateField.text()))
    QtGui.QDialog.accept(self)


class NewProjectDialog(QtGui.QDialog, Ui_NewProjectDialog):
  def __init__(self):
    QtGui.QDialog.__init__(self)
    self.setupUi(self)
    Qt.connect(self.pickFileButton, Qt.SIGNAL("clicked()"), self.pickFilePath)

  def pickFilePath(self):
    filePath = QtGui.QFileDialog.getSaveFileName(self, "New Project File", g_CWD, "Translator Project (*%s)" % g_projectExt);
    if filePath:
      filePath = str(filePath) # Convert QString
      if os.path.splitext(filePath)[1] != g_projectExt:
        filePath += g_projectExt
      self.projectFilePath.setText(filePath)

  def accept(self):
    projectName = str(self.projectName.text())
    filePath = str(self.projectFilePath.text())

    MB = QtGui.QMessageBox
    if projectName == "":
      MB.warning(self, "Warning", "Please, enter a name for the project.")
      return
    if filePath == "":
      MB.warning(self, "Warning", "Please, choose or enter a path for the project file.")
      return

    projectData = newProjectData(projectName)

    if os.path.splitext(filePath)[1] != g_projectExt:
      filePath += g_projectExt

    try:
      yaml.dump(projectData, open(filePath, "w"), default_flow_style=False)
    except Exception, e:
      MB.critical(self, "Error", str(e))
      return

    # Accept and close dialog.
    QtGui.QDialog.accept(self)

if __name__ == "__main__":
  app = QtGui.QApplication(sys.argv)
  main = MainWindow()
  main.show()
  sys.exit(app.exec_())