Довольно часто при работе с классом QCanvas требуется обеспечить индивидуальное ручное масштабирование элементов QCanvasItem. В этой статье показано, как реализуется выбор, перемещение и масштабирование элементов QCanvasItem на примере небольшой демонстрационной программы.
В программе Scale представлено двухмерное пространство и некоторое число объектов (планет). Объекты можно свободно выбирать, перемещать и масштабировать с помощью мыши. Положение, название и текущий размер объекта отображаются в верхнем левом углу главного окна программы. С помощью меню может быть выбрана одна из двух функций и один из трех режимов масштабирования.
Мы используем 3 класса:
Конструктор Scale создает объект QCanvas и настраивает ScaleView для его просмотра. Также мы создаем и инициализируем планеты, и устанавливаем их начальное положение и размеры.
Scale::Scale() : QMainWindow()
{
setCaption("Scale Example");
// Создаем и настраиваем QCanvas и ScaleView
QCanvas* canvas = new QCanvas(this);
canvas->setBackgroundColor(black);
canvas->resize(500, 370);
ScaleView* view = new ScaleView(canvas, this);
setCentralWidget(view);
// Добавляем планеты
new ScaleItem("Sun", QImage("pics/sun.png"), 300, 150, 50, 50, canvas);
new ScaleItem("Mars", QImage("pics/mars.png"), 100, 100, 30, 30, canvas);
new ScaleItem("Saturn", QImage("pics/saturn.png"), 400, 50, 50, 50, canvas);
new ScaleItem("Jupiter", QImage("pics/jupiter.png"), 50, 200, 150, 150, canvas);
new ScaleItem("Mercury", QImage("pics/mercury.png"), 300, 200, 10, 10, canvas);
new ScaleItem("Neptune", QImage("pics/neptune.png"), 350, 250, 30, 30, canvas);
...
}
|
Конструктор ScaleView создает три текстовых объекта QCanvasText в верхнем левом углу QCanvas, предназначенные для вывода информации о выбранной планете, и включает режим отслеживания перемещений мыши. Выбор планеты и обеспечение захвата ее границ осуществляется обработчиком событий contentsMousePressEvent().
void ScaleView::contentsMousePressEvent(QMouseEvent* e) { pressed = true; int cursor = viewport()->cursor().shape(); // Определяем форму курсора if( cursor == SizeVerCursor || cursor == SizeFDiagCursor || cursor == SizeHorCursor || cursor == SizeBDiagCursor) { // Осуществляем захват границы планеты, сохраняем положение курсора. selectedPos = e->pos(); return; } // Осуществляем выбор планеты // Получаем список всех объектов QCanvasItem, находящихся под курсором QCanvasItemList l = canvas()->collisions(e->pos()); for(QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) { // Выбираем только планеты if((*it)->rtti() != ItemRtti) continue; ScaleItem* si = (ScaleItem*)(*it); // Выбранная точка не должна быть прозрачной if(!si->hit(e->pos())) continue; selectedPos = e->pos(); selectItem(si); // делаем выбор return; } // Под курсором не обнаружено ни одной планеты selectItem(0); } |
Обработчик событий contentsMouseMoveEvent() определяет положение указателя мыши относительно границ планеты и устанавливает соответствующую форму курсора:
void ScaleView::contentsMouseMoveEvent(QMouseEvent* e) { if(!selected) return; // Ни одна из планет не выбрана if(!pressed) // Если мы не удерживаем планету... { // ...то определяем положение указателя мыши относительно нее static const int sens = 3; // Чувствительность мыши int x = e->x(); int y = e->y(); int x1 = (int) selected->x(); // Наружный левый край int x2 = x1 + sens; // Внутренний левый край int x4 = x1 + selected->width(); // Наружный правый край int x3 = x4 - sens; // Внутренний правый край int y1 = (int) selected->y(); // Наружный верхний край int y2 = y1 + sens; // Внутренний верхний край int y4 = y1 + selected->height(); // Наружный нижний край int y3 = y4 - sens; // Внутренний нижний край int cursor = ArrowCursor; // Форма курсора мыши по-умолчанию if(x > x4 || y > y4); // Ничего не делаем, если курсор за пределами планеты else if(x > x3) if(y > y3) cursor = SizeFDiagCursor; // Курсор над нижним правым углом else if(y > y2) cursor = SizeHorCursor; // Курсор над правым краем else if(y > y1) cursor = SizeBDiagCursor; // Курсор над верхним правым углом else ; else if(x > x2) if(y > y3) cursor = SizeVerCursor; // Курсор над нижним краем else if(y > y2) ; // Курсор над внутренней частью планеты else if(y > y1) cursor = SizeVerCursor; // Курсор над верхним краем else ; else if(x > x1) if(y > y3) cursor = SizeBDiagCursor; // Курсор над нижним левым углом else if(y > y2) cursor = SizeHorCursor; // Курсор над левым краем else if(y > y1) cursor = SizeFDiagCursor; // Курсор над верхним левым углом else ; else ; viewport()->setCursor(cursor); // Устанавливаем форму курсора мыши } else // Мы удерживаем планету { static const int minimum = 8; // Минимальный размер планеты QPoint offset = e->pos() - selectedPos; // Смещение мыши int w = selected->width(); int h = selected->height(); int cursor = viewport()->cursor().shape(); // Определяем форму курсора // Требуется изменить ширину планеты if(cursor == SizeHorCursor || cursor == SizeFDiagCursor || cursor == SizeBDiagCursor) // Курсор находится справа от центра планеты if(selectedPos.x() > selected->rect().center().x()) { w = QMAX(w + offset.x(), minimum); selectedPos.setX(QMAX(selected->x() + minimum, e->x())); } else // Курсор находится слева от центра планеты { selected->setX(QMIN(selected->x() + offset.x(), selected->rect().right() - minimum + 1)); w = QMAX(w - offset.x(), minimum); selectedPos.setX(QMIN(e->x(), selected->x())); } // Требуется изменить высоту планеты if(cursor == SizeVerCursor || cursor == SizeFDiagCursor || cursor == SizeBDiagCursor) // Курсор находится ниже центра планеты if(selectedPos.y() > selected->rect().center().y()) { h = QMAX(h + offset.y(), minimum); selectedPos.setY(QMAX(selected->y() + minimum, e->y())); } else // Курсор находится выше центра планеты { selected->setY(QMIN(selected->y() + offset.y(), selected->rect().bottom() - minimum + 1)); h = QMAX(h - offset.y(), minimum); selectedPos.setY(QMIN(e->y(), selected->y())); } // Необходимо переместить планету if(cursor == ArrowCursor) { selected->moveBy(offset.x(), offset.y()); selectedPos = e->pos(); } else selected->scale(w, h); // Осуществляем масштабирование планеты updateInformation(); // Обновляем информацию о планете canvas()->update(); // Обновляем содержимое QCanvas } } |
Для осуществления масштабирования изображений Qt предлагает две функции:
Различие в работе функций scale и smoothScale наиболее заметно в получаемом изображении "колец Сатурна". Функции масштабирования могут работать в одном из трех режимов:
Класс ScaleItem обеспечивает отрисовку планет и масштабирование изображений (функции drawShape() и scale()). Функция hit() проверяет, попали ли мы в прозрачную область планеты. Функция adjust() производит выравнивание планеты по границам видимой области QCanvas. Для повышения производительности класс ScaleItem содержит два изображения: оригинальное и масштабированное.
void ScaleItem::drawShape(QPainter& p) { // Прорисовка масштабированного изображения p.drawImage(x(), y(), scaled, 0, 0, width(), height()); if(isSelected()) p.drawRect(rect()); // Прорисовка размерной рамки } void ScaleItem::scale(int w, int h) { // Создание масштабированного изображения if(scaling == Simple) // Используется быстрое масштабирование scaled = image.scale(w, h, mode); else // Используется масштабирование со сглаживанием scaled = image.smoothScale(w, h, mode); setSize(w, h); update(); } bool ScaleItem::hit(const QPoint& p) const { // Проверка прозрачности в данной точке изображения int dx = p.x() - (int)x(); int dy = p.y() - (int)y(); if(!scaled.valid(dx, dy)) return false; return qAlpha(scaled.pixel(dx, dy)); } void ScaleItem::adjust() { // Сокращение размеров объекта до размеров изображения планеты setSize(QMIN(width(), scaled.width()), QMIN(height(), scaled.height())); // Выравнивание планеты по границам области QCanvas int xmax = canvas()->width() - width(); int ymax = canvas()->height() - height(); if(x() > xmax) setX(xmax); if(y() > ymax) setY(ymax); if(x() < 0) setX(0); if(y() < 0) setY(0); } |
Исходный код программы Scale: scale.tar.gz