# ============================================================================
#
# Copyright (C) 2007-2013 Conceptive Engineering bvba. All rights reserved.
# www.conceptive.be / info@conceptive.be
#
# This file is part of the Camelot Library.
#
# This file may be used under the terms of the GNU General Public
# License version 2.0 as published by the Free Software Foundation
# and appearing in the file license.txt included in the packaging of
# this file. Please review this information to ensure GNU
# General Public Licensing requirements will be met.
#
# If you are unsure which license is appropriate for your use, please
# visit www.python-camelot.com or contact info@conceptive.be
#
# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# For use of this library in commercial applications, please contact
# info@conceptive.be
#
# ============================================================================
import itertools
import logging
import os
logger = logging.getLogger('camelot.admin.application_admin')
from PyQt4.QtCore import Qt
from PyQt4 import QtCore
from camelot.admin.action import application_action, form_action, list_action
from camelot.core.utils import ugettext_lazy as _
from camelot.view import art
from camelot.view import database_selection
from camelot.view.model_thread import model_function
_application_admin_ = []
#
# The translations data needs to be kept alive during the
# running of the application
#
_translations_data_ = []
def get_application_admin():
if not len(_application_admin_):
raise Exception('No application admin class has been constructed yet')
return _application_admin_[0]
[docs]class ApplicationAdmin(QtCore.QObject):
"""The ApplicationAdmin class defines how the application should look
like, it also ties Python classes to their associated
:class:`camelot.admin.object_admin.ObjectAdmin` class or subclass. It's
behaviour can be steered by overwriting its static attributes or it's
methods :
.. attribute:: name
The name of the application, as it will appear in the title of the main
window.
.. attribute:: application_url
The url of the web site where the user can find more information on
the application.
.. attribute:: help_url
Points to either a local html file or a web site that contains the
documentation of the application.
.. attribute:: author
The name of the author of the application
.. attribute:: domain
The domain name of the author of the application, eg 'mydomain.com', this
domain will be used to store settings of the application.
.. attribute:: version
A string with the version of the application
.. attribute:: database_profile_wizard
The wizard that should be used to create new database profiles. Defaults
to :class:`camelot.view.database_selection.ProfileWizard`
.. attribute:: database_selection
if this is set to True, present the user with a database selection
wizard prior to starting the application. Defaults to :const:`False`.
When the same action is returned in the :meth:`get_toolbar_actions` and
:meth:`get_main_menu` method, it should be exactly the same object, to avoid
shortcut confusion and reduce the number of status updates.
"""
database_profile_wizard = database_selection.ProfileWizard
name = 'Camelot'
application_url = None
help_url = 'http://www.python-camelot.com/docs.html'
author = 'Conceptive Engineering'
domain = 'python-camelot.com'
version = '1.0'
admins = {}
# This signal is emitted whenever the sections are changed, and the views
# should be updated
sections_changed_signal = QtCore.pyqtSignal()
# This signal is emitted whenever the tile of the main window needs to
# be changed.
title_changed_signal = QtCore.pyqtSignal(str)
# Emitted whenever the application actions need to be changed
actions_changed_signal = QtCore.pyqtSignal()
database_selection = False
#
# actions that will be shared between the toolbar and the main menu
#
change_row_actions = [ list_action.ToFirstRow(),
list_action.ToPreviousRow(),
list_action.ToNextRow(),
list_action.ToLastRow(), ]
edit_actions = [ list_action.OpenNewView(),
list_action.DeleteSelection(),
list_action.DuplicateSelection(),]
help_actions = [ application_action.ShowHelp(), ]
export_actions = [ list_action.PrintPreview(),
list_action.ExportSpreadsheet() ]
form_toolbar_actions = [ form_action.CloseForm(),
form_action.ToFirstForm(),
form_action.ToPreviousForm(),
form_action.ToNextForm(),
form_action.ToLastForm(),
application_action.Refresh(),
form_action.ShowHistory() ]
hidden_actions = [ application_action.DumpState(),
application_action.RuntimeInfo() ]
def __init__(self):
"""Construct an ApplicationAdmin object and register it as the
prefered ApplicationAdmin to use througout the application"""
QtCore.QObject.__init__(self)
_application_admin_.append(self)
#
# Cache created ObjectAdmin objects
#
self._object_admin_cache = {}
self._memento = None
[docs] def register(self, entity, admin_class):
"""Associate a certain ObjectAdmin class with another class. This
ObjectAdmin will be used as default to render object the specified
type.
:param entity: :class:`class`
:param admin_class: a subclass of
:class:`camelot.admin.object_admin.ObjectAdmin` or
:class:`camelot.admin.entity_admin.EntityAdmin`
"""
self.admins[entity] = admin_class
@model_function
[docs] def get_sections( self ):
"""A list of :class:`camelot.admin.section.Section` objects,
these are the sections to be displayed in the left panel.
.. image:: /_static/picture2.png
"""
from camelot.admin.section import Section
return [ Section( _('Relations'), self ),
Section( _('Configuration'), self ),
]
[docs] def get_settings( self ):
"""A :class:`QtCore.QSettings` object in which Camelot related settings
can be stored. This object is intended for Camelot internal use. If an
application specific settings object is needed, simply construct one.
:return: a :class:`QtCore.QSettings` object
"""
settings = QtCore.QSettings()
settings.beginGroup( 'Camelot' )
return settings
[docs] def get_memento( self ):
"""Returns an instance of :class:`camelot.core.memento.SqlMemento` that
can be used to store changes made to objects. Overwrite this method to
make it return `None` if no changes should be stored to the database, or
to return another instance if the changes should be stored elsewhere.
:return: `None` or an :class:`camelot.core.memento.SqlMemento` instance
"""
from camelot.core.memento import SqlMemento
if self._memento == None:
self._memento = SqlMemento()
return self._memento
[docs] def get_application_admin( self ):
"""Get the :class:`ApplicationAdmin` class of this application, this
method is here for compatibility with the :class:`ObjectAdmin`
:return: this object itself
"""
return self
[docs] def get_entity_admin(self, entity):
"""Get the default :class:`camelot.admin.object_admin.ObjectAdmin` class
for a specific entity, return None, if not known. The ObjectAdmin
should either be registered through the :meth:`register` method or be
defined as an inner class with name :keyword:`Admin` of the entity.
:param entity: a :class:`class`
deprecated : use get_related_admin instead
"""
admin_class = None
try:
admin_class = self.admins[entity]
except KeyError:
pass
if not admin_class and hasattr(entity, 'Admin'):
admin_class = entity.Admin
if admin_class:
try:
return self._object_admin_cache[admin_class]
except KeyError:
admin = admin_class(self, entity)
self._object_admin_cache[admin_class] = admin
return admin
[docs] def create_main_window(self):
"""Create the main window that will be shown when the application
starts up. By default, returns an instance of
:class:`camelot.view.mainwindow.MainWindow`
:return: a :class:`PyQt4.QtGui.QWidget`
"""
from camelot.admin.action.application_action import ApplicationActionGuiContext
from camelot.view.mainwindow import MainWindow
gui_context = ApplicationActionGuiContext()
gui_context.admin = self
mainwindow = MainWindow( gui_context )
return mainwindow
[docs] def get_actions(self):
"""
:return: a list of :class:`camelot.admin.action.base.Action` objects
that should be displayed on the desktop of the user.
"""
return []
[docs] def get_hidden_actions( self ):
"""
:return: a list of :class:`camelot.admin.action.base.Action` objects
that can only be triggered using shortcuts and are not visibile in
the UI.
"""
return self.hidden_actions
[docs] def get_main_menu( self ):
"""
:return: a list of :class:`camelot.admin.menu.Menu` objects, or None if
there should be no main menu
"""
from camelot.admin.menu import Menu
return [ Menu( _('&File'),
[ application_action.Backup(),
application_action.Restore(),
None,
Menu( _('Export To'),
self.export_actions ),
Menu( _('Import From'),
[list_action.ImportFromFile()] ),
None,
application_action.Exit(),
] ),
Menu( _('&Edit'),
self.edit_actions + [
None,
list_action.SelectAll(),
None,
list_action.ReplaceFieldContents(),
]),
Menu( _('View'),
[ application_action.Refresh(),
Menu( _('Go To'), self.change_row_actions) ] ),
Menu( _('&Help'),
self.help_actions + [
application_action.ShowAbout() ] )
]
[docs] def get_name(self):
"""
:return: the name of the application, by default this is the class
attribute name"""
return unicode( self.name )
[docs] def get_version(self):
""":return: string representing version of the application, by default this
is the class attribute verion"""
return self.version
[docs] def get_icon(self):
""":return: the :class:`camelot.view.art.Icon` that should be used for the application"""
from camelot.view.art import Icon
return Icon('tango/32x32/apps/system-users.png').getQIcon()
[docs] def get_splashscreen(self):
""":return: a :class:`PyQt4.QtGui.QPixmap` to be used as splash screen"""
from camelot.view.art import Pixmap
qpm = Pixmap('splashscreen.png').getQPixmap()
img = qpm.toImage()
# support transparency
if not qpm.mask():
if img.hasAlphaBuffer(): bm = img.createAlphaMask()
else: bm = img.createHeuristicMask()
qpm.setMask(bm)
return qpm
[docs] def get_organization_name(self):
"""
:return: a string with the name of the organization that wrote the
application. By default returns the :attr:`ApplicationAdmin.author`
attribute.
"""
return self.author
[docs] def get_organization_domain(self):
"""
:return: a string with the domain name of the organization that wrote the
application. By default returns the :attr:`ApplicationAdmin.domain`
attribute.
"""
return self.domain
[docs] def get_help_url(self):
""":return: a :class:`PyQt4.QtCore.QUrl` pointing to the index page for help"""
from PyQt4.QtCore import QUrl
if self.help_url:
return QUrl( self.help_url )
[docs] def get_stylesheet(self):
"""
:return: a string with the content of a qt stylesheet to be used for
this application as a string or None if no stylesheet needed.
Camelot comes with a couple of default stylesheets :
* stylesheet/office2007_blue.qss
* stylesheet/office2007_black.qss
* stylesheet/office2007_silver.qss
Have a look at the default implementation to use another stylesheet.
"""
#
# Try to load a custom QStyle, if that fails use a stylesheet from
# a file
#
try:
from PyTitan import QtnOfficeStyle
QtnOfficeStyle.setApplicationStyle( QtnOfficeStyle.Windows7Scenic )
except:
pass
return art.read('stylesheet/office2007_blue.qss')
def _load_translator_from_file( self,
module_name,
file_name,
directory = '',
search_delimiters = '_',
suffix = '.qm' ):
"""
Tries to create a translator based on a file stored within a module.
The file is loaded through the pkg_resources, to enable loading it from
within a Python egg. This method tries to mimic the behavior of
:meth:`QtCore.QTranslator.load` while looking for an appropriate
translation file.
:param module_name: the name of the module in which to look for
the translation file with pkg_resources.
:param file_name: the filename of the the tranlations file, without
suffix
:param directory: the directory, relative to the module in which
to look for translation files
:param suffix: the suffix of the filename
:param search_delimiters: list of characters by which to split the file
name to search for variations of the file name
:return: :keyword:None if unable to load the file, otherwise a
:obj:`QtCore.QTranslator` object.
This method tries to load all file names with or without suffix, and
with or without the part after the search delimiter.
"""
from camelot.core.resources import resource_string
#
# split the directory names and file name
#
file_name_parts = [ file_name ]
head, tail = os.path.split( file_name_parts[0] )
while tail:
file_name_parts[0] = tail
file_name_parts = [ head ] + file_name_parts
head, tail = os.path.split( file_name_parts[0] )
#
# for each directory and file name, generate all possibilities
#
file_name_parts_possibilities = []
for file_name_part in file_name_parts:
part_possibilities = []
for search_delimiter in search_delimiters:
delimited_parts = file_name_part.split( search_delimiter )
for i in range( len( delimited_parts ) ):
part_possibility = search_delimiter.join( delimited_parts[:len(delimited_parts)-i] )
part_possibilities.append( part_possibility )
file_name_parts_possibilities.append( part_possibilities )
#
# make the combination of all those possibilities
#
file_names = []
for parts_possibility in itertools.product( *file_name_parts_possibilities ):
file_name = os.path.join( *parts_possibility )
file_names.append( file_name )
file_names.append( file_name + suffix )
#
# now try all file names
#
translations = None
for file_name in file_names:
try:
logger.debug( u'try %s'%file_name )
translations = resource_string( module_name, os.path.join(directory,file_name) )
break
except IOError:
pass
if translations:
_translations_data_.append( translations ) # keep the data alive
translator = QtCore.QTranslator()
# PySide workaround for missing loadFromData method
if not hasattr( translator, 'loadFromData' ):
return
if translator.loadFromData( translations ):
logger.info("add translation %s" % (directory + file_name))
return translator
[docs] def get_translator(self):
"""Reimplement this method to add application specific translations
to your application. The default method returns a list with the
default Qt and the default Camelot translator for the current system
locale. Call :meth:`QLocale.setDefault` before this method is called
if you want to load different translations then the system default.
:return: a list of :obj:`QtCore.QTranslator` objects that should be
used to translate the application
"""
translators = []
qt_translator = QtCore.QTranslator()
locale_name = QtCore.QLocale().name()
logger.info( u'using locale %s'%locale_name )
if qt_translator.load( "qt_" + locale_name,
QtCore.QLibraryInfo.location( QtCore.QLibraryInfo.TranslationsPath ) ):
translators.append( qt_translator )
camelot_translator = self._load_translator_from_file( 'camelot',
os.path.join( '%s/LC_MESSAGES/'%locale_name, 'camelot' ),
'art/translations/' )
if camelot_translator:
translators.append( camelot_translator )
else:
logger.debug( 'no camelot translations found for %s'%locale_name )
return translators
[docs] def get_about(self):
""":return: the content of the About dialog, a string with html
syntax"""
import datetime
from camelot.core import license
today = datetime.date.today()
return """<b>Camelot</b><br/>
Building desktop applications at warp speed
<p>
Copyright © 2007-%s Conceptive Engineering.
All rights reserved.
</p>
<p>
%s
</p>
<p>
http://www.python-camelot.com<br/>
http://www.conceptive.be
</p>
"""%(today.year, license.license_type)