Найти - Пользователи
Полная версия: PyQt4: поворот и drag-and-drop картинок
Начало » GUI » PyQt4: поворот и drag-and-drop картинок
1 2 3 4 5 6 7
poltergeist
Посмотрите примеры реализации dragEnterEvent в примерах, там надо проверить наличие в MimeData нужных данных (изображение или что-то другое, в зависимости от того как реализуете) и подтвердить это (event.setAccepted(True)). Иначе, dropEvent не будет вызываться.
Добираться до объекта QDrag не нужно, всё что переносите, храниться в mimeData, так что тут стоит задуматься, как переносить информацию… Можно запихнуть изображение целиком (поток данных), можно просто путь к файлу изображения, сами решайте как вам удобнее…

кстати, правильно ли это?
Да, вроде правильно делаете.
The gray Cardinal
Сделал, но коряво до ужаса.
Картинки, которые необходимы для примера (должны лежать рядом с примером): http://stream.ifolder.ru/9445057 (архив 30 Кб).
Код примера (пригоден для запуска):
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore

class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 600, 500)

# создание сцены для отображения элементов-рисунков:
self.scene = Scene()
# создание виджета представления для отображения сцены:
view = QtGui.QGraphicsView(self.scene, self)
# параметры качества прорисовки для виджета представления:
view.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
view.setBackgroundBrush(QtGui.QColor(0, 128, 64)) # цвет фона представления
self.setCentralWidget(view) # размещение виджета представления в главном окне

# первый элемент:
item = Element(QtGui.QPixmap(u'011.jpg'), None, self.scene)
item.setZValue(1)

# второй элемент (с поворотом)
item2 = Element(QtGui.QPixmap(u'113.jpg'), None, self.scene)
item2.rotate(25) # поворот
item2.setOffset(100, -30) # смещение
item2.setZValue(2)

class Scene(QtGui.QGraphicsScene):
def __init__(self, parent = None):
QtGui.QGraphicsScene.__init__(self, parent)
self.dndElement = None
self.dndPixmap = None

# операция drag and drop входит в область сцены
def dragEnterEvent(self, event):
pass

# операция drag and drop покидает область сцены
def dragLeaveEvent(self, event):
pass

# в процессе выполнения операции drag and drop
def dragMoveEvent(self, event):
pass

# завершение операции drag and drop
def dropEvent(self, event):
# создание копии перенесённого элемента на новом месте:
item = Element(self.dndPixmap, None, self)
item.setOffset(event.scenePos().x() - 80, event.scenePos().y() - 110)
# удаление перенесённого элемента:
self.removeItem(self.dndElement)
self.dndElement = None
self.dndPixmap = None

class Element(QtGui.QGraphicsPixmapItem):
def __init__(self, pixmap, parent = None, scene = None):
QtGui.QGraphicsPixmapItem.__init__(self, pixmap, parent, scene)
self.setTransformationMode(QtCore.Qt.SmoothTransformation) # качество прорисовки
self.setCursor(QtCore.Qt.OpenHandCursor) # вид курсора мыши над элементом
#self.setAcceptDrops(True)

def mousePressEvent(self, event):
if event.button() != QtCore.Qt.LeftButton: # только левая клавиша мыши
event.ignore()
return
drag = QtGui.QDrag(event.widget()) # объект Drag
mime = QtCore.QMimeData()
drag.setMimeData(mime)
self.scene().dndElement = self # запоминаем элемент, который переносится
self.scene().dndPixmap = self.pixmap()
drag.setPixmap(self.pixmap()) # рисунок, отображающийся в процессе переноса

drag.setHotSpot(QtCore.QPoint(80, 110)) # позиция "ухватки"
# x = int(self.mapToScene(event.scenePos()).x())
# y = int(self.mapToScene(event.scenePos()).y())
# drag.setHotSpot(QtCore.QPoint(x, y))

# временный "затемнённый" рисунок перетаскиваемой картинки
tempPixmap = QtGui.QPixmap(self.pixmap())
painter = QtGui.QPainter()
painter.begin(tempPixmap)
painter.fillRect(self.pixmap().rect(), QtGui.QColor(127, 127, 127, 127))
painter.end()
self.setPixmap(tempPixmap)

drag.start() # запуск (начало) перетаскивания

if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
Проблемы и вопросы:

