source: orange/Orange/OrangeCanvas/resources.py @ 11099:a98ee714623b

Revision 11099:a98ee714623b, 7.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added a basic icon loader.

Line 
1"""
2Orange Canvas Resource Loader
3
4"""
5
6import os
7import logging
8
9log = logging.getLogger(__name__)
10
11
12def package_dirname(package):
13    """Return the directory path where package is located.
14
15    """
16    if isinstance(package, basestring):
17        package = __import__(package, fromlist=[""])
18    filename = package.__file__
19    dirname = os.path.dirname(filename)
20    return dirname
21
22
23def package(qualified_name):
24    """Return the enclosing package name where qualified_name is located.
25
26    `qualified_name` can be a module inside the package or even an object
27    inside the module. If a package name itself is provided it is returned.
28
29    """
30    try:
31        module = __import__(qualified_name, fromlist=[""])
32    except ImportError:
33        # qualified_name could name an object inside a module/package
34        if "." in qualified_name:
35            qualified_name, attr_name = qualified_name.rsplit(".", 1)
36            module = __import__(qualified_name, fromlist=[attr_name])
37        else:
38            raise
39
40    if module.__package__ is not None:
41        # the modules enclosing package
42        return module.__package__
43    else:
44        # 'qualified_name' is itself the package
45        assert(module.__name__ == qualified_name)
46        return qualified_name
47
48dirname = os.path.abspath(os.path.dirname(__file__))
49
50DEFAULT_SEARCH_PATHS = \
51    [("", dirname),
52     ("", os.path.join(dirname, "../OrangeWidgets"))]
53
54del dirname
55
56
57def default_search_paths():
58    return DEFAULT_SEARCH_PATHS
59
60
61def add_default_search_paths(search_paths):
62    DEFAULT_SEARCH_PATHS.extend(search_paths)
63
64
65def search_paths_from_description(desc):
66    """Return the search paths for the Category/WidgetDescription.
67    """
68    paths = []
69    if desc.package:
70        dirname = package_dirname(desc.package)
71        paths.append(("", dirname))
72    elif desc.qualified_name:
73        dirname = package_dirname(package(desc.qualified_name))
74        paths.append(("", dirname))
75
76    if hasattr(desc, "search_paths"):
77        paths.extend(desc.search_paths)
78    return paths
79
80
81class resource_loader(object):
82    def __init__(self, search_paths=[]):
83        self._search_paths = []
84        self.add_search_paths(search_paths)
85
86    @classmethod
87    def from_description(cls, desc):
88        """Construct an resource from a Widget or Category
89        description.
90
91        """
92        paths = search_paths_from_description(desc)
93        return icon_loader(search_paths=paths)
94
95    def add_search_paths(self, paths):
96        """Add `paths` to the list of search paths.
97        """
98        self._search_paths.extend(paths)
99
100    def search_paths(self):
101        """Return a list of all search paths.
102        """
103        return self._search_paths + default_search_paths()
104
105    def split_prefix(self, path):
106        """Split prefixed path.
107        """
108        if self.is_valid_prefixed(path) and ":" in path:
109            prefix, path = path.split(":", 1)
110        else:
111            prefix = ""
112        return prefix, path
113
114    def is_valid_prefixed(self, path):
115        i = path.find(":")
116        return i != 1
117
118    def find(self, name):
119        """Find a resource matching `name`.
120        """
121        prefix, path = self.split_prefix(name)
122        if prefix == "" and self.match(path):
123            return path
124        elif self.is_valid_prefixed(path):
125            for pp, search_path in self.search_paths():
126                if pp == prefix and \
127                        self.match(os.path.join(search_path, path)):
128                    return os.path.join(search_path, path)
129
130        return None
131
132    def match(self, path):
133        return os.path.exists(path)
134
135    def get(self, name):
136        return self.load(name)
137
138    def load(self, name):
139        return self.open(name).read()
140
141    def open(self, name):
142        path = self.find(name)
143        if path is not None:
144            return open(path, "rb")
145        else:
146            raise IOError(2, "Cannot find %r" % name)
147
148import glob
149
150
151class icon_loader(resource_loader):
152    DEFAULT_ICON = "icons/Unknown.png"
153
154    def match(self, path):
155        if resource_loader.match(self, path):
156            return True
157        return self.is_icon_glob(path)
158
159    def icon_glob(self, path):
160        name, ext = os.path.splitext(path)
161        pattern = name + "_*" + ext
162        return glob.glob(pattern)
163
164    def is_icon_glob(self, path):
165        name, ext = os.path.splitext(path)
166        pattern = name + "_*" + ext
167        return bool(glob.glob(pattern))
168
169    def get(self, name, default=None):
170        path = self.find(name)
171        if not path:
172            path = self.find(self.DEFAULT_ICON if default is None else default)
173        if not path:
174            raise IOError(2, "Cannot find %r in %s" % \
175                          (name, self.search_paths()))
176        if self.is_icon_glob(path):
177            icons = self.icon_glob(path)
178        else:
179            icons = [path]
180
181        from PyQt4.QtGui import QIcon
182
183        icon = QIcon()
184        for path in icons:
185            icon.addFile(path)
186        return icon
187
188    def open(self, name):
189        raise NotImplementedError
190
191    def load(self, name):
192        return self.get(name)
193
194
195import unittest
196
197
198class TestIconLoader(unittest.TestCase):
199    def setUp(self):
200        from PyQt4.QtGui import QApplication
201        self.app = QApplication([])
202
203    def tearDown(self):
204        self.app.exit()
205        del self.app
206
207    def test_loader(self):
208        loader = icon_loader()
209        self.assertEqual(loader.search_paths(), DEFAULT_SEARCH_PATHS)
210        icon = loader.get("icons/CanvasIcon.png")
211        self.assertTrue(not icon.isNull())
212
213        path = loader.find(":icons/CanvasIcon.png")
214        self.assertTrue(os.path.isfile(path))
215        icon = loader.get(":icons/CanvasIcon.png")
216        self.assertTrue(not icon.isNull())
217
218    def test_from_desc(self):
219        from .registry.description import (
220            WidgetDescription, CategoryDescription
221        )
222
223        desc = WidgetDescription.from_module(
224            "Orange.OrangeWidgets.Data.OWFile"
225        )
226
227        loader = icon_loader.from_description(desc)
228        path = loader.find(desc.icon)
229        self.assertTrue(os.path.isfile(path))
230        icon = loader.get(desc.icon)
231        self.assertTrue(not icon.isNull())
232
233        desc = CategoryDescription.from_package("Orange.OrangeWidgets.Data")
234        loader = icon_loader.from_description(desc)
235        path = loader.find("icons/file.svg")
236        self.assertTrue(os.path.isfile(path))
237        icon = loader.get("icons/file.svg")
238        self.assertTrue(not icon.isNull())
239
240    def test_package_reflection(self):
241        from Orange.OrangeWidgets.Data import OWFile
242        from Orange.OrangeWidgets import Data
243        package_name = Data.__name__
244        p1 = package("Orange.OrangeWidgets.Data.OWFile.OWFile")
245        self.assertEqual(p1, package_name)
246
247        p2 = package("Orange.OrangeWidgets.Data.OWFile")
248        self.assertEqual(p2, package_name)
249
250        p3 = package("Orange.OrangeWidgets.Data")
251        self.assertEqual(p3, package_name)
252
253        p4 = package(OWFile.__name__)
254        self.assertEqual(p4, package_name)
255
256        dirname = package_dirname(package_name)
257        self.assertEqual(dirname, os.path.dirname(Data.__file__))
258
259
260if __name__ == "__main__":
261    unittest.main()
Note: See TracBrowser for help on using the repository browser.