Project/ProjectBaseBrowser.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the baseclass for the various project browsers.
8 """
9
10 import os
11 import sys
12
13 from PyQt4.QtCore import *
14 from PyQt4.QtGui import *
15
16 from E4Gui.E4Application import e4App
17
18 from UI.Browser import *
19
20 from ProjectBrowserModel import *
21 from ProjectBrowserSortFilterProxyModel import ProjectBrowserSortFilterProxyModel
22
23 import UI.PixmapCache
24 import Preferences
25
26 class ProjectBaseBrowser(Browser):
27 """
28 Baseclass implementing common functionality for the various project browsers.
29 """
30 def __init__(self, project, type_, parent = None):
31 """
32 Constructor
33
34 @param project reference to the project object
35 @param type project browser type (string)
36 @param parent parent widget of this browser
37 """
38 QTreeView.__init__(self, parent)
39
40 self.project = project
41
42 self._model = project.getModel()
43 self._sortModel = ProjectBrowserSortFilterProxyModel(type_)
44 self._sortModel.setSourceModel(self._model)
45 self.setModel(self._sortModel)
46
47 self.selectedItemsFilter = [ProjectBrowserFileItem]
48
49 # contains codes for special menu entries
50 # 1 = specials for Others browser
51 self.specialMenuEntries = []
52 self.isTranslationsBrowser = False
53 self.expandedNames = []
54
55 self.SelectFlags = QItemSelectionModel.SelectionFlags(\
56 QItemSelectionModel.Select | QItemSelectionModel.Rows)
57 self.DeselectFlags = QItemSelectionModel.SelectionFlags(\
58 QItemSelectionModel.Deselect | QItemSelectionModel.Rows)
59
60 self.setContextMenuPolicy(Qt.CustomContextMenu)
61 self.connect(self, SIGNAL("customContextMenuRequested(const QPoint &)"),
62 self._contextMenuRequested)
63 self.connect(self, SIGNAL("activated(const QModelIndex &)"), self._openItem)
64 self.connect(self._model, SIGNAL("rowsInserted(const QModelIndex &, int, int)"),
65 self.__modelRowsInserted)
66 self._connectExpandedCollapsed()
67
68 self._createPopupMenus()
69
70 self.currentItemName = None
71
72 self._init() # perform common initialization tasks
73
74 self._initHookMethods() # perform initialization of the hooks
75 self.hooksMenuEntries = {}
76
77 def _connectExpandedCollapsed(self):
78 """
79 Protected method to connect the expanded and collapsed signals.
80 """
81 self.connect(self, SIGNAL("expanded(const QModelIndex &)"),
82 self._resizeColumns)
83 self.connect(self, SIGNAL("collapsed(const QModelIndex &)"),
84 self._resizeColumns)
85
86 def _disconnectExpandedCollapsed(self):
87 """
88 Protected method to disconnect the expanded and collapsed signals.
89 """
90 self.disconnect(self, SIGNAL("expanded(const QModelIndex &)"),
91 self._resizeColumns)
92 self.disconnect(self, SIGNAL("collapsed(const QModelIndex &)"),
93 self._resizeColumns)
94
95 def _createPopupMenus(self):
96 """
97 Protected overloaded method to generate the popup menus.
98 """
99 # create the popup menu for source files
100 self.sourceMenu = QMenu(self)
101 self.sourceMenu.addAction(
102 QApplication.translate('ProjectBaseBrowser', 'Open'),
103 self._openItem)
104
105 # create the popup menu for general use
106 self.menu = QMenu(self)
107 self.menu.addAction(
108 QApplication.translate('ProjectBaseBrowser', 'Open'),
109 self._openItem)
110
111 # create the menu for multiple selected files
112 self.multiMenu = QMenu(self)
113 self.multiMenu.addAction(QApplication.translate('ProjectBaseBrowser', 'Open'),
114 self._openItem)
115
116 # create the background menu
117 self.backMenu = None
118
119 # create the directories menu
120 self.dirMenu = None
121
122 # create the directory for multiple selected directories
123 self.dirMultiMenu = None
124
125 self.menuActions = []
126 self.multiMenuActions = []
127 self.dirMenuActions = []
128 self.dirMultiMenuActions = []
129
130 self.mainMenu = None
131
132 def _contextMenuRequested(self, coord):
133 """
134 Protected slot to show the context menu.
135
136 @param coord the position of the mouse pointer (QPoint)
137 """
138 if not self.project.isOpen():
139 return
140
141 cnt = self.getSelectedItemsCount()
142 if cnt > 1:
143 self.multiMenu.popup(self.mapToGlobal(coord))
144 else:
145 index = self.indexAt(coord)
146
147 if index.isValid():
148 self.menu.popup(self.mapToGlobal(coord))
149 else:
150 self.backMenu and self.backMenu.popup(self.mapToGlobal(coord))
151
152 def _selectSingleItem(self, index):
153 """
154 Protected method to select a single item.
155
156 @param index index of item to be selected (QModelIndex)
157 """
158 if index.isValid():
159 self.setCurrentIndex(index)
160 flags = QItemSelectionModel.SelectionFlags(\
161 QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
162 self.selectionModel().select(index, flags)
163
164 def _setItemSelected(self, index, selected):
165 """
166 Protected method to set the selection status of an item.
167
168 @param index index of item to set (QModelIndex)
169 @param selected flag giving the new selection status (boolean)
170 """
171 if index.isValid():
172 self.selectionModel().select(index,
173 selected and self.SelectFlags or self.DeselectFlags)
174
175 def _setItemRangeSelected(self, startIndex, endIndex, selected):
176 """
177 Protected method to set the selection status of a range of items.
178
179 @param startIndex start index of range of items to set (QModelIndex)
180 @param endIndex end index of range of items to set (QModelIndex)
181 @param selected flag giving the new selection status (boolean)
182 """
183 selection = QItemSelection(startIndex, endIndex)
184 self.selectionModel().select(selection,
185 selected and self.SelectFlags or self.DeselectFlags)
186
187 def __modelRowsInserted(self, parent, start, end):
188 """
189 Private slot called after rows have been inserted into the model.
190
191 @param parent parent index of inserted rows (QModelIndex)
192 @param start start row number (integer)
193 @param end end row number (integer)
194 """
195 self._resizeColumns(parent)
196
197 def __modelDataChanged(self, startIndex, endIndex):
198 """
199 Private slot called after data has been changed in the model.
200
201 @param startIndex start index of the changed data (QModelIndex)
202 @param endIndex end index of the changed data (QModelIndex)
203 """
204 self._resizeColumns(startIndex)
205
206 def _projectClosed(self):
207 """
208 Protected slot to handle the projectClosed signal.
209 """
210 self.layoutDisplay()
211 if self.backMenu is not None:
212 self.backMenu.setEnabled(False)
213
214 self._createPopupMenus()
215
216 def _projectOpened(self):
217 """
218 Protected slot to handle the projectOpened signal.
219 """
220 self.layoutDisplay()
221 self.sortByColumn(0, Qt.DescendingOrder)
222 self.sortByColumn(0, Qt.AscendingOrder)
223 self._initMenusAndVcs()
224
225 def _initMenusAndVcs(self):
226 """
227 Protected slot to initialize the menus and the Vcs interface.
228 """
229 self._createPopupMenus()
230
231 if self.backMenu is not None:
232 self.backMenu.setEnabled(True)
233
234 if self.project.vcs is not None:
235 self.vcsHelper = self.project.vcs.vcsGetProjectBrowserHelper(self,
236 self.project, self.isTranslationsBrowser)
237 self.vcsHelper.addVCSMenus(self.mainMenu, self.multiMenu,
238 self.backMenu, self.dirMenu, self.dirMultiMenu)
239
240 def _newProject(self):
241 """
242 Protected slot to handle the newProject signal.
243 """
244 self.layoutDisplay()
245 self.sortByColumn(0, Qt.DescendingOrder)
246 self.sortByColumn(0, Qt.AscendingOrder)
247
248 self._createPopupMenus()
249
250 if self.backMenu is not None:
251 self.backMenu.setEnabled(True)
252
253 if self.project.vcs is not None:
254 self.vcsHelper = self.project.vcs.vcsGetProjectBrowserHelper(self,
255 self.project, self.isTranslationsBrowser)
256 self.vcsHelper.addVCSMenus(self.mainMenu, self.multiMenu,
257 self.backMenu, self.dirMenu, self.dirMultiMenu)
258
259 def _removeFile(self):
260 """
261 Protected method to remove a file or files from the project.
262 """
263 itmList = self.getSelectedItems()
264
265 for itm in itmList[:]:
266 fn = itm.fileName()
267 self.emit(SIGNAL('closeSourceWindow'), fn)
268 self.project.removeFile(fn)
269
270 def _removeDir(self):
271 """
272 Protected method to remove a (single) directory from the project.
273 """
274 itmList = self.getSelectedItems(\
275 [ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem])
276 for itm in itmList[:]:
277 dn = itm.dirName()
278 self.project.removeDirectory(dn)
279
280 def _renameFile(self):
281 """
282 Protected method to rename a file of the project.
283 """
284 itm = self.model().item(self.currentIndex())
285 fn = itm.fileName()
286 self.project.renameFile(fn)
287
288 def _copyToClipboard(self):
289 """
290 Protected method to copy the path of an entry to the clipboard.
291 """
292 itm = self.model().item(self.currentIndex())
293 try:
294 fn = itm.fileName()
295 except AttributeError:
296 try:
297 fn = itm.dirName()
298 except AttributeError:
299 fn = ""
300
301 cb = QApplication.clipboard()
302 cb.setText(fn)
303
304 def selectFile(self, fn):
305 """
306 Public method to highlight a node given its filename.
307
308 @param fn filename of file to be highlighted (string)
309 """
310 newfn = os.path.abspath(fn)
311 newfn = newfn.replace(self.project.ppath + os.sep, '')
312 sindex = self._model.itemIndexByName(newfn)
313 if sindex.isValid():
314 index = self.model().mapFromSource(sindex)
315 if index.isValid():
316 self._selectSingleItem(index)
317 self.scrollTo(index, QAbstractItemView.PositionAtTop)
318
319 def _expandAllDirs(self):
320 """
321 Protected slot to handle the 'Expand all directories' menu action.
322 """
323 self._disconnectExpandedCollapsed()
324 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
325 QApplication.processEvents()
326 index = self.model().index(0, 0)
327 while index.isValid():
328 itm = self.model().item(index)
329 if (isinstance(itm, ProjectBrowserSimpleDirectoryItem) or \
330 isinstance(itm, ProjectBrowserDirectoryItem)) and \
331 not self.isExpanded(index):
332 self.expand(index)
333 index = self.indexBelow(index)
334 self.layoutDisplay()
335 self._connectExpandedCollapsed()
336 QApplication.restoreOverrideCursor()
337
338 def _collapseAllDirs(self):
339 """
340 Protected slot to handle the 'Collapse all directories' menu action.
341 """
342 self._disconnectExpandedCollapsed()
343 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
344 QApplication.processEvents()
345 # step 1: find last valid index
346 vindex = QModelIndex()
347 index = self.model().index(0, 0)
348 while index.isValid():
349 vindex = index
350 index = self.indexBelow(index)
351
352 # step 2: go up collapsing all directory items
353 index = vindex
354 while index.isValid():
355 itm = self.model().item(index)
356 if (isinstance(itm, ProjectBrowserSimpleDirectoryItem) or \
357 isinstance(itm, ProjectBrowserDirectoryItem)) and \
358 self.isExpanded(index):
359 self.collapse(index)
360 index = self.indexAbove(index)
361 self.layoutDisplay()
362 self._connectExpandedCollapsed()
363 QApplication.restoreOverrideCursor()
364
365 def _showContextMenu(self, menu):
366 """
367 Protected slot called before the context menu is shown.
368
369 It enables/disables the VCS menu entries depending on the overall
370 VCS status and the file status.
371
372 @param menu reference to the menu to be shown (QMenu)
373 """
374 if self.project.vcs is None:
375 for act in self.menuActions:
376 act.setEnabled(True)
377 else:
378 self.vcsHelper.showContextMenu(menu, self.menuActions)
379
380 def _showContextMenuMulti(self, menu):
381 """
382 Protected slot called before the context menu (multiple selections) is shown.
383
384 It enables/disables the VCS menu entries depending on the overall
385 VCS status and the files status.
386
387 @param menu reference to the menu to be shown (QMenu)
388 """
389 if self.project.vcs is None:
390 for act in self.multiMenuActions:
391 act.setEnabled(True)
392 else:
393 self.vcsHelper.showContextMenuMulti(menu, self.multiMenuActions)
394
395 def _showContextMenuDir(self, menu):
396 """
397 Protected slot called before the context menu is shown.
398
399 It enables/disables the VCS menu entries depending on the overall
400 VCS status and the directory status.
401
402 @param menu reference to the menu to be shown (QMenu)
403 """
404 if self.project.vcs is None:
405 for act in self.dirMenuActions:
406 act.setEnabled(True)
407 else:
408 self.vcsHelper.showContextMenuDir(menu, self.dirMenuActions)
409
410 def _showContextMenuDirMulti(self, menu):
411 """
412 Protected slot called before the context menu is shown.
413
414 It enables/disables the VCS menu entries depending on the overall
415 VCS status and the directory status.
416
417 @param menu reference to the menu to be shown (QMenu)
418 """
419 if self.project.vcs is None:
420 for act in self.dirMultiMenuActions:
421 act.setEnabled(True)
422 else:
423 self.vcsHelper.showContextMenuDirMulti(menu, self.dirMultiMenuActions)
424
425 def _showContextMenuBack(self, menu):
426 """
427 Protected slot called before the context menu is shown.
428
429 @param menu reference to the menu to be shown (QMenu)
430 """
431 # nothing to do for now
432 return
433
434 def _selectEntries(self, local = True, filter = None):
435 """
436 Protected method to select entries based on their VCS status.
437
438 @param local flag indicating local (i.e. non VCS controlled) file/directory
439 entries should be selected (boolean)
440 @param filter list of classes to check against
441 """
442 if self.project.vcs is None:
443 return
444
445 if local:
446 compareString = QApplication.translate('ProjectBaseBrowser', "local")
447 else:
448 compareString = self.project.vcs.vcsName()
449
450 # expand all directories in order to iterate over all entries
451 self._expandAllDirs()
452
453 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
454 QApplication.processEvents()
455 self.selectionModel().clear()
456 QApplication.processEvents()
457
458 # now iterate over all entries
459 startIndex = None
460 endIndex = None
461 selectedEntries = 0
462 index = self.model().index(0, 0)
463 while index.isValid():
464 itm = self.model().item(index)
465 if self.wantedItem(itm, filter) and \
466 compareString == itm.data(1):
467 if startIndex is not None and \
468 startIndex.parent() != index.parent():
469 self._setItemRangeSelected(startIndex, endIndex, True)
470 startIndex = None
471 selectedEntries += 1
472 if startIndex is None:
473 startIndex = index
474 endIndex = index
475 else:
476 if startIndex is not None:
477 self._setItemRangeSelected(startIndex, endIndex, True)
478 startIndex = None
479 index = self.indexBelow(index)
480 if startIndex is not None:
481 self._setItemRangeSelected(startIndex, endIndex, True)
482 QApplication.restoreOverrideCursor()
483 QApplication.processEvents()
484
485 if selectedEntries == 0:
486 QMessageBox.information(None,
487 QApplication.translate('ProjectBaseBrowser', "Select entries"),
488 QApplication.translate('ProjectBaseBrowser',
489 """There were no matching entries found."""))
490
491 def selectLocalEntries(self):
492 """
493 Public slot to handle the select local files context menu entries
494 """
495 self._selectEntries(local = True, filter = [ProjectBrowserFileItem])
496
497 def selectVCSEntries(self):
498 """
499 Public slot to handle the select VCS files context menu entries
500 """
501 self._selectEntries(local = False, filter = [ProjectBrowserFileItem])
502
503 def selectLocalDirEntries(self):
504 """
505 Public slot to handle the select local directories context menu entries
506 """
507 self._selectEntries(local = True,
508 filter=[ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem])
509
510 def selectVCSDirEntries(self):
511 """
512 Public slot to handle the select VCS directories context menu entries
513 """
514 self._selectEntries(local = False,
515 filter=[ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem])
516
517 def _prepareRepopulateItem(self, name):
518 """
519 Protected slot to handle the prepareRepopulateItem signal.
520
521 @param name relative name of file item to be repopulated (string)
522 """
523 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
524 QApplication.processEvents()
525 itm = self.currentItem()
526 if itm is not None:
527 self.currentItemName = itm.data(0)
528 self.expandedNames = []
529 sindex = self._model.itemIndexByName(name)
530 if not sindex.isValid():
531 return
532
533 index = self.model().mapFromSource(sindex)
534 if not index.isValid():
535 return
536
537 childIndex = self.indexBelow(index)
538 while childIndex.isValid():
539 if childIndex.parent() == index.parent():
540 break
541 if self.isExpanded(childIndex):
542 self.expandedNames.append(self.model().item(childIndex).data(0))
543 childIndex = self.indexBelow(childIndex)
544
545 def _completeRepopulateItem(self, name):
546 """
547 Protected slot to handle the completeRepopulateItem signal.
548
549 @param name relative name of file item to be repopulated (string)
550 """
551 sindex = self._model.itemIndexByName(name)
552 if sindex.isValid():
553 index = self.model().mapFromSource(sindex)
554 if index.isValid():
555 childIndex = self.indexBelow(index)
556 while childIndex.isValid():
557 if not childIndex.isValid() or childIndex.parent() == index.parent():
558 break
559 itm = self.model().item(childIndex)
560 if itm is not None:
561 itemData = itm.data(0)
562 if self.currentItemName and self.currentItemName == itemData:
563 self._selectSingleItem(childIndex)
564 if itemData in self.expandedNames:
565 self.setExpanded(childIndex, True)
566 childIndex = self.indexBelow(childIndex)
567 self.expandedNames = []
568 self.currentItemName = None
569 QApplication.restoreOverrideCursor()
570 QApplication.processEvents()
571 self._resort()
572
573 def currentItem(self):
574 """
575 Public method to get a reference to the current item.
576
577 @return reference to the current item
578 """
579 itm = self.model().item(self.currentIndex())
580 return itm
581
582 ############################################################################
583 ## Support for hooks below
584 ############################################################################
585
586 def _initHookMethods(self):
587 """
588 Protected method to initialize the hooks dictionary.
589
590 This method should be overridden by subclasses. All supported
591 hook methods should be initialized with a None value. The keys
592 must be strings.
593 """
594 self.hooks = {}
595
596 def __checkHookKey(self, key):
597 """
598 Private method to check a hook key
599 """
600 if len(self.hooks) == 0:
601 raise KeyError("Hooks are not initialized.")
602
603 if key not in self.hooks:
604 raise KeyError(key)
605
606 def addHookMethod(self, key, method):
607 """
608 Public method to add a hook method to the dictionary.
609
610 @param key for the hook method (string)
611 @param method reference to the hook method (method object)
612 """
613 self.__checkHookKey(key)
614 self.hooks[key] = method
615
616 def addHookMethodAndMenuEntry(self, key, method, menuEntry):
617 """
618 Public method to add a hook method to the dictionary.
619
620 @param key for the hook method (string)
621 @param method reference to the hook method (method object)
622 @param menuEntry entry to be shown in the context menu (string)
623 """
624 self.addHookMethod(key, method)
625 self.hooksMenuEntries[key] = menuEntry
626
627 def removeHookMethod(self, key):
628 """
629 Public method to remove a hook method from the dictionary.
630
631 @param key for the hook method (string)
632 """
633 self.__checkHookKey(key)
634 self.hooks[key] = None
635 if key in self.hooksMenuEntries:
636 del self.hooksMenuEntries[key]
637
638 ##################################################################
639 ## Configure method below
640 ##################################################################
641
642 def _configure(self):
643 """
644 Protected method to open the configuration dialog.
645 """
646 e4App().getObject("UserInterface").showPreferences("projectBrowserPage")

eric ide

mercurial