Merge with trunk.

This commit is contained in:
Martin Zibricky 2012-10-18 00:20:22 +02:00
commit fa7d3142db
12 changed files with 158 additions and 60 deletions

View File

@ -85,6 +85,14 @@ class PluginManager(object):
log.debug(u'finding plugins in %s at depth %d', log.debug(u'finding plugins in %s at depth %d',
unicode(plugin_dir), startdepth) unicode(plugin_dir), startdepth)
for root, dirs, files in os.walk(plugin_dir): for root, dirs, files in os.walk(plugin_dir):
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it. The following code will hide it
# in settings dialog.
if sys.platform == 'darwin':
present_plugin_dir = os.path.join(plugin_dir, 'presentations')
# Ignore files from the presentation plugin directory.
if root.startswith(present_plugin_dir):
continue
for name in files: for name in files:
if name.endswith(u'.py') and not name.startswith(u'__'): if name.endswith(u'.py') and not name.startswith(u'__'):
path = os.path.abspath(os.path.join(root, name)) path = os.path.abspath(os.path.join(root, name))

View File

@ -82,10 +82,10 @@ class Ui_FirstTimeWizard(object):
self.imageCheckBox.setChecked(True) self.imageCheckBox.setChecked(True)
self.imageCheckBox.setObjectName(u'imageCheckBox') self.imageCheckBox.setObjectName(u'imageCheckBox')
self.pluginLayout.addWidget(self.imageCheckBox) self.pluginLayout.addWidget(self.imageCheckBox)
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it.
if sys.platform != 'darwin':
self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage) self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage)
if sys.platform == "darwin":
self.presentationCheckBox.setChecked(False)
else:
self.presentationCheckBox.setChecked(True) self.presentationCheckBox.setChecked(True)
self.presentationCheckBox.setObjectName(u'presentationCheckBox') self.presentationCheckBox.setObjectName(u'presentationCheckBox')
self.pluginLayout.addWidget(self.presentationCheckBox) self.pluginLayout.addWidget(self.presentationCheckBox)
@ -214,10 +214,11 @@ class Ui_FirstTimeWizard(object):
self.bibleCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Bible')) self.bibleCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Bible'))
self.imageCheckBox.setText(translate('OpenLP.FirstTimeWizard', self.imageCheckBox.setText(translate('OpenLP.FirstTimeWizard',
'Images')) 'Images'))
# TODO Presentation plugin is not yet working on Mac OS X.
# For now just ignore it.
if sys.platform != 'darwin':
self.presentationCheckBox.setText(translate('OpenLP.FirstTimeWizard', self.presentationCheckBox.setText(translate('OpenLP.FirstTimeWizard',
'Presentations')) 'Presentations'))
if sys.platform == "darwin":
self.presentationCheckBox.setEnabled(False)
self.mediaCheckBox.setText(translate('OpenLP.FirstTimeWizard', self.mediaCheckBox.setText(translate('OpenLP.FirstTimeWizard',
'Media (Audio and Video)')) 'Media (Audio and Video)'))
self.remoteCheckBox.setText(translate('OpenLP.FirstTimeWizard', self.remoteCheckBox.setText(translate('OpenLP.FirstTimeWizard',

View File

@ -284,6 +284,7 @@ class ThemeManager(QtGui.QWidget):
plugin.renameTheme(old_theme_name, new_theme_name) plugin.renameTheme(old_theme_name, new_theme_name)
self.mainwindow.renderer.update_theme( self.mainwindow.renderer.update_theme(
new_theme_name, old_theme_name) new_theme_name, old_theme_name)
self.loadThemes()
def onCopyTheme(self): def onCopyTheme(self):
""" """

View File

@ -498,8 +498,13 @@ def locale_compare(string1, string2):
""" """
# Function locale.strcol() from standard Python library does not work # Function locale.strcol() from standard Python library does not work
# properly on Windows and probably somewhere else. # properly on Windows and probably somewhere else.
return int(QtCore.QString.localeAwareCompare( return QtCore.QString.localeAwareCompare(string1.lower(), string2.lower())
QtCore.QString(string1).toLower(), QtCore.QString(string2).toLower()))
# For performance reasons provide direct reference to compare function
# without wrapping it in another function making te string lowercase.
# This is needed for sorting songs.
locale_direct_compare = QtCore.QString.localeAwareCompare
from languagemanager import LanguageManager from languagemanager import LanguageManager
@ -508,4 +513,5 @@ from actions import ActionList
__all__ = [u'AppLocation', u'get_application_version', u'check_latest_version', __all__ = [u'AppLocation', u'get_application_version', u'check_latest_version',
u'add_actions', u'get_filesystem_encoding', u'LanguageManager', u'add_actions', u'get_filesystem_encoding', u'LanguageManager',
u'ActionList', u'get_web_page', u'get_uno_command', u'get_uno_instance', u'ActionList', u'get_web_page', u'get_uno_command', u'get_uno_instance',
u'delete_file', u'clean_filename', u'format_time', u'locale_compare'] u'delete_file', u'clean_filename', u'format_time', u'locale_compare',
u'locale_direct_compare']

View File

@ -153,7 +153,7 @@ class ImpressController(PresentationController):
desktop = None desktop = None
try: try:
desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop') desktop = self.manager.createInstance(u'com.sun.star.frame.Desktop')
except AttributeError: except (AttributeError, pywintypes.com_error):
log.warn(u'Failure to find desktop - Impress may have closed') log.warn(u'Failure to find desktop - Impress may have closed')
return desktop if desktop else None return desktop if desktop else None
@ -284,6 +284,8 @@ class ImpressDocument(PresentationDocument):
props = tuple(props) props = tuple(props)
doc = self.document doc = self.document
pages = doc.getDrawPages() pages = doc.getDrawPages()
if not pages:
return
if not os.path.isdir(self.get_temp_folder()): if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder()) os.makedirs(self.get_temp_folder())
for idx in range(pages.getCount()): for idx in range(pages.getCount()):
@ -359,7 +361,7 @@ class ImpressDocument(PresentationDocument):
log.debug(u'is active OpenOffice') log.debug(u'is active OpenOffice')
if not self.is_loaded(): if not self.is_loaded():
return False return False
return self.control is not None return self.control.isRunning() if self.control else False
def unblank_screen(self): def unblank_screen(self):
""" """
@ -380,7 +382,7 @@ class ImpressDocument(PresentationDocument):
Returns true if screen is blank Returns true if screen is blank
""" """
log.debug(u'is blank OpenOffice') log.debug(u'is blank OpenOffice')
if self.control: if self.control and self.control.isRunning():
return self.control.isPaused() return self.control.isPaused()
else: else:
return False return False
@ -436,7 +438,11 @@ class ImpressDocument(PresentationDocument):
""" """
Triggers the next effect of slide on the running presentation Triggers the next effect of slide on the running presentation
""" """
is_paused = self.control.isPaused()
self.control.gotoNextEffect() self.control.gotoNextEffect()
time.sleep(0.1)
if not is_paused and self.control.isPaused():
self.control.gotoPreviousEffect()
def previous_step(self): def previous_step(self):
""" """

View File

@ -49,6 +49,7 @@ class Controller(object):
""" """
self.is_live = live self.is_live = live
self.doc = None self.doc = None
self.hide_mode = None
log.info(u'%s controller loaded' % live) log.info(u'%s controller loaded' % live)
def add_handler(self, controller, file, hide_mode, slide_no): def add_handler(self, controller, file, hide_mode, slide_no):
@ -67,6 +68,7 @@ class Controller(object):
# Inform slidecontroller that the action failed? # Inform slidecontroller that the action failed?
return return
self.doc.slidenumber = slide_no self.doc.slidenumber = slide_no
self.hide_mode = hide_mode
if self.is_live: if self.is_live:
if hide_mode == HideMode.Screen: if hide_mode == HideMode.Screen:
Receiver.send_message(u'live_display_hide', HideMode.Screen) Receiver.send_message(u'live_display_hide', HideMode.Screen)
@ -78,7 +80,7 @@ class Controller(object):
else: else:
self.doc.start_presentation() self.doc.start_presentation()
Receiver.send_message(u'live_display_hide', HideMode.Screen) Receiver.send_message(u'live_display_hide', HideMode.Screen)
self.doc.slidenumber = 0 self.doc.slidenumber = 1
if slide_no > 1: if slide_no > 1:
self.slide(slide_no) self.slide(slide_no)
@ -88,100 +90,134 @@ class Controller(object):
Use the last slide number. Use the last slide number.
""" """
log.debug(u'Live = %s, activate' % self.is_live) log.debug(u'Live = %s, activate' % self.is_live)
if not self.doc:
return False
if self.doc.is_active(): if self.doc.is_active():
return return True
if not self.doc.is_loaded(): if not self.doc.is_loaded():
if not self.doc.load_presentation(): if not self.doc.load_presentation():
return log.warn(u'Failed to activate %s' % self.doc.filepath)
return False
if self.is_live: if self.is_live:
self.doc.start_presentation() self.doc.start_presentation()
if self.doc.slidenumber > 1: if self.doc.slidenumber > 1:
if self.doc.slidenumber > self.doc.get_slide_count(): if self.doc.slidenumber > self.doc.get_slide_count():
self.doc.slidenumber = self.doc.get_slide_count() self.doc.slidenumber = self.doc.get_slide_count()
self.doc.goto_slide(self.doc.slidenumber) self.doc.goto_slide(self.doc.slidenumber)
if self.doc.is_active():
return True
else:
log.warn(u'Failed to activate %s' % self.doc.filepath)
return False
def slide(self, slide): def slide(self, slide):
""" """
Go to a specific slide Go to a specific slide
""" """
log.debug(u'Live = %s, slide' % self.is_live) log.debug(u'Live = %s, slide' % self.is_live)
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if self.doc.is_blank(): if self.hide_mode:
self.doc.slidenumber = int(slide) + 1 self.doc.slidenumber = int(slide) + 1
self.poll()
return
if not self.activate():
return return
self.activate()
self.doc.goto_slide(int(slide) + 1) self.doc.goto_slide(int(slide) + 1)
self.doc.poll_slidenumber(self.is_live) self.poll()
def first(self): def first(self):
""" """
Based on the handler passed at startup triggers the first slide Based on the handler passed at startup triggers the first slide
""" """
log.debug(u'Live = %s, first' % self.is_live) log.debug(u'Live = %s, first' % self.is_live)
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if self.doc.is_blank(): if self.hide_mode:
self.doc.slidenumber = 1 self.doc.slidenumber = 1
self.poll()
return
if not self.activate():
return return
self.activate()
self.doc.start_presentation() self.doc.start_presentation()
self.doc.poll_slidenumber(self.is_live) self.poll()
def last(self): def last(self):
""" """
Based on the handler passed at startup triggers the last slide Based on the handler passed at startup triggers the last slide
""" """
log.debug(u'Live = %s, last' % self.is_live) log.debug(u'Live = %s, last' % self.is_live)
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if self.doc.is_blank(): if self.hide_mode:
self.doc.slidenumber = self.doc.get_slide_count() self.doc.slidenumber = self.doc.get_slide_count()
self.poll()
return
if not self.activate():
return return
self.activate()
self.doc.goto_slide(self.doc.get_slide_count()) self.doc.goto_slide(self.doc.get_slide_count())
self.doc.poll_slidenumber(self.is_live) self.poll()
def next(self): def next(self):
""" """
Based on the handler passed at startup triggers the next slide event Based on the handler passed at startup triggers the next slide event
""" """
log.debug(u'Live = %s, next' % self.is_live) log.debug(u'Live = %s, next' % self.is_live)
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if self.doc.is_blank(): if self.hide_mode:
if not self.doc.is_active():
return
if self.doc.slidenumber < self.doc.get_slide_count(): if self.doc.slidenumber < self.doc.get_slide_count():
self.doc.slidenumber = self.doc.slidenumber + 1 self.doc.slidenumber = self.doc.slidenumber + 1
self.poll()
return
if not self.activate():
return return
# The "End of slideshow" screen is after the last slide # The "End of slideshow" screen is after the last slide
# Note, we can't just stop on the last slide, since it may # Note, we can't just stop on the last slide, since it may
# contain animations that need to be stepped through. # contain animations that need to be stepped through.
if self.doc.slidenumber > self.doc.get_slide_count(): if self.doc.slidenumber > self.doc.get_slide_count():
return return
self.activate()
self.doc.next_step() self.doc.next_step()
self.doc.poll_slidenumber(self.is_live) self.poll()
def previous(self): def previous(self):
""" """
Based on the handler passed at startup triggers the previous slide event Based on the handler passed at startup triggers the previous slide event
""" """
log.debug(u'Live = %s, previous' % self.is_live) log.debug(u'Live = %s, previous' % self.is_live)
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if self.doc.is_blank(): if self.hide_mode:
if not self.doc.is_active():
return
if self.doc.slidenumber > 1: if self.doc.slidenumber > 1:
self.doc.slidenumber = self.doc.slidenumber - 1 self.doc.slidenumber = self.doc.slidenumber - 1
self.poll()
return
if not self.activate():
return return
self.activate()
self.doc.previous_step() self.doc.previous_step()
self.doc.poll_slidenumber(self.is_live) self.poll()
def shutdown(self): def shutdown(self):
""" """
Based on the handler passed at startup triggers slide show to shut down Based on the handler passed at startup triggers slide show to shut down
""" """
log.debug(u'Live = %s, shutdown' % self.is_live) log.debug(u'Live = %s, shutdown' % self.is_live)
if not self.doc:
return
self.doc.close_presentation() self.doc.close_presentation()
self.doc = None self.doc = None
@ -190,14 +226,20 @@ class Controller(object):
Instruct the controller to blank the presentation Instruct the controller to blank the presentation
""" """
log.debug(u'Live = %s, blank' % self.is_live) log.debug(u'Live = %s, blank' % self.is_live)
self.hide_mode = hide_mode
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if hide_mode == HideMode.Theme:
if not self.doc.is_loaded(): if not self.doc.is_loaded():
return return
if not self.doc.is_active(): if not self.doc.is_active():
return return
if hide_mode == HideMode.Theme:
Receiver.send_message(u'live_display_hide', HideMode.Theme) Receiver.send_message(u'live_display_hide', HideMode.Theme)
elif hide_mode == HideMode.Blank:
if not self.activate():
return
self.doc.blank_screen() self.doc.blank_screen()
def stop(self): def stop(self):
@ -205,6 +247,9 @@ class Controller(object):
Instruct the controller to stop and hide the presentation Instruct the controller to stop and hide the presentation
""" """
log.debug(u'Live = %s, stop' % self.is_live) log.debug(u'Live = %s, stop' % self.is_live)
self.hide_mode = HideMode.Screen
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
if not self.doc.is_loaded(): if not self.doc.is_loaded():
@ -218,9 +263,13 @@ class Controller(object):
Instruct the controller to unblank the presentation Instruct the controller to unblank the presentation
""" """
log.debug(u'Live = %s, unblank' % self.is_live) log.debug(u'Live = %s, unblank' % self.is_live)
self.hide_mode = None
if not self.doc:
return
if not self.is_live: if not self.is_live:
return return
self.activate() if not self.activate():
return
if self.doc.slidenumber and \ if self.doc.slidenumber and \
self.doc.slidenumber != self.doc.get_slide_number(): self.doc.slidenumber != self.doc.get_slide_number():
self.doc.goto_slide(self.doc.slidenumber) self.doc.goto_slide(self.doc.slidenumber)
@ -228,7 +277,9 @@ class Controller(object):
Receiver.send_message(u'live_display_hide', HideMode.Screen) Receiver.send_message(u'live_display_hide', HideMode.Screen)
def poll(self): def poll(self):
self.doc.poll_slidenumber(self.is_live) if not self.doc:
return
self.doc.poll_slidenumber(self.is_live, self.hide_mode)
class MessageListener(object): class MessageListener(object):

View File

@ -94,9 +94,9 @@ class PowerpointController(PresentationController):
self.docs[0].close_presentation() self.docs[0].close_presentation()
if self.process is None: if self.process is None:
return return
try:
if self.process.Presentations.Count > 0: if self.process.Presentations.Count > 0:
return return
try:
self.process.Quit() self.process.Quit()
except pywintypes.com_error: except pywintypes.com_error:
pass pass
@ -210,6 +210,13 @@ class PowerpointDocument(PresentationDocument):
self.presentation.SlideShowSettings.Run() self.presentation.SlideShowSettings.Run()
self.presentation.SlideShowWindow.View.State = 1 self.presentation.SlideShowWindow.View.State = 1
self.presentation.SlideShowWindow.Activate() self.presentation.SlideShowWindow.Activate()
if self.presentation.Application.Version == u'14.0':
# Unblanking is broken in PowerPoint 2010, need to redisplay
slide = self.presentation.SlideShowWindow.View.CurrentShowPosition
click = self.presentation.SlideShowWindow.View.GetClickIndex()
self.presentation.SlideShowWindow.View.GotoSlide(slide)
if click:
self.presentation.SlideShowWindow.View.GotoClick(click)
def blank_screen(self): def blank_screen(self):
""" """
@ -253,6 +260,8 @@ class PowerpointDocument(PresentationDocument):
renderer = self.controller.plugin.renderer renderer = self.controller.plugin.renderer
rect = renderer.screens.current[u'size'] rect = renderer.screens.current[u'size']
ppt_window = self.presentation.SlideShowSettings.Run() ppt_window = self.presentation.SlideShowSettings.Run()
if not ppt_window:
return
ppt_window.Top = rect.y() * 72 / dpi ppt_window.Top = rect.y() * 72 / dpi
ppt_window.Height = rect.height() * 72 / dpi ppt_window.Height = rect.height() * 72 / dpi
ppt_window.Left = rect.x() * 72 / dpi ppt_window.Left = rect.x() * 72 / dpi
@ -286,6 +295,8 @@ class PowerpointDocument(PresentationDocument):
""" """
log.debug(u'next_step') log.debug(u'next_step')
self.presentation.SlideShowWindow.View.Next() self.presentation.SlideShowWindow.View.Next()
if self.get_slide_number() > self.get_slide_count():
self.previous_step()
def previous_step(self): def previous_step(self):
""" """

View File

@ -260,12 +260,13 @@ class PresentationDocument(object):
else: else:
return None return None
def poll_slidenumber(self, is_live): def poll_slidenumber(self, is_live, hide_mode):
""" """
Check the current slide number Check the current slide number
""" """
if not self.is_active(): if not self.is_active():
return return
if not hide_mode:
current = self.get_slide_number() current = self.get_slide_number()
if current == self.slidenumber: if current == self.slidenumber:
return return

View File

@ -37,6 +37,7 @@ from openlp.core.lib import build_icon, Receiver, SettingsManager, translate, \
create_separated_list create_separated_list
from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.ui import UiStrings, critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.core.utils import locale_direct_compare
from openlp.plugins.songs.lib.db import Song from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
@ -251,7 +252,8 @@ class SongExportForm(OpenLPWizard):
# Load the list of songs. # Load the list of songs.
Receiver.send_message(u'cursor_busy') Receiver.send_message(u'cursor_busy')
songs = self.plugin.manager.get_all_objects(Song) songs = self.plugin.manager.get_all_objects(Song)
songs.sort() songs.sort(
cmp=locale_direct_compare, key=lambda song: song.sort_string)
for song in songs: for song in songs:
# No need to export temporary songs. # No need to export temporary songs.
if song.temporary: if song.temporary:

View File

@ -31,11 +31,11 @@ the Songs plugin
""" """
from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
from PyQt4 import QtCore
from openlp.core.lib.db import BaseModel, init_db from openlp.core.lib.db import BaseModel, init_db
from openlp.core.utils import locale_compare
class Author(BaseModel): class Author(BaseModel):
""" """
@ -64,14 +64,22 @@ class Song(BaseModel):
""" """
Song model Song model
""" """
# By default sort the songs by its title considering language specific def __init__(self):
# characters. self.sort_string = ''
def __lt__(self, other):
r = locale_compare(self.title, other.title)
return True if r < 0 else False
def __eq__(self, other): # This decorator tells sqlalchemy to call this method everytime
return 0 == locale_compare(self.title, other.title) # any data on this object are updated.
@reconstructor
def init_on_load(self):
"""
Precompute string to be used for sorting.
Song sorting is performance sensitive operation.
To get maximum speed lets precompute the string
used for comparison.
"""
# Avoid the overhead of converting string to lowercase and to QString
self.sort_string = QtCore.QString(self.title.lower())
class Topic(BaseModel): class Topic(BaseModel):

View File

@ -39,7 +39,7 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \
check_directory_exists check_directory_exists
from openlp.core.lib.ui import UiStrings, create_widget_action from openlp.core.lib.ui import UiStrings, create_widget_action
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
from openlp.core.utils import AppLocation from openlp.core.utils import AppLocation, locale_direct_compare
from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
SongImportForm, SongExportForm SongImportForm, SongExportForm
from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \ from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \
@ -259,7 +259,8 @@ class SongMediaItem(MediaManagerItem):
log.debug(u'display results Song') log.debug(u'display results Song')
self.saveAutoSelectId() self.saveAutoSelectId()
self.listView.clear() self.listView.clear()
searchresults.sort() searchresults.sort(
cmp=locale_direct_compare, key=lambda song: song.sort_string)
for song in searchresults: for song in searchresults:
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:

View File

@ -27,6 +27,7 @@
############################################################################### ###############################################################################
import logging import logging
import os import os
import time
from PyQt4 import QtCore from PyQt4 import QtCore
@ -123,6 +124,7 @@ class OooImport(SongImport):
try: try:
uno_instance = get_uno_instance(resolver) uno_instance = get_uno_instance(resolver)
except NoConnectException: except NoConnectException:
time.sleep(0.1)
log.exception("Failed to resolve uno connection") log.exception("Failed to resolve uno connection")
self.startOooProcess() self.startOooProcess()
loop += 1 loop += 1