Merge lp:~phill-ridout/openlp/bug1209515 into lp:openlp

Proposed by Phill
Status: Merged
Approved by: Tim Bentley
Approved revision: 2292
Merged at revision: 2314
Proposed branch: lp:~phill-ridout/openlp/bug1209515
Merge into: lp:openlp
Diff against target: 287 lines (+151/-10)
9 files modified
openlp/core/common/uistrings.py (+2/-0)
openlp/core/lib/__init__.py (+1/-1)
openlp/core/lib/filedialog.py (+66/-0)
openlp/core/lib/mediamanageritem.py (+2/-2)
openlp/core/ui/thememanager.py (+2/-2)
openlp/plugins/songs/forms/editsongform.py (+2/-2)
openlp/plugins/songs/forms/songimportform.py (+2/-2)
tests/functional/openlp_core_lib/test_file_dialog.py (+73/-0)
tests/functional/openlp_core_lib/test_htmlbuilder.py (+1/-1)
To merge this branch: bzr merge lp:~phill-ridout/openlp/bug1209515
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Raoul Snyman Approve
Review via email: mp+195486@code.launchpad.net

This proposal supersedes a proposal from 2013-11-16.

Description of the change

Fixes Bug 1209515 by sub classing QFileDialog and reimplementing getOpenFileNames and attempting to urlunquote and file paths which do not exist

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Please resubmit due to the age of this request and additionally it will need converting to Python 3 as on 1/9/2013 truck will be converted.

review: Needs Resubmitting
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Please merge from trunk.

