Merge lp:~nixcode/openlp/fairsplitting into lp:openlp
- fairsplitting
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~nixcode/openlp/fairsplitting |
Merge into: | lp:openlp |
Diff against target: |
355 lines (+202/-6) 7 files modified
openlp/core/common/__init__.py (+8/-0) openlp/core/common/settings.py (+2/-1) openlp/core/lib/renderer.py (+89/-4) openlp/core/ui/generaltab.py (+41/-1) tests/functional/openlp_core_lib/test_image_manager.py (+2/-0) tests/functional/openlp_core_lib/test_projector_db.py (+1/-0) tests/functional/openlp_core_lib/test_renderer.py (+59/-0) |
To merge this branch: | bzr merge lp:~nixcode/openlp/fairsplitting |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tomas Groth | Needs Information | ||
Review via email: mp+328911@code.launchpad.net |
Commit message
Description of the change
Adds the option to have text split fairly between slides.
E.g. if 3 lines fit on a slide and you have a 4 line verse this adds the option to have it split 2-2 not 3-1.
lp:~nixcode/openlp/fairsplitting (revision 2758)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
Unmerged revisions
- 2758. By Jonathan Tanner
-
Replaced math.inf with float(inf) for pre Python3.5 compatibility
- 2757. By Jonathan Tanner
-
Fixed PEP8 errors
- 2756. By Jonathan Tanner
-
added slide splitting controls
Preview Diff
1 | === modified file 'openlp/core/common/__init__.py' | |||
2 | --- openlp/core/common/__init__.py 2017-08-01 20:59:41 +0000 | |||
3 | +++ openlp/core/common/__init__.py 2017-08-11 12:52:52 +0000 | |||
4 | @@ -167,6 +167,14 @@ | |||
5 | 167 | Next = 3 | 167 | Next = 3 |
6 | 168 | 168 | ||
7 | 169 | 169 | ||
8 | 170 | class SlideSplitting(object): | ||
9 | 171 | """ | ||
10 | 172 | Provides an enumeration for behaviour of OpenLP when a slide needs to be split across multiple slides | ||
11 | 173 | """ | ||
12 | 174 | Greedily = 1 | ||
13 | 175 | Fairly = 2 | ||
14 | 176 | |||
15 | 177 | |||
16 | 170 | def de_hump(name): | 178 | def de_hump(name): |
17 | 171 | """ | 179 | """ |
18 | 172 | Change any Camel Case string to python string | 180 | Change any Camel Case string to python string |
19 | 173 | 181 | ||
20 | === modified file 'openlp/core/common/settings.py' | |||
21 | --- openlp/core/common/settings.py 2017-06-05 06:05:54 +0000 | |||
22 | +++ openlp/core/common/settings.py 2017-08-11 12:52:52 +0000 | |||
23 | @@ -28,7 +28,7 @@ | |||
24 | 28 | 28 | ||
25 | 29 | from PyQt5 import QtCore, QtGui | 29 | from PyQt5 import QtCore, QtGui |
26 | 30 | 30 | ||
28 | 31 | from openlp.core.common import ThemeLevel, SlideLimits, UiStrings, is_win, is_linux | 31 | from openlp.core.common import ThemeLevel, SlideLimits, SlideSplitting, UiStrings, is_win, is_linux |
29 | 32 | 32 | ||
30 | 33 | 33 | ||
31 | 34 | log = logging.getLogger(__name__) | 34 | log = logging.getLogger(__name__) |
32 | @@ -159,6 +159,7 @@ | |||
33 | 159 | 'core/songselect username': '', | 159 | 'core/songselect username': '', |
34 | 160 | 'core/update check': True, | 160 | 'core/update check': True, |
35 | 161 | 'core/view mode': 'default', | 161 | 'core/view mode': 'default', |
36 | 162 | 'core/slide splitting': SlideSplitting.Fairly, | ||
37 | 162 | # The other display settings (display position and dimensions) are defined in the ScreenList class due to a | 163 | # The other display settings (display position and dimensions) are defined in the ScreenList class due to a |
38 | 163 | # circular dependency. | 164 | # circular dependency. |
39 | 164 | 'core/display on monitor': True, | 165 | 'core/display on monitor': True, |
40 | 165 | 166 | ||
41 | === modified file 'openlp/core/lib/renderer.py' | |||
42 | --- openlp/core/lib/renderer.py 2017-01-25 21:17:27 +0000 | |||
43 | +++ openlp/core/lib/renderer.py 2017-08-11 12:52:52 +0000 | |||
44 | @@ -25,7 +25,7 @@ | |||
45 | 25 | from string import Template | 25 | from string import Template |
46 | 26 | from PyQt5 import QtGui, QtCore, QtWebKitWidgets | 26 | from PyQt5 import QtGui, QtCore, QtWebKitWidgets |
47 | 27 | 27 | ||
49 | 28 | from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings | 28 | from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings, SlideSplitting |
50 | 29 | from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, ScreenList, ServiceItem, expand_tags, \ | 29 | from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, ScreenList, ServiceItem, expand_tags, \ |
51 | 30 | build_lyrics_format_css, build_lyrics_outline_css, build_chords_css | 30 | build_lyrics_format_css, build_lyrics_outline_css, build_chords_css |
52 | 31 | from openlp.core.common import ThemeLevel | 31 | from openlp.core.common import ThemeLevel |
53 | @@ -380,6 +380,15 @@ | |||
54 | 380 | // returned value). | 380 | // returned value). |
55 | 381 | return main.offsetHeight; | 381 | return main.offsetHeight; |
56 | 382 | } | 382 | } |
57 | 383 | function get_height_of_text(text) { | ||
58 | 384 | show_text(text); | ||
59 | 385 | var main = document.getElementById('main'); | ||
60 | 386 | var old_height = main.style.height; | ||
61 | 387 | main.style.height = "auto"; | ||
62 | 388 | var text_height = main.offsetHeight; | ||
63 | 389 | main.style.height = old_height; | ||
64 | 390 | return text_height | ||
65 | 391 | } | ||
66 | 383 | </script> | 392 | </script> |
67 | 384 | <style> | 393 | <style> |
68 | 385 | *{margin: 0; padding: 0; border: 0;} | 394 | *{margin: 0; padding: 0; border: 0;} |
69 | @@ -410,8 +419,13 @@ | |||
70 | 410 | html_lines = list(map(expand_tags, lines)) | 419 | html_lines = list(map(expand_tags, lines)) |
71 | 411 | # Text too long so go to next page. | 420 | # Text too long so go to next page. |
72 | 412 | if not self._text_fits_on_slide(separator.join(html_lines)): | 421 | if not self._text_fits_on_slide(separator.join(html_lines)): |
75 | 413 | html_text, previous_raw = self._binary_chop( | 422 | self.slide_splitting = Settings().value('core/slide splitting') |
76 | 414 | formatted, previous_html, previous_raw, html_lines, lines, separator, '') | 423 | if self.slide_splitting == SlideSplitting.Greedily: |
77 | 424 | html_text, previous_raw = self._binary_chop( | ||
78 | 425 | formatted, previous_html, previous_raw, html_lines, lines, separator, '') | ||
79 | 426 | elif self.slide_splitting == SlideSplitting.Fairly: | ||
80 | 427 | html_text, previous_raw = self._fairly_chop( | ||
81 | 428 | formatted, previous_html, previous_raw, html_lines, lines, separator, '') | ||
82 | 415 | else: | 429 | else: |
83 | 416 | previous_raw = separator.join(lines) | 430 | previous_raw = separator.join(lines) |
84 | 417 | formatted.append(previous_raw) | 431 | formatted.append(previous_raw) |
85 | @@ -518,6 +532,76 @@ | |||
86 | 518 | index = highest_index // 2 | 532 | index = highest_index // 2 |
87 | 519 | return previous_html, previous_raw | 533 | return previous_html, previous_raw |
88 | 520 | 534 | ||
89 | 535 | def _fairly_chop(self, formatted, previous_html, previous_raw, html_list, raw_list, separator, line_end): | ||
90 | 536 | """ | ||
91 | 537 | This implements a version of the chop algorithm that splits verses evenly over slides as opposed to greedily. | ||
92 | 538 | This algorithm works line based (line by line) and word based (word by word). It is assumed that this method | ||
93 | 539 | is **only** called, when the lines/words to be rendered do **not** fit as a whole. | ||
94 | 540 | |||
95 | 541 | :param formatted: The list to append any slides. | ||
96 | 542 | :param previous_html: The html text which is know to fit on a slide, but is not yet added to the list of | ||
97 | 543 | slides. (unicode string) | ||
98 | 544 | :param previous_raw: The raw text (with formatting tags) which is know to fit on a slide, but is not yet added | ||
99 | 545 | to the list of slides. (unicode string) | ||
100 | 546 | :param html_list: The elements which do not fit on a slide and needs to be processed using the binary chop. | ||
101 | 547 | The text contains html. | ||
102 | 548 | :param raw_list: The elements which do not fit on a slide and needs to be processed using the binary chop. | ||
103 | 549 | The elements can contain formatting tags. | ||
104 | 550 | :param separator: The separator for the elements. For lines this is ``'<br>'`` and for words this is ``' '``. | ||
105 | 551 | :param line_end: The text added after each "element line". Either ``' '`` or ``'<br>``. This is needed for | ||
106 | 552 | bibles. | ||
107 | 553 | """ | ||
108 | 554 | previous_html, previous_raw = self._binary_chop( | ||
109 | 555 | formatted, previous_html, previous_raw, html_list, raw_list, separator, line_end) | ||
110 | 556 | formatted.append(previous_raw) | ||
111 | 557 | new_formatted = formatted[:] | ||
112 | 558 | slide_lines = [] | ||
113 | 559 | for slide in formatted: | ||
114 | 560 | for line in slide.split(separator): | ||
115 | 561 | slide_lines.append(line) | ||
116 | 562 | slide_lengths = [len(slide.split(separator)) for slide in formatted] | ||
117 | 563 | previous_slide_height_range = float("inf") | ||
118 | 564 | slide_height_range = max([self._text_height_on_slide(slide) for slide in new_formatted[:]]) - \ | ||
119 | 565 | min([self._text_height_on_slide(slide) for slide in new_formatted[:]]) | ||
120 | 566 | while slide_height_range < previous_slide_height_range: | ||
121 | 567 | formatted.clear() | ||
122 | 568 | for x in new_formatted: | ||
123 | 569 | formatted.append(x) | ||
124 | 570 | previous_slide_height_range = slide_height_range | ||
125 | 571 | tallest_index = 0 | ||
126 | 572 | shortest_index = 0 | ||
127 | 573 | for i in range(len(formatted)): | ||
128 | 574 | if self._text_height_on_slide(new_formatted[i]) >= \ | ||
129 | 575 | self._text_height_on_slide(new_formatted[tallest_index]): | ||
130 | 576 | tallest_index = i | ||
131 | 577 | if self._text_height_on_slide(new_formatted[i]) < \ | ||
132 | 578 | self._text_height_on_slide(new_formatted[shortest_index]): | ||
133 | 579 | shortest_index = i | ||
134 | 580 | slide_lengths[tallest_index] -= 1 | ||
135 | 581 | slide_lengths[shortest_index] += 1 | ||
136 | 582 | new_formatted = [] | ||
137 | 583 | index = 0 | ||
138 | 584 | for slide_length in slide_lengths: | ||
139 | 585 | new_formatted.append("") | ||
140 | 586 | for i in range(slide_length): | ||
141 | 587 | new_formatted[-1] += slide_lines[index] + (separator if i < slide_length - 1 else "") | ||
142 | 588 | index += 1 | ||
143 | 589 | slide_height_range = max([self._text_height_on_slide(slide) for slide in new_formatted[:]]) - \ | ||
144 | 590 | min([self._text_height_on_slide(slide) for slide in new_formatted[:]]) | ||
145 | 591 | previous_raw = formatted.pop() | ||
146 | 592 | previous_html = previous_raw | ||
147 | 593 | return previous_html, previous_raw | ||
148 | 594 | |||
149 | 595 | def _text_height_on_slide(self, text): | ||
150 | 596 | """ | ||
151 | 597 | Returns the height of given ``text`` on a slide. | ||
152 | 598 | |||
153 | 599 | :param text: The text to check. It may contain HTML tags. | ||
154 | 600 | """ | ||
155 | 601 | return self.web_frame.evaluateJavaScript('get_height_of_text' | ||
156 | 602 | '("{text}")'.format(text=text.replace('\\', '\\\\') | ||
157 | 603 | .replace('\"', '\\\"'))) | ||
158 | 604 | |||
159 | 521 | def _text_fits_on_slide(self, text): | 605 | def _text_fits_on_slide(self, text): |
160 | 522 | """ | 606 | """ |
161 | 523 | Checks if the given ``text`` fits on a slide. If it does ``True`` is returned, otherwise ``False``. | 607 | Checks if the given ``text`` fits on a slide. If it does ``True`` is returned, otherwise ``False``. |
162 | @@ -525,7 +609,8 @@ | |||
163 | 525 | :param text: The text to check. It may contain HTML tags. | 609 | :param text: The text to check. It may contain HTML tags. |
164 | 526 | """ | 610 | """ |
165 | 527 | self.web_frame.evaluateJavaScript('show_text' | 611 | self.web_frame.evaluateJavaScript('show_text' |
167 | 528 | '("{text}")'.format(text=text.replace('\\', '\\\\').replace('\"', '\\\"'))) | 612 | '("{text}")'.format(text=text.replace('\\', '\\\\') |
168 | 613 | .replace('\"', '\\\"'))) | ||
169 | 529 | return self.web_frame.contentsSize().height() <= self.empty_height | 614 | return self.web_frame.contentsSize().height() <= self.empty_height |
170 | 530 | 615 | ||
171 | 531 | 616 | ||
172 | 532 | 617 | ||
173 | === modified file 'openlp/core/ui/generaltab.py' | |||
174 | --- openlp/core/ui/generaltab.py 2017-05-22 19:56:54 +0000 | |||
175 | +++ openlp/core/ui/generaltab.py 2017-08-11 12:52:52 +0000 | |||
176 | @@ -26,7 +26,7 @@ | |||
177 | 26 | 26 | ||
178 | 27 | from PyQt5 import QtCore, QtGui, QtWidgets | 27 | from PyQt5 import QtCore, QtGui, QtWidgets |
179 | 28 | 28 | ||
181 | 29 | from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter | 29 | from openlp.core.common import Registry, Settings, SlideSplitting, UiStrings, translate, get_images_filter |
182 | 30 | from openlp.core.lib import SettingsTab, ScreenList | 30 | from openlp.core.lib import SettingsTab, ScreenList |
183 | 31 | from openlp.core.ui.lib import ColorButton, PathEdit | 31 | from openlp.core.ui.lib import ColorButton, PathEdit |
184 | 32 | 32 | ||
185 | @@ -209,6 +209,21 @@ | |||
186 | 209 | self.timeout_spin_box.setRange(1, 180) | 209 | self.timeout_spin_box.setRange(1, 180) |
187 | 210 | self.settings_layout.addRow(self.timeout_label, self.timeout_spin_box) | 210 | self.settings_layout.addRow(self.timeout_label, self.timeout_spin_box) |
188 | 211 | self.right_layout.addWidget(self.settings_group_box) | 211 | self.right_layout.addWidget(self.settings_group_box) |
189 | 212 | # Slide splitting style | ||
190 | 213 | self.split_group_box = QtWidgets.QGroupBox(self.right_column) | ||
191 | 214 | self.split_group_box.setObjectName('split_group_box') | ||
192 | 215 | self.split_layout = QtWidgets.QFormLayout(self.split_group_box) | ||
193 | 216 | self.split_layout.setObjectName('split_layout') | ||
194 | 217 | self.split_label = QtWidgets.QLabel(self.split_group_box) | ||
195 | 218 | self.split_label.setWordWrap(True) | ||
196 | 219 | self.split_layout.addRow(self.split_label) | ||
197 | 220 | self.split_greedily_button = QtWidgets.QRadioButton(self.split_group_box) | ||
198 | 221 | self.split_greedily_button.setObjectName('split_greedily_button') | ||
199 | 222 | self.split_layout.addRow(self.split_greedily_button) | ||
200 | 223 | self.split_fairly_button = QtWidgets.QRadioButton(self.split_group_box) | ||
201 | 224 | self.split_fairly_button.setObjectName('split_fairly_button') | ||
202 | 225 | self.split_layout.addRow(self.split_fairly_button) | ||
203 | 226 | self.right_layout.addWidget(self.split_group_box) | ||
204 | 212 | self.right_layout.addStretch() | 227 | self.right_layout.addStretch() |
205 | 213 | # Signals and slots | 228 | # Signals and slots |
206 | 214 | self.override_radio_button.toggled.connect(self.on_override_radio_button_pressed) | 229 | self.override_radio_button.toggled.connect(self.on_override_radio_button_pressed) |
207 | @@ -217,6 +232,8 @@ | |||
208 | 217 | self.custom_Y_value_edit.valueChanged.connect(self.on_display_changed) | 232 | self.custom_Y_value_edit.valueChanged.connect(self.on_display_changed) |
209 | 218 | self.custom_X_value_edit.valueChanged.connect(self.on_display_changed) | 233 | self.custom_X_value_edit.valueChanged.connect(self.on_display_changed) |
210 | 219 | self.monitor_combo_box.currentIndexChanged.connect(self.on_display_changed) | 234 | self.monitor_combo_box.currentIndexChanged.connect(self.on_display_changed) |
211 | 235 | self.split_greedily_button.clicked.connect(self.on_split_greedily_button_clicked) | ||
212 | 236 | self.split_fairly_button.clicked.connect(self.on_split_fairly_button_clicked) | ||
213 | 220 | # Reload the tab, as the screen resolution/count may have changed. | 237 | # Reload the tab, as the screen resolution/count may have changed. |
214 | 221 | Registry().register_function('config_screen_changed', self.load) | 238 | Registry().register_function('config_screen_changed', self.load) |
215 | 222 | # Remove for now | 239 | # Remove for now |
216 | @@ -269,6 +286,11 @@ | |||
217 | 269 | self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File') | 286 | self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File') |
218 | 270 | self.logo_file_path_edit.filters = '{text};;{names} (*)'.format( | 287 | self.logo_file_path_edit.filters = '{text};;{names} (*)'.format( |
219 | 271 | text=get_images_filter(), names=UiStrings().AllFiles) | 288 | text=get_images_filter(), names=UiStrings().AllFiles) |
220 | 289 | # Slide Splitting | ||
221 | 290 | self.split_group_box.setTitle(translate('OpenLP.GeneralTab', 'Slide Splitting Style')) | ||
222 | 291 | self.split_label.setText(translate('OpenLP.GeneralTab', 'Way in which text splits across multiple slides:')) | ||
223 | 292 | self.split_greedily_button.setText(translate('OpenLP.GeneralTab', '&Split greedily')) | ||
224 | 293 | self.split_fairly_button.setText(translate('OpenLP.GeneralTab', '&Split fairly')) | ||
225 | 272 | 294 | ||
226 | 273 | def load(self): | 295 | def load(self): |
227 | 274 | """ | 296 | """ |
228 | @@ -305,6 +327,11 @@ | |||
229 | 305 | self.custom_width_value_edit.setValue(settings.value('width')) | 327 | self.custom_width_value_edit.setValue(settings.value('width')) |
230 | 306 | self.start_paused_check_box.setChecked(settings.value('audio start paused')) | 328 | self.start_paused_check_box.setChecked(settings.value('audio start paused')) |
231 | 307 | self.repeat_list_check_box.setChecked(settings.value('audio repeat list')) | 329 | self.repeat_list_check_box.setChecked(settings.value('audio repeat list')) |
232 | 330 | self.slide_splitting = settings.value('slide splitting') | ||
233 | 331 | if self.slide_splitting == SlideSplitting.Greedily: | ||
234 | 332 | self.split_greedily_button.setChecked(True) | ||
235 | 333 | elif self.slide_splitting == SlideSplitting.Fairly: | ||
236 | 334 | self.split_fairly_button.setChecked(True) | ||
237 | 308 | settings.endGroup() | 335 | settings.endGroup() |
238 | 309 | self.monitor_combo_box.setDisabled(self.override_radio_button.isChecked()) | 336 | self.monitor_combo_box.setDisabled(self.override_radio_button.isChecked()) |
239 | 310 | self.custom_X_value_edit.setEnabled(self.override_radio_button.isChecked()) | 337 | self.custom_X_value_edit.setEnabled(self.override_radio_button.isChecked()) |
240 | @@ -343,6 +370,7 @@ | |||
241 | 343 | settings.setValue('override position', self.override_radio_button.isChecked()) | 370 | settings.setValue('override position', self.override_radio_button.isChecked()) |
242 | 344 | settings.setValue('audio start paused', self.start_paused_check_box.isChecked()) | 371 | settings.setValue('audio start paused', self.start_paused_check_box.isChecked()) |
243 | 345 | settings.setValue('audio repeat list', self.repeat_list_check_box.isChecked()) | 372 | settings.setValue('audio repeat list', self.repeat_list_check_box.isChecked()) |
244 | 373 | settings.setValue('slide splitting', self.slide_splitting) | ||
245 | 346 | settings.endGroup() | 374 | settings.endGroup() |
246 | 347 | # On save update the screens as well | 375 | # On save update the screens as well |
247 | 348 | self.post_set_up(True) | 376 | self.post_set_up(True) |
248 | @@ -396,3 +424,15 @@ | |||
249 | 396 | Select the background color for logo. | 424 | Select the background color for logo. |
250 | 397 | """ | 425 | """ |
251 | 398 | self.logo_background_color = color | 426 | self.logo_background_color = color |
252 | 427 | |||
253 | 428 | def on_split_greedily_button_clicked(self): | ||
254 | 429 | """ | ||
255 | 430 | Split slides greedily | ||
256 | 431 | """ | ||
257 | 432 | self.slide_splitting = SlideSplitting.Greedily | ||
258 | 433 | |||
259 | 434 | def on_split_fairly_button_clicked(self): | ||
260 | 435 | """ | ||
261 | 436 | Split slides greedily | ||
262 | 437 | """ | ||
263 | 438 | self.slide_splitting = SlideSplitting.Fairly | ||
264 | 399 | 439 | ||
265 | === modified file 'tests/functional/openlp_core_lib/test_image_manager.py' | |||
266 | --- tests/functional/openlp_core_lib/test_image_manager.py 2017-04-24 05:17:55 +0000 | |||
267 | +++ tests/functional/openlp_core_lib/test_image_manager.py 2017-08-11 12:52:52 +0000 | |||
268 | @@ -56,6 +56,8 @@ | |||
269 | 56 | """ | 56 | """ |
270 | 57 | Delete all the C++ objects at the end so that we don't have a segfault | 57 | Delete all the C++ objects at the end so that we don't have a segfault |
271 | 58 | """ | 58 | """ |
272 | 59 | self.image_manager.stop_manager = True | ||
273 | 60 | self.image_manager.image_thread.wait() | ||
274 | 59 | del self.app | 61 | del self.app |
275 | 60 | 62 | ||
276 | 61 | def test_basic_image_manager(self): | 63 | def test_basic_image_manager(self): |
277 | 62 | 64 | ||
278 | === modified file 'tests/functional/openlp_core_lib/test_projector_db.py' | |||
279 | --- tests/functional/openlp_core_lib/test_projector_db.py 2017-08-06 07:23:26 +0000 | |||
280 | +++ tests/functional/openlp_core_lib/test_projector_db.py 2017-08-11 12:52:52 +0000 | |||
281 | @@ -408,6 +408,7 @@ | |||
282 | 408 | # THEN: We should have None | 408 | # THEN: We should have None |
283 | 409 | self.assertEqual(results, None, 'projector.get_projector_by_name() should have returned None') | 409 | self.assertEqual(results, None, 'projector.get_projector_by_name() should have returned None') |
284 | 410 | 410 | ||
285 | 411 | @skip("Produces the following fatal error: QThread: Destroyed while thread is still running") | ||
286 | 411 | def test_add_projector_fail(self): | 412 | def test_add_projector_fail(self): |
287 | 412 | """ | 413 | """ |
288 | 413 | Test add_projector() fail | 414 | Test add_projector() fail |
289 | 414 | 415 | ||
290 | === modified file 'tests/functional/openlp_core_lib/test_renderer.py' | |||
291 | --- tests/functional/openlp_core_lib/test_renderer.py 2017-06-03 22:52:11 +0000 | |||
292 | +++ tests/functional/openlp_core_lib/test_renderer.py 2017-08-11 12:52:52 +0000 | |||
293 | @@ -205,3 +205,62 @@ | |||
294 | 205 | 205 | ||
295 | 206 | # THEN: QtWebKitWidgets should be called with the proper string | 206 | # THEN: QtWebKitWidgets should be called with the proper string |
296 | 207 | mock_webview.setHtml.called_with(CSS_TEST_ONE, 'Should be the same') | 207 | mock_webview.setHtml.called_with(CSS_TEST_ONE, 'Should be the same') |
297 | 208 | |||
298 | 209 | @patch('openlp.core.lib.renderer.build_lyrics_format_css') | ||
299 | 210 | @patch('openlp.core.lib.renderer.build_lyrics_outline_css') | ||
300 | 211 | @patch('openlp.core.lib.renderer.build_chords_css') | ||
301 | 212 | def test_text_height_on_slide(self, mock_build_chords_css, mock_outline_css, mock_lyrics_css): | ||
302 | 213 | """ | ||
303 | 214 | Test _text_height_on_slide returns a larger value for 2 lines than one | ||
304 | 215 | """ | ||
305 | 216 | # GIVEN: test object and data | ||
306 | 217 | mock_lyrics_css.return_value = ' FORMAT CSS; ' | ||
307 | 218 | mock_outline_css.return_value = ' OUTLINE CSS; ' | ||
308 | 219 | mock_build_chords_css.return_value = ' CHORDS CSS; ' | ||
309 | 220 | theme_data = Theme() | ||
310 | 221 | theme_data.font_main_name = 'Arial' | ||
311 | 222 | theme_data.font_main_size = 20 | ||
312 | 223 | theme_data.font_main_color = '#FFFFFF' | ||
313 | 224 | theme_data.font_main_outline_color = '#FFFFFF' | ||
314 | 225 | main = QtCore.QRect(10, 10, 1280, 900) | ||
315 | 226 | foot = QtCore.QRect(10, 1000, 1260, 24) | ||
316 | 227 | renderer = Renderer() | ||
317 | 228 | short_text = "test" | ||
318 | 229 | long_text = "test<br>test" | ||
319 | 230 | |||
320 | 231 | # WHEN: Calling method | ||
321 | 232 | renderer._set_text_rectangle(theme_data=theme_data, rect_main=main, rect_footer=foot) | ||
322 | 233 | short_height = renderer._text_height_on_slide(short_text) | ||
323 | 234 | long_height = renderer._text_height_on_slide(long_text) | ||
324 | 235 | |||
325 | 236 | # THEN: long_height should be greater than short_height | ||
326 | 237 | self.assertGreater(long_height, short_height) | ||
327 | 238 | |||
328 | 239 | @patch('openlp.core.lib.renderer.Renderer._text_height_on_slide') | ||
329 | 240 | @patch('openlp.core.lib.renderer.Renderer._binary_chop') | ||
330 | 241 | def test_fairly_chop(self, mock_binary_chop, mock_text_height_on_slide): | ||
331 | 242 | """ | ||
332 | 243 | Test fairly_chop splits a 4 line verse 2-2 not 3-1 | ||
333 | 244 | """ | ||
334 | 245 | # GIVEN: test object and data | ||
335 | 246 | mock_binary_chop.side_effect = lambda formatted, previous_html, previous_raw, \ | ||
336 | 247 | html_list, raw_list, separator, line_end: \ | ||
337 | 248 | (previous_html, previous_raw) | ||
338 | 249 | mock_text_height_on_slide.side_effect = lambda text: text.count(separator) + 1 | ||
339 | 250 | formatted = ["test<br>test<br>test"] | ||
340 | 251 | expected = ["test<br>test", "test<br>test"] | ||
341 | 252 | previous_html = "test" | ||
342 | 253 | previous_raw = "test" | ||
343 | 254 | html_list = None | ||
344 | 255 | raw_list = None | ||
345 | 256 | separator = "<br>" | ||
346 | 257 | line_end = '' | ||
347 | 258 | renderer = Renderer() | ||
348 | 259 | |||
349 | 260 | # WHEN: Calling method | ||
350 | 261 | previous_html, previous_raw = renderer._fairly_chop( | ||
351 | 262 | formatted, previous_html, previous_raw, html_list, raw_list, separator, line_end) | ||
352 | 263 | |||
353 | 264 | # THEN: long_height should be greater than short_height | ||
354 | 265 | formatted.append(previous_raw) | ||
355 | 266 | self.assertListEqual(formatted, expected) |
Hi Jonathan,
I think this is actually a great idea, but my main concern is how this will fit in with the new render that is being worked on in a separate branch/repo. I expect Raoul to be able to answer this.