camelot.camelot.view.mainwindow

Package camelot :: Package camelot :: Package view :: Module mainwindow |
[hide private]
[frames] | no frames]

# Source Code for Module camelot.camelot.view.mainwindow

      1  #  ============================================================================
      2  #
      3  #  Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved.
      4  #  www.conceptive.be / project-camelot@conceptive.be
      5  #
      6  #  This file is part of the Camelot Library.
      7  #
      8  #  This file may be used under the terms of the GNU General Public
      9  #  License version 2.0 as published by the Free Software Foundation
     10  #  and appearing in the file LICENSE.GPL included in the packaging of
     11  #  this file.  Please review the following information to ensure GNU
     12  #  General Public Licensing requirements will be met:
     13  #  http://www.trolltech.com/products/qt/opensource.html
     14  #
     15  #  If you are unsure which license is appropriate for your use, please
     16  #  review the following information:
     17  #  http://www.trolltech.com/products/qt/licensing.html or contact
     18  #  project-camelot@conceptive.be.
     19  #
     20  #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
     21  #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
     22  #
     23  #  For use of this library in commercial applications, please contact
     24  #  project-camelot@conceptive.be
     25  #
     26  #  ============================================================================
     27
     28  """Main window GUI"""
     29
     30  import settings
     31  import os
     32  import sys
     33  import logging
     34
     35  logger = logging.getLogger('mainwindow')
     36  logger.setLevel(logging.INFO)
     37
     38  #
     39  # Dummy imports to fool the windows installer and force
     40  # it to include the right packages
     41  #
     42  from sqlalchemy.databases import sqlite
     43  import sqlite3
     44
     45  from PyQt4 import QtGui
     46  from PyQt4 import QtCore
     47  from PyQt4.QtCore import Qt
     48  from PyQt4.QtTest import QTest
     49
     50  from camelot.view import art
     51  from camelot.view.helpers import createAction, addActions
     52  from camelot.view.controls.navpane import PaneButton, NavigationPane
     53  from camelot.view.controls.printer import Printer
     54  from camelot.view.controls.animation import Throbber
     55  from camelot.view.model_thread import get_model_thread, construct_model_thread
     56  from camelot.view.response_handler import ResponseHandler
     57  from camelot.view.remote_signals import construct_signal_handler
     58
     59  QT_MAJOR_VERSION = float('.'.join(str(QtCore.QT_VERSION_STR).split('.')[0:2]))
     60
     61  _ = lambda x: x
     62
     63
     64 -class MainWindow(QtGui.QMainWindow):
     65
     66 -  def __init__(self, app_admin, parent=None):
     67      from workspace import construct_workspace
     68      logger.debug('initializing main window')
     69      super(MainWindow, self).__init__(parent)
     70
     71      self.app_admin = app_admin
     72
     73      logger.debug('setting up workspace')
     74      self.workspace = construct_workspace(self)
     75
     76      logger.debug('setting child windows dictionary')
     77
     78      logger.debug('setting central widget to our workspace')
     79      self.setCentralWidget(self.workspace)
     80
     81      self.connect(self.workspace, QtCore.SIGNAL('subWindowActivated(QMdiSubWindow *)'),
     82                   self.updateMenus)
     83
     84      logger.debug('creating navigation pane')
     85      self.createNavigationPane()
     86
     87      logger.debug('creating all the required actions')
     88      self.createActions()
     89
     90      logger.debug('creating the menus')
     91      self.createMenus()
     92
     93      logger.debug('creating the toolbars')
     94      self.createToolBars()
     95
     96      logger.debug('creating status bar')
     97      self.createStatusBar()
     98
     99      logger.debug('updating menus')
    100      self.updateMenus()
    101
    102      logger.debug('reading saved settings')
    103      self.readSettings()
    104
    105      logger.debug('setting up printer object')
    106      self.printer = Printer()
    107
    108      logger.debug('setting up window title')
    109      self.setWindowTitle(self.app_admin.getName())
    110
    111      #QtCore.QTimer.singleShot(0, self.doInitialization)
    112      logger.debug('initialization complete')
    113
    114    # Application settings
    115
    116 -  def about(self):
    117      logger.debug('showing about message box')
    118      abtmsg = self.app_admin.getAbout()
    119      QtGui.QMessageBox.about(self, _('About'), _(abtmsg))
    120      logger.debug('about message closed')
    121
    122 -  def readSettings(self):
    123      # TODO: improve settings reading
    124      settings = QtCore.QSettings()
    125      self.restoreGeometry(settings.value('geometry').toByteArray())
    126      # Don't restore state, since it messes up the toolbar if stuff was
    127      # added
    128      # self.restoreState(settings.value('state').toByteArray())
    129
    130 -  def writeSettings(self):
    131      # TODO: improve settings saving
    132      logger.debug('writing application settings')
    133      settings = QtCore.QSettings()
    134      settings.setValue('geometry', QtCore.QVariant(self.saveGeometry()))
    135      settings.setValue('state', QtCore.QVariant(self.saveState()))
    136      logger.debug('settings written')
    137
    138 -  def runAction(self, name, callable):
    139      progress = QtGui.QProgressDialog('Please wait', 'Run in background', 0, 0)
    140      progress.setWindowTitle(name)
    141      progress.show()
    142      mt = get_model_thread()
    143
    144      def exception_in_action(e):
    145        progress.close()
    146        QtGui.QMessageBox.warning(self, 'Error while %s'%name, str(e))
    147
    148      mt.post(callable, lambda *args:progress.close(), exception_in_action)
    149
    150    # QAction objects creation methods
    151 -  def createActions(self):
    152      # TODO: change status tip
    153      self.saveAct = createAction(self,
    154                                  _('&Save'),
    155                                  self.save,
    156                                  QtGui.QKeySequence.Save,
    157                                  art.icon16('actions/document-save'),
    158                                  _('Save'))
    159
    160      temp = art.icon16('actions/document-properties')
    161      self.pageSetupAct = createAction(self,
    162                                       _('Page Setup...'),
    163                                       self.pageSetup,
    164                                       actionicon=temp,
    165                                       tip=_('Page Setup...'))
    166
    167      self.printAct = createAction(self,
    168                                   _('Print...'),
    169                                   self.printDoc,
    170                                   QtGui.QKeySequence.Print,
    171                                   art.icon16('actions/document-print'),
    172                                   _('Print...'))
    173
    174      temp = art.icon16('actions/document-print-preview')
    175      self.previewAct = createAction(self,
    176                                     _('Print Preview'),
    177                                     self.previewDoc,
    178                                     actionicon=temp,
    179                                     tip=_('Print Preview'))
    180
    181      self.exitAct = createAction(self,
    182                                  _('E&xit'),
    183                                  self.close,
    184                                  tip=_('Exit the application'))
    185
    186      tip = _("Cut the current selection's contents to the clipboard")
    187      self.cutAct = createAction(self,
    188                                 _('Cu&t'),
    189                                 self.cut,
    190                                 QtGui.QKeySequence.Cut,
    191                                 art.icon16('actions/edit-cut'),
    192                                 tip)
    193
    194      tip = _("Copy the current selection's contents to the clipboard")
    195      self.copyAct = createAction(self,
    196                                  _('&Copy'),
    197                                  self.copy,
    198                                  QtGui.QKeySequence.Copy,
    199                                  art.icon16('actions/edit-copy'),
    200                                  tip)
    201
    202      tip = _("Paste the clipboard's contents into the current selection")
    203      self.pasteAct = createAction(self,
    204                                   _('&Paste'),
    205                                   self.paste,
    206                                   QtGui.QKeySequence.Paste,
    207                                   art.icon16('actions/edit-paste'),
    208                                   tip)
    209
    210      self.closeAct = createAction(self,
    211                                   _('Cl&ose'),
    212                                   self.workspace.closeActiveSubWindow,
    213                                   shortcut = QtGui.QKeySequence.Close,
    214                                   tip=_('Close the active window'))
    215
    216      self.closeAllAct = createAction(self,
    217                                      _('Close &All'),
    218                                      self.workspace.closeAllSubWindows,
    219                                      tip=_('Close all the windows'))
    220
    221      self.cascadeAct = createAction(self,
    222                                     _('&Cascade windows'),
    223                                     self.workspace.cascadeSubWindows,
    224                                     tip=_('Arranges all the child windows '
    225                                           'in a cascade pattern.'))
    226
    227      self.separatorAct = QtGui.QAction(self)
    228      self.separatorAct.setSeparator(True)
    229
    230      self.aboutAct = createAction(self,
    231                                   _('&About'),
    232                                   self.about,
    233                                   tip=_("Show the application's About box"))
    234
    235      self.newAct = createAction(self,
    236                                 _('New'),
    237                                 self.new,
    238                                 QtGui.QKeySequence.New,
    239                                 art.icon16('actions/document-new'),
    240                                 _('New'))
    241
    242      self.deleteAct = createAction(self,
    243                                    _('Delete'),
    244                                    self.delete,
    245                                    QtGui.QKeySequence.Delete,
    246                                    art.icon16('places/user-trash'),
    247                                    _('Delete'))
    248
    249      self.viewFirstAct = createAction(self,
    250                                       _('First'),
    251                                       self.viewFirst,
    252                                       QtGui.QKeySequence.MoveToStartOfDocument,
    253                                       art.icon16('actions/go-first'),
    254                                       _('First'))
    255
    256      self.viewLastAct = createAction(self,
    257                                      _('Last'),
    258                                      self.viewLast,
    259                                      QtGui.QKeySequence.MoveToEndOfDocument,
    260                                      art.icon16('actions/go-last'),
    261                                      _('Last'))
    262
    263      self.viewNextAct = createAction(self,
    264                                      _('Next'),
    265                                      self.viewNext,
    266                                      QtGui.QKeySequence.MoveToNextPage,
    267                                      art.icon16('actions/go-next'),
    268                                      _('Next'))
    269
    270      self.viewPreviousAct = createAction(self,
    271                                          _('Previous'),
    272                                          self.viewPrevious,
    273                                          QtGui.QKeySequence.MoveToPreviousPage,
    274                                          art.icon16('actions/go-previous'),
    275                                          _('Previous'))
    276
    277      if QT_MAJOR_VERSION > 4.3:
    278        self.viewFirstAct.setIconVisibleInMenu(False)
    279        self.viewLastAct.setIconVisibleInMenu(False)
    280        self.viewNextAct.setIconVisibleInMenu(False)
    281        self.viewPreviousAct.setIconVisibleInMenu(False)
    282
    283      temp = art.icon16('mimetypes/x-office-spreadsheet')
    284      self.exportToExcelAct = createAction(self,
    285                                           _('Export to MS Excel'),
    286                                           self.exportToExcel,
    287                                           actionicon=temp,
    288                                           tip=_('Export to MS Excel'))
    289
    290      temp = art.icon16('mimetypes/x-office-document')
    291      self.exportToWordAct = createAction(self,
    292                                          _('Export to MS Word'),
    293                                          self.exportToWord,
    294                                          actionicon=temp,
    295                                          tip=_('Export to MS Word'))
    296      temp = art.icon16('actions/mail-message-new')
    297      self.exportToMailAct = createAction(self,
    298                                          _('Send by e-mail'),
    299                                          self.exportToMail,
    300                                          actionicon=temp,
    301                                          tip=_('Send by e-mail'))
    302
    303      self.app_actions = []
    304      for name, icon, callable in self.app_admin.getActions():
    305
    306        def bind_callable(name, callable):
    307          return lambda:self.runAction(name, callable)
    308
    309        self.app_actions.append(createAction(self,
    310                                             name,
    311                                             slot=bind_callable(name, callable),
    312                                             actionicon=icon,
    313                                             tip=name))
    314
    315    # QAction slots and methods implementations
    316
    317 -  def save(self):
    318      pass
    319
    320 -  def saveAs(self):
    321      pass
    322
    323 -  def cut(self):
    324      pass
    325
    326 -  def copy(self):
    327      pass
    328
    329 -  def paste(self):
    330      pass
    331
    332 -  def printDoc(self):
    333      active = self.activeMdiChild()
    334      self.printer.printView(active.widget(), self)
    335
    336 -  def previewDoc(self):
    337      self.printer.preview(self.activeMdiChild().widget(), self)
    338
    339 -  def new(self):
    340      self.activeMdiChild().widget().newRow()
    341
    342 -  def delete(self):
    343      self.activeMdiChild().widget().deleteSelectedRows()
    344
    345 -  def pageSetup(self):
    346      pass
    347
    348 -  def viewFirst(self):
    349      """selects view's first row"""
    350      active = self.activeMdiChild()
    351      active.widget().viewFirst()
    352
    353 -  def viewLast(self):
    354      """selects view's last row"""
    355      active = self.activeMdiChild()
    356      active.widget().viewLast()
    357
    358 -  def viewNext(self):
    359      """selects view's next row"""
    360      active = self.activeMdiChild()
    361      active.widget().viewNext()
    362
    363 -  def viewPrevious(self):
    364      """selects view's previous row"""
    365      active = self.activeMdiChild()
    366      active.widget().viewPrevious()
    367
    368 -  def exportToExcel(self):
    369      """creates an excel file from the view"""
    370
    371      mt = get_model_thread()
    372
    373      def export():
    374        import os
    375        import tempfile
    376        from export.excel import ExcelExport
    377        title = self.activeMdiChild().widget().getTitle()
    378        columns = self.activeMdiChild().widget().getColumns()
    379        data = [d for d in self.activeMdiChild().widget().getData()]
    380        objExcel = ExcelExport()
    381        xls_fd, xls_fn = tempfile.mkstemp(suffix='.xls')
    382        objExcel.exportToFile(xls_fn, title, columns, data)
    383
    384        try:
    385          import pythoncom
    386          import win32com.client
    387          pythoncom.CoInitialize()
    388          excel_app = win32com.client.Dispatch("Excel.Application")
    389        except Exception, e:
    390          """We're probably not running windows, so try gnumeric"""
    391          logger.warning('Unable to launch excel', exc_info=e)
    392          os.system('gnumeric "%s"'%xls_fn)
    393          return
    394
    395        excel_app.Visible = True
    396        excel_app.Workbooks.Open(xls_fn)
    397
    398      mt.post(export)
    399
    400 -  def exportToWord(self):
    401      """Use windows COM to export the active child window to MS word,
    402      by using its toHtml function"""
    403
    404      mt = get_model_thread()
    405
    406      def export():
    407        from export.word import open_html_in_word
    408        html = self.activeMdiChild().widget().toHtml()
    409        open_html_in_word(html)
    410
    411      mt.post(export)
    412
    413 -  def exportToMail(self):
    414
    415      mt = get_model_thread()
    416
    417      def export():
    418        from export.outlook import open_html_in_outlook
    419        html = self.activeMdiChild().widget().toHtml()
    420        open_html_in_outlook(html)
    421
    422      mt.post(export)
    423    # Menus
    424
    425 -  def createMenus(self):
    426      self.fileMenu = self.menuBar().addMenu(_('&File'))
    427      addActions(self.fileMenu, (self.closeAct,
    428                                 None, self.saveAct,
    429                                 None, self.pageSetupAct,
    430                                 self.previewAct,
    431                                 self.printAct, None))
    432
    433      self.exportMenu = QtGui.QMenu(_('Export To'))
    434      addActions(self.exportMenu, (self.exportToExcelAct,
    435                                   self.exportToWordAct,
    436                                   self.exportToMailAct,
    437                                   ))
    438      self.fileMenu.addMenu(self.exportMenu)
    439
    440      addActions(self.fileMenu, (None, self.exitAct))
    441
    442      self.editMenu = self.menuBar().addMenu(_('&Edit'))
    443
    444      addActions(self.editMenu, (self.cutAct,
    445                                 self.copyAct,
    446                                 self.pasteAct))
    447      # TODO: add refresh action
    448      self.viewMenu = self.menuBar().addMenu(_('View'))
    449      gotoMenu = self.viewMenu.addMenu(_('Go To'))
    450      addActions(gotoMenu, (self.viewFirstAct,
    451                            self.viewPreviousAct,
    452                            self.viewNextAct,
    453                            self.viewLastAct))
    454
    455      self.windowMenu = self.menuBar().addMenu(_('&Window'))
    456      self.connect(self.windowMenu, QtCore.SIGNAL('aboutToShow()'),
    457                   self.updateWindowMenu)
    458
    459      self.menuBar().addSeparator()
    460
    461      self.helpMenu = self.menuBar().addMenu(_('&Help'))
    462      addActions(self.helpMenu, (self.aboutAct, ))
    463
    464      #self.throbber = Throbber(self.menuBar())
    465
    466 -  def resizeEvent(self, event):
    467      """Needed to correctly position the throbber"""
    468      QtGui.QMainWindow.resizeEvent(self, event)
    469      # always put throbber in top right corner
    470      #x = self.width() - self.throbber.sizeHint().width() - 5
    471      #y = 2
    472      #self.throbber.move(x, y)
    473
    474 -  def updateMenus(self):
    475      hasMdiChild = (self.activeMdiChild() is not None)
    476      self.saveAct.setEnabled(hasMdiChild)
    477      self.pasteAct.setEnabled(hasMdiChild)
    478      self.closeAct.setEnabled(hasMdiChild)
    479
    480      self.closeAllAct.setEnabled(hasMdiChild)
    481      self.cascadeAct.setEnabled(hasMdiChild)
    482
    483      self.pageSetupAct.setEnabled(hasMdiChild)
    484      self.previewAct.setEnabled(hasMdiChild)
    485      self.printAct.setEnabled(hasMdiChild)
    486
    487      self.newAct.setEnabled(hasMdiChild)
    488      self.deleteAct.setEnabled(hasMdiChild)
    489      self.viewFirstAct.setEnabled(hasMdiChild)
    490      self.viewPreviousAct.setEnabled(hasMdiChild)
    491      self.viewNextAct.setEnabled(hasMdiChild)
    492      self.viewLastAct.setEnabled(hasMdiChild)
    493
    494      self.exportToWordAct.setEnabled(hasMdiChild)
    495      self.exportToExcelAct.setEnabled(hasMdiChild)
    496      self.exportToMailAct.setEnabled(hasMdiChild)
    497
    498      self.separatorAct.setVisible(hasMdiChild)
    499
    500      # TODO: selecting text in views for copy/cut/paste
    501      #hasSelection = (self.activeMdiChild() is not None and
    502      #                self.activeMdiChild().textCursor().hasSelection())
    503      hasSelection = False
    504      self.cutAct.setEnabled(hasSelection)
    505      self.copyAct.setEnabled(hasSelection)
    506
    507 -  def updateWindowMenu(self):
    508      self.windowMenu.clear()
    509      self.windowMenu.addAction(self.closeAllAct)
    510      self.windowMenu.addAction(self.cascadeAct)
    511      self.windowMenu.addAction(self.separatorAct)
    512
    513      windows = self.workspace.subWindowList()
    514
    515      self.separatorAct.setVisible(len(windows) != 0)
    516
    517      i = 0
    518
    519      for child in windows:
    520        if i < 9:
    521          text = self.tr('&%1 %2').arg(i + 1).arg(child.windowTitle())
    522        else:
    523          text = self.tr('%1 %2').arg(i + 1).arg(child.windowTitle())
    524
    525        i += 1
    526
    527        action = self.windowMenu.addAction(text)
    528        action.setCheckable(True)
    529        action.setChecked(child == self.activeMdiChild())
    530
    531        def create_window_activator(window):
    532
    533          def activate_window():
    534            self.workspace.setActiveSubWindow(window)
    535
    536          return activate_window
    537
    538        self.connect(action, QtCore.SIGNAL('triggered()'), create_window_activator(child))
    539
    540    # Toolbars
    541
    542 -  def createToolBars(self):
    543      self.printToolBar = self.addToolBar(_('Print'))
    544      self.printToolBar.setObjectName('PrintToolBar')
    545      self.printToolBar.setMovable(False)
    546      self.printToolBar.setFloatable(False)
    547      addActions(self.printToolBar, (self.printAct, self.previewAct))
    548
    549      self.editToolBar = self.addToolBar(_('Edit'))
    550      self.editToolBar.setObjectName('EditToolBar')
    551      self.editToolBar.setMovable(False)
    552      self.editToolBar.setFloatable(False)
    553      addActions(self.editToolBar, (self.cutAct,
    554                                    self.copyAct,
    555                                    self.pasteAct))
    556
    557      self.viewToolBar = self.addToolBar(_('View'))
    558      self.viewToolBar.setObjectName('ViewToolBar')
    559      self.viewToolBar.setMovable(False)
    560      self.viewToolBar.setFloatable(False)
    561      addActions(self.viewToolBar, (self.newAct,
    562                                    self.deleteAct,
    563                                    self.viewFirstAct,
    564                                    self.viewPreviousAct,
    565                                    self.viewNextAct,
    566                                    self.viewLastAct))
    567
    568      self.exportToolBar = self.addToolBar(_('Export'))
    569      self.exportToolBar.setObjectName('ExportToolBar')
    570      self.exportToolBar.setMovable(False)
    571      self.exportToolBar.setFloatable(False)
    572      addActions(self.exportToolBar, (self.exportToExcelAct,
    573                                      self.exportToWordAct,
    574                                      self.exportToMailAct,))
    575
    576
    577      if self.app_actions:
    578        self.applicationToolBar = self.addToolBar(_('Application'))
    579        self.applicationToolBar.setObjectName('ApplicationToolBar')
    580        self.applicationToolBar.setMovable(False)
    581        self.applicationToolBar.setFloatable(False)
    582        addActions(self.exportToolBar, self.app_actions)
    583
    584    # Navigation Pane
    585
    586 -  def createNavigationPane(self):
    587      self.navpane = NavigationPane(self.app_admin, parent=self)
    588      self.addDockWidget(Qt.LeftDockWidgetArea, self.navpane)
    589
    590      self.connect(self.navpane.treewidget,
    591                   QtCore.SIGNAL('itemClicked(QTreeWidgetItem *, int)'),
    592                   self.createMdiChild)
    593
    594      # use QTest to auto select first button :)
    595      firstbutton = self.navpane.buttons[0]
    596      QTest.mousePress(firstbutton, Qt.LeftButton)
    597
    598    # Interface for child windows
    599
    600 -  def createMdiChild(self, item):
    601      index = self.navpane.treewidget.indexFromItem(item)
    602      model = self.navpane.models[index.row()]
    603      logger.debug('creating model %s' % str(model[0]))
    604
    605      child = model[0].createTableView(model[1], parent=self)
    606      self.workspace.addSubWindow(child)
    607
    608      self.connect(child, QtCore.SIGNAL("copyAvailable(bool)"),
    609             self.cutAct.setEnabled)
    610      self.connect(child, QtCore.SIGNAL("copyAvailable(bool)"),
    611             self.copyAct.setEnabled)
    612      child.showMaximized()
    613
    614 -  def activeMdiChild(self):
    615      return self.workspace.activeSubWindow()
    616
    617    # Statusbar
    618
    619 -  def createStatusBar(self):
    620      self.status = self.statusBar()
    621      self.status.showMessage(_('Ready'), 5000)
    622
    623    # Events (re)implementations
    624
    625 -  def closeEvent(self, event):
    626      self.workspace.closeAllSubWindows()
    627      if self.activeMdiChild():
    628        event.ignore()
    629      else:
    630        self.writeSettings()
    631        event.accept()
    632