review: Needs Resubmitting
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
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/uistrings.py'
2--- openlp/core/common/uistrings.py 2013-10-13 20:36:42 +0000
3+++ openlp/core/common/uistrings.py 2013-11-16 21:11:06 +0000
4@@ -83,6 +83,8 @@
5 self.Error = translate('OpenLP.Ui', 'Error')
6 self.Export = translate('OpenLP.Ui', 'Export')
7 self.File = translate('OpenLP.Ui', 'File')
8+ self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
9+ self.FileNotFoundMessage = translate('OpenLP.Ui', 'File %s not found.\nPlease try selecting it individually.')
10 self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
11 self.Help = translate('OpenLP.Ui', 'Help')
12 self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
13
14=== modified file 'openlp/core/lib/__init__.py'
15--- openlp/core/lib/__init__.py 2013-10-13 20:36:42 +0000
16+++ openlp/core/lib/__init__.py 2013-11-16 21:11:06 +0000
17@@ -330,6 +330,7 @@
18
19
20 from .registry import Registry
21+from .filedialog import FileDialog
22 from .screen import ScreenList
23 from .listwidgetwithdnd import ListWidgetWithDnD
24 from .treewidgetwithdnd import TreeWidgetWithDnD
25@@ -345,4 +346,3 @@
26 from .imagemanager import ImageManager
27 from .renderer import Renderer
28 from .mediamanageritem import MediaManagerItem
29-
30
31=== added file 'openlp/core/lib/filedialog.py'
32--- openlp/core/lib/filedialog.py 1970-01-01 00:00:00 +0000
33+++ openlp/core/lib/filedialog.py 2013-11-16 21:11:06 +0000
34@@ -0,0 +1,66 @@
35+# -*- coding: utf-8 -*-
36+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
37+
38+###############################################################################
39+# OpenLP - Open Source Lyrics Projection #
40+# --------------------------------------------------------------------------- #
41+# Copyright (c) 2008-2013 Raoul Snyman #
42+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
43+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
44+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
45+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
46+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
47+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
48+# Frode Woldsund, Martin Zibricky #
49+# --------------------------------------------------------------------------- #
50+# This program is free software; you can redistribute it and/or modify it #
51+# under the terms of the GNU General Public License as published by the Free #
52+# Software Foundation; version 2 of the License. #
53+# #
54+# This program is distributed in the hope that it will be useful, but WITHOUT #
55+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
56+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
57+# more details. #
58+# #
59+# You should have received a copy of the GNU General Public License along #
60+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
61+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
62+###############################################################################
63+
64+"""
65+Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
66+"""
67+import logging
68+import os
69+from urllib import parse
70+
71+from PyQt4 import QtGui
72+
73+from openlp.core.common import UiStrings
74+
75+log = logging.getLogger(__name__)
76+
77+class FileDialog(QtGui.QFileDialog):
78+ """
79+ Subclass QFileDialog to work round a bug
80+ """
81+ @staticmethod
82+ def getOpenFileNames(parent, *args, **kwargs):
83+ """
84+ Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
85+ files
86+ """
87+ files = QtGui.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
88+ file_list = []
89+ for file in files:
90+ if not os.path.exists(file):
91+ log.info('File %s not found. Attempting to unquote.')
92+ file = parse.unquote(file)
93+ if not os.path.exists(file):
94+ log.error('File %s not found.' % file)
95+ QtGui.QMessageBox.information(parent, UiStrings().FileNotFound,
96+ UiStrings().FileNotFoundMessage % file)
97+ continue
98+ log.info('File %s found.')
99+ file_list.append(file)
100+ return file_list
101\ No newline at end of file
102
103=== modified file 'openlp/core/lib/mediamanageritem.py'
104--- openlp/core/lib/mediamanageritem.py 2013-10-13 20:36:42 +0000
105+++ openlp/core/lib/mediamanageritem.py 2013-11-16 21:11:06 +0000
106@@ -36,7 +36,7 @@
107 from PyQt4 import QtCore, QtGui
108
109 from openlp.core.common import Settings, UiStrings, translate
110-from openlp.core.lib import OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \
111+from openlp.core.lib import FileDialog, OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \
112 ServiceItemContext, Registry
113 from openlp.core.lib.searchedit import SearchEdit
114 from openlp.core.lib.ui import create_widget_action, critical_error_message_box
115@@ -319,7 +319,7 @@
116 """
117 Add a file to the list widget to make it available for showing
118 """
119- files = QtGui.QFileDialog.getOpenFileNames(self, self.on_new_prompt,
120+ files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
121 Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks)
122 log.info('New files(s) %s', files)
123 if files:
124
125=== modified file 'openlp/core/ui/thememanager.py'
126--- openlp/core/ui/thememanager.py 2013-10-13 21:07:28 +0000
127+++ openlp/core/ui/thememanager.py 2013-11-16 21:11:06 +0000
128@@ -39,7 +39,7 @@
129 from PyQt4 import QtCore, QtGui
130
131 from openlp.core.common import AppLocation, Settings, check_directory_exists, UiStrings, translate
132-from openlp.core.lib import ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \
133+from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \
134 check_item_selected, create_thumb, validate_thumb
135 from openlp.core.lib.theme import ThemeXML, BackgroundType
136 from openlp.core.lib.ui import critical_error_message_box, create_widget_action
137@@ -374,7 +374,7 @@
138 Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
139 those files. This process will load both OpenLP version 1 and version 2 themes.
140 """
141- files = QtGui.QFileDialog.getOpenFileNames(self,
142+ files = FileDialog.getOpenFileNames(self,
143 translate('OpenLP.ThemeManager', 'Select Theme Import File'),
144 Settings().value(self.settings_section + '/last directory import'),
145 translate('OpenLP.ThemeManager', 'OpenLP Themes (*.theme *.otz)'))
146
147=== modified file 'openlp/plugins/songs/forms/editsongform.py'
148--- openlp/plugins/songs/forms/editsongform.py 2013-10-13 20:36:42 +0000
149+++ openlp/plugins/songs/forms/editsongform.py 2013-11-16 21:11:06 +0000
150@@ -39,7 +39,7 @@
151 from PyQt4 import QtCore, QtGui
152
153 from openlp.core.common import AppLocation, UiStrings, check_directory_exists, translate
154-from openlp.core.lib import Registry, PluginStatus, MediaType, create_separated_list
155+from openlp.core.lib import FileDialog, Registry, PluginStatus, MediaType, create_separated_list
156 from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
157 from openlp.plugins.songs.lib import VerseType, clean_song
158 from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
159@@ -758,7 +758,7 @@
160 Loads file(s) from the filesystem.
161 """
162 filters = '%s (*)' % UiStrings().AllFiles
163- filenames = QtGui.QFileDialog.getOpenFileNames(self,
164+ filenames = FileDialog.getOpenFileNames(self,
165 translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters)
166 for filename in filenames:
167 item = QtGui.QListWidgetItem(os.path.split(str(filename))[1])
168
169=== modified file 'openlp/plugins/songs/forms/songimportform.py'
170--- openlp/plugins/songs/forms/songimportform.py 2013-10-13 20:36:42 +0000
171+++ openlp/plugins/songs/forms/songimportform.py 2013-11-16 21:11:06 +0000
172@@ -37,7 +37,7 @@
173
174 from openlp.core.common import UiStrings, translate
175 from openlp.core.common import Settings
176-from openlp.core.lib import Registry
177+from openlp.core.lib import FileDialog, Registry
178 from openlp.core.lib.ui import critical_error_message_box
179 from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
180 from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
181@@ -246,7 +246,7 @@
182 if filters:
183 filters += ';;'
184 filters += '%s (*)' % UiStrings().AllFiles
185- filenames = QtGui.QFileDialog.getOpenFileNames(self, title,
186+ filenames = FileDialog.getOpenFileNames(self, title,
187 Settings().value(self.plugin.settings_section + '/last directory import'), filters)
188 if filenames:
189 listbox.addItems(filenames)
190
191=== added file 'tests/functional/openlp_core_lib/test_file_dialog.py'
192--- tests/functional/openlp_core_lib/test_file_dialog.py 1970-01-01 00:00:00 +0000
193+++ tests/functional/openlp_core_lib/test_file_dialog.py 2013-11-16 21:11:06 +0000
194@@ -0,0 +1,73 @@
195+"""
196+Package to test the openlp.core.lib.filedialog package.
197+"""
198+from unittest import TestCase
199+
200+from openlp.core.common import UiStrings
201+from openlp.core.lib.filedialog import FileDialog
202+from tests.functional import MagicMock, patch
203+
204+class TestFileDialog(TestCase):
205+ """
206+ Test the functions in the :mod:`filedialog` module.
207+ """
208+ def setUp(self):
209+ self.os_patcher = patch('openlp.core.lib.filedialog.os')
210+ self.qt_gui_patcher = patch('openlp.core.lib.filedialog.QtGui')
211+ self.ui_strings_patcher = patch('openlp.core.lib.filedialog.UiStrings')
212+ self.mocked_os = self.os_patcher.start()
213+ self.mocked_qt_gui = self.qt_gui_patcher.start()
214+ self.mocked_ui_strings = self.ui_strings_patcher.start()
215+ self.mocked_parent = MagicMock()
216+
217+ def tearDown(self):
218+ self.os_patcher.stop()
219+ self.qt_gui_patcher.stop()
220+ self.ui_strings_patcher.stop()
221+
222+ def get_open_file_names_canceled_test(self):
223+ """
224+ Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
225+ (returns an empty QStringList)
226+ """
227+ self.mocked_os.reset()
228+
229+ # GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
230+ self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = []
231+
232+ # WHEN: FileDialog.getOpenFileNames is called
233+ result = FileDialog.getOpenFileNames(self.mocked_parent)
234+
235+ # THEN: The returned value should be an empty QStringList and os.path.exists should not have been called
236+ assert not self.mocked_os.path.exists.called
237+ self.assertEqual(result, [],
238+ 'FileDialog.getOpenFileNames should return and empty list when QFileDialog.getOpenFileNames is canceled')
239+
240+ def returned_file_list_test(self):
241+ """
242+ Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
243+ returns a good file name, a urlencoded file name and a non-existing file
244+ """
245+ self.mocked_os.rest()
246+ self.mocked_qt_gui.reset()
247+
248+ # GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid
249+ # file names.
250+ self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = [
251+ '/Valid File', '/url%20encoded%20file%20%231', '/non-existing']
252+ self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
253+ '/Valid File', '/url encoded file #1']
254+
255+ # WHEN: FileDialog.getOpenFileNames is called
256+ result = FileDialog.getOpenFileNames(self.mocked_parent)
257+
258+ # THEN: os.path.exists should have been called with known args. QmessageBox.information should have been
259+ # called. The returned result should corrilate with the input.
260+ self.mocked_os.path.exists.assert_callde_with('/Valid File')
261+ self.mocked_os.path.exists.assert_callde_with('/url%20encoded%20file%20%231')
262+ self.mocked_os.path.exists.assert_callde_with('/url encoded file #1')
263+ self.mocked_os.path.exists.assert_callde_with('/non-existing')
264+ self.mocked_os.path.exists.assert_callde_with('/non-existing')
265+ self.mocked_qt_gui.QmessageBox.information.called_with(self.mocked_parent, UiStrings().FileNotFound,
266+ UiStrings().FileNotFoundMessage % '/non-existing')
267+ self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')
268\ No newline at end of file
269
270=== modified file 'tests/functional/openlp_core_lib/test_htmlbuilder.py'
271--- tests/functional/openlp_core_lib/test_htmlbuilder.py 2013-10-21 07:38:18 +0000
272+++ tests/functional/openlp_core_lib/test_htmlbuilder.py 2013-11-16 21:11:06 +0000
273@@ -3,13 +3,13 @@
274 """
275
276 from unittest import TestCase
277-from mock import MagicMock, patch
278
279 from PyQt4 import QtCore
280
281 from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \
282 build_lyrics_format_css, build_footer_css
283 from openlp.core.lib.theme import HorizontalType, VerticalType
284+from tests.functional import MagicMock, patch
285
286
287 HTML = """