Status: | Merged |
---|---|
Merged at revision: | 2889 |
Proposed branch: | lp:~phill-ridout/openlp/fixes-IV |
Merge into: | lp:openlp |
Diff against target: |
852 lines (+172/-161) 25 files modified
openlp/core/common/__init__.py (+16/-1) openlp/core/common/actions.py (+1/-1) openlp/core/common/i18n.py (+4/-14) openlp/core/common/registry.py (+2/-23) openlp/core/display/html/display.js (+29/-22) openlp/core/display/render.py (+1/-1) openlp/core/display/screens.py (+2/-10) openlp/core/lib/theme.py (+1/-0) openlp/core/state.py (+2/-11) openlp/core/ui/advancedtab.py (+1/-1) openlp/core/ui/icons.py (+4/-14) openlp/plugins/bibles/lib/__init__.py (+23/-29) openlp/plugins/bibles/lib/db.py (+22/-25) openlp/plugins/bibles/lib/manager.py (+4/-2) openlp/plugins/custom/forms/editcustomdialog.py (+2/-0) openlp/plugins/songs/forms/editsongdialog.py (+2/-0) openlp/plugins/songs/lib/db.py (+3/-1) openlp/plugins/songs/lib/importers/cclifile.py (+2/-0) openlp/plugins/songs/lib/importers/dreambeam.py (+2/-1) openlp/plugins/songs/lib/importers/easyslides.py (+2/-0) openlp/plugins/songs/lib/importers/easyworship.py (+1/-1) openlp/plugins/songs/lib/importers/songbeamer.py (+1/-1) tests/functional/openlp_core/common/test_common.py (+43/-1) tests/functional/openlp_core/lib/test_theme.py (+1/-1) tests/functional/openlp_core/ui/test_icons.py (+1/-1) |
To merge this branch: | bzr merge lp:~phill-ridout/openlp/fixes-IV |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Review via email: mp+371023@code.launchpad.net |
Commit message
Refactors and fixes
Description of the change
Refactor `singleton` classes to use a `singleton` metaclass
Fixes for a number of reported issues
Tidy ups, and improvements as suggested by pycharm
To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
Linting passed!
Revision history for this message
Tim Bentley (trb143) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openlp/core/common/__init__.py' | |||
2 | --- openlp/core/common/__init__.py 2019-07-19 18:43:14 +0000 | |||
3 | +++ openlp/core/common/__init__.py 2019-08-06 21:48:11 +0000 | |||
4 | @@ -172,6 +172,21 @@ | |||
5 | 172 | Next = 3 | 172 | Next = 3 |
6 | 173 | 173 | ||
7 | 174 | 174 | ||
8 | 175 | class Singleton(type): | ||
9 | 176 | """ | ||
10 | 177 | Provide a `Singleton` metaclass https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python | ||
11 | 178 | """ | ||
12 | 179 | _instances = {} | ||
13 | 180 | |||
14 | 181 | def __call__(cls, *args, **kwargs): | ||
15 | 182 | """ | ||
16 | 183 | Create a new instance if one does not already exist. | ||
17 | 184 | """ | ||
18 | 185 | if cls not in cls._instances: | ||
19 | 186 | cls._instances[cls] = super().__call__(*args, **kwargs) | ||
20 | 187 | return cls._instances[cls] | ||
21 | 188 | |||
22 | 189 | |||
23 | 175 | def de_hump(name): | 190 | def de_hump(name): |
24 | 176 | """ | 191 | """ |
25 | 177 | Change any Camel Case string to python string | 192 | Change any Camel Case string to python string |
26 | @@ -385,7 +400,7 @@ | |||
27 | 385 | global IMAGES_FILTER | 400 | global IMAGES_FILTER |
28 | 386 | if not IMAGES_FILTER: | 401 | if not IMAGES_FILTER: |
29 | 387 | log.debug('Generating images filter.') | 402 | log.debug('Generating images filter.') |
31 | 388 | formats = list(map(bytes.decode, list(map(bytes, QtGui.QImageReader.supportedImageFormats())))) | 403 | formats = list(map(bytes.decode, map(bytes, QtGui.QImageReader.supportedImageFormats()))) |
32 | 389 | visible_formats = '(*.{text})'.format(text='; *.'.join(formats)) | 404 | visible_formats = '(*.{text})'.format(text='; *.'.join(formats)) |
33 | 390 | actual_formats = '(*.{text})'.format(text=' *.'.join(formats)) | 405 | actual_formats = '(*.{text})'.format(text=' *.'.join(formats)) |
34 | 391 | IMAGES_FILTER = '{text} {visible} {actual}'.format(text=translate('OpenLP', 'Image Files'), | 406 | IMAGES_FILTER = '{text} {visible} {actual}'.format(text=translate('OpenLP', 'Image Files'), |
35 | 392 | 407 | ||
36 | === modified file 'openlp/core/common/actions.py' | |||
37 | --- openlp/core/common/actions.py 2019-04-13 13:00:22 +0000 | |||
38 | +++ openlp/core/common/actions.py 2019-08-06 21:48:11 +0000 | |||
39 | @@ -260,7 +260,7 @@ | |||
40 | 260 | return | 260 | return |
41 | 261 | # We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O, | 261 | # We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O, |
42 | 262 | # which is only done when we convert the strings in this way (QKeySequencet -> uncode). | 262 | # which is only done when we convert the strings in this way (QKeySequencet -> uncode). |
44 | 263 | shortcuts = list(map(QtGui.QKeySequence.toString, list(map(QtGui.QKeySequence, shortcuts)))) | 263 | shortcuts = list(map(QtGui.QKeySequence.toString, map(QtGui.QKeySequence, shortcuts))) |
45 | 264 | # Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut | 264 | # Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut |
46 | 265 | # after removing the (initial) primary shortcut due to conflicts. | 265 | # after removing the (initial) primary shortcut due to conflicts. |
47 | 266 | if len(shortcuts) == 2: | 266 | if len(shortcuts) == 2: |
48 | 267 | 267 | ||
49 | === modified file 'openlp/core/common/i18n.py' | |||
50 | --- openlp/core/common/i18n.py 2019-07-03 13:23:23 +0000 | |||
51 | +++ openlp/core/common/i18n.py 2019-08-06 21:48:11 +0000 | |||
52 | @@ -29,7 +29,7 @@ | |||
53 | 29 | 29 | ||
54 | 30 | from PyQt5 import QtCore, QtWidgets | 30 | from PyQt5 import QtCore, QtWidgets |
55 | 31 | 31 | ||
57 | 32 | from openlp.core.common import is_macosx, is_win | 32 | from openlp.core.common import Singleton, is_macosx, is_win |
58 | 33 | from openlp.core.common.applocation import AppLocation | 33 | from openlp.core.common.applocation import AppLocation |
59 | 34 | from openlp.core.common.settings import Settings | 34 | from openlp.core.common.settings import Settings |
60 | 35 | 35 | ||
61 | @@ -327,22 +327,11 @@ | |||
62 | 327 | return LanguageManager.__qm_list__ | 327 | return LanguageManager.__qm_list__ |
63 | 328 | 328 | ||
64 | 329 | 329 | ||
66 | 330 | class UiStrings(object): | 330 | class UiStrings(metaclass=Singleton): |
67 | 331 | """ | 331 | """ |
68 | 332 | Provide standard strings for objects to use. | 332 | Provide standard strings for objects to use. |
69 | 333 | """ | 333 | """ |
82 | 334 | __instance__ = None | 334 | def __init__(self): |
71 | 335 | |||
72 | 336 | def __new__(cls): | ||
73 | 337 | """ | ||
74 | 338 | Override the default object creation method to return a single instance. | ||
75 | 339 | """ | ||
76 | 340 | if not cls.__instance__: | ||
77 | 341 | cls.__instance__ = super().__new__(cls) | ||
78 | 342 | cls.__instance__.load() | ||
79 | 343 | return cls.__instance__ | ||
80 | 344 | |||
81 | 345 | def load(self): | ||
83 | 346 | """ | 335 | """ |
84 | 347 | These strings should need a good reason to be retranslated elsewhere. | 336 | These strings should need a good reason to be retranslated elsewhere. |
85 | 348 | Should some/more/less of these have an & attached? | 337 | Should some/more/less of these have an & attached? |
86 | @@ -436,6 +425,7 @@ | |||
87 | 436 | self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.') | 425 | self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.') |
88 | 437 | self.RequiredShowInFooter = translate('OpenLP.Ui', 'Required, this will be displayed in footer.') | 426 | self.RequiredShowInFooter = translate('OpenLP.Ui', 'Required, this will be displayed in footer.') |
89 | 438 | self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds') | 427 | self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds') |
90 | 428 | self.SaveAndClose = translate('OpenLP.ui', translate('SongsPlugin.EditSongForm', '&Save && Close')) | ||
91 | 439 | self.SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview') | 429 | self.SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview') |
92 | 440 | self.Search = translate('OpenLP.Ui', 'Search') | 430 | self.Search = translate('OpenLP.Ui', 'Search') |
93 | 441 | self.SearchThemes = translate('OpenLP.Ui', 'Search Themes...', 'Search bar place holder text ') | 431 | self.SearchThemes = translate('OpenLP.Ui', 'Search Themes...', 'Search bar place holder text ') |
94 | 442 | 432 | ||
95 | === modified file 'openlp/core/common/registry.py' | |||
96 | --- openlp/core/common/registry.py 2019-05-05 18:41:17 +0000 | |||
97 | +++ openlp/core/common/registry.py 2019-08-06 21:48:11 +0000 | |||
98 | @@ -23,29 +23,19 @@ | |||
99 | 23 | Provide Registry Services | 23 | Provide Registry Services |
100 | 24 | """ | 24 | """ |
101 | 25 | import logging | 25 | import logging |
102 | 26 | import sys | ||
103 | 27 | 26 | ||
105 | 28 | from openlp.core.common import de_hump, trace_error_handler | 27 | from openlp.core.common import Singleton, de_hump, trace_error_handler |
106 | 29 | 28 | ||
107 | 30 | 29 | ||
108 | 31 | log = logging.getLogger(__name__) | 30 | log = logging.getLogger(__name__) |
109 | 32 | 31 | ||
110 | 33 | 32 | ||
112 | 34 | class Registry(object): | 33 | class Registry(metaclass=Singleton): |
113 | 35 | """ | 34 | """ |
114 | 36 | This is the Component Registry. It is a singleton object and is used to provide a look up service for common | 35 | This is the Component Registry. It is a singleton object and is used to provide a look up service for common |
115 | 37 | objects. | 36 | objects. |
116 | 38 | """ | 37 | """ |
117 | 39 | log.info('Registry loaded') | 38 | log.info('Registry loaded') |
118 | 40 | __instance__ = None | ||
119 | 41 | |||
120 | 42 | def __new__(cls): | ||
121 | 43 | """ | ||
122 | 44 | Re-implement the __new__ method to make sure we create a true singleton. | ||
123 | 45 | """ | ||
124 | 46 | if not cls.__instance__: | ||
125 | 47 | cls.__instance__ = object.__new__(cls) | ||
126 | 48 | return cls.__instance__ | ||
127 | 49 | 39 | ||
128 | 50 | @classmethod | 40 | @classmethod |
129 | 51 | def create(cls): | 41 | def create(cls): |
130 | @@ -57,20 +47,9 @@ | |||
131 | 57 | registry.service_list = {} | 47 | registry.service_list = {} |
132 | 58 | registry.functions_list = {} | 48 | registry.functions_list = {} |
133 | 59 | registry.working_flags = {} | 49 | registry.working_flags = {} |
134 | 60 | # Allow the tests to remove Registry entries but not the live system | ||
135 | 61 | registry.running_under_test = 'nose' in sys.argv[0] or 'pytest' in sys.argv[0] | ||
136 | 62 | registry.initialising = True | 50 | registry.initialising = True |
137 | 63 | return registry | 51 | return registry |
138 | 64 | 52 | ||
139 | 65 | @classmethod | ||
140 | 66 | def destroy(cls): | ||
141 | 67 | """ | ||
142 | 68 | Destroy the Registry. | ||
143 | 69 | """ | ||
144 | 70 | if cls.__instance__.running_under_test: | ||
145 | 71 | del cls.__instance__ | ||
146 | 72 | cls.__instance__ = None | ||
147 | 73 | |||
148 | 74 | def get(self, key): | 53 | def get(self, key): |
149 | 75 | """ | 54 | """ |
150 | 76 | Extracts the registry value from the list based on the key passed in | 55 | Extracts the registry value from the list based on the key passed in |
151 | 77 | 56 | ||
152 | === modified file 'openlp/core/display/html/display.js' | |||
153 | --- openlp/core/display/html/display.js 2019-06-21 20:53:42 +0000 | |||
154 | +++ openlp/core/display/html/display.js 2019-08-06 21:48:11 +0000 | |||
155 | @@ -281,8 +281,9 @@ | |||
156 | 281 | * Checks if the present slide content fits within the slide | 281 | * Checks if the present slide content fits within the slide |
157 | 282 | */ | 282 | */ |
158 | 283 | doesContentFit: function () { | 283 | doesContentFit: function () { |
161 | 284 | console.debug("scrollHeight: " + $(".slides")[0].scrollHeight + ", clientHeight: " + $(".slides")[0].clientHeight); | 284 | var currSlide = $(".slides")[0]; |
162 | 285 | return $(".slides")[0].clientHeight >= $(".slides")[0].scrollHeight; | 285 | console.debug("scrollHeight: " + currSlide.scrollHeight + ", clientHeight: " + currSlide.clientHeight); |
163 | 286 | return currSlide.clientHeight >= currSlide.scrollHeight; | ||
164 | 286 | }, | 287 | }, |
165 | 287 | /** | 288 | /** |
166 | 288 | * Generate the OpenLP startup splashscreen | 289 | * Generate the OpenLP startup splashscreen |
167 | @@ -333,7 +334,7 @@ | |||
168 | 333 | /** | 334 | /** |
169 | 334 | * Set fullscreen image from base64 data | 335 | * Set fullscreen image from base64 data |
170 | 335 | * @param {string} bg_color - The background color | 336 | * @param {string} bg_color - The background color |
172 | 336 | * @param {string} image - Path to the image | 337 | * @param {string} image_data - base64 encoded image data |
173 | 337 | */ | 338 | */ |
174 | 338 | setFullscreenImageFromData: function(bg_color, image_data) { | 339 | setFullscreenImageFromData: function(bg_color, image_data) { |
175 | 339 | Display.clearSlides(); | 340 | Display.clearSlides(); |
176 | @@ -372,7 +373,6 @@ | |||
177 | 372 | * @param {string} verse - The verse number, e.g. "v1" | 373 | * @param {string} verse - The verse number, e.g. "v1" |
178 | 373 | * @param {string} text - The HTML for the verse, e.g. "line1<br>line2" | 374 | * @param {string} text - The HTML for the verse, e.g. "line1<br>line2" |
179 | 374 | * @param {string} footer_text - The HTML for the footer" | 375 | * @param {string} footer_text - The HTML for the footer" |
180 | 375 | * @param {bool} [reinit=true] - Re-initialize Reveal. Defaults to true. | ||
181 | 376 | */ | 376 | */ |
182 | 377 | addTextSlide: function (verse, text, footer_text) { | 377 | addTextSlide: function (verse, text, footer_text) { |
183 | 378 | var html = _prepareText(text); | 378 | var html = _prepareText(text); |
184 | @@ -476,25 +476,28 @@ | |||
185 | 476 | * Play a video | 476 | * Play a video |
186 | 477 | */ | 477 | */ |
187 | 478 | playVideo: function () { | 478 | playVideo: function () { |
190 | 479 | if ($("#video").length == 1) { | 479 | var videoElem = $("#video"); |
191 | 480 | $("#video")[0].play(); | 480 | if (videoElem.length == 1) { |
192 | 481 | videoElem[0].play(); | ||
193 | 481 | } | 482 | } |
194 | 482 | }, | 483 | }, |
195 | 483 | /** | 484 | /** |
196 | 484 | * Pause a video | 485 | * Pause a video |
197 | 485 | */ | 486 | */ |
198 | 486 | pauseVideo: function () { | 487 | pauseVideo: function () { |
201 | 487 | if ($("#video").length == 1) { | 488 | var videoElem = $("#video"); |
202 | 488 | $("#video")[0].pause(); | 489 | if (videoElem.length == 1) { |
203 | 490 | videoElem[0].pause(); | ||
204 | 489 | } | 491 | } |
205 | 490 | }, | 492 | }, |
206 | 491 | /** | 493 | /** |
207 | 492 | * Stop a video | 494 | * Stop a video |
208 | 493 | */ | 495 | */ |
209 | 494 | stopVideo: function () { | 496 | stopVideo: function () { |
213 | 495 | if ($("#video").length == 1) { | 497 | var videoElem = $("#video"); |
214 | 496 | $("#video")[0].pause(); | 498 | if (videoElem.length == 1) { |
215 | 497 | $("#video")[0].currentTime = 0.0; | 499 | videoElem[0].pause(); |
216 | 500 | videoElem[0].currentTime = 0.0; | ||
217 | 498 | } | 501 | } |
218 | 499 | }, | 502 | }, |
219 | 500 | /** | 503 | /** |
220 | @@ -502,8 +505,9 @@ | |||
221 | 502 | * @param seconds The position in seconds to seek to | 505 | * @param seconds The position in seconds to seek to |
222 | 503 | */ | 506 | */ |
223 | 504 | seekVideo: function (seconds) { | 507 | seekVideo: function (seconds) { |
226 | 505 | if ($("#video").length == 1) { | 508 | var videoElem = $("#video"); |
227 | 506 | $("#video")[0].currentTime = seconds; | 509 | if (videoElem.length == 1) { |
228 | 510 | videoElem[0].currentTime = seconds; | ||
229 | 507 | } | 511 | } |
230 | 508 | }, | 512 | }, |
231 | 509 | /** | 513 | /** |
232 | @@ -511,8 +515,9 @@ | |||
233 | 511 | * @param rate A Double of the rate. 1.0 => 100% speed, 0.75 => 75% speed, 1.25 => 125% speed, etc. | 515 | * @param rate A Double of the rate. 1.0 => 100% speed, 0.75 => 75% speed, 1.25 => 125% speed, etc. |
234 | 512 | */ | 516 | */ |
235 | 513 | setPlaybackRate: function (rate) { | 517 | setPlaybackRate: function (rate) { |
238 | 514 | if ($("#video").length == 1) { | 518 | var videoElem = $("#video"); |
239 | 515 | $("#video")[0].playbackRate = rate; | 519 | if (videoElem.length == 1) { |
240 | 520 | videoElem[0].playbackRate = rate; | ||
241 | 516 | } | 521 | } |
242 | 517 | }, | 522 | }, |
243 | 518 | /** | 523 | /** |
244 | @@ -520,24 +525,27 @@ | |||
245 | 520 | * @param level The volume level from 0 to 100. | 525 | * @param level The volume level from 0 to 100. |
246 | 521 | */ | 526 | */ |
247 | 522 | setVideoVolume: function (level) { | 527 | setVideoVolume: function (level) { |
250 | 523 | if ($("#video").length == 1) { | 528 | var videoElem = $("#video"); |
251 | 524 | $("#video")[0].volume = level / 100.0; | 529 | if (videoElem.length == 1) { |
252 | 530 | videoElem[0].volume = level / 100.0; | ||
253 | 525 | } | 531 | } |
254 | 526 | }, | 532 | }, |
255 | 527 | /** | 533 | /** |
256 | 528 | * Mute the volume | 534 | * Mute the volume |
257 | 529 | */ | 535 | */ |
258 | 530 | toggleVideoMute: function () { | 536 | toggleVideoMute: function () { |
261 | 531 | if ($("#video").length == 1) { | 537 | var videoElem = $("#video"); |
262 | 532 | $("#video")[0].muted = !$("#video")[0].muted; | 538 | if (videoElem.length == 1) { |
263 | 539 | videoElem[0].muted = !videoElem[0].muted; | ||
264 | 533 | } | 540 | } |
265 | 534 | }, | 541 | }, |
266 | 535 | /** | 542 | /** |
267 | 536 | * Clear the background audio playlist | 543 | * Clear the background audio playlist |
268 | 537 | */ | 544 | */ |
269 | 538 | clearPlaylist: function () { | 545 | clearPlaylist: function () { |
272 | 539 | if ($("#background-audio").length == 1) { | 546 | var backgroundAudoElem = $("#background-audio"); |
273 | 540 | var audio = $("#background-audio")[0]; | 547 | if (backgroundAudoElem.length == 1) { |
274 | 548 | var audio = backgroundAudoElem[0]; | ||
275 | 541 | /* audio.playList */ | 549 | /* audio.playList */ |
276 | 542 | } | 550 | } |
277 | 543 | }, | 551 | }, |
278 | @@ -619,7 +627,6 @@ | |||
279 | 619 | }, | 627 | }, |
280 | 620 | setTheme: function (theme) { | 628 | setTheme: function (theme) { |
281 | 621 | this._theme = theme; | 629 | this._theme = theme; |
282 | 622 | var slidesDiv = $(".slides") | ||
283 | 623 | // Set the background | 630 | // Set the background |
284 | 624 | var globalBackground = $("#global-background")[0]; | 631 | var globalBackground = $("#global-background")[0]; |
285 | 625 | var backgroundStyle = {}; | 632 | var backgroundStyle = {}; |
286 | 626 | 633 | ||
287 | === modified file 'openlp/core/display/render.py' | |||
288 | --- openlp/core/display/render.py 2019-07-28 15:56:28 +0000 | |||
289 | +++ openlp/core/display/render.py 2019-08-06 21:48:11 +0000 | |||
290 | @@ -47,7 +47,7 @@ | |||
291 | 47 | 47 | ||
292 | 48 | SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' | 48 | SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\' |
293 | 49 | CHORD_LINE_MATCH = re.compile(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)' | 49 | CHORD_LINE_MATCH = re.compile(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)' |
295 | 50 | r'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?') | 50 | r'([\u0080-\uFFFF\w\s\.\,\!\?\;\:\|\"\'\-\_]*)(\Z)?') |
296 | 51 | CHORD_TEMPLATE = '<span class="chordline">{chord}</span>' | 51 | CHORD_TEMPLATE = '<span class="chordline">{chord}</span>' |
297 | 52 | FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>' | 52 | FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>' |
298 | 53 | CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}' | 53 | CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}' |
299 | 54 | 54 | ||
300 | === modified file 'openlp/core/display/screens.py' | |||
301 | --- openlp/core/display/screens.py 2019-08-04 14:06:00 +0000 | |||
302 | +++ openlp/core/display/screens.py 2019-08-06 21:48:11 +0000 | |||
303 | @@ -28,6 +28,7 @@ | |||
304 | 28 | 28 | ||
305 | 29 | from PyQt5 import QtCore, QtWidgets | 29 | from PyQt5 import QtCore, QtWidgets |
306 | 30 | 30 | ||
307 | 31 | from openlp.core.common import Singleton | ||
308 | 31 | from openlp.core.common.i18n import translate | 32 | from openlp.core.common.i18n import translate |
309 | 32 | from openlp.core.common.registry import Registry | 33 | from openlp.core.common.registry import Registry |
310 | 33 | from openlp.core.common.settings import Settings | 34 | from openlp.core.common.settings import Settings |
311 | @@ -147,24 +148,15 @@ | |||
312 | 147 | screen_dict['custom_geometry']['height']) | 148 | screen_dict['custom_geometry']['height']) |
313 | 148 | 149 | ||
314 | 149 | 150 | ||
316 | 150 | class ScreenList(object): | 151 | class ScreenList(metaclass=Singleton): |
317 | 151 | """ | 152 | """ |
318 | 152 | Wrapper to handle the parameters of the display screen. | 153 | Wrapper to handle the parameters of the display screen. |
319 | 153 | 154 | ||
320 | 154 | To get access to the screen list call ``ScreenList()``. | 155 | To get access to the screen list call ``ScreenList()``. |
321 | 155 | """ | 156 | """ |
322 | 156 | log.info('Screen loaded') | 157 | log.info('Screen loaded') |
323 | 157 | __instance__ = None | ||
324 | 158 | screens = [] | 158 | screens = [] |
325 | 159 | 159 | ||
326 | 160 | def __new__(cls): | ||
327 | 161 | """ | ||
328 | 162 | Re-implement __new__ to create a true singleton. | ||
329 | 163 | """ | ||
330 | 164 | if not cls.__instance__: | ||
331 | 165 | cls.__instance__ = object.__new__(cls) | ||
332 | 166 | return cls.__instance__ | ||
333 | 167 | |||
334 | 168 | def __iter__(self): | 160 | def __iter__(self): |
335 | 169 | """ | 161 | """ |
336 | 170 | Convert this object into an iterable, so that we can iterate over it instead of the inner list | 162 | Convert this object into an iterable, so that we can iterate over it instead of the inner list |
337 | 171 | 163 | ||
338 | === modified file 'openlp/core/lib/theme.py' | |||
339 | --- openlp/core/lib/theme.py 2019-06-21 22:09:36 +0000 | |||
340 | +++ openlp/core/lib/theme.py 2019-08-06 21:48:11 +0000 | |||
341 | @@ -170,6 +170,7 @@ | |||
342 | 170 | jsn = get_text_file_string(json_path) | 170 | jsn = get_text_file_string(json_path) |
343 | 171 | self.load_theme(jsn) | 171 | self.load_theme(jsn) |
344 | 172 | self.background_filename = None | 172 | self.background_filename = None |
345 | 173 | self.version = 2 | ||
346 | 173 | 174 | ||
347 | 174 | def expand_json(self, var, prev=None): | 175 | def expand_json(self, var, prev=None): |
348 | 175 | """ | 176 | """ |
349 | 176 | 177 | ||
350 | === modified file 'openlp/core/state.py' | |||
351 | --- openlp/core/state.py 2019-04-13 13:00:22 +0000 | |||
352 | +++ openlp/core/state.py 2019-08-06 21:48:11 +0000 | |||
353 | @@ -28,6 +28,7 @@ | |||
354 | 28 | """ | 28 | """ |
355 | 29 | import logging | 29 | import logging |
356 | 30 | 30 | ||
357 | 31 | from openlp.core.common import Singleton | ||
358 | 31 | from openlp.core.common.registry import Registry | 32 | from openlp.core.common.registry import Registry |
359 | 32 | from openlp.core.common.mixins import LogMixin | 33 | from openlp.core.common.mixins import LogMixin |
360 | 33 | from openlp.core.lib.plugin import PluginStatus | 34 | from openlp.core.lib.plugin import PluginStatus |
361 | @@ -52,17 +53,7 @@ | |||
362 | 52 | self.text = None | 53 | self.text = None |
363 | 53 | 54 | ||
364 | 54 | 55 | ||
376 | 55 | class State(LogMixin): | 56 | class State(LogMixin, metaclass=Singleton): |
366 | 56 | |||
367 | 57 | __instance__ = None | ||
368 | 58 | |||
369 | 59 | def __new__(cls): | ||
370 | 60 | """ | ||
371 | 61 | Re-implement the __new__ method to make sure we create a true singleton. | ||
372 | 62 | """ | ||
373 | 63 | if not cls.__instance__: | ||
374 | 64 | cls.__instance__ = object.__new__(cls) | ||
375 | 65 | return cls.__instance__ | ||
377 | 66 | 57 | ||
378 | 67 | def load_settings(self): | 58 | def load_settings(self): |
379 | 68 | self.modules = {} | 59 | self.modules = {} |
380 | 69 | 60 | ||
381 | === modified file 'openlp/core/ui/advancedtab.py' | |||
382 | --- openlp/core/ui/advancedtab.py 2019-05-22 06:47:00 +0000 | |||
383 | +++ openlp/core/ui/advancedtab.py 2019-08-06 21:48:11 +0000 | |||
384 | @@ -81,7 +81,7 @@ | |||
385 | 81 | self.ui_layout.addRow(self.media_plugin_check_box) | 81 | self.ui_layout.addRow(self.media_plugin_check_box) |
386 | 82 | self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box) | 82 | self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box) |
387 | 83 | self.hide_mouse_check_box.setObjectName('hide_mouse_check_box') | 83 | self.hide_mouse_check_box.setObjectName('hide_mouse_check_box') |
389 | 84 | self.ui_layout.addWidget(self.hide_mouse_check_box) | 84 | self.ui_layout.addRow(self.hide_mouse_check_box) |
390 | 85 | self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box) | 85 | self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box) |
391 | 86 | self.double_click_live_check_box.setObjectName('double_click_live_check_box') | 86 | self.double_click_live_check_box.setObjectName('double_click_live_check_box') |
392 | 87 | self.ui_layout.addRow(self.double_click_live_check_box) | 87 | self.ui_layout.addRow(self.double_click_live_check_box) |
393 | 88 | 88 | ||
394 | === modified file 'openlp/core/ui/icons.py' | |||
395 | --- openlp/core/ui/icons.py 2019-07-03 13:23:23 +0000 | |||
396 | +++ openlp/core/ui/icons.py 2019-08-06 21:48:11 +0000 | |||
397 | @@ -27,6 +27,7 @@ | |||
398 | 27 | import qtawesome as qta | 27 | import qtawesome as qta |
399 | 28 | from PyQt5 import QtGui, QtWidgets | 28 | from PyQt5 import QtGui, QtWidgets |
400 | 29 | 29 | ||
401 | 30 | from openlp.core.common import Singleton | ||
402 | 30 | from openlp.core.common.applocation import AppLocation | 31 | from openlp.core.common.applocation import AppLocation |
403 | 31 | from openlp.core.lib import build_icon | 32 | from openlp.core.lib import build_icon |
404 | 32 | 33 | ||
405 | @@ -34,22 +35,11 @@ | |||
406 | 34 | log = logging.getLogger(__name__) | 35 | log = logging.getLogger(__name__) |
407 | 35 | 36 | ||
408 | 36 | 37 | ||
410 | 37 | class UiIcons(object): | 38 | class UiIcons(metaclass=Singleton): |
411 | 38 | """ | 39 | """ |
412 | 39 | Provide standard icons for objects to use. | 40 | Provide standard icons for objects to use. |
413 | 40 | """ | 41 | """ |
426 | 41 | __instance__ = None | 42 | def __init__(self): |
415 | 42 | |||
416 | 43 | def __new__(cls): | ||
417 | 44 | """ | ||
418 | 45 | Override the default object creation method to return a single instance. | ||
419 | 46 | """ | ||
420 | 47 | if not cls.__instance__: | ||
421 | 48 | cls.__instance__ = super().__new__(cls) | ||
422 | 49 | cls.__instance__.load() | ||
423 | 50 | return cls.__instance__ | ||
424 | 51 | |||
425 | 52 | def load(self): | ||
427 | 53 | """ | 43 | """ |
428 | 54 | These are the font icons used in the code. | 44 | These are the font icons used in the code. |
429 | 55 | """ | 45 | """ |
430 | @@ -165,6 +155,7 @@ | |||
431 | 165 | 'volunteer': {'icon': 'fa.group'} | 155 | 'volunteer': {'icon': 'fa.group'} |
432 | 166 | } | 156 | } |
433 | 167 | self.load_icons(icon_list) | 157 | self.load_icons(icon_list) |
434 | 158 | self.main_icon = build_icon(':/icon/openlp-logo.svg') | ||
435 | 168 | 159 | ||
436 | 169 | def load_icons(self, icon_list): | 160 | def load_icons(self, icon_list): |
437 | 170 | """ | 161 | """ |
438 | @@ -184,7 +175,6 @@ | |||
439 | 184 | setattr(self, key, qta.icon('fa.plus-circle', color='red')) | 175 | setattr(self, key, qta.icon('fa.plus-circle', color='red')) |
440 | 185 | except Exception: | 176 | except Exception: |
441 | 186 | setattr(self, key, qta.icon('fa.plus-circle', color='red')) | 177 | setattr(self, key, qta.icon('fa.plus-circle', color='red')) |
442 | 187 | self.main_icon = build_icon(':/icon/openlp-logo.svg') | ||
443 | 188 | 178 | ||
444 | 189 | @staticmethod | 179 | @staticmethod |
445 | 190 | def _print_icons(): | 180 | def _print_icons(): |
446 | 191 | 181 | ||
447 | === modified file 'openlp/plugins/bibles/lib/__init__.py' | |||
448 | --- openlp/plugins/bibles/lib/__init__.py 2019-04-13 13:00:22 +0000 | |||
449 | +++ openlp/plugins/bibles/lib/__init__.py 2019-08-06 21:48:11 +0000 | |||
450 | @@ -26,6 +26,7 @@ | |||
451 | 26 | import logging | 26 | import logging |
452 | 27 | import re | 27 | import re |
453 | 28 | 28 | ||
454 | 29 | from openlp.core.common import Singleton | ||
455 | 29 | from openlp.core.common.i18n import translate | 30 | from openlp.core.common.i18n import translate |
456 | 30 | from openlp.core.common.settings import Settings | 31 | from openlp.core.common.settings import Settings |
457 | 31 | 32 | ||
458 | @@ -64,20 +65,10 @@ | |||
459 | 64 | English = 2 | 65 | English = 2 |
460 | 65 | 66 | ||
461 | 66 | 67 | ||
463 | 67 | class BibleStrings(object): | 68 | class BibleStrings(metaclass=Singleton): |
464 | 68 | """ | 69 | """ |
465 | 69 | Provide standard strings for objects to use. | 70 | Provide standard strings for objects to use. |
466 | 70 | """ | 71 | """ |
467 | 71 | __instance__ = None | ||
468 | 72 | |||
469 | 73 | def __new__(cls): | ||
470 | 74 | """ | ||
471 | 75 | Override the default object creation method to return a single instance. | ||
472 | 76 | """ | ||
473 | 77 | if not cls.__instance__: | ||
474 | 78 | cls.__instance__ = object.__new__(cls) | ||
475 | 79 | return cls.__instance__ | ||
476 | 80 | |||
477 | 81 | def __init__(self): | 72 | def __init__(self): |
478 | 82 | """ | 73 | """ |
479 | 83 | These strings should need a good reason to be retranslated elsewhere. | 74 | These strings should need a good reason to be retranslated elsewhere. |
480 | @@ -336,11 +327,13 @@ | |||
481 | 336 | log.debug('Matched reference {text}'.format(text=reference)) | 327 | log.debug('Matched reference {text}'.format(text=reference)) |
482 | 337 | book = match.group('book') | 328 | book = match.group('book') |
483 | 338 | if not book_ref_id: | 329 | if not book_ref_id: |
485 | 339 | book_ref_id = bible.get_book_ref_id_by_localised_name(book, language_selection) | 330 | book_ref_ids = bible.get_book_ref_id_by_localised_name(book, language_selection) |
486 | 340 | elif not bible.get_book_by_book_ref_id(book_ref_id): | 331 | elif not bible.get_book_by_book_ref_id(book_ref_id): |
487 | 341 | return [] | 332 | return [] |
488 | 333 | else: | ||
489 | 334 | book_ref_ids = [book_ref_id] | ||
490 | 342 | # We have not found the book so do not continue | 335 | # We have not found the book so do not continue |
492 | 343 | if not book_ref_id: | 336 | if not book_ref_ids: |
493 | 344 | return [] | 337 | return [] |
494 | 345 | ranges = match.group('ranges') | 338 | ranges = match.group('ranges') |
495 | 346 | range_list = get_reference_match('range_separator').split(ranges) | 339 | range_list = get_reference_match('range_separator').split(ranges) |
496 | @@ -381,22 +374,23 @@ | |||
497 | 381 | to_chapter = to_verse | 374 | to_chapter = to_verse |
498 | 382 | to_verse = None | 375 | to_verse = None |
499 | 383 | # Append references to the list | 376 | # Append references to the list |
516 | 384 | if has_range: | 377 | for book_ref_id in book_ref_ids: |
517 | 385 | if not from_verse: | 378 | if has_range: |
518 | 386 | from_verse = 1 | 379 | if not from_verse: |
519 | 387 | if not to_verse: | 380 | from_verse = 1 |
520 | 388 | to_verse = -1 | 381 | if not to_verse: |
521 | 389 | if to_chapter and to_chapter > from_chapter: | 382 | to_verse = -1 |
522 | 390 | ref_list.append((book_ref_id, from_chapter, from_verse, -1)) | 383 | if to_chapter and to_chapter > from_chapter: |
523 | 391 | for i in range(from_chapter + 1, to_chapter): | 384 | ref_list.append((book_ref_id, from_chapter, from_verse, -1)) |
524 | 392 | ref_list.append((book_ref_id, i, 1, -1)) | 385 | for i in range(from_chapter + 1, to_chapter): |
525 | 393 | ref_list.append((book_ref_id, to_chapter, 1, to_verse)) | 386 | ref_list.append((book_ref_id, i, 1, -1)) |
526 | 394 | elif to_verse >= from_verse or to_verse == -1: | 387 | ref_list.append((book_ref_id, to_chapter, 1, to_verse)) |
527 | 395 | ref_list.append((book_ref_id, from_chapter, from_verse, to_verse)) | 388 | elif to_verse >= from_verse or to_verse == -1: |
528 | 396 | elif from_verse: | 389 | ref_list.append((book_ref_id, from_chapter, from_verse, to_verse)) |
529 | 397 | ref_list.append((book_ref_id, from_chapter, from_verse, from_verse)) | 390 | elif from_verse: |
530 | 398 | else: | 391 | ref_list.append((book_ref_id, from_chapter, from_verse, from_verse)) |
531 | 399 | ref_list.append((book_ref_id, from_chapter, 1, -1)) | 392 | else: |
532 | 393 | ref_list.append((book_ref_id, from_chapter, 1, -1)) | ||
533 | 400 | return ref_list | 394 | return ref_list |
534 | 401 | else: | 395 | else: |
535 | 402 | log.debug('Invalid reference: {text}'.format(text=reference)) | 396 | log.debug('Invalid reference: {text}'.format(text=reference)) |
536 | 403 | 397 | ||
537 | === modified file 'openlp/plugins/bibles/lib/db.py' | |||
538 | --- openlp/plugins/bibles/lib/db.py 2019-05-22 06:47:00 +0000 | |||
539 | +++ openlp/plugins/bibles/lib/db.py 2019-08-06 21:48:11 +0000 | |||
540 | @@ -281,13 +281,14 @@ | |||
541 | 281 | log.debug('BibleDB.get_book("{book}")'.format(book=book)) | 281 | log.debug('BibleDB.get_book("{book}")'.format(book=book)) |
542 | 282 | return self.get_object_filtered(Book, Book.name.like(book + '%')) | 282 | return self.get_object_filtered(Book, Book.name.like(book + '%')) |
543 | 283 | 283 | ||
545 | 284 | def get_books(self): | 284 | def get_books(self, book=None): |
546 | 285 | """ | 285 | """ |
547 | 286 | A wrapper so both local and web bibles have a get_books() method that | 286 | A wrapper so both local and web bibles have a get_books() method that |
548 | 287 | manager can call. Used in the media manager advanced search tab. | 287 | manager can call. Used in the media manager advanced search tab. |
549 | 288 | """ | 288 | """ |
552 | 289 | log.debug('BibleDB.get_books()') | 289 | log.debug('BibleDB.get_books("{book}")'.format(book=book)) |
553 | 290 | return self.get_all_objects(Book, order_by_ref=Book.id) | 290 | filter = Book.name.like(book + '%') if book else None |
554 | 291 | return self.get_all_objects(Book, filter_clause=filter, order_by_ref=Book.id) | ||
555 | 291 | 292 | ||
556 | 292 | def get_book_by_book_ref_id(self, ref_id): | 293 | def get_book_by_book_ref_id(self, ref_id): |
557 | 293 | """ | 294 | """ |
558 | @@ -300,39 +301,35 @@ | |||
559 | 300 | 301 | ||
560 | 301 | def get_book_ref_id_by_localised_name(self, book, language_selection): | 302 | def get_book_ref_id_by_localised_name(self, book, language_selection): |
561 | 302 | """ | 303 | """ |
563 | 303 | Return the id of a named book. | 304 | Return the ids of a matching named book. |
564 | 304 | 305 | ||
565 | 305 | :param book: The name of the book, according to the selected language. | 306 | :param book: The name of the book, according to the selected language. |
566 | 306 | :param language_selection: The language selection the user has chosen in the settings section of the Bible. | 307 | :param language_selection: The language selection the user has chosen in the settings section of the Bible. |
567 | 308 | :rtype: list[int] | ||
568 | 307 | """ | 309 | """ |
569 | 308 | log.debug('get_book_ref_id_by_localised_name("{book}", "{lang}")'.format(book=book, lang=language_selection)) | 310 | log.debug('get_book_ref_id_by_localised_name("{book}", "{lang}")'.format(book=book, lang=language_selection)) |
570 | 309 | from openlp.plugins.bibles.lib import LanguageSelection, BibleStrings | 311 | from openlp.plugins.bibles.lib import LanguageSelection, BibleStrings |
571 | 310 | book_names = BibleStrings().BookNames | 312 | book_names = BibleStrings().BookNames |
572 | 311 | # escape reserved characters | 313 | # escape reserved characters |
573 | 312 | book_escaped = book | ||
574 | 313 | for character in RESERVED_CHARACTERS: | 314 | for character in RESERVED_CHARACTERS: |
576 | 314 | book_escaped = book_escaped.replace(character, '\\' + character) | 315 | book_escaped = book.replace(character, '\\' + character) |
577 | 315 | regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), re.IGNORECASE) | 316 | regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), re.IGNORECASE) |
578 | 316 | if language_selection == LanguageSelection.Bible: | 317 | if language_selection == LanguageSelection.Bible: |
598 | 317 | db_book = self.get_book(book) | 318 | db_books = self.get_books(book) |
599 | 318 | if db_book: | 319 | return [db_book.book_reference_id for db_book in db_books] |
600 | 319 | return db_book.book_reference_id | 320 | else: |
601 | 320 | elif language_selection == LanguageSelection.Application: | 321 | book_list = [] |
602 | 321 | books = [key for key in list(book_names.keys()) if regex_book.match(str(book_names[key]))] | 322 | if language_selection == LanguageSelection.Application: |
603 | 322 | books = [_f for _f in map(BiblesResourcesDB.get_book, books) if _f] | 323 | books = [key for key in list(book_names.keys()) if regex_book.match(book_names[key])] |
604 | 323 | for value in books: | 324 | book_list = [_f for _f in map(BiblesResourcesDB.get_book, books) if _f] |
605 | 324 | if self.get_book_by_book_ref_id(value['id']): | 325 | elif language_selection == LanguageSelection.English: |
606 | 325 | return value['id'] | 326 | books = BiblesResourcesDB.get_books_like(book) |
607 | 326 | elif language_selection == LanguageSelection.English: | 327 | if books: |
608 | 327 | books = BiblesResourcesDB.get_books_like(book) | 328 | book_list = [value for value in books if regex_book.match(value['name'])] |
609 | 328 | if books: | 329 | if not book_list: |
610 | 329 | book_list = [value for value in books if regex_book.match(value['name'])] | 330 | book_list = books |
611 | 330 | if not book_list: | 331 | return [value['id'] for value in book_list if self.get_book_by_book_ref_id(value['id'])] |
612 | 331 | book_list = books | 332 | return [] |
594 | 332 | for value in book_list: | ||
595 | 333 | if self.get_book_by_book_ref_id(value['id']): | ||
596 | 334 | return value['id'] | ||
597 | 335 | return False | ||
613 | 336 | 333 | ||
614 | 337 | def get_verses(self, reference_list, show_error=True): | 334 | def get_verses(self, reference_list, show_error=True): |
615 | 338 | """ | 335 | """ |
616 | 339 | 336 | ||
617 | === modified file 'openlp/plugins/bibles/lib/manager.py' | |||
618 | --- openlp/plugins/bibles/lib/manager.py 2019-07-03 13:23:23 +0000 | |||
619 | +++ openlp/plugins/bibles/lib/manager.py 2019-08-06 21:48:11 +0000 | |||
620 | @@ -240,8 +240,10 @@ | |||
621 | 240 | book=book, | 240 | book=book, |
622 | 241 | chapter=chapter)) | 241 | chapter=chapter)) |
623 | 242 | language_selection = self.get_language_selection(bible) | 242 | language_selection = self.get_language_selection(bible) |
626 | 243 | book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name(book, language_selection) | 243 | book_ref_ids = self.db_cache[bible].get_book_ref_id_by_localised_name(book, language_selection) |
627 | 244 | return self.db_cache[bible].get_verse_count(book_ref_id, chapter) | 244 | if book_ref_ids: |
628 | 245 | return self.db_cache[bible].get_verse_count(book_ref_ids[0], chapter) | ||
629 | 246 | return 0 | ||
630 | 245 | 247 | ||
631 | 246 | def get_verse_count_by_book_ref_id(self, bible, book_ref_id, chapter): | 248 | def get_verse_count_by_book_ref_id(self, bible, book_ref_id, chapter): |
632 | 247 | """ | 249 | """ |
633 | 248 | 250 | ||
634 | === modified file 'openlp/plugins/custom/forms/editcustomdialog.py' | |||
635 | --- openlp/plugins/custom/forms/editcustomdialog.py 2019-04-13 13:00:22 +0000 | |||
636 | +++ openlp/plugins/custom/forms/editcustomdialog.py 2019-08-06 21:48:11 +0000 | |||
637 | @@ -97,6 +97,7 @@ | |||
638 | 97 | self.preview_button = QtWidgets.QPushButton() | 97 | self.preview_button = QtWidgets.QPushButton() |
639 | 98 | self.button_box = create_button_box(custom_edit_dialog, 'button_box', ['cancel', 'save'], | 98 | self.button_box = create_button_box(custom_edit_dialog, 'button_box', ['cancel', 'save'], |
640 | 99 | [self.preview_button]) | 99 | [self.preview_button]) |
641 | 100 | self.save_button = self.button_box.button(QtWidgets.QDialogButtonBox.Save) | ||
642 | 100 | self.dialog_layout.addWidget(self.button_box) | 101 | self.dialog_layout.addWidget(self.button_box) |
643 | 101 | self.retranslate_ui(custom_edit_dialog) | 102 | self.retranslate_ui(custom_edit_dialog) |
644 | 102 | 103 | ||
645 | @@ -112,3 +113,4 @@ | |||
646 | 112 | self.theme_label.setText(translate('CustomPlugin.EditCustomForm', 'The&me:')) | 113 | self.theme_label.setText(translate('CustomPlugin.EditCustomForm', 'The&me:')) |
647 | 113 | self.credit_label.setText(translate('CustomPlugin.EditCustomForm', '&Credits:')) | 114 | self.credit_label.setText(translate('CustomPlugin.EditCustomForm', '&Credits:')) |
648 | 114 | self.preview_button.setText(UiStrings().SaveAndPreview) | 115 | self.preview_button.setText(UiStrings().SaveAndPreview) |
649 | 116 | self.save_button.setText(UiStrings().SaveAndClose) | ||
650 | 115 | 117 | ||
651 | === modified file 'openlp/plugins/songs/forms/editsongdialog.py' | |||
652 | --- openlp/plugins/songs/forms/editsongdialog.py 2019-04-13 13:00:22 +0000 | |||
653 | +++ openlp/plugins/songs/forms/editsongdialog.py 2019-08-06 21:48:11 +0000 | |||
654 | @@ -291,6 +291,7 @@ | |||
655 | 291 | self.warning_label.setObjectName('warning_label') | 291 | self.warning_label.setObjectName('warning_label') |
656 | 292 | self.bottom_layout.addWidget(self.warning_label) | 292 | self.bottom_layout.addWidget(self.warning_label) |
657 | 293 | self.button_box = create_button_box(edit_song_dialog, 'button_box', ['cancel', 'save']) | 293 | self.button_box = create_button_box(edit_song_dialog, 'button_box', ['cancel', 'save']) |
658 | 294 | self.save_button = self.button_box.button(QtWidgets.QDialogButtonBox.Save) | ||
659 | 294 | self.bottom_layout.addWidget(self.button_box) | 295 | self.bottom_layout.addWidget(self.button_box) |
660 | 295 | self.dialog_layout.addLayout(self.bottom_layout) | 296 | self.dialog_layout.addLayout(self.bottom_layout) |
661 | 296 | self.retranslate_ui(edit_song_dialog) | 297 | self.retranslate_ui(edit_song_dialog) |
662 | @@ -341,6 +342,7 @@ | |||
663 | 341 | translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> Not all of the verses are in use.') | 342 | translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> Not all of the verses are in use.') |
664 | 342 | self.no_verse_order_entered_warning = \ | 343 | self.no_verse_order_entered_warning = \ |
665 | 343 | translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.') | 344 | translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.') |
666 | 345 | self.save_button.setText(UiStrings().SaveAndPreview) | ||
667 | 344 | 346 | ||
668 | 345 | 347 | ||
669 | 346 | def create_combo_box(parent, name, editable=True): | 348 | def create_combo_box(parent, name, editable=True): |
670 | 347 | 349 | ||
671 | === modified file 'openlp/plugins/songs/lib/db.py' | |||
672 | --- openlp/plugins/songs/lib/db.py 2019-04-13 13:00:22 +0000 | |||
673 | +++ openlp/plugins/songs/lib/db.py 2019-08-06 21:48:11 +0000 | |||
674 | @@ -374,7 +374,9 @@ | |||
675 | 374 | mapper(SongBookEntry, songs_songbooks_table, properties={ | 374 | mapper(SongBookEntry, songs_songbooks_table, properties={ |
676 | 375 | 'songbook': relation(Book) | 375 | 'songbook': relation(Book) |
677 | 376 | }) | 376 | }) |
679 | 377 | mapper(Book, song_books_table) | 377 | mapper(Book, song_books_table, properties={ |
680 | 378 | 'songs': relation(Song, secondary=songs_songbooks_table) | ||
681 | 379 | }) | ||
682 | 378 | mapper(MediaFile, media_files_table) | 380 | mapper(MediaFile, media_files_table) |
683 | 379 | mapper(Song, songs_table, properties={ | 381 | mapper(Song, songs_table, properties={ |
684 | 380 | # Use the authors_songs relation when you need access to the 'author_type' attribute | 382 | # Use the authors_songs relation when you need access to the 'author_type' attribute |
685 | 381 | 383 | ||
686 | === modified file 'openlp/plugins/songs/lib/importers/cclifile.py' | |||
687 | --- openlp/plugins/songs/lib/importers/cclifile.py 2019-04-13 13:00:22 +0000 | |||
688 | +++ openlp/plugins/songs/lib/importers/cclifile.py 2019-08-06 21:48:11 +0000 | |||
689 | @@ -146,7 +146,9 @@ | |||
690 | 146 | """ | 146 | """ |
691 | 147 | log.debug('USR file text: {text}'.format(text=text_list)) | 147 | log.debug('USR file text: {text}'.format(text=text_list)) |
692 | 148 | song_author = '' | 148 | song_author = '' |
693 | 149 | song_fields = '' | ||
694 | 149 | song_topics = '' | 150 | song_topics = '' |
695 | 151 | song_words = '' | ||
696 | 150 | for line in text_list: | 152 | for line in text_list: |
697 | 151 | if line.startswith('[S '): | 153 | if line.startswith('[S '): |
698 | 152 | ccli, line = line.split(']', 1) | 154 | ccli, line = line.split(']', 1) |
699 | 153 | 155 | ||
700 | === modified file 'openlp/plugins/songs/lib/importers/dreambeam.py' | |||
701 | --- openlp/plugins/songs/lib/importers/dreambeam.py 2019-04-13 13:00:22 +0000 | |||
702 | +++ openlp/plugins/songs/lib/importers/dreambeam.py 2019-08-06 21:48:11 +0000 | |||
703 | @@ -87,6 +87,7 @@ | |||
704 | 87 | if self.stop_import_flag: | 87 | if self.stop_import_flag: |
705 | 88 | return | 88 | return |
706 | 89 | self.set_defaults() | 89 | self.set_defaults() |
707 | 90 | author_copyright = '' | ||
708 | 90 | parser = etree.XMLParser(remove_blank_text=True) | 91 | parser = etree.XMLParser(remove_blank_text=True) |
709 | 91 | try: | 92 | try: |
710 | 92 | with file_path.open('r') as xml_file: | 93 | with file_path.open('r') as xml_file: |
711 | @@ -142,7 +143,7 @@ | |||
712 | 142 | author_copyright = song_xml.Text2.Text.text | 143 | author_copyright = song_xml.Text2.Text.text |
713 | 143 | if author_copyright: | 144 | if author_copyright: |
714 | 144 | author_copyright = str(author_copyright) | 145 | author_copyright = str(author_copyright) |
716 | 145 | if author_copyright.find(str(SongStrings.CopyrightSymbol)) >= 0: | 146 | if author_copyright.find(SongStrings.CopyrightSymbol) >= 0: |
717 | 146 | self.add_copyright(author_copyright) | 147 | self.add_copyright(author_copyright) |
718 | 147 | else: | 148 | else: |
719 | 148 | self.parse_author(author_copyright) | 149 | self.parse_author(author_copyright) |
720 | 149 | 150 | ||
721 | === modified file 'openlp/plugins/songs/lib/importers/easyslides.py' | |||
722 | --- openlp/plugins/songs/lib/importers/easyslides.py 2019-04-13 13:00:22 +0000 | |||
723 | +++ openlp/plugins/songs/lib/importers/easyslides.py 2019-08-06 21:48:11 +0000 | |||
724 | @@ -137,9 +137,11 @@ | |||
725 | 137 | except UnicodeDecodeError: | 137 | except UnicodeDecodeError: |
726 | 138 | log.exception('Unicode decode error while decoding Contents') | 138 | log.exception('Unicode decode error while decoding Contents') |
727 | 139 | self._success = False | 139 | self._success = False |
728 | 140 | return | ||
729 | 140 | except AttributeError: | 141 | except AttributeError: |
730 | 141 | log.exception('no Contents') | 142 | log.exception('no Contents') |
731 | 142 | self._success = False | 143 | self._success = False |
732 | 144 | return | ||
733 | 143 | lines = lyrics.split('\n') | 145 | lines = lyrics.split('\n') |
734 | 144 | # we go over all lines first, to determine information, | 146 | # we go over all lines first, to determine information, |
735 | 145 | # which tells us how to parse verses later | 147 | # which tells us how to parse verses later |
736 | 146 | 148 | ||
737 | === modified file 'openlp/plugins/songs/lib/importers/easyworship.py' | |||
738 | --- openlp/plugins/songs/lib/importers/easyworship.py 2019-07-03 13:23:23 +0000 | |||
739 | +++ openlp/plugins/songs/lib/importers/easyworship.py 2019-08-06 21:48:11 +0000 | |||
740 | @@ -268,13 +268,13 @@ | |||
741 | 268 | self.db_set_record_struct(field_descriptions) | 268 | self.db_set_record_struct(field_descriptions) |
742 | 269 | # Pick out the field description indexes we will need | 269 | # Pick out the field description indexes we will need |
743 | 270 | try: | 270 | try: |
744 | 271 | success = True | ||
745 | 272 | fi_title = self.db_find_field(b'Title') | 271 | fi_title = self.db_find_field(b'Title') |
746 | 273 | fi_author = self.db_find_field(b'Author') | 272 | fi_author = self.db_find_field(b'Author') |
747 | 274 | fi_copy = self.db_find_field(b'Copyright') | 273 | fi_copy = self.db_find_field(b'Copyright') |
748 | 275 | fi_admin = self.db_find_field(b'Administrator') | 274 | fi_admin = self.db_find_field(b'Administrator') |
749 | 276 | fi_words = self.db_find_field(b'Words') | 275 | fi_words = self.db_find_field(b'Words') |
750 | 277 | fi_ccli = self.db_find_field(b'Song Number') | 276 | fi_ccli = self.db_find_field(b'Song Number') |
751 | 277 | success = True | ||
752 | 278 | except IndexError: | 278 | except IndexError: |
753 | 279 | # This is the wrong table | 279 | # This is the wrong table |
754 | 280 | success = False | 280 | success = False |
755 | 281 | 281 | ||
756 | === modified file 'openlp/plugins/songs/lib/importers/songbeamer.py' | |||
757 | --- openlp/plugins/songs/lib/importers/songbeamer.py 2019-05-22 06:47:00 +0000 | |||
758 | +++ openlp/plugins/songs/lib/importers/songbeamer.py 2019-08-06 21:48:11 +0000 | |||
759 | @@ -128,7 +128,7 @@ | |||
760 | 128 | # The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode. | 128 | # The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode. |
761 | 129 | # So if it doesn't start with 'u' we default to cp1252. See: | 129 | # So if it doesn't start with 'u' we default to cp1252. See: |
762 | 130 | # https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2 | 130 | # https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2 |
764 | 131 | if not self.input_file_encoding.lower().startswith('u'): | 131 | if self.input_file_encoding and not self.input_file_encoding.lower().startswith('u'): |
765 | 132 | self.input_file_encoding = 'cp1252' | 132 | self.input_file_encoding = 'cp1252' |
766 | 133 | with file_path.open(encoding=self.input_file_encoding) as song_file: | 133 | with file_path.open(encoding=self.input_file_encoding) as song_file: |
767 | 134 | song_data = song_file.readlines() | 134 | song_data = song_file.readlines() |
768 | 135 | 135 | ||
769 | === modified file 'tests/functional/openlp_core/common/test_common.py' | |||
770 | --- tests/functional/openlp_core/common/test_common.py 2019-05-22 06:47:00 +0000 | |||
771 | +++ tests/functional/openlp_core/common/test_common.py 2019-08-06 21:48:11 +0000 | |||
772 | @@ -26,7 +26,7 @@ | |||
773 | 26 | from unittest import TestCase | 26 | from unittest import TestCase |
774 | 27 | from unittest.mock import MagicMock, call, patch | 27 | from unittest.mock import MagicMock, call, patch |
775 | 28 | 28 | ||
777 | 29 | from openlp.core.common import clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \ | 29 | from openlp.core.common import Singleton, clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \ |
778 | 30 | normalize_str, path_to_module, trace_error_handler | 30 | normalize_str, path_to_module, trace_error_handler |
779 | 31 | 31 | ||
780 | 32 | 32 | ||
781 | @@ -163,6 +163,48 @@ | |||
782 | 163 | mocked_logger.error.assert_called_with( | 163 | mocked_logger.error.assert_called_with( |
783 | 164 | 'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test') | 164 | 'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test') |
784 | 165 | 165 | ||
785 | 166 | def test_singleton_metaclass_multiple_init(self): | ||
786 | 167 | """ | ||
787 | 168 | Test that a class using the Singleton Metaclass is only initialised once despite being called several times and | ||
788 | 169 | that the same instance is returned each time.. | ||
789 | 170 | """ | ||
790 | 171 | # GIVEN: The Singleton Metaclass and a test class using it | ||
791 | 172 | class SingletonClass(metaclass=Singleton): | ||
792 | 173 | def __init__(self): | ||
793 | 174 | pass | ||
794 | 175 | |||
795 | 176 | with patch.object(SingletonClass, '__init__', return_value=None) as patched_init: | ||
796 | 177 | |||
797 | 178 | # WHEN: Initialising the class multiple times | ||
798 | 179 | inst_1 = SingletonClass() | ||
799 | 180 | inst_2 = SingletonClass() | ||
800 | 181 | |||
801 | 182 | # THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values | ||
802 | 183 | # should be the same instance. | ||
803 | 184 | assert inst_1 is inst_2 | ||
804 | 185 | assert patched_init.call_count == 1 | ||
805 | 186 | |||
806 | 187 | def test_singleton_metaclass_multiple_classes(self): | ||
807 | 188 | """ | ||
808 | 189 | Test that multiple classes using the Singleton Metaclass return the different an appropriate instances. | ||
809 | 190 | """ | ||
810 | 191 | # GIVEN: Two different classes using the Singleton Metaclass | ||
811 | 192 | class SingletonClass1(metaclass=Singleton): | ||
812 | 193 | def __init__(self): | ||
813 | 194 | pass | ||
814 | 195 | |||
815 | 196 | class SingletonClass2(metaclass=Singleton): | ||
816 | 197 | def __init__(self): | ||
817 | 198 | pass | ||
818 | 199 | |||
819 | 200 | # WHEN: Initialising both classes | ||
820 | 201 | s_c1 = SingletonClass1() | ||
821 | 202 | s_c2 = SingletonClass2() | ||
822 | 203 | |||
823 | 204 | # THEN: The instances should be an instance of the appropriate class | ||
824 | 205 | assert isinstance(s_c1, SingletonClass1) | ||
825 | 206 | assert isinstance(s_c2, SingletonClass2) | ||
826 | 207 | |||
827 | 166 | def test_is_win(self): | 208 | def test_is_win(self): |
828 | 167 | """ | 209 | """ |
829 | 168 | Test the is_win() function | 210 | Test the is_win() function |
830 | 169 | 211 | ||
831 | === modified file 'tests/functional/openlp_core/lib/test_theme.py' | |||
832 | --- tests/functional/openlp_core/lib/test_theme.py 2019-07-18 19:14:58 +0000 | |||
833 | +++ tests/functional/openlp_core/lib/test_theme.py 2019-08-06 21:48:11 +0000 | |||
834 | @@ -182,4 +182,4 @@ | |||
835 | 182 | assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0' | 182 | assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0' |
836 | 183 | assert theme.font_footer_bold is False, 'font_footer_bold should be False' | 183 | assert theme.font_footer_bold is False, 'font_footer_bold should be False' |
837 | 184 | assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"' | 184 | assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"' |
839 | 185 | assert 47 == len(theme.__dict__), 'The theme should have 47 attributes' | 185 | assert 48 == len(theme.__dict__), 'The theme should have 48 attributes' |
840 | 186 | 186 | ||
841 | === modified file 'tests/functional/openlp_core/ui/test_icons.py' | |||
842 | --- tests/functional/openlp_core/ui/test_icons.py 2019-04-13 13:00:22 +0000 | |||
843 | +++ tests/functional/openlp_core/ui/test_icons.py 2019-08-06 21:48:11 +0000 | |||
844 | @@ -33,7 +33,7 @@ | |||
845 | 33 | 33 | ||
846 | 34 | class TestIcons(TestCase, TestMixin): | 34 | class TestIcons(TestCase, TestMixin): |
847 | 35 | 35 | ||
849 | 36 | @patch('openlp.core.ui.icons.UiIcons.load') | 36 | @patch('openlp.core.ui.icons.UiIcons.__init__', return_value=None) |
850 | 37 | def test_simple_icon(self, _): | 37 | def test_simple_icon(self, _): |
851 | 38 | # GIVEN: an basic set of icons | 38 | # GIVEN: an basic set of icons |
852 | 39 | icons = UiIcons() | 39 | icons = UiIcons() |
Linux tests passed!