This commit is contained in:
Andreas Preikschat 2013-04-15 17:43:18 +02:00
commit 908e048d1a
24 changed files with 560 additions and 248 deletions

View File

@ -69,6 +69,14 @@ try:
MAKO_VERSION = mako.__version__
except ImportError:
MAKO_VERSION = u'-'
try:
import icu
try:
ICU_VERSION = icu.VERSION
except AttributeError:
ICU_VERSION = u'OK'
except ImportError:
ICU_VERSION = u'-'
try:
import uno
arg = uno.createUnoStruct(u'com.sun.star.beans.PropertyValue')
@ -143,6 +151,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
u'PyEnchant: %s\n' % ENCHANT_VERSION + \
u'PySQLite: %s\n' % SQLITE_VERSION + \
u'Mako: %s\n' % MAKO_VERSION + \
u'pyICU: %s\n' % ICU_VERSION + \
u'pyUNO bridge: %s\n' % UNO_VERSION + \
u'VLC: %s\n' % VLC_VERSION
if platform.system() == u'Linux':

View File

@ -48,7 +48,7 @@ import sys
from inspect import getargspec
__version__ = "N/A"
build_date = "Thu Mar 21 22:33:03 2013"
build_date = "Mon Apr 1 23:47:38 2013"
if sys.version_info[0] > 2:
str = str
@ -70,7 +70,7 @@ if sys.version_info[0] > 2:
if isinstance(b, bytes):
return b.decode(sys.getfilesystemencoding())
else:
return str(b)
return b
else:
str = str
unicode = unicode
@ -278,6 +278,11 @@ def class_result(classname):
return classname(result)
return wrap_errcheck
# Wrapper for the opaque struct libvlc_log_t
class Log(ctypes.Structure):
pass
Log_ptr = ctypes.POINTER(Log)
# FILE* ctypes wrapper, copied from
# http://svn.python.org/projects/ctypes/trunk/ctypeslib/ctypeslib/contrib/pythonhdr.py
class FILE(ctypes.Structure):
@ -675,11 +680,14 @@ class Callback(ctypes.c_void_p):
pass
class LogCb(ctypes.c_void_p):
"""Callback prototype for LibVLC log message handler.
\param data data pointer as given to L{libvlc_log_subscribe}()
\param data data pointer as given to L{libvlc_log_set}()
\param level message level (@ref enum libvlc_log_level)
\param ctx message context (meta-informations about the message)
\param fmt printf() format string (as defined by ISO C11)
\param args variable argument list for the format
\note Log message handlers <b>must</b> be thread-safe.
\warning The message context pointer, the format string parameters and the
variable arguments are only valid until the callback returns.
"""
pass
class VideoLockCb(ctypes.c_void_p):
@ -813,13 +821,16 @@ class CallbackDecorators(object):
Callback.__doc__ = '''Callback function notification
\param p_event the event triggering the callback
'''
LogCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_void_p)
LogCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, Log_ptr, ctypes.c_char_p, ctypes.c_void_p)
LogCb.__doc__ = '''Callback prototype for LibVLC log message handler.
\param data data pointer as given to L{libvlc_log_subscribe}()
\param data data pointer as given to L{libvlc_log_set}()
\param level message level (@ref enum libvlc_log_level)
\param ctx message context (meta-informations about the message)
\param fmt printf() format string (as defined by ISO C11)
\param args variable argument list for the format
\note Log message handlers <b>must</b> be thread-safe.
\warning The message context pointer, the format string parameters and the
variable arguments are only valid until the callback returns.
'''
VideoLockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p))
VideoLockCb.__doc__ = '''Callback prototype to allocate and lock a picture buffer.
@ -1407,7 +1418,7 @@ class Instance(_Ctype):
@param name: interface name, or NULL for default.
@return: 0 on success, -1 on error.
'''
return libvlc_add_intf(self, name)
return libvlc_add_intf(self, str_to_bytes(name))
def set_user_agent(self, name, http):
'''Sets the application name. LibVLC passes this as the user agent string
@ -1416,7 +1427,33 @@ class Instance(_Ctype):
@param http: HTTP User Agent, e.g. "FooBar/1.2.3 Python/2.6.0".
@version: LibVLC 1.1.1 or later.
'''
return libvlc_set_user_agent(self, name, http)
return libvlc_set_user_agent(self, str_to_bytes(name), str_to_bytes(http))
def log_unset(self):
'''Unsets the logging callback for a LibVLC instance. This is rarely needed:
the callback is implicitly unset when the instance is destroyed.
This function will wait for any pending callbacks invocation to complete
(causing a deadlock if called from within the callback).
@version: LibVLC 2.1.0 or later.
'''
return libvlc_log_unset(self)
def log_set(self, data, p_instance):
'''Sets the logging callback for a LibVLC instance.
This function is thread-safe: it will wait for any pending callbacks
invocation to complete.
@param data: opaque data pointer for the callback function @note Some log messages (especially debug) are emitted by LibVLC while is being initialized. These messages cannot be captured with this interface. @warning A deadlock may occur if this function is called from the callback.
@param p_instance: libvlc instance.
@version: LibVLC 2.1.0 or later.
'''
return libvlc_log_set(self, data, p_instance)
def log_set_file(self, stream):
'''Sets up logging to a file.
@param stream: FILE pointer opened for writing (the FILE pointer must remain valid until L{log_unset}()).
@version: LibVLC 2.1.0 or later.
'''
return libvlc_log_set_file(self, stream)
def media_new_location(self, psz_mrl):
'''Create a media with a certain given media resource location,
@ -1429,7 +1466,7 @@ class Instance(_Ctype):
@param psz_mrl: the media location.
@return: the newly created media or NULL on error.
'''
return libvlc_media_new_location(self, psz_mrl)
return libvlc_media_new_location(self, str_to_bytes(psz_mrl))
def media_new_path(self, path):
'''Create a media for a certain file path.
@ -1437,7 +1474,7 @@ class Instance(_Ctype):
@param path: local filesystem path.
@return: the newly created media or NULL on error.
'''
return libvlc_media_new_path(self, path)
return libvlc_media_new_path(self, str_to_bytes(path))
def media_new_fd(self, fd):
'''Create a media for an already open file descriptor.
@ -1465,14 +1502,14 @@ class Instance(_Ctype):
@param psz_name: the name of the node.
@return: the new empty media or NULL on error.
'''
return libvlc_media_new_as_node(self, psz_name)
return libvlc_media_new_as_node(self, str_to_bytes(psz_name))
def media_discoverer_new_from_name(self, psz_name):
'''Discover media service by name.
@param psz_name: service name.
@return: media discover object or NULL in case of error.
'''
return libvlc_media_discoverer_new_from_name(self, psz_name)
return libvlc_media_discoverer_new_from_name(self, str_to_bytes(psz_name))
def media_library_new(self):
'''Create an new Media Library object.
@ -1500,7 +1537,7 @@ class Instance(_Ctype):
@return: A NULL-terminated linked list of potential audio output devices. It must be freed it with L{audio_output_device_list_release}().
@version: LibVLC 2.1.0 or later.
'''
return libvlc_audio_output_device_list_get(self, aout)
return libvlc_audio_output_device_list_get(self, str_to_bytes(aout))
def vlm_release(self):
'''Release the vlm instance related to the given L{Instance}.
@ -1518,7 +1555,7 @@ class Instance(_Ctype):
@param b_loop: Should this broadcast be played in loop ?
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_add_broadcast(self, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop)
return libvlc_vlm_add_broadcast(self, str_to_bytes(psz_name), str_to_bytes(psz_input), str_to_bytes(psz_output), i_options, ppsz_options, b_enabled, b_loop)
def vlm_add_vod(self, psz_name, psz_input, i_options, ppsz_options, b_enabled, psz_mux):
'''Add a vod, with one input.
@ -1530,14 +1567,14 @@ class Instance(_Ctype):
@param psz_mux: the muxer of the vod media.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_add_vod(self, psz_name, psz_input, i_options, ppsz_options, b_enabled, psz_mux)
return libvlc_vlm_add_vod(self, str_to_bytes(psz_name), str_to_bytes(psz_input), i_options, ppsz_options, b_enabled, str_to_bytes(psz_mux))
def vlm_del_media(self, psz_name):
'''Delete a media (VOD or broadcast).
@param psz_name: the media to delete.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_del_media(self, psz_name)
return libvlc_vlm_del_media(self, str_to_bytes(psz_name))
def vlm_set_enabled(self, psz_name, b_enabled):
'''Enable or disable a media (VOD or broadcast).
@ -1545,7 +1582,7 @@ class Instance(_Ctype):
@param b_enabled: the new status.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_set_enabled(self, psz_name, b_enabled)
return libvlc_vlm_set_enabled(self, str_to_bytes(psz_name), b_enabled)
def vlm_set_output(self, psz_name, psz_output):
'''Set the output for a media.
@ -1553,7 +1590,7 @@ class Instance(_Ctype):
@param psz_output: the output MRL (the parameter to the "sout" variable).
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_set_output(self, psz_name, psz_output)
return libvlc_vlm_set_output(self, str_to_bytes(psz_name), str_to_bytes(psz_output))
def vlm_set_input(self, psz_name, psz_input):
'''Set a media's input MRL. This will delete all existing inputs and
@ -1562,7 +1599,7 @@ class Instance(_Ctype):
@param psz_input: the input MRL.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_set_input(self, psz_name, psz_input)
return libvlc_vlm_set_input(self, str_to_bytes(psz_name), str_to_bytes(psz_input))
def vlm_add_input(self, psz_name, psz_input):
'''Add a media's input MRL. This will add the specified one.
@ -1570,7 +1607,7 @@ class Instance(_Ctype):
@param psz_input: the input MRL.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_add_input(self, psz_name, psz_input)
return libvlc_vlm_add_input(self, str_to_bytes(psz_name), str_to_bytes(psz_input))
def vlm_set_loop(self, psz_name, b_loop):
'''Set a media's loop status.
@ -1578,7 +1615,7 @@ class Instance(_Ctype):
@param b_loop: the new status.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_set_loop(self, psz_name, b_loop)
return libvlc_vlm_set_loop(self, str_to_bytes(psz_name), b_loop)
def vlm_set_mux(self, psz_name, psz_mux):
'''Set a media's vod muxer.
@ -1586,7 +1623,7 @@ class Instance(_Ctype):
@param psz_mux: the new muxer.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_set_mux(self, psz_name, psz_mux)
return libvlc_vlm_set_mux(self, str_to_bytes(psz_name), str_to_bytes(psz_mux))
def vlm_change_media(self, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop):
'''Edit the parameters of a media. This will delete all existing inputs and
@ -1600,28 +1637,28 @@ class Instance(_Ctype):
@param b_loop: Should this broadcast be played in loop ?
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_change_media(self, psz_name, psz_input, psz_output, i_options, ppsz_options, b_enabled, b_loop)
return libvlc_vlm_change_media(self, str_to_bytes(psz_name), str_to_bytes(psz_input), str_to_bytes(psz_output), i_options, ppsz_options, b_enabled, b_loop)
def vlm_play_media(self, psz_name):
'''Play the named broadcast.
@param psz_name: the name of the broadcast.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_play_media(self, psz_name)
return libvlc_vlm_play_media(self, str_to_bytes(psz_name))
def vlm_stop_media(self, psz_name):
'''Stop the named broadcast.
@param psz_name: the name of the broadcast.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_stop_media(self, psz_name)
return libvlc_vlm_stop_media(self, str_to_bytes(psz_name))
def vlm_pause_media(self, psz_name):
'''Pause the named broadcast.
@param psz_name: the name of the broadcast.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_pause_media(self, psz_name)
return libvlc_vlm_pause_media(self, str_to_bytes(psz_name))
def vlm_seek_media(self, psz_name, f_percentage):
'''Seek in the named broadcast.
@ -1629,7 +1666,7 @@ class Instance(_Ctype):
@param f_percentage: the percentage to seek to.
@return: 0 on success, -1 on error.
'''
return libvlc_vlm_seek_media(self, psz_name, f_percentage)
return libvlc_vlm_seek_media(self, str_to_bytes(psz_name), f_percentage)
def vlm_show_media(self, psz_name):
'''Return information about the named media as a JSON
@ -1643,7 +1680,7 @@ class Instance(_Ctype):
@param psz_name: the name of the media, if the name is an empty string, all media is described.
@return: string with information about named media, or NULL on error.
'''
return libvlc_vlm_show_media(self, psz_name)
return libvlc_vlm_show_media(self, str_to_bytes(psz_name))
def vlm_get_media_instance_position(self, psz_name, i_instance):
'''Get vlm_media instance position by name or instance id.
@ -1651,7 +1688,7 @@ class Instance(_Ctype):
@param i_instance: instance id.
@return: position as float or -1. on error.
'''
return libvlc_vlm_get_media_instance_position(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_position(self, str_to_bytes(psz_name), i_instance)
def vlm_get_media_instance_time(self, psz_name, i_instance):
'''Get vlm_media instance time by name or instance id.
@ -1659,7 +1696,7 @@ class Instance(_Ctype):
@param i_instance: instance id.
@return: time as integer or -1 on error.
'''
return libvlc_vlm_get_media_instance_time(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_time(self, str_to_bytes(psz_name), i_instance)
def vlm_get_media_instance_length(self, psz_name, i_instance):
'''Get vlm_media instance length by name or instance id.
@ -1667,7 +1704,7 @@ class Instance(_Ctype):
@param i_instance: instance id.
@return: length of media item or -1 on error.
'''
return libvlc_vlm_get_media_instance_length(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_length(self, str_to_bytes(psz_name), i_instance)
def vlm_get_media_instance_rate(self, psz_name, i_instance):
'''Get vlm_media instance playback rate by name or instance id.
@ -1675,7 +1712,7 @@ class Instance(_Ctype):
@param i_instance: instance id.
@return: playback rate or -1 on error.
'''
return libvlc_vlm_get_media_instance_rate(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_rate(self, str_to_bytes(psz_name), i_instance)
def vlm_get_media_instance_title(self, psz_name, i_instance):
'''Get vlm_media instance title number by name or instance id.
@ -1684,7 +1721,7 @@ class Instance(_Ctype):
@return: title as number or -1 on error.
@bug: will always return 0.
'''
return libvlc_vlm_get_media_instance_title(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_title(self, str_to_bytes(psz_name), i_instance)
def vlm_get_media_instance_chapter(self, psz_name, i_instance):
'''Get vlm_media instance chapter number by name or instance id.
@ -1693,7 +1730,7 @@ class Instance(_Ctype):
@return: chapter as number or -1 on error.
@bug: will always return 0.
'''
return libvlc_vlm_get_media_instance_chapter(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_chapter(self, str_to_bytes(psz_name), i_instance)
def vlm_get_media_instance_seekable(self, psz_name, i_instance):
'''Is libvlc instance seekable ?
@ -1702,7 +1739,7 @@ class Instance(_Ctype):
@return: 1 if seekable, 0 if not, -1 if media does not exist.
@bug: will always return 0.
'''
return libvlc_vlm_get_media_instance_seekable(self, psz_name, i_instance)
return libvlc_vlm_get_media_instance_seekable(self, str_to_bytes(psz_name), i_instance)
def vlm_get_event_manager(self):
'''Get libvlc_event_manager from a vlm media.
@ -1751,7 +1788,7 @@ class Media(_Ctype):
self.add_option(o)
def add_option(self, ppsz_options):
def add_option(self, psz_options):
'''Add an option to the media.
This option will be used to determine how the media_player will
read the media. This allows to use VLC's advanced
@ -1763,11 +1800,11 @@ class Media(_Ctype):
Specifically, due to architectural issues most audio and video options,
such as text renderer options, have no effects on an individual media.
These options must be set through L{new}() instead.
@param ppsz_options: the options (as a string).
@param psz_options: the options (as a string).
'''
return libvlc_media_add_option(self, ppsz_options)
return libvlc_media_add_option(self, str_to_bytes(psz_options))
def add_option_flag(self, ppsz_options, i_flags):
def add_option_flag(self, psz_options, i_flags):
'''Add an option to the media with configurable flags.
This option will be used to determine how the media_player will
read the media. This allows to use VLC's advanced
@ -1777,10 +1814,10 @@ class Media(_Ctype):
specifically, due to architectural issues, video-related options
such as text renderer options cannot be set on a single media. They
must be set on the whole libvlc instance instead.
@param ppsz_options: the options (as a string).
@param psz_options: the options (as a string).
@param i_flags: the flags for this option.
'''
return libvlc_media_add_option_flag(self, ppsz_options, i_flags)
return libvlc_media_add_option_flag(self, str_to_bytes(psz_options), i_flags)
def retain(self):
'''Retain a reference to a media descriptor object (libvlc_media_t). Use
@ -1829,7 +1866,7 @@ class Media(_Ctype):
@param e_meta: the meta to write.
@param psz_value: the media's meta.
'''
return libvlc_media_set_meta(self, e_meta, psz_value)
return libvlc_media_set_meta(self, e_meta, str_to_bytes(psz_value))
def save_meta(self):
'''Save the meta previously set.
@ -2490,7 +2527,7 @@ class MediaPlayer(_Ctype):
@version: LibVLC 1.1.1 or later.
@bug: All pixel planes are expected to have the same pitch. To use the YCbCr color space with chrominance subsampling, consider using L{video_set_format_callbacks}() instead.
'''
return libvlc_video_set_format(self, chroma, width, height, pitch)
return libvlc_video_set_format(self, str_to_bytes(chroma), width, height, pitch)
def video_set_format_callbacks(self, setup, cleanup):
'''Set decoded video chroma and dimensions. This only works in combination with
@ -2617,7 +2654,7 @@ class MediaPlayer(_Ctype):
@param channels: channels count.
@version: LibVLC 2.0.0 or later.
'''
return libvlc_audio_set_format(self, format, rate, channels)
return libvlc_audio_set_format(self, str_to_bytes(format), rate, channels)
def get_length(self):
'''Get the current movie length (in ms).
@ -2843,7 +2880,7 @@ class MediaPlayer(_Ctype):
'''Set new video aspect ratio.
@param psz_aspect: new video aspect-ratio or NULL to reset to default @note Invalid aspect ratios are ignored.
'''
return libvlc_video_set_aspect_ratio(self, psz_aspect)
return libvlc_video_set_aspect_ratio(self, str_to_bytes(psz_aspect))
def video_get_spu(self):
'''Get current video subtitle.
@ -2859,7 +2896,7 @@ class MediaPlayer(_Ctype):
def video_set_spu(self, i_spu):
'''Set new video subtitle.
@param i_spu: new video subtitle to select.
@param i_spu: video subtitle track to select (i_id from track description).
@return: 0 on success, -1 if out of range.
'''
return libvlc_video_set_spu(self, i_spu)
@ -2869,7 +2906,7 @@ class MediaPlayer(_Ctype):
@param psz_subtitle: new video subtitle file.
@return: the success status (boolean).
'''
return libvlc_video_set_subtitle_file(self, psz_subtitle)
return libvlc_video_set_subtitle_file(self, str_to_bytes(psz_subtitle))
def video_get_spu_delay(self):
'''Get the current subtitle delay. Positive values means subtitles are being
@ -2900,7 +2937,7 @@ class MediaPlayer(_Ctype):
'''Set new crop filter geometry.
@param psz_geometry: new crop filter geometry (NULL to unset).
'''
return libvlc_video_set_crop_geometry(self, psz_geometry)
return libvlc_video_set_crop_geometry(self, str_to_bytes(psz_geometry))
def video_get_teletext(self):
'''Get current teletext page requested.
@ -2948,13 +2985,13 @@ class MediaPlayer(_Ctype):
@param i_height: the snapshot's height.
@return: 0 on success, -1 if the video was not found.
'''
return libvlc_video_take_snapshot(self, num, psz_filepath, i_width, i_height)
return libvlc_video_take_snapshot(self, num, str_to_bytes(psz_filepath), i_width, i_height)
def video_set_deinterlace(self, psz_mode):
'''Enable or disable deinterlace filter.
@param psz_mode: type of deinterlace filter, NULL to disable.
'''
return libvlc_video_set_deinterlace(self, psz_mode)
return libvlc_video_set_deinterlace(self, str_to_bytes(psz_mode))
def video_get_marquee_int(self, option):
'''Get an integer marquee option value.
@ -2982,7 +3019,7 @@ class MediaPlayer(_Ctype):
@param option: marq option to set See libvlc_video_marquee_string_option_t.
@param psz_text: marq option value.
'''
return libvlc_video_set_marquee_string(self, option, psz_text)
return libvlc_video_set_marquee_string(self, option, str_to_bytes(psz_text))
def video_get_logo_int(self, option):
'''Get integer logo option.
@ -3006,7 +3043,7 @@ class MediaPlayer(_Ctype):
@param option: logo option to set, values of libvlc_video_logo_option_t.
@param psz_value: logo option value.
'''
return libvlc_video_set_logo_string(self, option, psz_value)
return libvlc_video_set_logo_string(self, option, str_to_bytes(psz_value))
def video_get_adjust_int(self, option):
'''Get integer adjust option.
@ -3049,7 +3086,7 @@ class MediaPlayer(_Ctype):
@param psz_name: name of audio output, use psz_name of See L{AudioOutput}.
@return: 0 if function succeded, -1 on error.
'''
return libvlc_audio_output_set(self, psz_name)
return libvlc_audio_output_set(self, str_to_bytes(psz_name))
def audio_output_device_set(self, psz_audio_output, psz_device_id):
'''Configures an explicit audio output device for a given audio output plugin.
@ -3065,7 +3102,7 @@ class MediaPlayer(_Ctype):
@param psz_device_id: device.
@return: Nothing. Errors are ignored.
'''
return libvlc_audio_output_device_set(self, psz_audio_output, psz_device_id)
return libvlc_audio_output_device_set(self, str_to_bytes(psz_audio_output), str_to_bytes(psz_device_id))
def audio_toggle_mute(self):
'''Toggle mute status.
@ -3314,44 +3351,43 @@ def libvlc_event_type_name(event_type):
ctypes.c_char_p, ctypes.c_uint)
return f(event_type)
def libvlc_log_subscribe(sub, cb, data):
'''Registers a logging callback to LibVLC.
This function is thread-safe.
@param sub: uninitialized subscriber structure.
def libvlc_log_unset(p_instance):
'''Unsets the logging callback for a LibVLC instance. This is rarely needed:
the callback is implicitly unset when the instance is destroyed.
This function will wait for any pending callbacks invocation to complete
(causing a deadlock if called from within the callback).
@param p_instance: libvlc instance.
@version: LibVLC 2.1.0 or later.
'''
f = _Cfunctions.get('libvlc_log_unset', None) or \
_Cfunction('libvlc_log_unset', ((1,),), None,
None, Instance)
return f(p_instance)
def libvlc_log_set(cb, data, p_instance):
'''Sets the logging callback for a LibVLC instance.
This function is thread-safe: it will wait for any pending callbacks
invocation to complete.
@param cb: callback function pointer.
@param data: opaque data pointer for the callback function @note Some log messages (especially debug) are emitted by LibVLC while initializing, before any LibVLC instance even exists. Thus this function does not require a LibVLC instance parameter. @warning As a consequence of not depending on a LibVLC instance, all logging callbacks are shared by all LibVLC instances within the process / address space. This also enables log messages to be emitted by LibVLC components that are not specific to any given LibVLC instance. @warning Do not call this function from within a logging callback. It would trigger a dead lock.
@param data: opaque data pointer for the callback function @note Some log messages (especially debug) are emitted by LibVLC while is being initialized. These messages cannot be captured with this interface. @warning A deadlock may occur if this function is called from the callback.
@param p_instance: libvlc instance.
@version: LibVLC 2.1.0 or later.
'''
f = _Cfunctions.get('libvlc_log_subscribe', None) or \
_Cfunction('libvlc_log_subscribe', ((1,), (1,), (1,),), None,
None, ctypes.c_void_p, LogCb, ctypes.c_void_p)
return f(sub, cb, data)
f = _Cfunctions.get('libvlc_log_set', None) or \
_Cfunction('libvlc_log_set', ((1,), (1,), (1,),), None,
None, Instance, LogCb, ctypes.c_void_p)
return f(cb, data, p_instance)
def libvlc_log_subscribe_file(sub, stream):
'''Registers a logging callback to a file.
@param stream: FILE pointer opened for writing (the FILE pointer must remain valid until L{libvlc_log_unsubscribe}()).
def libvlc_log_set_file(p_instance, stream):
'''Sets up logging to a file.
@param p_instance: libvlc instance.
@param stream: FILE pointer opened for writing (the FILE pointer must remain valid until L{libvlc_log_unset}()).
@version: LibVLC 2.1.0 or later.
'''
f = _Cfunctions.get('libvlc_log_subscribe_file', None) or \
_Cfunction('libvlc_log_subscribe_file', ((1,), (1,),), None,
None, ctypes.c_void_p, FILE_ptr)
return f(sub, stream)
def libvlc_log_unsubscribe(sub):
'''Deregisters a logging callback from LibVLC.
This function is thread-safe.
@note: After (and only after) L{libvlc_log_unsubscribe}() has returned,
LibVLC warrants that there are no more pending calls of the subscription
callback function.
@warning: Do not call this function from within a logging callback.
It would trigger a dead lock.
@param sub: initialized subscriber structure.
@version: LibVLC 2.1.0 or later.
'''
f = _Cfunctions.get('libvlc_log_unsubscribe', None) or \
_Cfunction('libvlc_log_unsubscribe', ((1,),), None,
None, ctypes.c_void_p)
return f(sub)
f = _Cfunctions.get('libvlc_log_set_file', None) or \
_Cfunction('libvlc_log_set_file', ((1,), (1,),), None,
None, Instance, FILE_ptr)
return f(p_instance, stream)
def libvlc_module_description_list_release(p_list):
'''Release a list of module descriptions.
@ -3460,7 +3496,7 @@ def libvlc_media_new_as_node(p_instance, psz_name):
ctypes.c_void_p, Instance, ctypes.c_char_p)
return f(p_instance, psz_name)
def libvlc_media_add_option(p_md, ppsz_options):
def libvlc_media_add_option(p_md, psz_options):
'''Add an option to the media.
This option will be used to determine how the media_player will
read the media. This allows to use VLC's advanced
@ -3473,14 +3509,14 @@ def libvlc_media_add_option(p_md, ppsz_options):
such as text renderer options, have no effects on an individual media.
These options must be set through L{libvlc_new}() instead.
@param p_md: the media descriptor.
@param ppsz_options: the options (as a string).
@param psz_options: the options (as a string).
'''
f = _Cfunctions.get('libvlc_media_add_option', None) or \
_Cfunction('libvlc_media_add_option', ((1,), (1,),), None,
None, Media, ctypes.c_char_p)
return f(p_md, ppsz_options)
return f(p_md, psz_options)
def libvlc_media_add_option_flag(p_md, ppsz_options, i_flags):
def libvlc_media_add_option_flag(p_md, psz_options, i_flags):
'''Add an option to the media with configurable flags.
This option will be used to determine how the media_player will
read the media. This allows to use VLC's advanced
@ -3491,13 +3527,13 @@ def libvlc_media_add_option_flag(p_md, ppsz_options, i_flags):
such as text renderer options cannot be set on a single media. They
must be set on the whole libvlc instance instead.
@param p_md: the media descriptor.
@param ppsz_options: the options (as a string).
@param psz_options: the options (as a string).
@param i_flags: the flags for this option.
'''
f = _Cfunctions.get('libvlc_media_add_option_flag', None) or \
_Cfunction('libvlc_media_add_option_flag', ((1,), (1,), (1,),), None,
None, Media, ctypes.c_char_p, ctypes.c_uint)
return f(p_md, ppsz_options, i_flags)
return f(p_md, psz_options, i_flags)
def libvlc_media_retain(p_md):
'''Retain a reference to a media descriptor object (libvlc_media_t). Use
@ -4949,12 +4985,12 @@ def libvlc_video_get_spu_description(p_mi):
def libvlc_video_set_spu(p_mi, i_spu):
'''Set new video subtitle.
@param p_mi: the media player.
@param i_spu: new video subtitle to select.
@param i_spu: video subtitle track to select (i_id from track description).
@return: 0 on success, -1 if out of range.
'''
f = _Cfunctions.get('libvlc_video_set_spu', None) or \
_Cfunction('libvlc_video_set_spu', ((1,), (1,),), None,
ctypes.c_int, MediaPlayer, ctypes.c_uint)
ctypes.c_int, MediaPlayer, ctypes.c_int)
return f(p_mi, i_spu)
def libvlc_video_set_subtitle_file(p_mi, psz_subtitle):
@ -5791,7 +5827,7 @@ def libvlc_vlm_get_event_manager(p_instance):
# libvlc_printerr
# libvlc_set_exit_handler
# 18 function(s) not wrapped as methods:
# 15 function(s) not wrapped as methods:
# libvlc_audio_output_device_list_release
# libvlc_audio_output_list_release
# libvlc_clearerr
@ -5802,9 +5838,6 @@ def libvlc_vlm_get_event_manager(p_instance):
# libvlc_get_changeset
# libvlc_get_compiler
# libvlc_get_version
# libvlc_log_subscribe
# libvlc_log_subscribe_file
# libvlc_log_unsubscribe
# libvlc_media_tracks_release
# libvlc_module_description_list_release
# libvlc_new

View File

@ -234,18 +234,22 @@ class ServiceManagerDialog(object):
icon=u':/general/general_edit.png', triggers=self.create_custom)
self.menu.addSeparator()
# Add AutoPlay menu actions
self.auto_play_slides_group = QtGui.QMenu(translate('OpenLP.ServiceManager', '&Auto play slides'))
self.menu.addMenu(self.auto_play_slides_group)
self.auto_play_slides_loop = create_widget_action(self.auto_play_slides_group,
self.auto_play_slides_menu = QtGui.QMenu(translate('OpenLP.ServiceManager', '&Auto play slides'))
self.menu.addMenu(self.auto_play_slides_menu)
auto_play_slides_group = QtGui.QActionGroup(self.auto_play_slides_menu)
auto_play_slides_group.setExclusive(True)
self.auto_play_slides_loop = create_widget_action(self.auto_play_slides_menu,
text=translate('OpenLP.ServiceManager', 'Auto play slides &Loop'),
checked=False, triggers=self.toggle_auto_play_slides_loop)
self.auto_play_slides_once = create_widget_action(self.auto_play_slides_group,
auto_play_slides_group.addAction(self.auto_play_slides_loop)
self.auto_play_slides_once = create_widget_action(self.auto_play_slides_menu,
text=translate('OpenLP.ServiceManager', 'Auto play slides &Once'),
checked=False, triggers=self.toggle_auto_play_slides_once)
self.auto_play_slides_group.addSeparator()
self.timed_slide_interval = create_widget_action(self.auto_play_slides_group,
auto_play_slides_group.addAction(self.auto_play_slides_once)
self.auto_play_slides_menu.addSeparator()
self.timed_slide_interval = create_widget_action(self.auto_play_slides_menu,
text=translate('OpenLP.ServiceManager', '&Delay between slides'),
checked=False, triggers=self.on_timed_slide_interval)
triggers=self.on_timed_slide_interval)
self.menu.addSeparator()
self.preview_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'),
icon=u':/general/general_preview.png', triggers=self.make_preview)
@ -786,7 +790,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
self.notes_action.setVisible(True)
if service_item[u'service_item'].is_capable(ItemCapabilities.CanLoop) and \
len(service_item[u'service_item'].get_frames()) > 1:
self.auto_play_slides_group.menuAction().setVisible(True)
self.auto_play_slides_menu.menuAction().setVisible(True)
self.auto_play_slides_once.setChecked(service_item[u'service_item'].auto_play_slides_once)
self.auto_play_slides_loop.setChecked(service_item[u'service_item'].auto_play_slides_loop)
self.timed_slide_interval.setChecked(service_item[u'service_item'].timed_slide_interval > 0)
@ -798,7 +802,7 @@ class ServiceManager(QtGui.QWidget, ServiceManagerDialog):
translate('OpenLP.ServiceManager', '&Delay between slides') + delay_suffix)
# TODO for future: make group explains itself more visually
else:
self.auto_play_slides_group.menuAction().setVisible(False)
self.auto_play_slides_menu.menuAction().setVisible(False)
if service_item[u'service_item'].is_capable(ItemCapabilities.HasVariableStartTime):
self.time_action.setVisible(True)
if service_item[u'service_item'].is_capable(ItemCapabilities.CanAutoStartForLive):

View File

@ -44,6 +44,8 @@ from openlp.core.utils.actions import ActionList, CategoryOrder
log = logging.getLogger(__name__)
# Threshold which has to be trespassed to toggle.
HIDE_MENU_THRESHOLD = 27
AUDIO_TIME_LABEL_STYLESHEET = u'background-color: palette(background); ' \
u'border-top-color: palette(shadow); ' \
u'border-left-color: palette(shadow); ' \
@ -588,12 +590,12 @@ class SlideController(DisplayController):
if self.is_live:
# Space used by the toolbar.
used_space = self.toolbar.size().width() + self.hide_menu.size().width()
# The + 40 is needed to prevent flickering. This can be considered a "buffer".
if width > used_space + 40 and self.hide_menu.isVisible():
# Add the threshold to prevent flickering.
if width > used_space + HIDE_MENU_THRESHOLD and self.hide_menu.isVisible():
self.toolbar.set_widget_visible(self.narrow_menu, False)
self.toolbar.set_widget_visible(self.wide_menu)
# The - 40 is needed to prevent flickering. This can be considered a "buffer".
elif width < used_space - 40 and not self.hide_menu.isVisible():
# Take away a threshold to prevent flickering.
elif width < used_space - HIDE_MENU_THRESHOLD and not self.hide_menu.isVisible():
self.toolbar.set_widget_visible(self.wide_menu, False)
self.toolbar.set_widget_visible(self.narrow_menu)

View File

@ -44,7 +44,7 @@ from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, Backgr
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.theme import Theme
from openlp.core.ui import FileRenameForm, ThemeForm
from openlp.core.utils import AppLocation, delete_file, locale_compare, get_filesystem_encoding
from openlp.core.utils import AppLocation, delete_file, get_locale_key, get_filesystem_encoding
log = logging.getLogger(__name__)
@ -418,7 +418,7 @@ class ThemeManager(QtGui.QWidget):
self.theme_list_widget.clear()
files = AppLocation.get_files(self.settings_section, u'.png')
# Sort the themes by its name considering language specific
files.sort(key=lambda file_name: unicode(file_name), cmp=locale_compare)
files.sort(key=lambda file_name: get_locale_key(unicode(file_name)))
# now process the file list of png files
for name in files:
# check to see file is in theme root directory

View File

@ -38,6 +38,7 @@ import re
from subprocess import Popen, PIPE
import sys
import urllib2
import icu
from PyQt4 import QtGui, QtCore
@ -56,10 +57,12 @@ from openlp.core.lib import translate
log = logging.getLogger(__name__)
APPLICATION_VERSION = {}
IMAGES_FILTER = None
ICU_COLLATOR = None
UNO_CONNECTION_TYPE = u'pipe'
#UNO_CONNECTION_TYPE = u'socket'
CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
class VersionThread(QtCore.QThread):
@ -378,21 +381,32 @@ def format_time(text, local_time):
return re.sub('\%[a-zA-Z]', match_formatting, text)
def locale_compare(string1, string2):
def get_locale_key(string):
"""
Compares two strings according to the current locale settings.
As any other compare function, returns a negative, or a positive value,
or 0, depending on whether string1 collates before or after string2 or
is equal to it. Comparison is case insensitive.
Creates a key for case insensitive, locale aware string sorting.
"""
# Function locale.strcoll() from standard Python library does not work properly on Windows.
return locale.strcoll(string1.lower(), string2.lower())
string = string.lower()
# For Python 3 on platforms other than Windows ICU is not necessary. In those cases locale.strxfrm(str) can be used.
global ICU_COLLATOR
if ICU_COLLATOR is None:
from languagemanager import LanguageManager
locale = LanguageManager.get_language()
icu_locale = icu.Locale(locale)
ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
return ICU_COLLATOR.getSortKey(string)
# For performance reasons provide direct reference to compare function without wrapping it in another function making
# the string lowercase. This is needed for sorting songs.
locale_direct_compare = locale.strcoll
def get_natural_key(string):
"""
Generate a key for locale aware natural string sorting.
Returns a list of string compare keys and integers.
"""
key = DIGITS_OR_NONDIGITS.findall(string)
key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
# Python 3 does not support comparision of different types anymore. So make sure, that we do not compare str and int.
#if string[0].isdigit():
# return [''] + key
return key
from applocation import AppLocation
@ -402,4 +416,4 @@ from actions import ActionList
__all__ = [u'AppLocation', u'ActionList', u'LanguageManager', u'get_application_version', u'check_latest_version',
u'add_actions', u'get_filesystem_encoding', 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'locale_direct_compare']
u'delete_file', u'clean_filename', u'format_time', u'get_locale_key', u'get_natural_key']

View File

@ -38,7 +38,7 @@ from openlp.core.lib import Settings, UiStrings, translate
from openlp.core.lib.db import delete_database
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.core.utils import AppLocation, locale_compare
from openlp.core.utils import AppLocation, get_locale_key
from openlp.plugins.bibles.lib.manager import BibleFormat
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
@ -455,7 +455,7 @@ class BibleImportForm(OpenLPWizard):
"""
self.webTranslationComboBox.clear()
bibles = self.web_bible_list[index].keys()
bibles.sort(cmp=locale_compare)
bibles.sort(key=get_locale_key)
self.webTranslationComboBox.addItems(bibles)
def onOsisBrowseButtonClicked(self):

View File

@ -36,7 +36,7 @@ from openlp.core.lib import Registry, MediaManagerItem, ItemCapabilities, Servic
from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
critical_error_message_box, find_and_set_in_combo_box, build_icon
from openlp.core.utils import locale_compare
from openlp.core.utils import get_locale_key
from openlp.plugins.bibles.forms import BibleImportForm, EditBibleForm
from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \
LanguageSelection, BibleStrings
@ -325,7 +325,7 @@ class BibleMediaItem(MediaManagerItem):
# Get all bibles and sort the list.
bibles = self.plugin.manager.get_bibles().keys()
bibles = filter(None, bibles)
bibles.sort(cmp=locale_compare)
bibles.sort(key=get_locale_key)
# Load the bibles into the combo boxes.
self.quickVersionComboBox.addItems(bibles)
self.quickSecondComboBox.addItems(bibles)
@ -461,7 +461,7 @@ class BibleMediaItem(MediaManagerItem):
for book in book_data:
data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
books.append(data[u'name'] + u' ')
books.sort(cmp=locale_compare)
books.sort(key=get_locale_key)
set_case_insensitive_completer(books, self.quickSearchEdit)
def on_import_click(self):

View File

@ -35,7 +35,7 @@ from sqlalchemy import Column, Table, types
from sqlalchemy.orm import mapper
from openlp.core.lib.db import BaseModel, init_db
from openlp.core.utils import locale_compare
from openlp.core.utils import get_locale_key
class CustomSlide(BaseModel):
"""
@ -44,11 +44,10 @@ class CustomSlide(BaseModel):
# By default sort the customs by its title considering language specific
# characters.
def __lt__(self, other):
r = locale_compare(self.title, other.title)
return True if r < 0 else False
return get_locale_key(self.title) < get_locale_key(other.title)
def __eq__(self, other):
return 0 == locale_compare(self.title, other.title)
return get_locale_key(self.title) == get_locale_key(other.title)
def init_schema(url):

View File

@ -36,7 +36,7 @@ from openlp.core.lib import ItemCapabilities, MediaManagerItem, Registry, Servic
StringContent, TreeWidgetWithDnD, UiStrings, build_icon, check_directory_exists, check_item_selected, \
create_thumb, translate, validate_thumb
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.utils import AppLocation, delete_file, locale_compare, get_images_filter
from openlp.core.utils import AppLocation, delete_file, get_locale_key, get_images_filter
from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
@ -255,7 +255,7 @@ class ImageMediaItem(MediaManagerItem):
The ID of the group that will be added recursively
"""
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
image_groups.sort(cmp=locale_compare, key=lambda group_object: group_object.group_name)
image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
folder_icon = build_icon(u':/images/image_group.png')
for image_group in image_groups:
group = QtGui.QTreeWidgetItem()
@ -286,7 +286,7 @@ class ImageMediaItem(MediaManagerItem):
combobox.clear()
combobox.top_level_group_added = False
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
image_groups.sort(cmp=locale_compare, key=lambda group_object: group_object.group_name)
image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
for image_group in image_groups:
combobox.addItem(prefix + image_group.group_name, image_group.id)
self.fill_groups_combobox(combobox, image_group.id, prefix + ' ')
@ -338,7 +338,7 @@ class ImageMediaItem(MediaManagerItem):
self.expand_group(open_group.id)
# Sort the images by its filename considering language specific
# characters.
images.sort(cmp=locale_compare, key=lambda image_object: os.path.split(unicode(image_object.filename))[1])
images.sort(key=lambda image_object: get_locale_key(os.path.split(unicode(image_object.filename))[1]))
for imageFile in images:
log.debug(u'Loading image: %s', imageFile.filename)
filename = os.path.split(imageFile.filename)[1]
@ -525,9 +525,9 @@ class ImageMediaItem(MediaManagerItem):
group_items.append(item)
if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
image_items.append(item)
group_items.sort(cmp=locale_compare, key=lambda item: item.text(0))
group_items.sort(key=lambda item: get_locale_key(item.text(0)))
target_group.addChildren(group_items)
image_items.sort(cmp=locale_compare, key=lambda item: item.text(0))
image_items.sort(key=lambda item: get_locale_key(item.text(0)))
target_group.addChildren(image_items)
def generate_slide_data(self, service_item, item=None, xmlVersion=False,

View File

@ -37,7 +37,7 @@ from openlp.core.lib import ItemCapabilities, MediaManagerItem,MediaType, Regist
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
from openlp.core.ui import DisplayController, Display, DisplayControllerType
from openlp.core.ui.media import get_media_players, set_media_players
from openlp.core.utils import AppLocation, locale_compare
from openlp.core.utils import AppLocation, get_locale_key
log = logging.getLogger(__name__)
@ -261,7 +261,7 @@ class MediaMediaItem(MediaManagerItem):
def load_list(self, media, target_group=None):
# Sort the media by its filename considering language specific
# characters.
media.sort(cmp=locale_compare, key=lambda filename: os.path.split(unicode(filename))[1])
media.sort(key=lambda filename: get_locale_key(os.path.split(unicode(filename))[1]))
for track in media:
track_info = QtCore.QFileInfo(track)
if not os.path.exists(track):
@ -287,7 +287,7 @@ class MediaMediaItem(MediaManagerItem):
def getList(self, type=MediaType.Audio):
media = Settings().value(self.settings_section + u'/media files')
media.sort(cmp=locale_compare, key=lambda filename: os.path.split(unicode(filename))[1])
media.sort(key=lambda filename: get_locale_key(os.path.split(unicode(filename))[1]))
ext = []
if type == MediaType.Audio:
ext = self.media_controller.audio_extensions_list

View File

@ -35,7 +35,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, Registry, ItemCapabilities, ServiceItemContext, Settings, UiStrings, \
build_icon, check_item_selected, create_thumb, translate, validate_thumb
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
from openlp.core.utils import locale_compare
from openlp.core.utils import get_locale_key
from openlp.plugins.presentations.lib import MessageListener
log = logging.getLogger(__name__)
@ -153,8 +153,7 @@ class PresentationMediaItem(MediaManagerItem):
if not initialLoad:
self.main_window.display_progress_bar(len(files))
# Sort the presentations by its filename considering language specific characters.
files.sort(cmp=locale_compare,
key=lambda filename: os.path.split(unicode(filename))[1])
files.sort(key=lambda filename: get_locale_key(os.path.split(unicode(filename))[1]))
for file in files:
if not initialLoad:
self.main_window.increment_progress_bar()

View File

@ -37,7 +37,6 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry, UiStrings, create_separated_list, build_icon, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib import natcmp
from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
@ -222,7 +221,7 @@ class SongExportForm(OpenLPWizard):
# Load the list of songs.
self.application.set_busy_cursor()
songs = self.plugin.manager.get_all_objects(Song)
songs.sort(cmp=natcmp, key=lambda song: song.sort_key)
songs.sort(key=lambda song: song.sort_key)
for song in songs:
# No need to export temporary songs.
if song.temporary:

View File

@ -34,7 +34,7 @@ import re
from PyQt4 import QtGui
from openlp.core.lib import translate
from openlp.core.utils import CONTROL_CHARS, locale_direct_compare
from openlp.core.utils import CONTROL_CHARS
from db import Author
from ui import SongStrings
@ -168,6 +168,7 @@ class VerseType(object):
translate('SongsPlugin.VerseType', 'Intro'),
translate('SongsPlugin.VerseType', 'Ending'),
translate('SongsPlugin.VerseType', 'Other')]
translated_tags = [name[0].lower() for name in translated_names]
@staticmethod
@ -592,37 +593,3 @@ def strip_rtf(text, default_encoding=None):
text = u''.join(out)
return text, default_encoding
def natcmp(a, b):
"""
Natural string comparison which mimics the behaviour of Python's internal cmp function.
"""
if len(a) <= len(b):
for i, key in enumerate(a):
if isinstance(key, int) and isinstance(b[i], int):
result = cmp(key, b[i])
elif isinstance(key, int) and not isinstance(b[i], int):
result = locale_direct_compare(str(key), b[i])
elif not isinstance(key, int) and isinstance(b[i], int):
result = locale_direct_compare(key, str(b[i]))
else:
result = locale_direct_compare(key, b[i])
if result != 0:
return result
if len(a) == len(b):
return 0
else:
return -1
else:
for i, key in enumerate(b):
if isinstance(a[i], int) and isinstance(key, int):
result = cmp(a[i], key)
elif isinstance(a[i], int) and not isinstance(key, int):
result = locale_direct_compare(str(a[i]), key)
elif not isinstance(a[i], int) and isinstance(key, int):
result = locale_direct_compare(a[i], str(key))
else:
result = locale_direct_compare(a[i], key)
if result != 0:
return result
return 1

View File

@ -38,6 +38,7 @@ from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func
from openlp.core.lib.db import BaseModel, init_db
from openlp.core.utils import get_natural_key
class Author(BaseModel):
@ -69,36 +70,15 @@ class Song(BaseModel):
def __init__(self):
self.sort_key = ()
def _try_int(self, s):
"""
Convert to integer if possible.
"""
try:
return int(s)
except:
return s.lower()
def _natsort_key(self, s):
"""
Used internally to get a tuple by which s is sorted.
"""
return map(self._try_int, re.findall(r'(\d+|\D+)', s))
# This decorator tells sqlalchemy to call this method everytime
# any data on this object is updated.
@reconstructor
def init_on_load(self):
"""
Precompute a tuple to be used for sorting.
Precompute a natural sorting, locale aware sorting key.
Song sorting is performance sensitive operation.
To get maximum speed lets precompute the string
used for comparison.
To get maximum speed lets precompute the sorting key.
"""
# Avoid the overhead of converting string to lowercase and to QString
# with every call to sort().
self.sort_key = self._natsort_key(self.title)
self.sort_key = get_natural_key(self.title)
class Topic(BaseModel):

View File

@ -43,7 +43,7 @@ from openlp.plugins.songs.forms.editsongform import EditSongForm
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songexportform import SongExportForm
from openlp.plugins.songs.lib import VerseType, clean_string, natcmp
from openlp.plugins.songs.lib import VerseType, clean_string
from openlp.plugins.songs.lib.db import Author, Song, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.xml import OpenLyrics, SongXML
@ -225,7 +225,7 @@ class SongMediaItem(MediaManagerItem):
log.debug(u'display results Song')
self.save_auto_select_id()
self.list_view.clear()
searchresults.sort(cmp=natcmp, key=lambda song: song.sort_key)
searchresults.sort(key=lambda song: song.sort_key)
for song in searchresults:
# Do not display temporary songs
if song.temporary:

View File

@ -260,6 +260,9 @@ class SongImport(QtCore.QObject):
elif int(verse_def[1:]) > self.verseCounts[verse_def[0]]:
self.verseCounts[verse_def[0]] = int(verse_def[1:])
self.verses.append([verse_def, verse_text.rstrip(), lang])
# A verse_def refers to all verses with that name, adding it once adds every instance, so do not add if already
# used.
if verse_def not in self.verseOrderListGenerated:
self.verseOrderListGenerated.append(verse_def)
def repeatVerse(self):

View File

@ -32,6 +32,7 @@ SongShow Plus songs into the OpenLP database.
"""
import os
import logging
import re
import struct
from openlp.core.ui.wizard import WizardStrings
@ -44,43 +45,36 @@ COPYRIGHT = 3
CCLI_NO = 5
VERSE = 12
CHORUS = 20
BRIDGE = 24
TOPIC = 29
COMMENTS = 30
VERSE_ORDER = 31
SONG_BOOK = 35
SONG_NUMBER = 36
CUSTOM_VERSE = 37
BRIDGE = 24
log = logging.getLogger(__name__)
class SongShowPlusImport(SongImport):
"""
The :class:`SongShowPlusImport` class provides the ability to import song
files from SongShow Plus.
The :class:`SongShowPlusImport` class provides the ability to import song files from SongShow Plus.
**SongShow Plus Song File Format:**
The SongShow Plus song file format is as follows:
* Each piece of data in the song file has some information that precedes
it.
* Each piece of data in the song file has some information that precedes it.
* The general format of this data is as follows:
4 Bytes, forming a 32 bit number, a key if you will, this describes what
the data is (see blockKey below)
4 Bytes, forming a 32 bit number, which is the number of bytes until the
next block starts
4 Bytes, forming a 32 bit number, a key if you will, this describes what the data is (see blockKey below)
4 Bytes, forming a 32 bit number, which is the number of bytes until the next block starts
1 Byte, which tells how many bytes follows
1 or 4 Bytes, describes how long the string is, if its 1 byte, the string
is less than 255
1 or 4 Bytes, describes how long the string is, if its 1 byte, the string is less than 255
The next bytes are the actual data.
The next block of data follows on.
This description does differ for verses. Which includes extra bytes
stating the verse type or number. In some cases a "custom" verse is used,
in that case, this block will in include 2 strings, with the associated
string length descriptors. The first string is the name of the verse, the
second is the verse content.
This description does differ for verses. Which includes extra bytes stating the verse type or number. In some cases
a "custom" verse is used, in that case, this block will in include 2 strings, with the associated string length
descriptors. The first string is the name of the verse, the second is the verse content.
The file is ended with four null bytes.
@ -88,8 +82,9 @@ class SongShowPlusImport(SongImport):
* .sbsong
"""
otherList = {}
otherCount = 0
other_count = 0
other_list = {}
def __init__(self, manager, **kwargs):
"""
@ -107,9 +102,9 @@ class SongShowPlusImport(SongImport):
for file in self.import_source:
if self.stop_import_flag:
return
self.sspVerseOrderList = []
other_count = 0
other_list = {}
self.ssp_verse_order_list = []
self.other_count = 0
self.other_list = {}
file_name = os.path.split(file)[1]
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % file_name, 0)
song_data = open(file, 'rb')
@ -162,34 +157,37 @@ class SongShowPlusImport(SongImport):
elif block_key == COMMENTS:
self.comments = unicode(data, u'cp1252')
elif block_key == VERSE_ORDER:
verse_tag = self.toOpenLPVerseTag(data, True)
verse_tag = self.to_openlp_verse_tag(data, True)
if verse_tag:
if not isinstance(verse_tag, unicode):
verse_tag = unicode(verse_tag, u'cp1252')
self.sspVerseOrderList.append(verse_tag)
self.ssp_verse_order_list.append(verse_tag)
elif block_key == SONG_BOOK:
self.songBookName = unicode(data, u'cp1252')
elif block_key == SONG_NUMBER:
self.songNumber = ord(data)
elif block_key == CUSTOM_VERSE:
verse_tag = self.toOpenLPVerseTag(verse_name)
verse_tag = self.to_openlp_verse_tag(verse_name)
self.addVerse(unicode(data, u'cp1252'), verse_tag)
else:
log.debug("Unrecognised blockKey: %s, data: %s" % (block_key, data))
song_data.seek(next_block_starts)
self.verseOrderList = self.sspVerseOrderList
self.verseOrderList = self.ssp_verse_order_list
song_data.close()
if not self.finish():
self.logError(file)
def toOpenLPVerseTag(self, verse_name, ignore_unique=False):
if verse_name.find(" ") != -1:
verse_parts = verse_name.split(" ")
verse_type = verse_parts[0]
verse_number = verse_parts[1]
def to_openlp_verse_tag(self, verse_name, ignore_unique=False):
# Have we got any digits? If so, verse number is everything from the digits to the end (OpenLP does not have
# concept of part verses, so just ignore any non integers on the end (including floats))
match = re.match(r'(\D*)(\d+)', verse_name)
if match:
verse_type = match.group(1).strip()
verse_number = match.group(2)
else:
# otherwise we assume number 1 and take the whole prefix as the verse tag
verse_type = verse_name
verse_number = "1"
verse_number = u'1'
verse_type = verse_type.lower()
if verse_type == "verse":
verse_tag = VerseType.tags[VerseType.Verse]
@ -200,11 +198,11 @@ class SongShowPlusImport(SongImport):
elif verse_type == "pre-chorus":
verse_tag = VerseType.tags[VerseType.PreChorus]
else:
if verse_name not in self.otherList:
if verse_name not in self.other_list:
if ignore_unique:
return None
self.otherCount += 1
self.otherList[verse_name] = str(self.otherCount)
self.other_count += 1
self.other_list[verse_name] = str(self.other_count)
verse_tag = VerseType.tags[VerseType.Other]
verse_number = self.otherList[verse_name]
verse_number = self.other_list[verse_name]
return verse_tag + verse_number

View File

@ -83,6 +83,7 @@ MODULES = [
'mako',
'migrate',
'uno',
'icu',
]

View File

@ -5,7 +5,9 @@ from unittest import TestCase
from mock import patch
from openlp.core.utils import get_filesystem_encoding, _get_frozen_path, clean_filename, split_filename
from openlp.core.utils import clean_filename, get_filesystem_encoding, _get_frozen_path, get_locale_key, \
get_natural_key, split_filename
class TestUtils(TestCase):
"""
@ -89,7 +91,6 @@ class TestUtils(TestCase):
assert result == wanted_result, \
u'A two-entry tuple with the directory and file name (empty) should have been returned.'
def clean_filename_test(self):
"""
Test the clean_filename() function
@ -103,3 +104,30 @@ class TestUtils(TestCase):
# THEN: The file name should be cleaned.
assert result == wanted_name, u'The file name should not contain any special characters.'
def get_locale_key_test(self):
"""
Test the get_locale_key(string) function
"""
with patch(u'openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = u'de'
unsorted_list = [u'Auszug', u'Aushang', u'\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == [u'Aushang', u'\u00C4u\u00DFerung', u'Auszug']
assert test_passes, u'Strings should be sorted properly'
def get_natural_key_test(self):
"""
Test the get_natural_key(string) function
"""
with patch(u'openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is English (a language, which sorts digits before letters)
mocked_get_language.return_value = u'en'
unsorted_list = [u'item 10a', u'item 3b', u'1st item']
# WHEN: We sort the list and use get_natural_key() to generate the sorting keys
# THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_natural_key) == [u'1st item', u'item 3b', u'item 10a']
assert test_passes, u'Numbers should be sorted naturally'

View File

@ -0,0 +1,235 @@
"""
This module contains tests for the SongShow Plus song importer.
"""
import os
from unittest import TestCase
from mock import patch, MagicMock
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), u'../../../resources/songshowplussongs'))
SONG_TEST_DATA = {u'Amazing Grace.sbsong':
{u'title': u'Amazing Grace (Demonstration)',
u'authors': [u'John Newton', u'Edwin Excell', u'John P. Rees'],
u'copyright': u'Public Domain ',
u'ccli_number': 22025,
u'verses':
[(u'Amazing grace! How sweet the sound!\r\nThat saved a wretch like me!\r\n'
u'I once was lost, but now am found;\r\nWas blind, but now I see.', u'v1'),
(u'\'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\n'
u'How precious did that grace appear,\r\nThe hour I first believed.', u'v2'),
(u'The Lord has promised good to me,\r\nHis Word my hope secures.\r\n'
u'He will my shield and portion be\r\nAs long as life endures.', u'v3'),
(u'Thro\' many dangers, toils and snares\r\nI have already come.\r\n'
u'\'Tis grace that brought me safe thus far,\r\nAnd grace will lead me home.', u'v4'),
(u'When we\'ve been there ten thousand years,\r\nBright shining as the sun,\r\n'
u'We\'ve no less days to sing God\'s praise,\r\nThan when we first begun.', u'v5')],
u'topics': [u'Assurance', u'Grace', u'Praise', u'Salvation'],
u'comments': u'\n\n\n',
u'song_book_name': u'Demonstration Songs',
u'song_number': 0,
u'verse_order_list': []},
u'Beautiful Garden Of Prayer.sbsong':
{u'title': u'Beautiful Garden Of Prayer (Demonstration)',
u'authors': [u'Eleanor Allen Schroll', u'James H. Fillmore'],
u'copyright': u'Public Domain ',
u'ccli_number': 60252,
u'verses':
[(u'There\'s a garden where Jesus is waiting,\r\nThere\'s a place that is wondrously fair.\r\n'
u'For it glows with the light of His presence,\r\n\'Tis the beautiful garden of prayer.', u'v1'),
(u'There\'s a garden where Jesus is waiting,\r\nAnd I go with my burden and care.\r\n'
u'Just to learn from His lips, words of comfort,\r\nIn the beautiful garden of prayer.', u'v2'),
(u'There\'s a garden where Jesus is waiting,\r\nAnd He bids you to come meet Him there,\r\n'
u'Just to bow and receive a new blessing,\r\nIn the beautiful garden of prayer.', u'v3'),
(u'O the beautiful garden, the garden of prayer,\r\nO the beautiful garden of prayer.\r\n'
u'There my Savior awaits, and He opens the gates\r\nTo the beautiful garden of prayer.', u'c1')],
u'topics': [u'Devotion', u'Prayer'],
u'comments': u'',
u'song_book_name': u'',
u'song_number': 0,
u'verse_order_list': []}}
class TestSongShowPlusImport(TestCase):
"""
Test the functions in the :mod:`songshowplusimport` module.
"""
def create_importer_test(self):
"""
Test creating an instance of the SongShow Plus file importer
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch(u'openlp.plugins.songs.lib.songshowplusimport.SongImport'):
mocked_manager = MagicMock()
# WHEN: An importer object is created
importer = SongShowPlusImport(mocked_manager)
# THEN: The importer object should not be None
self.assertIsNotNone(importer, u'Import should not be none')
def invalid_import_source_test(self):
"""
Test SongShowPlusImport.doImport handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch(u'openlp.plugins.songs.lib.songshowplusimport.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SongShowPlusImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
# WHEN: Import source is not a list
for source in [u'not a list', 0]:
importer.import_source = source
# THEN: doImport should return none and the progress bar maximum should not be set.
self.assertIsNone(importer.doImport(), u'doImport should return None when import_source is not a list')
self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False,
u'setMaxium on import_wizard.progress_bar should not have been called')
def valid_import_source_test(self):
"""
Test SongShowPlusImport.doImport handles different invalid import_source values
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch(u'openlp.plugins.songs.lib.songshowplusimport.SongImport'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = SongShowPlusImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = True
# WHEN: Import source is a list
importer.import_source = [u'List', u'of', u'files']
# THEN: doImport should return none and the progress bar setMaximum should be called with the length of
# import_source.
self.assertIsNone(importer.doImport(),
u'doImport should return None when import_source is a list and stop_import_flag is True')
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
def to_openlp_verse_tag_test(self):
"""
Test to_openlp_verse_tag method by simulating adding a verse
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch(u'openlp.plugins.songs.lib.songshowplusimport.SongImport'):
mocked_manager = MagicMock()
importer = SongShowPlusImport(mocked_manager)
# WHEN: Supplied with the following arguments replicating verses being added
test_values = [(u'Verse 1', VerseType.tags[VerseType.Verse] + u'1'),
(u'Verse 2', VerseType.tags[VerseType.Verse] + u'2'),
(u'verse1', VerseType.tags[VerseType.Verse] + u'1'),
(u'Verse', VerseType.tags[VerseType.Verse] + u'1'),
(u'Verse1', VerseType.tags[VerseType.Verse] + u'1'),
(u'chorus 1', VerseType.tags[VerseType.Chorus] + u'1'),
(u'bridge 1', VerseType.tags[VerseType.Bridge] + u'1'),
(u'pre-chorus 1', VerseType.tags[VerseType.PreChorus] + u'1'),
(u'different 1', VerseType.tags[VerseType.Other] + u'1'),
(u'random 1', VerseType.tags[VerseType.Other] + u'2')]
# THEN: The returned value should should correlate with the input arguments
for original_tag, openlp_tag in test_values:
self.assertEquals(importer.to_openlp_verse_tag(original_tag), openlp_tag,
u'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"'
% (openlp_tag, original_tag))
def to_openlp_verse_tag_verse_order_test(self):
"""
Test to_openlp_verse_tag method by simulating adding a verse to the verse order
"""
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
with patch(u'openlp.plugins.songs.lib.songshowplusimport.SongImport'):
mocked_manager = MagicMock()
importer = SongShowPlusImport(mocked_manager)
# WHEN: Supplied with the following arguments replicating a verse order being added
test_values = [(u'Verse 1', VerseType.tags[VerseType.Verse] + u'1'),
(u'Verse 2', VerseType.tags[VerseType.Verse] + u'2'),
(u'verse1', VerseType.tags[VerseType.Verse] + u'1'),
(u'Verse', VerseType.tags[VerseType.Verse] + u'1'),
(u'Verse1', VerseType.tags[VerseType.Verse] + u'1'),
(u'chorus 1', VerseType.tags[VerseType.Chorus] + u'1'),
(u'bridge 1', VerseType.tags[VerseType.Bridge] + u'1'),
(u'pre-chorus 1', VerseType.tags[VerseType.PreChorus] + u'1'),
(u'different 1', VerseType.tags[VerseType.Other] + u'1'),
(u'random 1', VerseType.tags[VerseType.Other] + u'2'),
(u'unused 2', None)]
# THEN: The returned value should should correlate with the input arguments
for original_tag, openlp_tag in test_values:
self.assertEquals(importer.to_openlp_verse_tag(original_tag, ignore_unique=True), openlp_tag,
u'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"'
% (openlp_tag, original_tag))
def file_import_test(self):
"""
Test the actual import of real song files and check that the imported data is correct.
"""
# GIVEN: Test files with a mocked out SongImport class, a mocked out "manager", a mocked out "import_wizard",
# and mocked out "author", "add_copyright", "add_verse", "finish" methods.
with patch(u'openlp.plugins.songs.lib.songshowplusimport.SongImport'):
for song_file in SONG_TEST_DATA:
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
mocked_parse_author = MagicMock()
mocked_add_copyright = MagicMock()
mocked_add_verse = MagicMock()
mocked_finish = MagicMock()
mocked_finish.return_value = True
importer = SongShowPlusImport(mocked_manager)
importer.import_wizard = mocked_import_wizard
importer.stop_import_flag = False
importer.parse_author = mocked_parse_author
importer.addCopyright = mocked_add_copyright
importer.addVerse = mocked_add_verse
importer.finish = mocked_finish
importer.topics = []
# WHEN: Importing each file
importer.import_source = [os.path.join(TEST_PATH, song_file)]
title = SONG_TEST_DATA[song_file][u'title']
author_calls = SONG_TEST_DATA[song_file][u'authors']
song_copyright = SONG_TEST_DATA[song_file][u'copyright']
ccli_number = SONG_TEST_DATA[song_file][u'ccli_number']
add_verse_calls = SONG_TEST_DATA[song_file][u'verses']
topics = SONG_TEST_DATA[song_file][u'topics']
comments = SONG_TEST_DATA[song_file][u'comments']
song_book_name = SONG_TEST_DATA[song_file][u'song_book_name']
song_number = SONG_TEST_DATA[song_file][u'song_number']
verse_order_list = SONG_TEST_DATA[song_file][u'verse_order_list']
# THEN: doImport should return none, the song data should be as expected, and finish should have been
# called.
self.assertIsNone(importer.doImport(), u'doImport should return None when it has completed')
self.assertEquals(importer.title, title, u'title for %s should be "%s"' % (song_file, title))
for author in author_calls:
mocked_parse_author.assert_any_call(author)
if song_copyright:
mocked_add_copyright.assert_called_with(song_copyright)
if ccli_number:
self.assertEquals(importer.ccliNumber, ccli_number, u'ccliNumber for %s should be %s'
% (song_file, ccli_number))
for verse_text, verse_tag in add_verse_calls:
mocked_add_verse.assert_any_call(verse_text, verse_tag)
if topics:
self.assertEquals(importer.topics, topics, u'topics for %s should be %s' % (song_file, topics))
if comments:
self.assertEquals(importer.comments, comments, u'comments for %s should be "%s"'
% (song_file, comments))
if song_book_name:
self.assertEquals(importer.songBookName, song_book_name, u'songBookName for %s should be "%s"'
% (song_file, song_book_name))
if song_number:
self.assertEquals(importer.songNumber, song_number, u'songNumber for %s should be %s'
% (song_file, song_number))
if verse_order_list:
self.assertEquals(importer.verseOrderList, [], u'verseOrderList for %s should be %s'
% (song_file, verse_order_list))
mocked_finish.assert_called_with()

View File

@ -3,11 +3,11 @@
"""
from unittest import TestCase
from mock import MagicMock, patch
from mock import MagicMock, Mock, patch
from PyQt4 import QtGui
from openlp.core.lib import Registry, ScreenList
from openlp.core.lib import Registry, ScreenList, ServiceItem
from openlp.core.ui.mainwindow import MainWindow
@ -42,3 +42,44 @@ class TestServiceManager(TestCase):
# THEN the count of items should be zero
self.assertEqual(self.service_manager.service_manager_list.topLevelItemCount(), 0,
u'The service manager list should be empty ')
def context_menu_test(self):
"""
Test the context_menu() method.
"""
# GIVEN: A service item added
with patch(u'PyQt4.QtGui.QTreeWidget.itemAt') as mocked_item_at_method, \
patch(u'PyQt4.QtGui.QWidget.mapToGlobal') as mocked_map_to_global, \
patch(u'PyQt4.QtGui.QMenu.exec_') as mocked_exec:
mocked_item = MagicMock()
mocked_item.parent.return_value = None
mocked_item_at_method.return_value = mocked_item
# We want 1 to be returned for the position
mocked_item.data.return_value = 1
# A service item without capabilities.
service_item = ServiceItem()
self.service_manager.service_items = [{u'service_item': service_item}]
q_point = None
# Mocked actions.
self.service_manager.edit_action.setVisible = Mock()
self.service_manager.create_custom_action.setVisible = Mock()
self.service_manager.maintain_action.setVisible = Mock()
self.service_manager.notes_action.setVisible = Mock()
self.service_manager.time_action.setVisible = Mock()
self.service_manager.auto_start_action.setVisible = Mock()
# WHEN: Show the context menu.
self.service_manager.context_menu(q_point)
# THEN: The following actions should be not visible.
self.service_manager.edit_action.setVisible.assert_called_once_with(False), \
u'The action should be set invisible.'
self.service_manager.create_custom_action.setVisible.assert_called_once_with(False), \
u'The action should be set invisible.'
self.service_manager.maintain_action.setVisible.assert_called_once_with(False), \
u'The action should be set invisible.'
self.service_manager.notes_action.setVisible.assert_called_with(True), u'The action should be set visible.'
self.service_manager.time_action.setVisible.assert_called_once_with(False), \
u'The action should be set invisible.'
self.service_manager.auto_start_action.setVisible.assert_called_once_with(False), \
u'The action should be set invisible.'

Binary file not shown.