Merge lp:~tomasgroth/openlp/bug-fixes-2-4-3 into lp:openlp
- bug-fixes-2-4-3
- Merge into trunk
Proposed by
Tomas Groth
Status: | Merged | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 2706 | ||||||||||||||||
Proposed branch: | lp:~tomasgroth/openlp/bug-fixes-2-4-3 | ||||||||||||||||
Merge into: | lp:openlp | ||||||||||||||||
Diff against target: |
299 lines (+92/-89) 6 files modified
openlp/core/__init__.py (+41/-5) openlp/core/lib/__init__.py (+15/-22) openlp/core/ui/advancedtab.py (+0/-21) openlp/core/ui/lib/treewidgetwithdnd.py (+6/-1) openlp/core/ui/slidecontroller.py (+2/-0) tests/functional/openlp_core_lib/test_lib.py (+28/-40) |
||||||||||||||||
To merge this branch: | bzr merge lp:~tomasgroth/openlp/bug-fixes-2-4-3 | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raoul Snyman | Approve | ||
Review via email: mp+311435@code.launchpad.net |
Commit message
Description of the change
Continuation of lp:~suutari-olli/openlp/bug-fixes-2-4-3
This branch fixes the following bugs:
Bug #1487788: Importing photos does not give focus to OpenLP
Bug #1512040: Loop tooltip gets stuck to "Stop playing..."
Bug #1513490: List of authors uses localized "and" instead of English
Bug #1624661: Missing DB in unmounted disk results in Traceback
To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) wrote : | # |
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : | # |
Looks fine to me
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openlp/core/__init__.py' | |||
2 | --- openlp/core/__init__.py 2016-04-27 12:49:55 +0000 | |||
3 | +++ openlp/core/__init__.py 2016-11-21 21:19:11 +0000 | |||
4 | @@ -177,6 +177,38 @@ | |||
5 | 177 | self.shared_memory.create(1) | 177 | self.shared_memory.create(1) |
6 | 178 | return False | 178 | return False |
7 | 179 | 179 | ||
8 | 180 | def is_data_path_missing(self): | ||
9 | 181 | """ | ||
10 | 182 | Check if the data folder path exists. | ||
11 | 183 | """ | ||
12 | 184 | data_folder_path = AppLocation.get_data_path() | ||
13 | 185 | if not os.path.exists(data_folder_path): | ||
14 | 186 | log.critical('Database was not found in: ' + data_folder_path) | ||
15 | 187 | status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'), | ||
16 | 188 | translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}' | ||
17 | 189 | '\n\nThe location of the data folder was ' | ||
18 | 190 | 'previously changed from the OpenLP\'s ' | ||
19 | 191 | 'default location. If the data was stored on ' | ||
20 | 192 | 'removable device, that device needs to be ' | ||
21 | 193 | 'made available.\n\nYou may reset the data ' | ||
22 | 194 | 'location back to the default location, ' | ||
23 | 195 | 'or you can try to make the current location ' | ||
24 | 196 | 'available.\n\nDo you want to reset to the ' | ||
25 | 197 | 'default data location? If not, OpenLP will be ' | ||
26 | 198 | 'closed so you can try to fix the the problem.') | ||
27 | 199 | .format(path=data_folder_path), | ||
28 | 200 | QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | | ||
29 | 201 | QtWidgets.QMessageBox.No), | ||
30 | 202 | QtWidgets.QMessageBox.No) | ||
31 | 203 | if status == QtWidgets.QMessageBox.No: | ||
32 | 204 | # If answer was "No", return "True", it will shutdown OpenLP in def main | ||
33 | 205 | log.info('User requested termination') | ||
34 | 206 | return True | ||
35 | 207 | # If answer was "Yes", remove the custom data path thus resetting the default location. | ||
36 | 208 | Settings().remove('advanced/data path') | ||
37 | 209 | log.info('Database location has been reset to the default settings.') | ||
38 | 210 | return False | ||
39 | 211 | |||
40 | 180 | def hook_exception(self, exc_type, value, traceback): | 212 | def hook_exception(self, exc_type, value, traceback): |
41 | 181 | """ | 213 | """ |
42 | 182 | Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where | 214 | Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where |
43 | @@ -208,8 +240,8 @@ | |||
44 | 208 | # If data_version is different from the current version ask if we should backup the data folder | 240 | # If data_version is different from the current version ask if we should backup the data folder |
45 | 209 | elif data_version != openlp_version: | 241 | elif data_version != openlp_version: |
46 | 210 | if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'), | 242 | if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'), |
49 | 211 | translate('OpenLP', 'OpenLP has been upgraded, do you want to create ' | 243 | translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n' |
50 | 212 | 'a backup of OpenLPs data folder?'), | 244 | 'a backup of the old data folder?'), |
51 | 213 | QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, | 245 | QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, |
52 | 214 | QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes: | 246 | QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes: |
53 | 215 | # Create copy of data folder | 247 | # Create copy of data folder |
54 | @@ -223,8 +255,8 @@ | |||
55 | 223 | translate('OpenLP', 'Backup of the data folder failed!')) | 255 | translate('OpenLP', 'Backup of the data folder failed!')) |
56 | 224 | return | 256 | return |
57 | 225 | message = translate('OpenLP', | 257 | message = translate('OpenLP', |
60 | 226 | 'A backup of the data folder has been created' | 258 | 'A backup of the data folder has been created at:\n\n' |
61 | 227 | 'at {text}').format(text=data_folder_backup_path) | 259 | '{text}').format(text=data_folder_backup_path) |
62 | 228 | QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message) | 260 | QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message) |
63 | 229 | 261 | ||
64 | 230 | # Update the version in the settings | 262 | # Update the version in the settings |
65 | @@ -368,9 +400,13 @@ | |||
66 | 368 | Registry.create() | 400 | Registry.create() |
67 | 369 | Registry().register('application', application) | 401 | Registry().register('application', application) |
68 | 370 | application.setApplicationVersion(get_application_version()['version']) | 402 | application.setApplicationVersion(get_application_version()['version']) |
70 | 371 | # Instance check | 403 | # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one |
71 | 372 | if application.is_already_running(): | 404 | if application.is_already_running(): |
72 | 373 | sys.exit() | 405 | sys.exit() |
73 | 406 | # If the custom data path is missing and the user wants to restore the data path, quit OpenLP. | ||
74 | 407 | if application.is_data_path_missing(): | ||
75 | 408 | application.shared_memory.detach() | ||
76 | 409 | sys.exit() | ||
77 | 374 | # Remove/convert obsolete settings. | 410 | # Remove/convert obsolete settings. |
78 | 375 | Settings().remove_obsolete_settings() | 411 | Settings().remove_obsolete_settings() |
79 | 376 | # First time checks in settings | 412 | # First time checks in settings |
80 | 377 | 413 | ||
81 | === modified file 'openlp/core/lib/__init__.py' | |||
82 | --- openlp/core/lib/__init__.py 2016-10-30 08:29:22 +0000 | |||
83 | +++ openlp/core/lib/__init__.py 2016-11-21 21:19:11 +0000 | |||
84 | @@ -310,30 +310,23 @@ | |||
85 | 310 | 310 | ||
86 | 311 | def create_separated_list(string_list): | 311 | def create_separated_list(string_list): |
87 | 312 | """ | 312 | """ |
94 | 313 | Returns a string that represents a join of a list of strings with a localized separator. This function corresponds | 313 | Returns a string that represents a join of a list of strings with a localized separator. |
95 | 314 | 314 | Localized separation will be done via the translate() function by the translators. | |
96 | 315 | to QLocale::createSeparatedList which was introduced in Qt 4.8 and implements the algorithm from | 315 | |
97 | 316 | http://www.unicode.org/reports/tr35/#ListPatterns | 316 | :param string_list: List of unicode strings |
98 | 317 | 317 | :return: Formatted string | |
93 | 318 | :param string_list: List of unicode strings | ||
99 | 319 | """ | 318 | """ |
110 | 320 | if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'): | 319 | list_length = len(string_list) |
111 | 321 | return QtCore.QLocale().createSeparatedList(string_list) | 320 | if list_length == 1: |
112 | 322 | if not string_list: | 321 | list_to_string = string_list[0] |
113 | 323 | return '' | 322 | elif list_length == 2: |
114 | 324 | elif len(string_list) == 1: | 323 | list_to_string = translate('OpenLP.core.lib', '{one} and {two}').format(one=string_list[0], two=string_list[1]) |
115 | 325 | return string_list[0] | 324 | elif list_length > 2: |
116 | 326 | # TODO: Verify mocking of translate() test before conversion | 325 | list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]), |
117 | 327 | elif len(string_list) == 2: | 326 | last=string_list[-1]) |
108 | 328 | return translate('OpenLP.core.lib', '%s and %s', | ||
109 | 329 | 'Locale list separator: 2 items') % (string_list[0], string_list[1]) | ||
118 | 330 | else: | 327 | else: |
125 | 331 | merged = translate('OpenLP.core.lib', '%s, and %s', | 328 | list_to_string = '' |
126 | 332 | 'Locale list separator: end') % (string_list[-2], string_list[-1]) | 329 | return list_to_string |
121 | 333 | for index in reversed(list(range(1, len(string_list) - 2))): | ||
122 | 334 | merged = translate('OpenLP.core.lib', '%s, %s', | ||
123 | 335 | 'Locale list separator: middle') % (string_list[index], merged) | ||
124 | 336 | return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged) | ||
127 | 337 | 330 | ||
128 | 338 | 331 | ||
129 | 339 | from .exceptions import ValidationError | 332 | from .exceptions import ValidationError |
130 | 340 | 333 | ||
131 | === modified file 'openlp/core/ui/advancedtab.py' | |||
132 | --- openlp/core/ui/advancedtab.py 2016-08-11 22:12:50 +0000 | |||
133 | +++ openlp/core/ui/advancedtab.py 2016-11-21 21:19:11 +0000 | |||
134 | @@ -397,27 +397,6 @@ | |||
135 | 397 | self.data_directory_cancel_button.hide() | 397 | self.data_directory_cancel_button.hide() |
136 | 398 | # Since data location can be changed, make sure the path is present. | 398 | # Since data location can be changed, make sure the path is present. |
137 | 399 | self.current_data_path = AppLocation.get_data_path() | 399 | self.current_data_path = AppLocation.get_data_path() |
138 | 400 | if not os.path.exists(self.current_data_path): | ||
139 | 401 | log.error('Data path not found {path}'.format(path=self.current_data_path)) | ||
140 | 402 | answer = QtWidgets.QMessageBox.critical( | ||
141 | 403 | self, translate('OpenLP.AdvancedTab', 'Data Directory Error'), | ||
142 | 404 | translate('OpenLP.AdvancedTab', 'OpenLP data directory was not found\n\n{path}\n\n' | ||
143 | 405 | 'This data directory was previously changed from the OpenLP ' | ||
144 | 406 | 'default location. If the new location was on removable ' | ||
145 | 407 | 'media, that media needs to be made available.\n\n' | ||
146 | 408 | 'Click "No" to stop loading OpenLP. allowing you to fix the the problem.\n\n' | ||
147 | 409 | 'Click "Yes" to reset the data directory to the default ' | ||
148 | 410 | 'location.').format(path=self.current_data_path), | ||
149 | 411 | QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), | ||
150 | 412 | QtWidgets.QMessageBox.No) | ||
151 | 413 | if answer == QtWidgets.QMessageBox.No: | ||
152 | 414 | log.info('User requested termination') | ||
153 | 415 | self.main_window.clean_up() | ||
154 | 416 | sys.exit() | ||
155 | 417 | # Set data location to default. | ||
156 | 418 | settings.remove('advanced/data path') | ||
157 | 419 | self.current_data_path = AppLocation.get_data_path() | ||
158 | 420 | log.warning('User requested data path set to default {path}'.format(path=self.current_data_path)) | ||
159 | 421 | self.data_directory_label.setText(os.path.abspath(self.current_data_path)) | 400 | self.data_directory_label.setText(os.path.abspath(self.current_data_path)) |
160 | 422 | # Don't allow data directory move if running portable. | 401 | # Don't allow data directory move if running portable. |
161 | 423 | if settings.value('advanced/is portable'): | 402 | if settings.value('advanced/is portable'): |
162 | 424 | 403 | ||
163 | === modified file 'openlp/core/ui/lib/treewidgetwithdnd.py' | |||
164 | --- openlp/core/ui/lib/treewidgetwithdnd.py 2016-04-17 19:32:15 +0000 | |||
165 | +++ openlp/core/ui/lib/treewidgetwithdnd.py 2016-11-21 21:19:11 +0000 | |||
166 | @@ -26,7 +26,7 @@ | |||
167 | 26 | 26 | ||
168 | 27 | from PyQt5 import QtCore, QtGui, QtWidgets | 27 | from PyQt5 import QtCore, QtGui, QtWidgets |
169 | 28 | 28 | ||
171 | 29 | from openlp.core.common import Registry | 29 | from openlp.core.common import Registry, is_win |
172 | 30 | 30 | ||
173 | 31 | 31 | ||
174 | 32 | class TreeWidgetWithDnD(QtWidgets.QTreeWidget): | 32 | class TreeWidgetWithDnD(QtWidgets.QTreeWidget): |
175 | @@ -108,6 +108,11 @@ | |||
176 | 108 | 108 | ||
177 | 109 | :param event: Handle of the event pint passed | 109 | :param event: Handle of the event pint passed |
178 | 110 | """ | 110 | """ |
179 | 111 | # If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and | ||
180 | 112 | # the folder stays on top of the group creation box. This piece of code fixes this issue. | ||
181 | 113 | if is_win(): | ||
182 | 114 | self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) | ||
183 | 115 | self.setWindowState(QtCore.Qt.WindowNoState) | ||
184 | 111 | if event.mimeData().hasUrls(): | 116 | if event.mimeData().hasUrls(): |
185 | 112 | event.setDropAction(QtCore.Qt.CopyAction) | 117 | event.setDropAction(QtCore.Qt.CopyAction) |
186 | 113 | event.accept() | 118 | event.accept() |
187 | 114 | 119 | ||
188 | === modified file 'openlp/core/ui/slidecontroller.py' | |||
189 | --- openlp/core/ui/slidecontroller.py 2016-09-12 12:11:59 +0000 | |||
190 | +++ openlp/core/ui/slidecontroller.py 2016-11-21 21:19:11 +0000 | |||
191 | @@ -722,8 +722,10 @@ | |||
192 | 722 | # Reset the button | 722 | # Reset the button |
193 | 723 | self.play_slides_once.setChecked(False) | 723 | self.play_slides_once.setChecked(False) |
194 | 724 | self.play_slides_once.setIcon(build_icon(':/media/media_time.png')) | 724 | self.play_slides_once.setIcon(build_icon(':/media/media_time.png')) |
195 | 725 | self.play_slides_once.setText(UiStrings().PlaySlidesToEnd) | ||
196 | 725 | self.play_slides_loop.setChecked(False) | 726 | self.play_slides_loop.setChecked(False) |
197 | 726 | self.play_slides_loop.setIcon(build_icon(':/media/media_time.png')) | 727 | self.play_slides_loop.setIcon(build_icon(':/media/media_time.png')) |
198 | 728 | self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop) | ||
199 | 727 | if item.is_text(): | 729 | if item.is_text(): |
200 | 728 | if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and | 730 | if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and |
201 | 729 | not self.song_menu.menu().isEmpty()): | 731 | not self.song_menu.menu().isEmpty()): |
202 | 730 | 732 | ||
203 | === modified file 'tests/functional/openlp_core_lib/test_lib.py' | |||
204 | --- tests/functional/openlp_core_lib/test_lib.py 2016-10-30 08:29:22 +0000 | |||
205 | +++ tests/functional/openlp_core_lib/test_lib.py 2016-11-21 21:19:11 +0000 | |||
206 | @@ -688,8 +688,8 @@ | |||
207 | 688 | string_result = create_separated_list(string_list) | 688 | string_result = create_separated_list(string_list) |
208 | 689 | 689 | ||
209 | 690 | # THEN: We should have "Author 1, Author 2, and Author 3" | 690 | # THEN: We should have "Author 1, Author 2, and Author 3" |
212 | 691 | assert string_result == 'Author 1, Author 2, and Author 3', 'The string should be u\'Author 1, ' \ | 691 | self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, ' |
213 | 692 | 'Author 2, and Author 3\'.' | 692 | 'Author 2, and Author 3".') |
214 | 693 | 693 | ||
215 | 694 | def test_create_separated_list_empty_list(self): | 694 | def test_create_separated_list_empty_list(self): |
216 | 695 | """ | 695 | """ |
217 | @@ -705,56 +705,44 @@ | |||
218 | 705 | string_result = create_separated_list(string_list) | 705 | string_result = create_separated_list(string_list) |
219 | 706 | 706 | ||
220 | 707 | # THEN: We shoud have an emptry string. | 707 | # THEN: We shoud have an emptry string. |
222 | 708 | assert string_result == '', 'The string sould be empty.' | 708 | self.assertEqual(string_result, '', 'The string sould be empty.') |
223 | 709 | 709 | ||
224 | 710 | def test_create_separated_list_with_one_item(self): | 710 | def test_create_separated_list_with_one_item(self): |
225 | 711 | """ | 711 | """ |
226 | 712 | Test the create_separated_list function with a list consisting of only one entry | 712 | Test the create_separated_list function with a list consisting of only one entry |
227 | 713 | """ | 713 | """ |
239 | 714 | with patch('openlp.core.lib.Qt') as mocked_qt: | 714 | # GIVEN: A list with a string. |
240 | 715 | # GIVEN: A list with a string and the mocked Qt module. | 715 | string_list = ['Author 1'] |
241 | 716 | mocked_qt.PYQT_VERSION_STR = '4.8' | 716 | |
242 | 717 | mocked_qt.qVersion.return_value = '4.7' | 717 | # WHEN: We get a string build from the entries it the list and a separator. |
243 | 718 | string_list = ['Author 1'] | 718 | string_result = create_separated_list(string_list) |
244 | 719 | 719 | ||
245 | 720 | # WHEN: We get a string build from the entries it the list and a separator. | 720 | # THEN: We should have "Author 1" |
246 | 721 | string_result = create_separated_list(string_list) | 721 | self.assertEqual(string_result, 'Author 1', 'The string should be "Author 1".') |
236 | 722 | |||
237 | 723 | # THEN: We should have "Author 1" | ||
238 | 724 | assert string_result == 'Author 1', 'The string should be u\'Author 1\'.' | ||
247 | 725 | 722 | ||
248 | 726 | def test_create_separated_list_with_two_items(self): | 723 | def test_create_separated_list_with_two_items(self): |
249 | 727 | """ | 724 | """ |
250 | 728 | Test the create_separated_list function with a list of two entries | 725 | Test the create_separated_list function with a list of two entries |
251 | 729 | """ | 726 | """ |
264 | 730 | with patch('openlp.core.lib.Qt') as mocked_qt, patch('openlp.core.lib.translate') as mocked_translate: | 727 | # GIVEN: A list with two strings. |
265 | 731 | # GIVEN: A list of strings and the mocked Qt module. | 728 | string_list = ['Author 1', 'Author 2'] |
266 | 732 | mocked_qt.PYQT_VERSION_STR = '4.8' | 729 | |
267 | 733 | mocked_qt.qVersion.return_value = '4.7' | 730 | # WHEN: We get a string build from the entries it the list and a seperator. |
268 | 734 | mocked_translate.return_value = '%s and %s' | 731 | string_result = create_separated_list(string_list) |
269 | 735 | string_list = ['Author 1', 'Author 2'] | 732 | |
270 | 736 | 733 | # THEN: We should have "Author 1 and Author 2" | |
271 | 737 | # WHEN: We get a string build from the entries it the list and a seperator. | 734 | self.assertEqual(string_result, 'Author 1 and Author 2', 'The string should be "Author 1 and Author 2".') |
260 | 738 | string_result = create_separated_list(string_list) | ||
261 | 739 | |||
262 | 740 | # THEN: We should have "Author 1 and Author 2" | ||
263 | 741 | assert string_result == 'Author 1 and Author 2', 'The string should be u\'Author 1 and Author 2\'.' | ||
272 | 742 | 735 | ||
273 | 743 | def test_create_separated_list_with_three_items(self): | 736 | def test_create_separated_list_with_three_items(self): |
274 | 744 | """ | 737 | """ |
275 | 745 | Test the create_separated_list function with a list of three items | 738 | Test the create_separated_list function with a list of three items |
276 | 746 | """ | 739 | """ |
291 | 747 | with patch('openlp.core.lib.Qt') as mocked_qt, patch('openlp.core.lib.translate') as mocked_translate: | 740 | # GIVEN: A list with three strings. |
292 | 748 | # GIVEN: A list with a string and the mocked Qt module. | 741 | string_list = ['Author 1', 'Author 2', 'Author 3'] |
293 | 749 | mocked_qt.PYQT_VERSION_STR = '4.8' | 742 | |
294 | 750 | mocked_qt.qVersion.return_value = '4.7' | 743 | # WHEN: We get a string build from the entries it the list and a seperator. |
295 | 751 | # Always return the untranslated string. | 744 | string_result = create_separated_list(string_list) |
296 | 752 | mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate | 745 | |
297 | 753 | string_list = ['Author 1', 'Author 2', 'Author 3'] | 746 | # THEN: We should have "Author 1, Author 2 and Author 3" |
298 | 754 | 747 | self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, ' | |
299 | 755 | # WHEN: We get a string build from the entries it the list and a seperator. | 748 | 'Author 2, and Author 3".') |
286 | 756 | string_result = create_separated_list(string_list) | ||
287 | 757 | |||
288 | 758 | # THEN: We should have "Author 1, Author 2, and Author 3" | ||
289 | 759 | assert string_result == 'Author 1, Author 2, and Author 3', 'The string should be u\'Author 1, ' \ | ||
290 | 760 | 'Author 2, and Author 3\'.' |
lp:~tomasgroth/openlp/bug-fixes-2-4-3 (revision 2726) /ci.openlp. io/job/ Branch- 01-Pull/ 1845/ /ci.openlp. io/job/ Branch- 02-Functional- Tests/1756/ /ci.openlp. io/job/ Branch- 03-Interface- Tests/1694/ /ci.openlp. io/job/ Branch- 04a-Windows_ Functional_ Tests/1439/ /ci.openlp. io/job/ Branch- 04b-Windows_ Interface_ Tests/1029/ /ci.openlp. io/job/ Branch- 05a-Code_ Analysis/ 1097/ /ci.openlp. io/job/ Branch- 05b-Test_ Coverage/ 965/ /ci.openlp. io/job/ Branch- 05c-Code_ Analysis2/ 119/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/