Commit cc1cec7f authored by Dan Pascu's avatar Dan Pascu

Added drag and drop support for the contact list

parent 3d2240fc
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
from __future__ import with_statement from __future__ import with_statement
__all__ = ['Contact', 'ContactGroup', 'ContactDelegate', 'ContactModel', 'ContactSearchModel'] __all__ = ['Contact', 'ContactGroup', 'ContactDelegate', 'ContactModel', 'ContactSearchModel', 'ContactListView']
from PyQt4 import uic from PyQt4 import uic
from PyQt4.QtCore import Qt, QAbstractListModel, QEvent, QModelIndex, QPointF, QSize from PyQt4.QtCore import Qt, QAbstractListModel, QByteArray, QDataStream, QEvent, QIODevice, QMimeData, QModelIndex, QPointF, QRectF, QSize, QStringList, QTimer, QVariant
from PyQt4.QtGui import QBrush, QColor, QKeyEvent, QLinearGradient, QMouseEvent, QPainter, QPalette, QPen, QPixmap, QPolygonF, QStyle, QSortFilterProxyModel, QStyledItemDelegate from PyQt4.QtGui import QBrush, QColor, QKeyEvent, QLinearGradient, QListView, QMouseEvent, QPainter, QPainterPath, QPalette, QPen, QPixmap, QPolygonF, QStyle
from PyQt4.QtGui import QSortFilterProxyModel, QStyledItemDelegate
from application.python.util import Null from application.python.util import Null
from functools import partial from functools import partial
...@@ -25,6 +26,7 @@ class ContactGroup(object): ...@@ -25,6 +26,7 @@ class ContactGroup(object):
obj = object.__new__(cls) obj = object.__new__(cls)
obj.name = name obj.name = name
obj.widget = Null obj.widget = Null
obj.saved_state = Null
cls.instances[name] = obj cls.instances[name] = obj
return obj return obj
...@@ -56,6 +58,23 @@ class ContactGroup(object): ...@@ -56,6 +58,23 @@ class ContactGroup(object):
name = property(_get_name, _set_name) name = property(_get_name, _set_name)
del _get_name, _set_name del _get_name, _set_name
def collapse(self):
if not self.widget.collapse_button.isChecked():
self.widget.collapse_button.click()
def expand(self):
if self.widget.collapse_button.isChecked():
self.widget.collapse_button.click()
def save_state(self):
"""Saves the current state of the group (collapsed or not)"""
self.saved_state = self.widget.collapse_button.isChecked()
def restore_state(self):
"""Restores the last saved state of the group (collapsed or not)"""
if self.saved_state != self.widget.collapse_button.isChecked():
self.widget.collapse_button.click()
class ContactIconDescriptor(object): class ContactIconDescriptor(object):
def __init__(self, filename): def __init__(self, filename):
...@@ -121,6 +140,7 @@ class ContactGroupWidget(base_class, ui_class): ...@@ -121,6 +140,7 @@ class ContactGroupWidget(base_class, ui_class):
self.setupUi(self) self.setupUi(self)
self.name = name self.name = name
self.selected = False self.selected = False
self.drop_indicator = None
self.setFocusProxy(parent) self.setFocusProxy(parent)
self.label_widget.setFocusProxy(self) self.label_widget.setFocusProxy(self)
self.name_view.setCurrentWidget(self.label_widget) self.name_view.setCurrentWidget(self.label_widget)
...@@ -154,6 +174,18 @@ class ContactGroupWidget(base_class, ui_class): ...@@ -154,6 +174,18 @@ class ContactGroupWidget(base_class, ui_class):
selected = property(_get_selected, _set_selected) selected = property(_get_selected, _set_selected)
del _get_selected, _set_selected del _get_selected, _set_selected
def _get_drop_indicator(self):
return self.__dict__['drop_indicator']
def _set_drop_indicator(self, value):
if self.__dict__.get('drop_indicator', Null) == value:
return
self.__dict__['drop_indicator'] = value
self.update()
drop_indicator = property(_get_drop_indicator, _set_drop_indicator)
del _get_drop_indicator, _set_drop_indicator
def _start_editing(self): def _start_editing(self):
#self.name_editor.setText(self.name_label.text()) #self.name_editor.setText(self.name_label.text())
self.name_editor.selectAll() self.name_editor.selectAll()
...@@ -189,16 +221,37 @@ class ContactGroupWidget(base_class, ui_class): ...@@ -189,16 +221,37 @@ class ContactGroupWidget(base_class, ui_class):
painter.drawLine(rect.bottomLeft(), rect.bottomRight()) painter.drawLine(rect.bottomLeft(), rect.bottomRight())
#painter.drawLine(option.rect.topRight(), option.rect.bottomRight()) #painter.drawLine(option.rect.topRight(), option.rect.bottomRight())
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(QPen(QBrush(QColor('#dc3169')), 2.0))
if self.drop_indicator is ContactListView.AboveItem:
line_rect = QRectF(rect.adjusted(18, 0, 0, 5-rect.height()))
arc_rect = line_rect.adjusted(-5, -3, -line_rect.width(), -3)
path = QPainterPath(line_rect.topRight())
path.lineTo(line_rect.topLeft())
path.arcTo(arc_rect, 0, -180)
painter.drawPath(path)
elif self.drop_indicator is ContactListView.BelowItem:
line_rect = QRectF(rect.adjusted(18, rect.height()-5, 0, 0))
arc_rect = line_rect.adjusted(-5, 2, -line_rect.width(), 2)
path = QPainterPath(line_rect.bottomRight())
path.lineTo(line_rect.bottomLeft())
path.arcTo(arc_rect, 0, 180)
painter.drawPath(path)
elif self.drop_indicator is ContactListView.OnItem:
painter.setBrush(Qt.NoBrush)
painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 3, 3)
if self.collapse_button.isChecked(): if self.collapse_button.isChecked():
arrow = QPolygonF([QPointF(0, 0), QPointF(0, 9), QPointF(8, 4.5)]) arrow = QPolygonF([QPointF(0, 0), QPointF(0, 9), QPointF(8, 4.5)])
arrow.translate(QPointF(5, 4)) arrow.translate(QPointF(5, 4))
else: else:
arrow = QPolygonF([QPointF(0, 0), QPointF(9, 0), QPointF(4.5, 8)]) arrow = QPolygonF([QPointF(0, 0), QPointF(9, 0), QPointF(4.5, 8)])
arrow.translate(QPointF(5, 5)) arrow.translate(QPointF(5, 5))
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setBrush(foreground) painter.setBrush(foreground)
painter.setPen(QPen(painter.brush(), 0, Qt.NoPen)) painter.setPen(QPen(painter.brush(), 0, Qt.NoPen))
painter.drawPolygon(arrow) painter.drawPolygon(arrow)
painter.end() painter.end()
def event(self, event): def event(self, event):
...@@ -245,9 +298,10 @@ class ContactDelegate(QStyledItemDelegate): ...@@ -245,9 +298,10 @@ class ContactDelegate(QStyledItemDelegate):
def createEditor(self, parent, options, index): def createEditor(self, parent, options, index):
item = index.model().data(index, Qt.DisplayRole) item = index.model().data(index, Qt.DisplayRole)
if type(item) is ContactGroup: if type(item) is ContactGroup:
if item.widget is Null: collapsed = item.collapsed # if there was a previous editor widget, preserve its collapsed state
item.widget = ContactGroupWidget(item.name, parent) item.widget = ContactGroupWidget(item.name, parent)
item.widget.collapse_button.toggled.connect(partial(self._update_list_view, item)) item.widget.collapse_button.setChecked(collapsed)
item.widget.collapse_button.toggled.connect(partial(self._update_list_view, item))
return item.widget return item.widget
else: else:
return None return None
...@@ -296,11 +350,20 @@ class ContactDelegate(QStyledItemDelegate): ...@@ -296,11 +350,20 @@ class ContactDelegate(QStyledItemDelegate):
def paintContactGroup(self, group, painter, option, index): def paintContactGroup(self, group, painter, option, index):
if group.widget.size() != option.rect.size(): if group.widget.size() != option.rect.size():
# For some reason updateEditorGeometry only receives the peak value of # For some reason updateEditorGeometry only receives the peak value
# the size that the widget ever had, so it will never shrink it. -Dan # of the size that the widget ever had, so it will never shrink it.
group.widget.resize(option.rect.size()) group.widget.resize(option.rect.size())
group.widget.selected = bool(option.state & QStyle.State_Selected) group.widget.selected = bool(option.state & QStyle.State_Selected)
if option.state & QStyle.State_Selected and not option.state & QStyle.State_HasFocus:
# This condition is met when dragging is started on this group.
# We use this to to draw the dragged item image.
painter.save()
pixmap = QPixmap(option.rect.size())
group.widget.render(pixmap)
painter.drawPixmap(option.rect, pixmap)
painter.restore()
def paint(self, painter, option, index): def paint(self, painter, option, index):
item = index.model().data(index, Qt.DisplayRole) item = index.model().data(index, Qt.DisplayRole)
handler = getattr(self, 'paint%s' % item.__class__.__name__, Null) handler = getattr(self, 'paint%s' % item.__class__.__name__, Null)
...@@ -311,6 +374,9 @@ class ContactDelegate(QStyledItemDelegate): ...@@ -311,6 +374,9 @@ class ContactDelegate(QStyledItemDelegate):
class ContactModel(QAbstractListModel): class ContactModel(QAbstractListModel):
# The MIME types we accept in the order they should be handled
accepted_mime_types = ['application/x-blink-contact-group-list', 'application/x-blink-contact-list', 'text/uri-list']
def __init__(self, parent=None): def __init__(self, parent=None):
super(ContactModel, self).__init__(parent) super(ContactModel, self).__init__(parent)
self.items = [] self.items = []
...@@ -321,9 +387,10 @@ class ContactModel(QAbstractListModel): ...@@ -321,9 +387,10 @@ class ContactModel(QAbstractListModel):
return [item for item in self.items if type(item) is ContactGroup] return [item for item in self.items if type(item) is ContactGroup]
def flags(self, index): def flags(self, index):
if not index.isValid(): if index.isValid():
return QAbstractListModel.flags(self, index) return QAbstractListModel.flags(self, index) | Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable
return QAbstractListModel.flags(self, index) | Qt.ItemIsEditable else:
return QAbstractListModel.flags(self, index) | Qt.ItemIsDropEnabled
def rowCount(self, parent=QModelIndex()): def rowCount(self, parent=QModelIndex()):
return len(self.items) return len(self.items)
...@@ -333,6 +400,136 @@ class ContactModel(QAbstractListModel): ...@@ -333,6 +400,136 @@ class ContactModel(QAbstractListModel):
return None return None
return self.items[index.row()] return self.items[index.row()]
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def mimeTypes(self):
return QStringList(['application/x-blink-contact-list'])
def mimeData(self, indexes):
mime_data = QMimeData()
contact_data = QByteArray()
contact_group_data = QByteArray()
contact_stream = QDataStream(contact_data, QIODevice.WriteOnly)
contact_group_stream = QDataStream(contact_group_data, QIODevice.WriteOnly)
for index in (index for index in indexes if index.isValid()):
row = index.row()
item = self.items[row]
stream = contact_group_stream if type(item) is ContactGroup else contact_stream
stream.writeInt32(row)
stream.writeQVariant(QVariant(item))
if contact_data:
mime_data.setData('application/x-blink-contact-list', contact_data)
if contact_group_data:
mime_data.setData('application/x-blink-contact-group-list', contact_group_data)
return mime_data
def dropMimeData(self, mime_data, action, row, column, parent_index):
# this is here just to keep the default Qt DnD API happy
# the custom handler is in handleDroppedData
return False
def handleDroppedData(self, mime_data, action, index):
if action == Qt.IgnoreAction:
return True
for mime_type in self.accepted_mime_types:
if mime_data.hasFormat(mime_type):
name = mime_type.replace('/', ' ').replace('-', ' ').title().replace(' ', '')
handler = getattr(self, '_DH_%s' % name)
return handler(mime_data, action, index)
else:
return False
def _DH_ApplicationXBlinkContactGroupList(self, mime_data, action, index):
contact_groups = self.contact_groups
group = self.items[index.row()] if index.isValid() else contact_groups[-1]
drop_indicator = group.widget.drop_indicator
if group.widget.drop_indicator is None:
return False
selected_indexes = self.contact_list.selectionModel().selectedIndexes()
moved_groups = set(self.items[index.row()] for index in selected_indexes if index.isValid())
if group is contact_groups[0] and group in moved_groups:
drop_group = (group for group in contact_groups if group not in moved_groups).next()
drop_position = self.contact_list.AboveItem
elif group is contact_groups[-1] and group in moved_groups:
drop_group = (group for group in reversed(contact_groups) if group not in moved_groups).next()
drop_position = self.contact_list.BelowItem
elif group in moved_groups:
position = contact_groups.index(group)
if drop_indicator is self.contact_list.AboveItem:
drop_group = (group for group in reversed(contact_groups[:position]) if group not in moved_groups).next()
drop_position = self.contact_list.BelowItem
else:
drop_group = (group for group in contact_groups[position:] if group not in moved_groups).next()
drop_position = self.contact_list.AboveItem
else:
drop_group = group
drop_position = drop_indicator
items = self.popItems(selected_indexes)
contact_groups = self.contact_groups # they changed so refresh them
if drop_position is self.contact_list.AboveItem:
position = self.items.index(drop_group)
else:
position = len(self.items) if drop_group is contact_groups[-1] else self.items.index(contact_groups[contact_groups.index(drop_group)+1])
self.beginInsertRows(QModelIndex(), position, position+len(items)-1)
self.items[position:position] = items
self.endInsertRows()
for index, item in enumerate(items):
if type(item) is ContactGroup:
self.contact_list.openPersistentEditor(self.index(position+index))
else:
self.contact_list.setRowHidden(position+index, item.group.collapsed)
return True
def _DH_ApplicationXBlinkContactList(self, mime_data, action, index):
group = self.items[index.row()] if index.isValid() else self.contact_groups[-1]
if group.widget.drop_indicator is None:
return False
for contact in self.popItems(self.contact_list.selectionModel().selectedIndexes()):
contact.group = group
self.addContact(contact)
return True
def _DH_TextUriList(self, mime_data, action, index):
return False
@staticmethod
def item_mime_data_iterator(data):
stream = QDataStream(data)
while not stream.atEnd():
yield stream.readInt32(), stream.readQVariant().toPyObject()
@staticmethod
def range_iterator(indexes):
"""Return contiguous ranges from indexes"""
start = last = None
for index in sorted(indexes):
if start is None:
start = index
elif index-last>1:
yield (start, last)
start = index
last = index
else:
if indexes:
yield (start, last)
@staticmethod
def reversed_range_iterator(indexes):
"""Return contiguous ranges from indexes starting from the end"""
end = last = None
for index in reversed(sorted(indexes)):
if end is None:
end = index
elif last-index>1:
yield (last, end)
end = index
last = index
else:
if indexes:
yield (last, end)
def addContact(self, contact): def addContact(self, contact):
if contact.group in self.items: if contact.group in self.items:
for position in xrange(self.items.index(contact.group)+1, len(self.items)): for position in xrange(self.items.index(contact.group)+1, len(self.items)):
...@@ -353,11 +550,12 @@ class ContactModel(QAbstractListModel): ...@@ -353,11 +550,12 @@ class ContactModel(QAbstractListModel):
self.contact_list.openPersistentEditor(self.index(position)) self.contact_list.openPersistentEditor(self.index(position))
self.endInsertRows() self.endInsertRows()
def deleteContacts(self, indexes): def removeContact(self, contact):
rows = sorted(index.row() for index in indexes if index.isValid()) if contact not in self.items:
self.beginRemoveRows(QModelIndex(), rows[0], rows[-1]) return
for row in reversed(rows): position = self.items.index(contact)
self.items.pop(row) self.beginRemoveRows(QModelIndex(), position, position)
del self.items[position]
self.endRemoveRows() self.endRemoveRows()
def addGroup(self, group): def addGroup(self, group):
...@@ -369,19 +567,52 @@ class ContactModel(QAbstractListModel): ...@@ -369,19 +567,52 @@ class ContactModel(QAbstractListModel):
self.contact_list.openPersistentEditor(self.index(position)) self.contact_list.openPersistentEditor(self.index(position))
self.endInsertRows() self.endInsertRows()
def removeGroup(self, group):
if group not in self.items:
return
start = self.items.index(group)
end = start + len([item for item in self.items if type(item) is Contact and item.group==group])
self.beginRemoveRows(QModelIndex(), start, end)
del self.items[start:end+1]
self.endRemoveRows()
def removeItems(self, indexes):
rows = set(index.row() for index in indexes if index.isValid())
removed_groups = set(self.items[row] for row in rows if type(self.items[row]) is ContactGroup)
rows.update(row for row, item in enumerate(self.items) if type(item) is Contact and item.group in removed_groups)
for start, end in self.reversed_range_iterator(rows):
self.beginRemoveRows(QModelIndex(), start, end)
del self.items[start:end+1]
self.endRemoveRows()
def popItems(self, indexes):
items = []
rows = set(index.row() for index in indexes if index.isValid())
removed_groups = set(self.items[row] for row in rows if type(self.items[row]) is ContactGroup)
rows.update(row for row, item in enumerate(self.items) if type(item) is Contact and item.group in removed_groups)
for start, end in self.reversed_range_iterator(rows):
self.beginRemoveRows(QModelIndex(), start, end)
items[0:0] = self.items[start:end+1]
del self.items[start:end+1]
self.endRemoveRows()
return items
def test(self): def test(self):
work_group = ContactGroup('Work') work_group = ContactGroup('Work')
test_group = ContactGroup('Test')
for contact in [Contact(work_group, 'Dan Pascu', '31208005167@ag-projects.com', 'icons/avatar.png'), Contact(work_group, 'Lucian Stanescu', '31208005164@ag-projects.com'), Contact(work_group, 'Test number', '3333@ag-projects.com')]: for contact in [Contact(work_group, 'Dan Pascu', '31208005167@ag-projects.com', 'icons/avatar.png'), Contact(work_group, 'Lucian Stanescu', '31208005164@ag-projects.com'), Contact(work_group, 'Test number', '3333@ag-projects.com')]:
if contact.uri.startswith('3333@') or contact.uri.startswith('31208005167@'): if contact.uri.startswith('3333@') or contact.uri.startswith('31208005167@'):
contact.status = 'online' contact.status = 'online'
else: else:
contact.status = 'busy' contact.status = 'busy'
self.addContact(contact) self.addContact(contact)
self.addGroup(test_group) self.addGroup(ContactGroup('Test'))
self.addGroup(ContactGroup('Test 2'))
self.addGroup(ContactGroup('Test 3'))
class ContactSearchModel(QSortFilterProxyModel): class ContactSearchModel(QSortFilterProxyModel):
accepted_mime_types = ['text/uri-list']
def __init__(self, model, parent=None): def __init__(self, model, parent=None):
super(ContactSearchModel, self).__init__(parent) super(ContactSearchModel, self).__init__(parent)
self.setSourceModel(model) self.setSourceModel(model)
...@@ -408,3 +639,162 @@ class ContactSearchModel(QSortFilterProxyModel): ...@@ -408,3 +639,162 @@ class ContactSearchModel(QSortFilterProxyModel):
return left_item.name < right_item.name return left_item.name < right_item.name
class ContactListView(QListView):
def __init__(self, parent=None):
super(ContactListView, self).__init__(parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(False)
self.restore_timer = QTimer(self)
self.restore_timer.setSingleShot(True)
self.restore_timer.setInterval(1250)
self.restore_timer.timeout.connect(self._restore_groups)
self.needs_restore = False
def _restore_groups(self):
for group in self.model().contact_groups:
group.restore_state()
self.needs_restore = False
def paintEvent(self, event):
super(ContactListView, self).paintEvent(event)
model = self.model()
last_group = model.contact_groups[-1]
if last_group.widget.drop_indicator is self.BelowItem:
# draw the bottom part of the drop indicator for the last group
rect = self.visualRect(model.index(model.items.index(last_group)))
line_rect = QRectF(rect.adjusted(18, rect.height(), 0, 5))
arc_rect = line_rect.adjusted(-5, -3, -line_rect.width(), -3)
path = QPainterPath(line_rect.topRight())
path.lineTo(line_rect.topLeft())
path.arcTo(arc_rect, 0, -180)
painter = QPainter(self.viewport())
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setPen(QPen(QBrush(QColor('#dc3169')), 2.0))
painter.drawPath(path)
painter.end()
def dragEnterEvent(self, event):
event_source = event.source()
accepted_mime_types = set(self.model().accepted_mime_types)
provided_mime_types = set(str(x) for x in event.mimeData().formats())
acceptable_mime_types = accepted_mime_types & provided_mime_types
has_blink_contacts = 'application/x-blink-contact-list' in provided_mime_types
has_blink_groups = 'application/x-blink-contact-group-list' in provided_mime_types
if not acceptable_mime_types:
event.ignore() # no acceptable mime types found
elif has_blink_contacts and has_blink_groups:
event.ignore() # we can't handle drops for both groups and contacts at the same time
elif event_source is not self and (has_blink_contacts or has_blink_groups):
event.ignore() # we don't handle drops for blink contacts or groups from other sources
else:
if event_source is self:
event.setDropAction(Qt.MoveAction)
if has_blink_contacts or has_blink_groups:
if not self.needs_restore:
for group in self.model().contact_groups:
group.save_state()
group.collapse()
self.needs_restore = True
self.restore_timer.stop()
event.accept()
self.setState(self.DraggingState)
def dragLeaveEvent(self, event):
super(ContactListView, self).dragLeaveEvent(event)
for group in self.model().contact_groups:
group.widget.drop_indicator = None
if self.needs_restore:
self.restore_timer.start()
def dragMoveEvent(self, event):
super(ContactListView, self).dragMoveEvent(event)
if event.source() is self:
event.setDropAction(Qt.MoveAction)
for mime_type in self.model().accepted_mime_types:
if event.provides(mime_type):
index = self.indexAt(event.pos())
rect = self.visualRect(index)
item = self.model().data(index)
name = mime_type.replace('/', ' ').replace('-', ' ').title().replace(' ', '')
handler = getattr(self, '_DH_%s' % name)
handler(event, index, rect, item)
break
else:
event.ignore()
def _DH_ApplicationXBlinkContactGroupList(self, event, index, rect, item):
model = self.model()
groups = model.contact_groups
for group in groups:
group.widget.drop_indicator = None
if not index.isValid():
drop_groups = (groups[-1], Null)
rect = self.viewport().rect()
rect.setTop(self.visualRect(model.index(model.items.index(groups[-1]))).bottom())
elif type(item) is ContactGroup:
index = groups.index(item)
rect.setHeight(rect.height()/2)
if rect.contains(event.pos()):
drop_groups = (groups[index-1], groups[index]) if index>0 else (Null, groups[index])
else:
drop_groups = (groups[index], groups[index+1]) if index<len(groups)-1 else (groups[index], Null)
rect.translate(0, rect.height())
selected_rows = sorted(index.row() for index in self.selectionModel().selectedIndexes())
if selected_rows:
first = groups.index(model.items[selected_rows[0]])
last = groups.index(model.items[selected_rows[-1]])
contiguous_selection = len(selected_rows) == last-first+1
else:
contiguous_selection = False
selected_groups = set(model.items[row] for row in selected_rows)
overlapping_groups = len(selected_groups.intersection(drop_groups))
allowed_overlapping = 0 if contiguous_selection else 1
if event.source() is not self or overlapping_groups <= allowed_overlapping:
drop_groups[0].widget.drop_indicator = self.BelowItem
drop_groups[1].widget.drop_indicator = self.AboveItem
if groups[-1] in drop_groups:
self.viewport().update()
event.accept(rect)
def _DH_ApplicationXBlinkContactList(self, event, index, rect, item):
model = self.model()
groups = model.contact_groups
for group in groups:
group.widget.drop_indicator = None
if not index.isValid():
group = groups[-1]
rect = self.viewport().rect()
rect.setTop(self.visualRect(model.index(model.items.index(group))).bottom())
elif type(item) is ContactGroup:
group = item
selected_contact_groups = set(model.items[index.row()].group for index in self.selectionModel().selectedIndexes())
if event.source() is not self or len(selected_contact_groups) > 1 or group not in selected_contact_groups:
group.widget.drop_indicator = self.OnItem
event.accept(rect)
def _DH_TextUriList(self, event, index, rect, item):
model = self.model()
if not index.isValid():
rect = self.viewport().rect()
rect.setTop(self.visualRect(model.index(len(model.items)-1)).bottom())
if type(item) is Contact:
event.accept(rect)
else:
event.ignore(rect)
def dropEvent(self, event):
model = self.model()
if event.source() is self:
event.setDropAction(Qt.MoveAction)
if model.handleDroppedData(event.mimeData(), event.dropAction(), self.indexAt(event.pos())):
event.accept()
for group in model.contact_groups:
group.widget.drop_indicator = None
if self.needs_restore:
group.restore_state()
self.needs_restore = False
super(ContactListView, self).dropEvent(event)
...@@ -289,13 +289,16 @@ ...@@ -289,13 +289,16 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QListView" name="contact_list"> <widget class="ContactListView" name="contact_list">
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
<property name="alternatingRowColors"> <property name="alternatingRowColors">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
...@@ -804,6 +807,11 @@ buttons below.</string> ...@@ -804,6 +807,11 @@ buttons below.</string>
<extends>QLineEdit</extends> <extends>QLineEdit</extends>
<header>blink.widgets.lineedit</header> <header>blink.widgets.lineedit</header>
</customwidget> </customwidget>
<customwidget>
<class>ContactListView</class>
<extends>QListView</extends>
<header>blink.contacts</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>search_box</tabstop> <tabstop>search_box</tabstop>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment