Package mrv :: Package maya :: Package ui :: Package browse :: Module control
[hide private]
[frames] | no frames]

Source Code for Module mrv.maya.ui.browse.control

  1  # -*- coding: utf-8 -*- 
  2  """Contains ui modules to build a finder-like browser for items of any kind""" 
  3  __docformat__ = "restructuredtext" 
  4   
  5  from interface import (iFinderProvider, iFinderFilter) 
  6   
  7  import mrv.maya.ui as ui 
  8  from mrv.maya.util import (OptionVarDict, logException ) 
  9  from util import concat_url 
 10   
 11  from mrv.path import Path 
 12  opts = OptionVarDict() 
 13   
 14  __all__ = ( 'FileProvider', 'BookmarkControl',   
 15                          'FilePathControl', 'FileFilterControl', 'FinderElement',  
 16                          'FileStack', 'FileRootSelectorControl', 'FilePathControlEditable') 
17 18 19 20 #{ Utilities 21 22 -class FileProvider(iFinderProvider):
23 """Implements a provider for a file system""" 24 __slots__ = "_root" 25
26 - def __init__(self, root):
27 super(FileProvider, self).__init__(root) 28 self._root = Path(self._root)
29
30 - def formatItem(self, url_base, url_index, url_item):
31 return url_item
32
33 - def urlItems(self, url):
34 """Return directory items alphabetically, directories first""" 35 path = self._root / url 36 dirs, files = list(), list() 37 38 try: 39 for abs_path in path.listdir(): 40 if abs_path.isdir(): 41 dirs.append(abs_path) 42 else: 43 files.append(abs_path) 44 # END sort by type 45 # END for each listed path 46 dirs.sort() 47 files.sort() 48 return [abspath.basename() for abspath in (dirs + files)] 49 except OSError: 50 # ignore attempts to get path on a file for instance 51 return list()
52 # END exception handling
53 54 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.""" 61 62
63 - def __init__(self, *args, **kwargs):
64 super(StackControlBase, self).__init__(*args, **kwargs) 65 # unformatted items 66 self.base_items = list()
67 68 #{ Interface 69
70 - def formatItem(self, item):
71 """:return: formatted version of item""" 72 return item
73
74 - def selectedUnformattedItem(self):
75 """:return: unformatted selected item or None""" 76 index = self.selectedIndex() 77 if index < 0: 78 return None 79 return self.base_items[index-1]
80
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""" 86 index = index_or_item 87 if not isinstance(index_or_item, int): 88 index = self.base_items.index(index_or_item) 89 self.p_selectIndexedItem = index+1
90 91 #} END Interface 92 93 #{ Overridden Methods 94
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""" 98 try: 99 index = self.items().index(item) 100 del(self.base_items[index]) 101 super(StackControlBase, self).removeItem(item) 102 except (ValueError, IndexError): 103 pass 104 # END exception handling 105 return self
106
107 - def addItem(self, item):
108 self.base_items.append(item) 109 return super(StackControlBase, self).addItem(self.formatItem(item))
110
111 #} END overridden methods 112 113 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. 117 118 Set the items to a list of unique identifiers which represent the possibly different 119 items actually present in the list."""
120
121 122 -class FileStack(StackControlBase):
123 """Implements a stack which shows only the base names of files""" 124 125 #{ Overrides 126
127 - def formatItem(self, item):
128 return Path(item).basename()
129
130 #} END overrides 131 132 133 -class FilePathControl(ui.TextField):
134 """Control displaying a relative url. If it is ediable, a filepath may be 135 entered and queried""" 136 137 #{ Interface
138 - def path(self):
139 """:return: string representing the currently active path""" 140 return self.p_text
141
142 - def setPath(self, path):
143 """Set the control to display the given path""" 144 if path is None: 145 path = '' 146 self.p_text = str(path)
147
148 - def setEditable(self, state):
149 self.p_editable = state
150
151 - def editable(self):
152 """:return: True if the control can be edited by the user""" 153 return self.p_editable
154 #} END interface
155 156 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 161 if possible""" 162
163 - def __init__(self, *args, **kwargs):
164 super(FilePathControlEditable, self).__init__(*args, **kwargs) 165 self._prev_path = ''
166
167 - def setPath(self, path):
168 # figure out the changes - only care for append operations 169 if path is not None: 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)
175
176 177 -class BookmarkControl(StackControlBase):
178 """Control allowing to display a set of custom bookmarks, which are stored 179 in optionVars""" 180 #{ Configuration 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" 184 #} END configuration 185 186 #{ Signals 187 # s(root, path) 188 bookmark_changed = ui.Signal() 189 #} END signals 190
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
197
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 203 else: 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 209 210 return root, path
211
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)]
216
217 - def _store_item_list(self, items):
218 """Store a list of pairs""" 219 flattened_list = list() 220 for pair in items: 221 flattened_list.extend(pair) 222 # END flatten list 223 opts[self.k_bookmark_store] = flattened_list
224
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: 233 if add: 234 return 235 else: 236 index_to_remove = index 237 break 238 # END skip existing 239 # END similar item is stored already 240 # END for each stored item 241 242 if add: 243 items.append((root, path)) 244 else: 245 if index_to_remove is None: 246 return 247 # END ignore items that do not exist 248 del(items[index_to_remove]) 249 # END end handle stored 250 251 self._store_item_list(items)
252 253 @logException
254 - def _selection_changed(self, *args):
255 """Convert the default callback into our signals""" 256 if not self.base_items: 257 return 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)
262 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() 267 else: 268 return Path(root_path_tuple).basename()
269 270 #} END stackcontrol interface 271
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(): 280 return 281 # END handle duplicates 282 self._store_bookmark(root, path, add=True) 283 super(BookmarkControl, self).addItem((root, path))
284 285
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""" 290 bms = list() 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])) 295 # END for each item 296 super(BookmarkControl, self).setItems(bms) 297 298 # store all items together 299 del(opts[self.k_bookmark_store]) 300 self._store_item_list(self.base_items)
301
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""" 306 items = self.items() 307 try: 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): 313 return
314 # END exception handling
315 316 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.""" 320 321 #{ Signals 322 # s(Provider) 323 root_changed = ui.Signal() 324 #} END signals 325
326 - def __init__(self, *args, **kwargs):
327 self._providers = list() 328 self.e_selectCommand = self._selection_changed
329
330 - def _provider_by_root(self, root):
331 """:return: provider instance having the given root, or None""" 332 for p in self._providers: 333 if p.root() == root: 334 return p 335 # END check match 336 # END for each of our providers 337 return None
338
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) 345 # END verify type 346 # END for each provider 347 self._providers = providers 348 super(FileRootSelectorControl, self).setItems(p.root() for p in self._providers)
349
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)
354
355 - def removeItem(self, provider):
356 """Remove the given provider from the list 357 :param provider: FileProvider instance or root from which the provider 358 can be determined""" 359 if isinstance(provider, basestring): 360 provider = self._provider_by_root(provider) 361 if provider is None: 362 return 363 # END abort if not found 364 # END handle provider type 365 366 try: 367 self._providers.remove(provider) 368 except ValueError: 369 return 370 else: 371 self.setItems(self._providers)
372 # END exception handling 373
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(): 379 return 380 # END skip if no change 381 self._selection_changed()
382 383 384 #{ Interface 385
386 - def providers(self):
387 """:return: list of currently used providers""" 388 return list(self._providers)
389 390 #} END interface 391 392 #{ Callbacks 393
394 - def _selection_changed(self, *args):
395 index = self.selectedIndex()-1 396 self.root_changed.send(self._providers[index])
397 #} END callbacks
398 399 #} END utilities 400 401 #{ Modules 402 403 -class FileFilterControl(ui.FormLayout, iFinderFilter):
404 """Control providing a filter for finder urls which are file paths"""
405 406 407 408 409 #} END modules 410