1. Я так и не понял, как правильно воспользоваться QMimeData для передачи данных в момент перетаскивания. Картинку таким способом передать вроде можно, а вот как передать сам перетаскиваемый элемент, я не понял. Мне сам этот элемент нужен для того, чтобы его удалить при окончании перетаскивания. В результате я сделал свойство в классе сцены, чтобы в нём хранить перетаскиваемый в данный момент элемент. Это сильный моветон? Кстати, как устанавливать (хранить?) Z-порядок при перетаскивании (это нужно, т.к. перетаскиваемый рисунок запросто может отрисоваться сзади)?

2. В строчке “drag = QtGui.QDrag(event.widget())” я сам не до конца понимаю, что делаю. Что такое event.widget()? Это объект представления QGraphicsView?

3. Насколько я понял, сцена меняет свои размеры автоматически, при необходимости. И она вначале меньше по площади, чем QGraphicsView, который к ней подключён. В момент перетаскивания картинок в примере всё начинает некрасиво дёргаться, если перетаскивание делается таким образом, что оно приводит к расширению сцены. Правильно ли я это понял? Как избежать дёрганий?

4. При начале перетаскивания можно задать для объекта QDrag “позицию ухватки мыши” методом setHotSpot, что в коде и сделано. Там же ты можешь наблюдать неуклюжую попытку пересчитать координаты, чтобы ухватываться за ту часть картинки, над которой визуально был курсор мыши в момент начала действия (эти строки в коде закомментированы). Попытка эта работает только для случая, когда элемент не трансформирован (в данном случае - не повёрнут). Как пересчитать универсально, с учётом поворота и масштабирования, если численные характеристики этих двух операций заранее известны?
ZZZ
:-)
Да… Это проблема, что люди, изучающие PyQt забывают, что пишут на питоне. Сам проходил. Слишком уж много Сишности в этом Qt… :-)
    def mousePressEvent(self, event):
drag = QtGui.QDrag(event.widget()) # объект Drag
mime = QtCore.QMimeData()
mime.Element = self
drag.setMimeData(mime)
    def dropEvent(self, event):
print event.mimeData().Element
Думаю, что первый вопрос это решит.
Но, обрати вримание, что в model-view это не всегда работает – как-то долго не мог понять, почему с QModelIndex такой фокус не проходит. :-)

Ещё обрати внимание на QDropEvent::source(), это разрешит второй вопрос и более Qt'шно решит первый.

Думаю, что четвёртый вопрос тебе поможет решить QMatrix, но как конкретно, я тебе не скажу, потому что сам уже не помню этих тонкостей. Ну или вспомнить школьную математику и пересчитать с учётом угла поворота. Я вообще, не занимался отрисовкой в Qt4 и QGraphicsView вместе с QGraphicsScene для меня пока магия… Хотя сейчас глянул и идея мне понравилась.

Мыслей по третьему вопросу пока нет.
ZZZ
Блин, ты мемя совсем запутал! Ну на кой, простите, хрен ты использовал QGraphicsItem::setOffset()? Используй QGraphicsItem::setPos()! Тогда QGraphicsSceneMouseEvent::pos() будет точно возвращать тебе нужные координаты: drag.setHotSpot(event.pos().toPoint()). И никаких матриц…
Дальше, я думаю, справишься.
poltergeist
ZZZ я бы всё же посоветовал не хакать по-питонски (mime.Element = self) и усложнять жизнь разработчику… можно ведь обойтись “правильным” способом переноса данных (mime.setData(mtype, data)) драг&дропом, который уже будет в себе иметь понятный интерфейс (mtype). Такой подход будет универсальным, т.е. можно будет передавать данные не только в пределах одного приложения… Этот человек пишет пример, в том числе и для того чтобы других учить, а твой пример я считаю не хорошим в этом плане:(
The gray Cardinal
ZZZ
Ещё обрати внимание на QDropEvent::source(), это разрешит второй вопрос и более Qt'шно решит первый.
Не понял. Вопрос в том, что я не пойму, что именно там за виджет сидит, в моём случае. Это не мой элемент-наследник QGraphicsPixmapItem, это не сцена, это не её QGraphicsView, и это не главное окно. Что это тогда? У меня больше никаких объектов-то нет :).
The gray Cardinal
Да, кстати: ZZZ, огромное спасибо за участие :) poltergeist - тоже :).
The gray Cardinal
ZZZ
print event.mimeData().Element
Спасибо, шикарненько :). Никогда не отдавал себе отчёта в этом…
class MyClass():
pass

