2
"""Contains ui modules to build a finder-like browser for items of any kind"""
3
__docformat__ = "restructuredtext"
5
from interface import (iFinderProvider, iFinderFilter)
7
import mrv.maya.ui as ui
8
from mrv.maya.util import (OptionVarDict, logException )
9
from util import concat_url
11
from mrv.path import Path
14
__all__ = ( 'FileProvider', 'BookmarkControl',
15
'FilePathControl', 'FileFilterControl', 'FinderElement',
16
'FileStack', 'FileRootSelectorControl', 'FilePathControlEditable')
22
class FileProvider(iFinderProvider):
23
"""Implements a provider for a file system"""
26
def __init__(self, root):
27
super(FileProvider, self).__init__(root)
28
self._root = Path(self._root)
30
def formatItem(self, url_base, url_index, url_item):
33
def urlItems(self, url):
34
"""Return directory items alphabetically, directories first"""
35
path = self._root / url
36
dirs, files = list(), list()
39
for abs_path in path.listdir():
43
files.append(abs_path)
45
# END for each listed path
48
return [abspath.basename() for abspath in (dirs + files)]
50
# ignore attempts to get path on a file for instance
52
# END exception handling
55
class StackControlBase(ui.TextScrollList):
56
"""stack base implementation. A stack keeps multiple items which can be added
57
and removed. Additionally, it allows to remap the items, effectively showing
58
a formatted item, which is kept in sync with an unformatted item.
59
:note: for now, only adding items, format will be applied. All other methods
60
operate on the formatted items."""
63
def __init__(self, *args, **kwargs):
64
super(StackControlBase, self).__init__(*args, **kwargs)
66
self.base_items = list()
70
def formatItem(self, item):
71
""":return: formatted version of item"""
74
def selectedUnformattedItem(self):
75
""":return: unformatted selected item or None"""
76
index = self.selectedIndex()
79
return self.base_items[index-1]
81
def selectUnformattedItem(self, index_or_item):
82
"""Select the unformatted item as identified by either the index or item
83
:param index_or_item: integer representing the 0-based index of the item to
84
select, or the item's id
85
:raise ValueError: if the item does not exist"""
87
if not isinstance(index_or_item, int):
88
index = self.base_items.index(index_or_item)
89
self.p_selectIndexedItem = index+1
95
def removeItem(self, item):
96
"""Remove the given formatted item from the list, as well as the corresponding
97
unformtted item. Its not an error if the item does not exist"""
99
index = self.items().index(item)
100
del(self.base_items[index])
101
super(StackControlBase, self).removeItem(item)
102
except (ValueError, IndexError):
104
# END exception handling
107
def addItem(self, item):
108
self.base_items.append(item)
109
return super(StackControlBase, self).addItem(self.formatItem(item))
111
#} END overridden methods
114
class FinderElement(StackControlBase):
115
"""Element with special abilities to suite the finder better. This involves
116
keeping a list of unformatted items which can be used as unique item identifiers.
118
Set the items to a list of unique identifiers which represent the possibly different
119
items actually present in the list."""
122
class FileStack(StackControlBase):
123
"""Implements a stack which shows only the base names of files"""
127
def formatItem(self, item):
128
return Path(item).basename()
133
class FilePathControl(ui.TextField):
134
"""Control displaying a relative url. If it is ediable, a filepath may be
135
entered and queried"""
139
""":return: string representing the currently active path"""
142
def setPath(self, path):
143
"""Set the control to display the given path"""
146
self.p_text = str(path)
148
def setEditable(self, state):
149
self.p_editable = state
152
""":return: True if the control can be edited by the user"""
153
return self.p_editable
157
class FilePathControlEditable(FilePathControl):
158
"""A filepath control which tries to maintain changes applied by the user.
159
It assumes that the system will use setPath to adjust the path, and checks
160
for changes in the path string which will be reapplied to the newly set path
163
def __init__(self, *args, **kwargs):
164
super(FilePathControlEditable, self).__init__(*args, **kwargs)
167
def setPath(self, path):
168
# figure out the changes - only care for append operations
170
appended_text = self.p_text.replace(self._prev_path, '', 1)
171
self._prev_path = path
172
path += appended_text
173
# END handle previous path
174
super(FilePathControlEditable, self).setPath(path)
177
class BookmarkControl(StackControlBase):
178
"""Control allowing to display a set of custom bookmarks, which are stored
181
# Default name used to store bookmarks in optionVars. Adjust this id in case
182
# you have different sets of bookmarks to store
183
k_bookmark_store = "MRV_bookmarks"
188
bookmark_changed = ui.Signal()
191
def __init__(self, *args, **kwargs):
192
# fill ourselves with the stored bookmarks
193
# List of tuples: root,relative_path
194
super(BookmarkControl, self).__init__(*args, **kwargs)
195
self.setItems(self._unpack_stored_bookmarks())
196
self.e_selectCommand = self._selection_changed
198
def _parse_bookmark(self, bookmark):
199
""":return: root,path tuple or raise"""
200
root, path = None, None
201
if isinstance(bookmark, tuple) and len(bookmark) == 2:
202
root, path = bookmark
204
bookmark = Path(bookmark)
205
root = bookmark.root()
206
root_with_sep = (root.endswith(root.sep) and root) or (root + root.sep)
207
path = Path(bookmark.replace(root_with_sep, '', 1))
208
# END handle bookmark
212
def _unpack_stored_bookmarks(self):
213
""":return: list of tuples of root,path pairs"""
214
miter = iter(opts.get(self.k_bookmark_store, list()))
215
return [item for item in zip(miter, miter)]
217
def _store_item_list(self, items):
218
"""Store a list of pairs"""
219
flattened_list = list()
221
flattened_list.extend(pair)
223
opts[self.k_bookmark_store] = flattened_list
225
def _store_bookmark(self, root, path, add=True):
226
"""Store the given path under the given root
227
:param add: if True, the path will be added to the bookmarks of the given
228
root, otherwise it will be removed"""
229
items = self._unpack_stored_bookmarks()
230
index_to_remove = None
231
for index, (oroot, opath) in enumerate(items):
232
if oroot == root and opath == path:
236
index_to_remove = index
239
# END similar item is stored already
240
# END for each stored item
243
items.append((root, path))
245
if index_to_remove is None:
247
# END ignore items that do not exist
248
del(items[index_to_remove])
249
# END end handle stored
251
self._store_item_list(items)
254
def _selection_changed(self, *args):
255
"""Convert the default callback into our signals"""
256
if not self.base_items:
258
root, path = self.base_items[self.selectedIndex()-1]
259
self.bookmark_changed.send(root, path)
260
# as we are one-time actions only, deselect everything
261
self.setSelectedItem(None)
263
#{ StackControl Interface
264
def formatItem(self, root_path_tuple):
265
if isinstance(root_path_tuple, tuple):
266
return Path(root_path_tuple[-1]).basename()
268
return Path(root_path_tuple).basename()
270
#} END stackcontrol interface
272
def addItem(self, bookmark):
273
"""Add a new bookmark
274
:param bookmark: tuple of root,relative_path or a single absolute path. In the
275
latter case, the root will be the natural root of the absolute path"""
276
root, path = self._parse_bookmark(bookmark)
277
bm_formatted = self.formatItem((root, path))
278
# duplicate prevention
279
if bm_formatted in self.items():
281
# END handle duplicates
282
self._store_bookmark(root, path, add=True)
283
super(BookmarkControl, self).addItem((root, path))
286
def setItems(self, bookmarks):
287
"""Set this control to a list of bookmarks
288
:param bookmarks: list of either tuples of (root, path) pairs or absolute paths
289
whose root will be chosen automatically"""
291
self.base_items = list()
292
for item in bookmarks:
293
self.base_items.append(self._parse_bookmark(item))
294
bms.append(self.formatItem(self.base_items[-1]))
296
super(BookmarkControl, self).setItems(bms)
298
# store all items together
299
del(opts[self.k_bookmark_store])
300
self._store_item_list(self.base_items)
302
def removeItem(self, bookmark):
303
"""Remove the given bookmark from the list of bookmarks
304
:param bookmark: full path to the bookmark to remove. Its not an error
305
if it doesn't exist in the first place"""
308
index = self.items().index(bookmark)
309
root, path = self.base_items[index]
310
super(BookmarkControl, self).removeItem(bookmark)
311
self._store_bookmark(root, path, add=False)
312
except (ValueError, IndexError):
314
# END exception handling
317
class FileRootSelectorControl(ui.TextScrollList):
318
"""Keeps a list of possible roots which can be chosen. Each root is represented
319
by a Provider instance."""
323
root_changed = ui.Signal()
326
def __init__(self, *args, **kwargs):
327
self._providers = list()
328
self.e_selectCommand = self._selection_changed
330
def _provider_by_root(self, root):
331
""":return: provider instance having the given root, or None"""
332
for p in self._providers:
336
# END for each of our providers
339
def setItems(self, providers):
340
"""Set the given providers to be used by this instance
341
:param providers: list of FileProvider instances"""
342
for provider in providers:
343
if not isinstance(provider, FileProvider):
344
raise ValueError("Require %s instances" % FileProvider)
346
# END for each provider
347
self._providers = providers
348
super(FileRootSelectorControl, self).setItems(p.root() for p in self._providers)
350
def addItem(self, provider):
351
"""Add the given provider to our list of provides"""
352
super(FileRootSelectorControl, self).addItem(provider.root())
353
self._providers.append(provider)
355
def removeItem(self, provider):
356
"""Remove the given provider from the list
357
:param provider: FileProvider instance or root from which the provider
359
if isinstance(provider, basestring):
360
provider = self._provider_by_root(provider)
363
# END abort if not found
364
# END handle provider type
367
self._providers.remove(provider)
371
self.setItems(self._providers)
372
# END exception handling
374
def setSelectedItem(self, item):
375
"""Fires a root_changed event if the item actually caused a selection change"""
376
cur_index = self.selectedIndex()
377
super(FileRootSelectorControl, self).setSelectedItem(item)
378
if cur_index == self.selectedIndex():
380
# END skip if no change
381
self._selection_changed()
387
""":return: list of currently used providers"""
388
return list(self._providers)
394
def _selection_changed(self, *args):
395
index = self.selectedIndex()-1
396
self.root_changed.send(self._providers[index])
403
class FileFilterControl(ui.FormLayout, iFinderFilter):
404
"""Control providing a filter for finder urls which are file paths"""