# -*- coding: utf-8 -*-
### Copyright (C) 2008-2012 Antonio Valentino <a_valentino@users.sf.net>
### This file is part of GSDView.
### GSDView is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation; either version 2 of the License, or
### (at your option) any later version.
### GSDView is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
### You should have received a copy of the GNU General Public License
### along with GSDView; if not, write to the Free Software
### Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
'''Components for layers management.'''
import logging
import itertools
from qt import QtCore, QtGui
__author__ = 'Antonio Valentino <a_valentino@users.sf.net>'
__date__ = '$Date$'
__revision__ = '$Revision$'
SelectCurrentRows = (QtGui.QItemSelectionModel.SelectCurrent |
QtGui.QItemSelectionModel.Rows)
[docs]class BaseLayerManager(QtCore.QObject):
def __init__(self, parent=None, **kargs):
super(BaseLayerManager, self).__init__(parent, **kargs)
self.actions = self._setupActions()
def _setupActions(self):
style = QtGui.QApplication.style()
actions = QtGui.QActionGroup(self)
icon = QtGui.QIcon(':/trolltech/styles/commonstyle/images/up-128.png')
QtGui.QAction(icon, self.tr('Move to top'), actions,
objectName='moveToTopAction',
statusTip=self.tr('Move to top'),
shortcut=self.tr('Ctrl+PgUp'))
icon = style.standardIcon(QtGui.QStyle.SP_ArrowUp)
QtGui.QAction(icon, self.tr('Move up'), actions,
objectName='moveUpAction',
statusTip=self.tr('Move up'),
shortcut=self.tr('Ctrl+Up'))
icon = style.standardIcon(QtGui.QStyle.SP_ArrowDown)
QtGui.QAction(icon, self.tr('Move down'), actions,
objectName='moveDownAction',
statusTip=self.tr('Move down'),
shortcut=self.tr('Ctrl+Down'))
icon = QtGui.QIcon(
':/trolltech/styles/commonstyle/images/down-128.png')
QtGui.QAction(icon, self.tr('Move to bottom'), actions,
objectName='moveToBottomAction',
statusTip=self.tr('Move to bottom'),
shortcut=self.tr('Ctrl+PgDown'))
#~ #'standardbutton-closetab-16.png'
icon = QtGui.QIcon(':/trolltech/styles/commonstyle/images/'
'standardbutton-cancel-128.png')
QtGui.QAction(icon, self.tr('Remove'), actions,
objectName='removeLayerAction',
statusTip=self.tr('Remove'),
shortcut=self.tr('Del'))
icon = QtGui.QIcon(
':/trolltech/styles/commonstyle/images/standardbutton-yes-128.png')
QtGui.QAction(icon, self.tr('Show'), actions,
objectName='showLayerAction',
statusTip=self.tr('Show the layer'))
icon = QtGui.QIcon(
':/trolltech/styles/commonstyle/images/standardbutton-no-128.png')
QtGui.QAction(icon, self.tr('Hide'), actions,
objectName='hideLayerAction',
statusTip=self.tr('Hide the layer'))
return actions
[docs] def action(self, name):
return self.actions.findChild(QtGui.QAction, name)
[docs] def isLayer(self, item):
# @TODO: complete
return True
@staticmethod
def _selectionmap(selection):
sortedselection = sorted(selection,
key=QtGui.QItemSelectionRange.parent)
selectionmap = {} # collections.OrderedDict()
for key, group in itertools.groupby(sortedselection,
QtGui.QItemSelectionRange.parent):
ranges = []
for item in sorted(group, key=QtGui.QItemSelectionRange.top):
if len(ranges) == 0:
ranges.append(item)
continue
lastitem = ranges[-1]
assert lastitem.parent() == item.parent()
if lastitem.bottom() + 1 >= item.top():
model = lastitem.model()
topleft = model.index(min(lastitem.top(), item.top()),
#min(lastitem.left(), item.left()),
0,
lastitem.parent())
bottomright = model.index(
max(lastitem.bottom(), item.bottom()),
#max(lastitem.right(), item.right()),
model.columnCount() - 1,
lastitem.parent())
ranges[-1] = QtGui.QItemSelectionRange(topleft,
bottomright)
else:
ranges.append(item)
selectionmap[key] = ranges
return selectionmap
@staticmethod
def _parentitem(selectionrange):
index = selectionrange.parent()
if index.isValid():
item = selectionrange.model().itemFromIndex(index)
else:
item = selectionrange.model().invisibleRootItem()
return item
@staticmethod
[docs] def updateStackOrder(rootitem, offset=0):
nrows = rootitem.rowCount()
for row in range(nrows):
qitem = rootitem.child(row).data()
if qitem:
qitem.setZValue(nrows + offset - row - 1)
else:
logging.warning('no graphics item associated to layer '
'n. %d' % row)
# @TODO: beginMoveRows, endMoveRows
def _takeRowsRange(self, selectionrange):
parent = self._parentitem(selectionrange)
rows = []
for row in range(selectionrange.bottom(),
selectionrange.top() - 1, -1):
# @TODO: check
if not self.isLayer(parent.child(row)):
#continue
break
items = parent.takeRow(row)
rows.insert(0, items)
return rows
# @TODO: beginMoveRows, endMoveRows
def _moveSelectionRange(self, selectionrange, dst):
if selectionrange.top() == dst:
return selectionrange
model = selectionrange.model()
parentindex = selectionrange.parent()
parentitem = self._parentitem(selectionrange)
nrows_selected = selectionrange.bottom() - selectionrange.top() + 1
ncols = model.columnCount()
if nrows_selected == parentitem.rowCount():
return selectionrange
if dst > parentitem.rowCount() - nrows_selected or dst < 0:
return selectionrange
selectedrows = self._takeRowsRange(selectionrange)
for index, items in enumerate(selectedrows):
parentitem.insertRow(dst + index, items)
topleft = model.index(dst, 0, parentindex)
bottomright = model.index(dst + nrows_selected - 1, ncols - 1,
parentindex)
return QtGui.QItemSelectionRange(topleft, bottomright)
[docs] def moveSelectionToTop(self, selectionmodel):
#assert selectionmodel.model() is self.model
selection = selectionmodel.selection()
selectionmap = self._selectionmap(selection)
newselection = QtGui.QItemSelection()
for parent, ranges in selectionmap.iteritems():
dst = 0
for selectionrange in ranges:
newrange = self._moveSelectionRange(selectionrange, dst)
newselection.append(newrange)
dst = newrange.bottom() + 1
selectionmodel.select(newselection, SelectCurrentRows)
[docs] def moveSelectionUp(self, selectionmodel):
#assert selectionmodel.model() is self.model
selection = selectionmodel.selection()
selectionmap = self._selectionmap(selection)
newselection = QtGui.QItemSelection()
for parent, ranges in selectionmap.iteritems():
for selectionrange in ranges:
dst = selectionrange.top() - 1
newrange = self._moveSelectionRange(selectionrange, dst)
newselection.append(newrange)
selectionmodel.select(newselection, SelectCurrentRows)
[docs] def moveSelectionDown(self, selectionmodel):
#assert selectionmodel.model() is self.model
selection = selectionmodel.selection()
selectionmap = self._selectionmap(selection)
newselection = QtGui.QItemSelection()
for parent, ranges in selectionmap.iteritems():
ranges.reverse()
for selectionrange in ranges:
dst = selectionrange.top() + 1
newrange = self._moveSelectionRange(selectionrange, dst)
newselection.append(newrange)
selectionmodel.select(newselection, SelectCurrentRows)
[docs] def moveSelectionToBottom(self, selectionmodel):
#assert selectionmodel.model() is self.model
selection = selectionmodel.selection()
selectionmap = self._selectionmap(selection)
newselection = QtGui.QItemSelection()
nrows = selectionmodel.model().rowCount()
for parent, ranges in selectionmap.iteritems():
ranges.reverse()
dst = nrows
for selectionrange in ranges:
dst -= selectionrange.height()
newrange = self._moveSelectionRange(selectionrange, dst)
newselection.append(newrange)
selectionmodel.select(newselection, SelectCurrentRows)
[docs] def removeSelectedLayers(self, selectionmodel):
selection = selectionmodel.selection()
selectionmap = self._selectionmap(selection)
for parentindex, ranges in selectionmap.iteritems():
ranges.reverse()
parentitem = self._parentitem(ranges[0])
for selectionrange in ranges:
for row in range(selectionrange.top(),
selectionrange.bottom() + 1):
item = parentitem.child(row)
if not self.isLayer(item):
break
graphicsitem = item.data()
assert isinstance(graphicsitem, QtGui.QGraphicsItem)
scene = graphicsitem.scene()
scene.removeItem(graphicsitem)
else:
parentitem.removeRows(selectionrange.top(),
selectionrange.height())
# @TODO: check
#self.updateStackOrder(parent)
def _updateActions(self, selectionmodel):
model = selectionmodel.model()
enabled = selectionmodel.hasSelection()
for action in self.actions.actions():
if action.objectName() == 'selectAllAction':
selectedrows = selectionmodel.selectedRows()
nselected = len(selectedrows)
nlayers = model.rowCount()
allselected = bool(nselected == nlayers)
action.setEnabled(nlayers and not allselected)
elif action.objectName() in ('showLayerAction', 'hideLayerAction'):
selectedrows = selectionmodel.selectedRows()
nselected = len(selectedrows)
if nselected == 0:
action.setEnabled(False)
else:
items = [model.itemFromIndex(index)
for index in selectedrows]
activerows = [item.row() for item in items
if item.checkState() == QtCore.Qt.Checked]
if action.objectName() == 'showLayerAction':
action.setEnabled(len(activerows) != nselected)
elif action.objectName() == 'hideLayerAction':
action.setEnabled(len(activerows) != 0)
else:
action.setEnabled(enabled)
@QtCore.Slot(QtCore.QModelIndex)
#@QtCore.Slot(QtGui.QStandardItem)
@QtCore.Slot('QStandardItem*') # @TODO: fix
[docs] def updateVisibility(self, index):
if isinstance(index, QtCore.QModelIndex):
if index.column() != 0:
return
item = index.model().itemFromIndex(index)
elif isinstance(index, QtGui.QStandardItem):
item = index
index = item.index()
if index.column() != 0:
return
else:
raise TypeError('unexpected type for index parameter: "%s"' %
type(index))
checked = bool(item.checkState() == QtCore.Qt.Checked)
qlayer = item.data() # index.data(QtCore.Qt.UserRole + 1)
qlayer.setVisible(checked)
[docs] def checkSelectedItems(self, selectionmodel, checked=True):
model = selectionmodel.model()
#assert model is self.model
if checked:
newstate = QtCore.Qt.Checked
else:
newstate = QtCore.Qt.Unchecked
update = False
for index in selectionmodel.selectedRows():
item = model.itemFromIndex(index)
if item.checkState() != newstate:
item.setCheckState(newstate)
self.updateVisibility(index)
update = True
if update:
self._updateActions(selectionmodel)
[docs] def toggleSelectedItems(self, selectionmodel):
model = selectionmodel.model()
#assert model is self.model
update = False
for index in selectionmodel.selectedRows():
item = model.itemFromIndex(index)
state = item.checkState()
if state == QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Unchecked)
update = True
elif state == QtCore.Qt.Unchecked:
item.setCheckState(QtCore.Qt.Checked)
update = True
else:
logging.debug('unexpected check state: "%s"' % state)
continue
self.updateVisibility(index)
if update:
self._updateActions(selectionmodel)
[docs]class LayerManager(BaseLayerManager):
def __init__(self, view, parent=None, **kargs):
self.view = view
super(LayerManager, self).__init__(parent, **kargs)
view.addActions(self.actions.actions())
view.installEventFilter(self) # spacebar
# connect signals
model = self.view.model()
model.layoutChanged.connect(self.updateStackOrder)
model.rowsInserted.connect(self.updateStackOrder)
model.rowsInserted.connect(self._updateActions)
model.rowsRemoved.connect(self.updateStackOrder)
model.rowsRemoved.connect(self._updateActions)
model.itemChanged.connect(self.updateVisibility)
view.clicked.connect(self.updateVisibility)
view.activated.connect(self.toggleSelectedItems)
view.selectionModel().selectionChanged.connect(self._updateActions)
self._updateActions()
@property
[docs] def model(self):
return self.view.model()
@property
[docs] def selectionmodel(self):
return self.view.selectionModel()
def _setupActions(self):
actions = super(LayerManager, self)._setupActions()
icon = QtGui.QIcon(
':/trolltech/styles/commonstyle/images/viewdetailed-128.png')
QtGui.QAction(icon, self.tr('Select all'), actions,
objectName='selectAllAction',
statusTip=self.tr('Select all'),
shortcut=self.tr('Ctrl-A'),
triggered=self.view.selectAll)
# connect actions
action = actions.findChild(QtGui.QAction, 'moveToTopAction')
action.triggered.connect(self.moveSelectionToTop)
action = actions.findChild(QtGui.QAction, 'moveUpAction')
action.triggered.connect(self.moveSelectionUp)
action = actions.findChild(QtGui.QAction, 'moveDownAction')
action.triggered.connect(self.moveSelectionDown)
action = actions.findChild(QtGui.QAction, 'moveToBottomAction')
action.triggered.connect(self.moveSelectionToBottom)
action = actions.findChild(QtGui.QAction, 'removeLayerAction')
action.triggered.connect(self.removeSelectedLayers)
action = actions.findChild(QtGui.QAction, 'showLayerAction')
action.triggered.connect(self.checkSelectedItems)
action = actions.findChild(QtGui.QAction, 'hideLayerAction')
action.triggered.connect(self.uncheckSelectedItems)
return actions
[docs] def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.KeyPress and
event.key() == QtCore.Qt.Key_Space):
self.toggleSelectedItems()
return True
else:
return super(LayerManager, self).eventFilter(obj, event)
@QtCore.Slot()
[docs] def moveSelectionToTop(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).moveSelectionToTop(selectionmodel)
@QtCore.Slot()
[docs] def moveSelectionUp(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).moveSelectionUp(selectionmodel)
@QtCore.Slot()
[docs] def moveSelectionDown(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).moveSelectionDown(selectionmodel)
@QtCore.Slot()
[docs] def moveSelectionToBottom(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).moveSelectionToBottom(selectionmodel)
@QtCore.Slot()
[docs] def removeSelectedLayers(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).removeSelectedLayers(selectionmodel)
@QtCore.Slot()
[docs] def checkSelectedItems(self, selectionmodel=None, checked=True):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).checkSelectedItems(selectionmodel, checked)
@QtCore.Slot()
[docs] def uncheckSelectedItems(self, selectionmodel=None):
self.checkSelectedItems(selectionmodel, False)
@QtCore.Slot()
[docs] def toggleSelectedItems(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self).toggleSelectedItems(selectionmodel)
@QtCore.Slot()
def _updateActions(self, selectionmodel=None):
if selectionmodel is None:
selectionmodel = self.selectionmodel
super(LayerManager, self)._updateActions(selectionmodel)
@QtCore.Slot()
[docs] def updateStackOrder(self, rootitem=None):
if rootitem is None:
rootitem = self.model.invisibleRootItem()
super(LayerManager, self).updateStackOrder(rootitem)