obj = MyClass()
obj.bla_bla = 'Python is debauched!'
print obj.bla_bla
Как эта возможность по-научному называется? “Разнузданный Python”? :)
The gray Cardinal
Кажется, осилил.
gmorgunov
Привет. Поздравляю. :)
А вот вариант решения на PyGTK. По-моему проще. Ухваченная картинка ложится всегда сверху ( по идее, так и должно быть). Движение на второй картинке(av3435.gif - движущийся пингвин в моем профиле) сохраняется.
#!/usr/bin/python
# coding: utf-8

import pygtk
from gtk import *
import time

class DNDImageButton:
file1 ="/home/mike/Desktop/lena.jpg"
file2 ="/home/mike/Desktop/av3435.gif"
fromImage=[("",0,0),("",0,0)]
toCanvas=[("",0,0)]
def __init__(self):
################### хэши - для распознавания кнопок ############################
global HASH,HASH1,HASH2
self.window = Window(WINDOW_TOPLEVEL)
self.window.set_default_size(500, 500)
self.window.connect("destroy", lambda w: main_quit())
self.window.show()
layout = self.makeLayout()
self.window.add(layout)
self.myaddImage(100, 100, self.file1)
self.myaddImage(200, 200, self.file2)
################### установки layout-a ###########################
def layout_resize(self, widget, event):
x, y, width, height = widget.get_allocation()
if width > self.lwidth or height > self.lheight:
self.lwidth = max(width, self.lwidth)
self.lheight = max(height, self.lheight)
widget.set_size(self.lwidth, self.lheight)
def makeLayout(self):
self.lwidth = 0
self.lheight = 0
box = VBox()
box.show()
table = Table()
table.show()
box.pack_start(table)
layout = Layout()
self.layout = layout
layout.set_size(self.lwidth, self.lheight)
layout.connect("size-allocate", self.layout_resize)
layout.show()
table.attach(layout, 0, 1, 0, 1, FILL|EXPAND, FILL|EXPAND, 0, 0)

################### испускаем сигналы при перетаскивании ##########################
layout.connect('drag_leave', self.target_drag_leave)
layout.connect('drag_motion', self.target_drag_motion)
layout.connect('drag_drop', self.target_drag_drop)
layout.connect("drag_data_received", self.receiveCallback)

################### установки цели ##########################
layout.drag_dest_set(DEST_DEFAULT_MOTION |
DEST_DEFAULT_HIGHLIGHT |
DEST_DEFAULT_DROP,
self.toCanvas, gdk.ACTION_MOVE )
return box
####################### устаеавл. виджет в коорд. xd, yd ##########################
def myaddImage(self, xd, yd, f):
global HASH,HASH1,HASH2
hadj = self.layout.get_hadjustment()
vadj = self.layout.get_vadjustment()
image = Image()
image.set_from_file(f)
button = Button()
button.add(image)
if f == self.file1:
HASH1 = button.__hash__()
else:
HASH2 = button.__hash__()
####################### соединяем виджет ##########################
button.connect('button_press_event', self.button_press)
button.connect("drag_data_get", self.sendCallback)
button.connect('drag_data_delete', self.delete_cb)
button.connect("drag_data_received", self.receiveCallback)
###################### установки источника ##########################
button.drag_source_set(gdk.BUTTON1_MASK, self.fromImage, gdk.ACTION_MOVE )
button.show_all()
self.layout.put(button, int(xd+hadj.value), int(yd+vadj.value))
print 'create button'
return

####################### функции CALLBACK ##########################
def button_press(self,button,event):
global HASH,HASH1,HASH2
print 'button_press'
HASH = button.__hash__()

def sendCallback(self, widget, context, selection, targetType, eventTime):
print "send_cb "
selection.set(selection.target, 8, "")


def receiveCallback(self, widget, context, x, y, selection, targetType, time):
global HASH,HASH1,HASH2
print "receive_cb "
if HASH == HASH1:
self.myaddImage(x,y,self.file1)
else:
self.myaddImage(x,y,self.file2)

def delete_cb(self, widget, context):
print "delete_cb "
widget.hide_on_delete()

def target_drag_leave(self, widget, context, time):
print 'leave'

def target_drag_motion(self, widget, context, x, y, time):
print 'motion'
print x,y
def target_drag_drop(self, widget, context, x, y, time):
print 'drop'
####################################################################
####################################################################
DNDImageButton()
main()
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB