#include "blockview.h"

#include "qad_types.h"

#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QElapsedTimer>
#include <QGraphicsSceneMouseEvent>
#include <QMimeData>
#include <QScrollBar>
#include <QShortcut>
#include <QVector2D>
#include <qmath.h>

const QString _BlockView_Mime_ = "_BlockView_copypaste_";


BlockView::BlockView(QWidget * parent): QGraphicsView(parent), tmp_bus(true) {
	_init();
}


BlockView::BlockView(QGraphicsScene * scene, QWidget * parent): QGraphicsView(scene, parent), tmp_bus(true) {
	_init();
}


BlockView::~BlockView() {}


void BlockView::_init() {
	qRegisterMetaType<BlockItem *>();
	qRegisterMetaType<BlockItemPin *>();
	qRegisterMetaType<BlockBusItem *>();
	grid_visible = grid_snap = pm_connect = navigation = m_connect = m_trace_with_buses = prev_tcb = minimap = true;
	mm_drag = moved = new_branch = new_bus = mm_cancel = iconnect = mm_copy = m_pin_mc = mm_thumb = move_bus_point = wheel_zoom = false;
	match_bus = bus_from = cur_bus = 0;
	mm_ci                          = 0;
	hpin                           = 0;
	ghost_                         = 0;
	grid_step                      = 10.;
	grid_points                    = 1;
	grid_pen                       = QPen(palette().color(QPalette::Disabled, QPalette::WindowText), 1, Qt::NoPen);
	thick                          = 1;
	thumb_hide_delay               = 500;
	timer_thumb                    = 0;
	smode                          = BlockView::MultiSelection;
	cur_scl = thumb_scl = prev_app_scale = 1.;
	_talpha                              = 0.;
	ae_enabled = is_block_anim = is_nav_anim = true;
	nav_prev_aa = nav_prev_imaa = nav_prev_grid = true;
	square_node = block_emit_selection = new_bus_started = false;
	thumb_size                                           = QSizeF(200, 200);
	if (scene() == 0) {
		scene_ = new QGraphicsScene;
		setScene(scene_);
	}
	scene_ = scene();
	scene_->setSceneRect(-2500, -2500, 5000, 5000);
	scene_->setItemIndexMethod(QGraphicsScene::NoIndex);
	scene_->installEventFilter(this);
	scene_->addItem(&tmp_bus);
	widget_thumb.setParent(this);
	widget_thumb.installEventFilter(this);
	widget_thumb.setMouseTracking(true);
	widget_thumb.show();
	widget_thumb.setWindowOpacity(0.);
	thumb_anim.setTargetObject(this);
	thumb_anim.setPropertyName("_thumb");
	thumb_anim.setEasingCurve(QEasingCurve::InCubic);
	nav_anim.setTargetObject(this);
	nav_anim.setPropertyName("_nav");
	nav_anim.setEasingCurve(QEasingCurve::Linear);
	nav_anim.setDuration(150);
	connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(checkPaste()));
	connect(&nav_anim, SIGNAL(finished()), this, SLOT(_navFinished()));
	connect(scene_, SIGNAL(sceneRectChanged(QRectF)), this, SLOT(adjustThumb()));
	connect(scene_, SIGNAL(selectionChanged()), this, SLOT(sceneSelectionChanged()));
	connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrolled()));
	connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrolled()));
	centerOn(scene_->sceneRect().center());
	setCacheMode(CacheBackground);
	setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
	setResizeAnchor(QGraphicsView::AnchorViewCenter);
	setRenderHint(QPainter::Antialiasing);
	setRenderHint(QPainter::SmoothPixmapTransform);
	setMouseTracking(true);
	sel_rect.setZValue(999.);
	sel_rect.hide();
	QColor sc = palette().color(QPalette::Highlight);
	sc.setAlphaF(0.6);
	QPen pen(sc.darker(200), lineThickness(this) + 1., Qt::DotLine);
	pen.setCosmetic(true);
	sel_rect.setPen(pen);
	sc.setAlphaF(0.2);
	sel_rect.setBrush(QBrush(sc));
	checkPaste(true);
}


void BlockView::_updateBack() {
	setCacheMode(CacheNone);
	invalidateScene();
	setCacheMode(CacheBackground);
}


bool BlockView::event(QEvent * e) {
	if (e->type() == QEvent::FontChange || e->type() == QEvent::Polish) {
		double cscl = appScale(this);
		QGraphicsView::scale(cscl / prev_app_scale, cscl / prev_app_scale);
		prev_app_scale = cscl;
	}
	if (e->type() == QEvent::EnabledChange) {
		_updateBack();
	}
	return QGraphicsView::event(e);
}


bool BlockView::eventFilter(QObject * o, QEvent * e) {
	if (o == &widget_thumb) {
		QMouseEvent * me = (QMouseEvent *)e;
		if (!me) return true;
		switch (e->type()) {
		case QEvent::Paint: drawThumb(); return true;
		case QEvent::Enter: thumbShow(); break;
		case QEvent::Leave: restartTimer(timer_thumb, thumb_hide_delay); break;
		case QEvent::MouseButtonPress:
			thumb_press = me->pos();
			widget_thumb.setCursor(Qt::ClosedHandCursor);
			if (!thumb_vr.contains(thumb_press)) {
				thumb_vr.moveCenter(thumb_press);
				scrollFromThumb();
			}
			break;
		case QEvent::MouseButtonRelease: widget_thumb.setCursor(Qt::OpenHandCursor); break;
		case QEvent::MouseMove:
			if (me->buttons() == 0) widget_thumb.setCursor(thumb_vr.contains(me->pos()) ? Qt::OpenHandCursor : Qt::CrossCursor);
			if (me->buttons().testFlag(Qt::LeftButton)) {
				thumb_vr.translate(me->pos() - thumb_press);
				scrollFromThumb();
				thumb_press = me->pos();
			}
			break;
		default: break;
		}
		return QGraphicsView::eventFilter(o, e);
	}
	if (o == scene_) {
		QGraphicsSceneMouseEvent * me = (QGraphicsSceneMouseEvent *)e;
		QList<QGraphicsItem *> mil;
		QPointF mdp;
		bool fmm_drag = false;
		int btncnt    = 0;
		switch (e->type()) {
		case QEvent::GraphicsSceneMouseDoubleClick:
			scene_point = me->scenePos();
			mil         = scene_->items(scene_point);
			foreach(QGraphicsItem * i, mil) {
				if (i->data(bvidTmpItem).toBool()) continue;
				if (i->data(bvidType).toInt() == bvitBlock) {
					QMetaObject::invokeMethod(
						this,
						[this, i]() { blockDoubleClicked(qgraphicsitem_cast<BlockItem *>(i)); },
						Qt::QueuedConnection);
					return true;
				}
				if (i->data(bvidType).toInt() == bvitBus) {
					if (qgraphicsitem_cast<BlockBusItem *>(i)->isBusSelected()) {
						QMetaObject::invokeMethod(
							this,
							[this, i]() { busDoubleClicked(qgraphicsitem_cast<BlockBusItem *>(i)); },
							Qt::QueuedConnection);
						return true;
					}
				}
			}
			break;
		case QEvent::GraphicsSceneMousePress:
			if (mm_ci != 0) {
				if (mm_ci->data(bvidTmpItem).toBool()) {
					mm_ci = 0;
					break;
				}
			}
			if (me->buttons().testFlag(Qt::LeftButton)) btncnt++;
			if (me->buttons().testFlag(Qt::RightButton)) btncnt++;
			if (me->buttons().testFlag(QT_MID_BUTTON)) btncnt++;
			mm_cancel = btncnt >= 2;
			match_bus = bus_from = 0;
			hpin                 = 0;
			copy_dp              = QPointF();
			// qDebug() << mm_cancel << moved << sel_rect.isVisible();
			if (sel_rect.isVisible()) {
				QList<QGraphicsItem *> gi = scene_->items();
				block_emit_selection      = true;
				foreach(QGraphicsItem * i, gi)
					i->setSelected(i->data(bvidSelected).toBool());
				block_emit_selection = false;
				emit selectionChanged();
			}
			// qDebug() << "cur_bus" << cur_bus;
			if (cur_bus) {
				return false;
			}
			if (mm_cancel) {
				if (mm_copy) {
					deleteCopyTemp();
					mm_copy = moved = false;
					unsetCursor();
				}
				if (new_bus) {
					new_bus = false;
					unmarkPins(true);
					reconnectAll();
					hideTmpBuses();
					m_trace_with_buses = prev_tcb;
				}
				if (new_branch) {
					new_branch = false;
					hideTmpBuses();
					m_trace_with_buses = prev_tcb;
				}
				if (moved) {
					moved = false;
					restoreSelState();
					hideTmpBuses();
					m_trace_with_buses = prev_tcb;
				}
				return true;
			}
			mm_mods = me->modifiers();
			mm_drag = moved = false;
			screen_point    = me->screenPos();
			scene_point     = me->scenePos();
			if ((me->button() == QT_MID_BUTTON) || (me->button() == Qt::RightButton)) {
				thumbShow();
				restartTimer(timer_thumb, thumb_hide_delay);
				return true;
			}
			mil = scene_->items(scene_point);
			// qDebug() << "mil" << mil;
			while (!mil.isEmpty()) {
				mm_ci = mil.front();
				if (mm_ci->data(bvidDTHandle).toBool()) return QGraphicsView::eventFilter(o, e);
				if (mm_ci->data(bvidTmpItem).toBool() || mm_ci->data(bvidItemSelection).toBool()) {
					mil.pop_front();
				} else
					break;
			}
			if (mil.isEmpty()) {
				mm_ci = 0;
				return true;
			}
			while (mm_ci->data(bvidType).toInt() == bvitBus) {
				if (qgraphicsitem_cast<BlockBusItem *>(mm_ci))
					if (qgraphicsitem_cast<BlockBusItem *>(mm_ci)->isBusSelected()) break;
				if (mil.size() > 1) {
					mm_ci = mil[1];
					mil.pop_front();
				} else {
					mm_ci = 0;
					break;
				}
			}
			if (mm_ci->data(bvidVisualizeSelection).toBool()) {
				if (mil.size() > 1) {
					mm_ci = mil[1];
					mil.pop_front();
					if (mm_ci->data(bvidVisualizeSelection).toBool())
						if (mil.size() > 1) mm_ci = mil[1];
				} else
					mm_ci = 0;
			}
			if (mm_ci->data(bvidMoveParent).toBool()) {
				QGraphicsItem * ti = mm_ci;
				while (mm_ci->parentItem() != 0)
					mm_ci = mm_ci->parentItem();
				if (!ti->data(bvidDecorText).toString().isEmpty()) { // text item, check for rect
					BlockItem * bi = qgraphicsitem_cast<BlockItem *>(mm_ci);
					if (bi) {
						if (!bi->sceneRect().contains(scene_point)) {
							// qDebug() << "return";
							mm_ci = 0;
						}
					}
				}
				return true;
			}
			if (mm_ci) {
				if ((mm_ci->data(bvidType).toInt() == bvitPin) && m_connect) {
					if (qgraphicsitem_cast<BlockItemPin *>(mm_ci)->state() == BlockItemPin::Hover) {
						trace_from = mm_ci->scenePos();
						qgraphicsitem_cast<BlockItemPin *>(mm_ci)->clearStateStack();
						hideTmpBuses();
						tmp_bus.setBusType(qgraphicsitem_cast<BlockItemPin *>(mm_ci)->busType());
						tmp_bus.show();
						new_bus = true;
						qDeleteAll(tmp_buses);
						tmp_buses.clear();
						foreach(BlockItemPin * p, last_multiconnect_pl) {
							tmp_buses << new BlockBusItem(true);
							tmp_buses.back()->setBusType(p->busType());
							addItem(tmp_buses.back());
						}
						// qDebug() << "new" << ;
						prev_tcb = m_trace_with_buses;
						startNewBus(tmp_bus.busType());
						if (qgraphicsitem_cast<BlockItemPin *>(mm_ci)->alignment() == Qt::AlignLeft ||
						    qgraphicsitem_cast<BlockItemPin *>(mm_ci)->alignment() == Qt::AlignRight)
							wavetrace.setPreferredDirection(BlockViewWavetrace::Horizontal);
						else
							wavetrace.setPreferredDirection(BlockViewWavetrace::Vertical);
					}
					return true;
				}
			}
			cur_scl = qSqrt(transform().determinant());
			break;
		case QEvent::GraphicsSceneMouseMove:
			// qDebug() << "move" << (mm_ci != 0 ? mm_ci : 0) << mm_mods << mm_cancel << mm_drag;
			//  if (cur_bus) return false;
			//  qDebug() << "move mm_ci" << mm_ci << mm_cancel;
			if (mm_ci)
				if (mm_ci->data(bvidTmpItem).toBool()) {
					mm_ci = 0;
					break;
				}
			if (mm_ci->data(bvidItemSelection).toBool()) break;
			if (mm_ci->data(bvidDTHandle).toBool()) break;
			if (mm_cancel) return true;
			// qDebug() << tmp_bus.isVisible();
			if ((me->buttons().testFlag(Qt::LeftButton) && mm_drag && tmp_bus.isVisible()) || new_branch) {
				mil  = scene_->items(me->scenePos());
				hpin = 0;
				foreach(QGraphicsItem * i, mil)
					if (i->data(bvidType).toInt() == bvitPin) {
						hpin = qgraphicsitem_cast<BlockItemPin *>(i);
						break;
					}
				if (hpin) {
					if (hpin->state() == BlockItemPin::Accept) {
						unhoverPins(hpin);
						hoverAcceptedPin(hpin, true);
					} else
						hpin = 0;
				} else
					unhoverPins();
				if (!cur_bus) {
					if (new_branch) {
						matchBus();
						break;
					}
					trace(trace_from, me->scenePos(), &tmp_bus);
					for (int i = 0; i < qMin(tmp_buses.size(), last_multiconnect_pl.size()); ++i) {
						QPointF dp = last_multiconnect_pl[i]->scenePos() - trace_from;
						// qDebug() << "trace" << i << dp;
						trace(trace_from + dp, me->scenePos() + dp, tmp_buses[i], false);
						tmp_buses[i]->show();
					}
					matchBus();
				}
			}
			if (cur_bus) return false;
			if (me->buttons().testFlag(Qt::LeftButton)) {
				if (!mm_drag) {
					if ((screen_point - me->screenPos()).manhattanLength() >= QApplication::startDragDistance()) {
						mm_drag = fmm_drag = true;
						saveBusesState();
					}
				}
				if (tmp_bus.isVisible()) return true;
				if (mm_mods.testFlag(Qt::ShiftModifier)) {
					if (fmm_drag) {
						fmm_drag = false;
						if (mm_ci) {
							if ((mm_ci->data(bvidType).toInt() == bvitBlock) && !mm_ci->data(bvidTmpItem).toBool()) {
								if (!mm_ci->isSelected()) {
									clearSelection();
									mm_ci->setSelected(true);
								}
							} else {
								mm_ci = 0;
								break;
							}
						} else {
							break;
						}
						sel_items = scene_->selectedItems();
						deleteCopyTemp();
						QList<BlockItem *> bi;
						foreach(QGraphicsItem * i, sel_items) {
							if ((i->data(bvidType).toInt() == bvitBlock) && !i->data(bvidTmpItem).toBool()) {
								// qDebug() << "copy";
								bi << qgraphicsitem_cast<BlockItem *>(i);
								BlockItem * ti = bi.back()->copy();
								ti->g_main.setPen(QPen(ti->g_main.pen().color(), ti->g_main.pen().widthF(), Qt::DashLine));
								QColor bc = ti->g_main.brush().color();
								bc.setAlphaF(bc.alphaF() * 0.5);
								ti->g_main.setBrush(bc);
								copy_items << ti;
								scene_->addItem(ti);
							}
						}
						QList<BlockBusItem *> ibi = internalBuses(bi);
						foreach(BlockBusItem * i, ibi) {
							i = i->copy();
							i->setOpacity(i->opacity() * 0.5);
							copy_buses << i;
							scene_->addItem(i);
						}
						mm_copy = true;
						setCursor(Qt::DragCopyCursor);
					}
				} else {
					// qDebug() << "move smode" << smode;
					if (smode == BlockView::SingleSelection) {
						if (fmm_drag) {
							clearSelection();
							if (mm_ci != 0) {
								mm_ci->setSelected(true);
							}
							saveSelState();
						}
					}
					if (smode == BlockView::MultiSelection) {
						sel_rect.setRect(QRectF(scene_point, me->scenePos()).normalized());
						if (fmm_drag) {
							if (mm_ci == 0) {
								scene_->addItem(&sel_rect);
								sel_rect.show();
								if (!mm_mods.testFlag(Qt::ControlModifier)) clearSelection();
							} else {
								if (!mm_mods.testFlag(Qt::ControlModifier) && !mm_ci->isSelected()) {
									clearSelection();
									mm_ci->setSelected(true);
								}
							}
							saveSelState();
							if (mm_ci != 0)
								if (!sel_items.contains(mm_ci)) mm_ci = 0;
						}
					}
				}
				if (sel_rect.isVisible())
					applySelRect(me);
				else {
					if (mm_drag && mm_ci != 0) {
						mdp = (me->scenePos() - scene_point);
						if (grid_snap) mdp = quantize(mdp, grid_step);
						if (!mdp.isNull()) {
							scene_point += mdp;
							copy_dp += mdp;
						}
						if (mm_copy) {
							if (!mdp.isNull()) moved = true;
							foreach(QGraphicsItem * i, copy_items)
								i->setPos(i->pos() + mdp);
							foreach(BlockBusItem * i, copy_buses)
								i->movePolyline(mdp);
						}
						if (!mm_mods.testFlag(Qt::ControlModifier) && !mm_mods.testFlag(Qt::ShiftModifier)) {
							if (!mdp.isNull()) moved = true;
							bool is_correct = true;
							if (!me->modifiers().testFlag(Qt::AltModifier)) is_correct = moveBuses(sel_items, mdp);
							if (is_correct) {
								for (auto * i: sel_items)
									if (i->flags().testFlag(QGraphicsItem::ItemIsMovable)) i->setPos(i->pos() + mdp);
							} else {
								scene_point -= mdp;
							}
							setCursor(Qt::ClosedHandCursor);
						}
						return true;
					}
				}
				if (mm_ci)
					if (mm_ci->data(bvidBlockDecor).toBool()) return true;
			}
			if (me->modifiers().testFlag(Qt::ControlModifier) && me->buttons() != 0 && mm_ci == 0) return true;
			// qDebug() << "scene mouse";
			break;
		case QEvent::GraphicsSceneMouseRelease:
			if (me->buttons().testFlag(Qt::LeftButton)) btncnt++;
			if (me->buttons().testFlag(Qt::RightButton)) btncnt++;
			if (me->buttons().testFlag(QT_MID_BUTTON)) btncnt++;
			maybeEndNewBus();
			cur_bus   = 0;
			mm_cancel = btncnt > 0;
			if (mm_cancel || (me->button() == QT_MID_BUTTON) || (me->button() == Qt::RightButton)) {
				mm_ci = 0;
				return true;
			}
			if (mm_ci)
				if (mm_ci->data(bvidTmpItem).toBool()) {
					mm_ci = 0;
					break;
				}
			if (mm_ci->data(bvidItemSelection).toBool()) break;
			if (mm_copy) {
				QList<QGraphicsItem *> ai;
				blockSignals(true);
				if (moved) {
					QList<BlockItem *> ci;
					QList<BlockBusItem *> bi;
					foreach(QGraphicsItem * b, sel_items)
						if ((b->data(bvidType).toInt() == bvitBlock) && !b->data(bvidTmpItem).toBool()) {
							ci << qgraphicsitem_cast<BlockItem *>(b);
							ai << qgraphicsitem_cast<QGraphicsItem *>(b);
						}
					bi = internalBuses(ci);
					if (!ci.isEmpty()) copyBlocks(ci, copy_dp);
					if (!bi.isEmpty()) copyBuses(bi, copy_dp);
				}
				deleteCopyTemp();
				blockSignals(false);
				if (moved) {
					moved = false;
					reconnectAll();
					emitActionEvent(BlockItemBase::BlockCopy, ai);
				}
			}
			if (new_branch) {
				hideTmpBuses(false);
			}
			if (moved && pm_connect) {
				QList<QGraphicsItem *> ci;
				foreach(QGraphicsItem * b, sel_items)
					if (b->data(bvidType).toInt() == bvitBlock) ci << b;
				simplifyBuses();
				emitActionEvent(BlockItemBase::BlockMove, ci);
				reconnectAll();
			}
			moved = mm_copy = false;
			if (tmp_bus.isVisible()) {
				if (!tmp_bus.pol.isEmpty()) {
					if (match_bus == 0) {
						BlockBusItem * nb = new BlockBusItem(tmp_bus);
						addItem(nb, tmp_buses.isEmpty());
						newBusCreated(nb);
						foreach(BlockBusItem * b, tmp_buses) {
							nb = new BlockBusItem(*b);
							addItem(nb, b == tmp_buses.back());
							newBusCreated(nb);
						}
					} else {
						if (connectTmpToBus(match_bus)) {
							newBusCreated(match_bus);
							emitActionEvent(BlockItemBase::BusAdd, QList<QGraphicsItem *>() << match_bus);
							emit connectionsChanged();
						}
					}
				}
				unmarkPins();
				hideTmpBuses();
				reconnectAll();
				BlockItemPin * pin = getPin(scene_->items(me->scenePos()));
				if (pin) pin->hoverEnterEvent(0);
				m_trace_with_buses = prev_tcb;
			}
			clearBusStates();
			if (!mm_drag) {
				switch (smode) {
				case SingleSelection:
					clearSelection();
					if (mm_ci) mm_ci->setSelected(true);
					break;
				case MultiSelection:
					if (mm_ci == 0 || !me->modifiers().testFlag(Qt::ControlModifier)) {
						clearSelection();
						if (mm_ci) mm_ci->setSelected(true);
					} else {
						if (mm_ci != 0) {
							if (me->modifiers().testFlag(Qt::ControlModifier)) {
								if (mm_ci->data(bvidType).toInt() == bvitBlock) mm_ci->setSelected(!mm_ci->isSelected());
							} else
								mm_ci->setSelected(true);
						}
					}
					break;
				default: clearSelection(); break;
				}
			}
			sel_rect.hide();
			if (sel_rect.scene()) scene_->removeItem(&sel_rect);
			mm_drag = false;
			mm_ci   = 0;
			unsetCursor();
			break;
		default: break;
		}
	}
	return QGraphicsView::eventFilter(o, e);
}


void BlockView::stopTimer(int & tid) {
	if (tid > 0) killTimer(tid);
	tid = 0;
}


void BlockView::restartTimer(int & tid, int msecs) {
	stopTimer(tid);
	tid = startTimer(msecs);
}


void BlockView::timerEvent(QTimerEvent * e) {
	if (e->timerId() == timer_thumb) {
		thumbHide();
		stopTimer(timer_thumb);
		return;
	}
	QGraphicsView::timerEvent(e);
}


void BlockView::wheelEvent(QWheelEvent * e) {
	if (!navigation) return;
	if (wheel_zoom || e->modifiers().testFlag(Qt::ControlModifier)) {
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
		double scl = 1. - e->delta() / 500.;
#else
		double scl = 1. - e->angleDelta().y() / 500.;
#endif
		if (!is_nav_anim || (nav_anim.state() != QPropertyAnimation::Running)) nav_target = _nav();
		QRectF r  = nav_target;
		double vw = viewport()->width(), vh = viewport()->height();
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
		double cx = double(e->pos().x()) / vw, cy = double(e->pos().y()) / vh;
#else
		double cx = double(e->position().x()) / vw, cy = double(e->position().y()) / vh;
#endif
		double pw = r.width(), ph = r.height();
		r.setWidth(r.width() * scl);
		r.setHeight(r.width() * vh / vw);
		r.translate(cx * (pw - r.width()), cy * (ph - r.height()));
		animateNav(r, scl);
		return;
	}
	if (!wheel_zoom) QGraphicsView::wheelEvent(e);
}


void BlockView::mousePressEvent(QMouseEvent * event) {
	press_point = event->pos();
	if (event->buttons().testFlag(QT_MID_BUTTON) || event->buttons().testFlag(Qt::RightButton)) {
		setCursor(Qt::ClosedHandCursor);
		if (sel_rect.scene()) scene_->removeItem(&sel_rect);
	}
	QGraphicsView::mousePressEvent(event);
}


void BlockView::mouseReleaseEvent(QMouseEvent * event) {
	unsetCursor();
	QGraphicsView::mouseReleaseEvent(event);
}


void BlockView::updateNavRect() {
	QPointF t = mapToScene(viewport()->rect().topLeft());
	QPointF b = mapToScene(viewport()->rect().bottomRight());
	nav_rect  = QRectF(t, b);
}


void BlockView::scrolled() {
	updateNavRect();
}


void BlockView::mouseMoveEvent(QMouseEvent * event) {
	if (!event) return;
	if (navigation) {
		if (event->buttons().testFlag(QT_MID_BUTTON) || event->buttons().testFlag(Qt::RightButton)) {
			QPoint dp = (press_point - event->pos());
			horizontalScrollBar()->setValue(horizontalScrollBar()->value() + dp.x());
			verticalScrollBar()->setValue(verticalScrollBar()->value() + dp.y());
			press_point = event->pos();
			updateNavRect();
			QRectF nr = nav_anim.endValue().toRectF();
			nr.moveCenter(_nav().center());
			nav_anim.setEndValue(nr);
			return;
		}
	}
	QGraphicsView::mouseMoveEvent(event);
}


void BlockView::mouseDoubleClickEvent(QMouseEvent * event) {
	if (event->buttons().testFlag(QT_MID_BUTTON) || event->buttons().testFlag(Qt::RightButton)) {
		fitInView();
		return;
	}
	QGraphicsView::mouseDoubleClickEvent(event);
}


void BlockView::keyPressEvent(QKeyEvent * e) {
	BlockItemPin * pin = getPin(items(mapFromGlobal(QCursor::pos())));
	if (pin) {
		// qDebug() << "pin" << pin->state();
		if (pin->state() == BlockItemPin::Hover) {
			highlightNearPins(pin, e->modifiers());
		}
	}
	if (tmp_bus.isVisible()) {
		bool retrace = false;
		if (e->key() == Qt::Key_Shift) {
			retrace = true;
			switch (wavetrace.preferredDirection()) {
			case BlockViewWavetrace::NoTrace: wavetrace.setPreferredDirection(BlockViewWavetrace::Horizontal); break;
			case BlockViewWavetrace::Horizontal: wavetrace.setPreferredDirection(BlockViewWavetrace::Vertical); break;
			case BlockViewWavetrace::Vertical: wavetrace.setPreferredDirection(BlockViewWavetrace::NoTrace); break;
			}
		}
		if (e->key() == Qt::Key_Alt) {
			retrace            = true;
			m_trace_with_buses = !m_trace_with_buses;
		}
		if (retrace) {
			trace(last_trace_from, trace_to, &tmp_bus);
			for (int i = 0; i < qMin(tmp_buses.size(), last_multiconnect_pl.size()); ++i) {
				QPointF dp = last_multiconnect_pl[i]->scenePos() - last_trace_from;
				// qDebug() << "trace" << i << dp;
				trace(last_trace_from + dp, trace_to + dp, tmp_buses[i], false);
				tmp_buses[i]->show();
			}
			return;
		}
	}
	QGraphicsView::keyPressEvent(e);
}


void BlockView::keyReleaseEvent(QKeyEvent * e) {
	BlockItemPin * pin = getPin(items(mapFromGlobal(QCursor::pos())));
	if (pin) {
		// qDebug() << "pin" << pin->state();
		if (pin->state() == BlockItemPin::Hover) {
			highlightNearPins(pin, e->modifiers());
		}
	}
	QGraphicsView::keyReleaseEvent(e);
}


void BlockView::resizeEvent(QResizeEvent * event) {
	QGraphicsView::resizeEvent(event);
	thick = lineThickness(this);
#if QT_VERSION_MAJOR >= 6
	thick *= devicePixelRatioF();
#endif
	adjustThumb();
	updateNavRect();
	nav_target = _nav();
}


void BlockView::scrollContentsBy(int dx, int dy) {
	QGraphicsView::scrollContentsBy(dx, dy);
	if (isHidden()) return;
	thumbShow();
	restartTimer(timer_thumb, thumb_hide_delay);
	QWidget * w = &widget_thumb;
	QMetaObject::invokeMethod(
		this,
		[w]() { w->update(); },
		Qt::QueuedConnection);
}


void BlockView::drawBackground(QPainter * painter, const QRectF & rect) {
	if (mm_thumb) return;
	float rx, ry, sx = grid_step, sy = grid_step;
	double scl = qRound(1. / qSqrt(transform().determinant()));
	painter->fillRect(rect, palette().brush(QPalette::Base));
	painter->setRenderHint(QPainter::Antialiasing, false);
	if (scl > 0.) {
		sx *= scl;
		sy *= scl;
	}
	if (!grid_visible) return;
	rx      = quantize(rect.left(), sx);
	ry      = quantize(rect.top(), sy);
	bool gp = grid_points > 0.5;
	if (gp) {
		QPen pp(grid_pen.color(), qMax<int>(grid_points, thick), Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
		pp.setCosmetic(true);
		painter->setPen(pp);
		for (int i = 0; i < qCeil(rect.width() / sx) + 1; ++i)
			for (int j = 0; j < qCeil(rect.height() / sy) + 1; ++j)
				painter->drawPoint(rx + i * sx, ry + j * sy);
	}
	if (grid_pen.style() == Qt::NoPen) return;
	QPen pen = grid_pen;
	pen.setWidth(qMax<int>(pen.width(), thick));
	painter->setPen(grid_pen);
	for (int i = 0; i < qCeil(rect.width() / sx) + 1; ++i) {
		double cx = rx + i * sx;
		painter->drawLine(cx, ry, cx, ry + rect.height());
	}
	for (int j = 0; j < qCeil(rect.height() / sy) + 1; ++j) {
		double cy = ry + j * sy;
		painter->drawLine(rx, cy, rx + rect.width(), cy);
	}
}


void BlockView::scrollFromThumb() {
	QRect r(thumb_vr);
	double scl(thumb_scl);
	horizontalScrollBar()->setValue(r.x() / scl + horizontalScrollBar()->minimum());
	verticalScrollBar()->setValue(r.y() / scl + verticalScrollBar()->minimum());
}


void BlockView::deleteCopyTemp() {
	qDeleteAll(copy_items);
	copy_items.clear();
	qDeleteAll(copy_buses);
	copy_buses.clear();
}


void BlockView::emitActionEvent(BlockItemBase::Action action, QList<QGraphicsItem *> items) {
	if (!ae_enabled) return;
	emit schemeAction(action, items);
}


void BlockView::setGhost(BlockItem * item) {
	clearGhost();
	if (!item) return;
	ghost_ = item;
	ghost_->setOpacity(0.5);
	addItem(item, false);
}


void BlockView::clearGhost() {
	if (!ghost_) return;
	delete ghost_;
	ghost_ = 0;
}


void BlockView::startNewBus(int bus_type) {
	newBusStarted(bus_type);
	markPins(bus_type);
	new_bus_started = true;
}


void BlockView::maybeEndNewBus() {
	if (!new_bus_started) return;
	new_bus_started = false;
	QMetaObject::invokeMethod(
		this,
		[this]() { newBusFinished(); },
		Qt::QueuedConnection);
}


void BlockView::getPinMC(bool * v) {
	if (v) *v = m_pin_mc;
}


void BlockView::drawThumb() {
	if (!minimap) return;
	QPainter p(&widget_thumb);
	QRect wr  = widget_thumb.rect().adjusted(0, 0, -1, -1);
	QSizeF sr = sceneRect().size(), tr;
	if (sr.width() >= sr.height())
		thumb_scl = thumb_size.width() / sr.width();
	else
		thumb_scl = thumb_size.height() / sr.height();
	tr      = sr * thumb_scl;
	cur_scl = qSqrt(transform().determinant());
	thumb_scl /= cur_scl;
	QSizeF vs(size().width() - verticalScrollBar()->width(), size().height() - horizontalScrollBar()->height());
	QRectF vr(QPointF(horizontalScrollBar()->value() - horizontalScrollBar()->minimum(),
	                  verticalScrollBar()->value() - verticalScrollBar()->minimum()) *
	              thumb_scl,
	          vs * thumb_scl);
	vr.adjust(0, 0, -1, -1);

	p.setBrush(Qt::lightGray);
	p.setOpacity(0.5 * _talpha);
	p.drawRect(wr);
	p.drawImage(0, 0, im_scene);
	p.setBrush(Qt::NoBrush);
	p.drawRect(wr);
	p.setBrush(Qt::white);
	p.setRenderHint(QPainter::Antialiasing);
	p.drawRect(vr);
	thumb_sr = wr;
	thumb_vr = vr.toRect();
}


void BlockView::drawSceneThumb() {
	if (!minimap) return;
	QRect wr = widget_thumb.rect().adjusted(0, 0, -1, -1);
	im_scene = QImage(wr.size(), QImage::Format_ARGB32);
	im_scene.fill(Qt::transparent);
	QPainter p(&im_scene);
	p.setRenderHint(QPainter::Antialiasing);
	mm_thumb = true;
	scene_->render(&p, im_scene.rect());
	mm_thumb = false;
}


void BlockView::thumbHide() {
	if (!minimap) {
		widget_thumb.hide();
		return;
	}
	if (QApplication::widgetAt(QCursor::pos()) == &widget_thumb) return;
	thumb_anim.stop();
	thumb_anim.setDuration(1000);
	thumb_anim.setStartValue(_thumb());
	thumb_anim.setEndValue(0.);
	thumb_anim.start();
	// qDebug() << "hide" << thumb_anim.startValue() << thumb_anim.endValue();
}


void BlockView::thumbShow() {
	if (!minimap) return;
	if (widget_thumb.isHidden() || (_talpha < 0.1)) drawSceneThumb();
	thumb_anim.stop();
	_setThumb(1.);
	// qDebug() << "show" << thumb_anim.startValue() << thumb_anim.endValue();
}


void BlockView::clearSelection() {
	bool pb              = block_emit_selection;
	block_emit_selection = true;
	sel_items.clear();
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if (i->flags().testFlag(QGraphicsItem::ItemIsSelectable)) i->setSelected(false);
	block_emit_selection = pb;
	if (!block_emit_selection) emit selectionChanged();
}


void BlockView::addItem(QGraphicsItem * item, bool emit_action) {
	scene_->addItem(item);
	applyGridStep();
	if (item->data(bvidType).toInt() == bvitBus) {
		loadBus(qgraphicsitem_cast<BlockBusItem *>(item));
		((BlockBusItem *)item)->setSquareNodes(square_node);
		connect((BlockBusItem *)item, SIGNAL(destroyed(QObject *)), this, SLOT(removedBus(QObject *)), Qt::UniqueConnection);
		if (emit_action) emitActionEvent(BlockItemBase::BusAdd, QList<QGraphicsItem *>() << item);
		emit connectionsChanged();
		return;
	}
	if (item->data(bvidType).toInt() == bvitBlock) {
		connect((BlockItem *)item, SIGNAL(destroyed(QObject *)), this, SLOT(removedBlock(QObject *)), Qt::UniqueConnection);
		connect((BlockItem *)item, SIGNAL(blockHoverEnter(BlockItem *)), this, SIGNAL(blockHoverEnter(BlockItem *)), Qt::UniqueConnection);
		connect((BlockItem *)item, SIGNAL(blockHoverLeave(BlockItem *)), this, SIGNAL(blockHoverLeave(BlockItem *)), Qt::UniqueConnection);
		if (emit_action) emitActionEvent(BlockItemBase::BlockAdd, QList<QGraphicsItem *>() << item);
		return;
	}
	item->setData(bvidType, bvitDecor);
}


QList<BlockBusItem *> BlockView::buses() const {
	QList<BlockBusItem *> ret;
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if ((i->data(bvidType).toInt() == bvitBus) && !i->data(bvidTmpItem).toBool())
			if (!copy_buses.contains((BlockBusItem *)i)) ret << qgraphicsitem_cast<BlockBusItem *>(i);
	return ret;
}


QList<BlockBusItem *> BlockView::wrongConnectedBuses() const {
	QList<BlockBusItem *> sl = buses(), ret;
	QList<BlockItem *> bl    = blocks();
	foreach(BlockItem * b, bl) {
		QVector<BlockItemPin *> pins = b->pins();
		foreach(BlockItemPin * p, pins)
			if (p->state() == BlockItemPin::Reject) {
				QPointF pp = p->scenePos();
				foreach(BlockBusItem * s, sl)
					if (s->pol.contains(pp))
						if (!ret.contains(s)) ret << s;
			}
	}
	return ret;
}


QList<BlockItem *> BlockView::blocks() const {
	QList<BlockItem *> ret;
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if ((i->data(bvidType).toInt() == bvitBlock) && !i->data(bvidTmpItem).toBool()) ret << qgraphicsitem_cast<BlockItem *>(i);
	return ret;
}


QList<QGraphicsItem *> BlockView::decors() const {
	QList<QGraphicsItem *> ret, gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if ((i->data(bvidType).toInt() == bvitDecor) && !i->data(bvidTmpItem).toBool() && !i->data(bvidItemSelection).toBool() &&
		    (i->parentItem() == 0) && (i != &sel_rect) && (i != &tmp_bus) && !tmp_buses.contains((BlockBusItem *)i))
			ret << i;
	return ret;
}


BlockBusItem * BlockView::connectionBus(BlockItem * b0, BlockItem * b1) const {
	QList<BlockBusItem *> cbl = connectionBuses(b0, b1);
	if (cbl.isEmpty()) return 0;
	return cbl.front();
}


QList<BlockBusItem *> BlockView::connectionBuses(BlockItem * b0, BlockItem * b1) const {
	if (!b0 || !b1) return QList<BlockBusItem *>();
	QSet<BlockBusItem *> bs0 = QList2QSet(b0->connectedBuses()), bs1 = QList2QSet(b1->connectedBuses());
	return (bs0 & bs1).values();
}


bool BlockView::connectPins(BlockItemPin * p0, BlockItemPin * p1) {
	if (!p0 || !p1) return false;
	if (p0 == p1) return false;
	if (p0->busType() != p1->busType()) return false;
	QList<BlockBusItem *> bl0 = p0->connectedBuses(), bl1 = p1->connectedBuses();
	if (!(QList2QSet(bl0) & QList2QSet(bl1)).isEmpty()) return true;
	BlockBusItem * nb = new BlockBusItem();
	nb->setBusType(p0->busType());
	loadBus(nb);
	if (!bl0.isEmpty() && !bl1.isEmpty()) {                                           // connect two existing buses
	} else {
		if ((bl0.isEmpty() && !bl1.isEmpty()) || (bl1.isEmpty() && !bl0.isEmpty())) { // connect empty pin to existing bus
			BlockItemPin * ep = 0;
			BlockBusItem * eb = 0;
			if (bl0.isEmpty()) {
				ep = p0;
				eb = bl1[0];
			} else {
				ep = p1;
				eb = bl0[0];
			}
			double md  = -1;
			int mi     = -1;
			QPointF sp = ep->scenePos();
			if (eb->pol.size() == 2) {
				eb->selSegment = 0;
				eb->addPoint((eb->pol[0] + eb->pol[1]) / 2.);
				eb->selPoint = -1;
				mi           = 2;
			} else {
				for (int i = 0; i < eb->pol.size(); ++i) {
					if (eb->ends.contains(i)) continue;
					double cd = QVector2D(sp - eb->pol[i]).lengthSquared();
					if (md < 0 || md > cd) {
						md = cd;
						mi = i;
					}
				}
			}
			if (mi < 0) {
				return false;
			}
			trace(ep->scenePos(), eb->pol[mi], nb);
			if (nb->pol.size() < 2) {
				delete nb;
				return false;
			}
			nb->pol.pop_back();
			int lp = eb->pol.size();
			eb->pol << nb->pol;
			for (int i = 0; i < nb->pol.size() - 1; ++i)
				eb->segments << QPair<int, int>(lp + i, lp + i + 1);
			eb->segments << QPair<int, int>(mi, lp + nb->pol.size() - 1);
			eb->updateGeometry();
			delete nb;
		} else { // connect two empty pins
			trace(p0->scenePos(), p1->scenePos(), nb);
			if (nb->pol.isEmpty()) {
				delete nb;
				return false;
			}
			addItem(nb);
		}
	}
	reconnectAll();
	newBusCreated(nb);
	emitActionEvent(BlockItemBase::BusAdd, QList<QGraphicsItem *>() << nb);
	emit connectionsChanged();
	return true;
}


void BlockView::setTransform(const QTransform & matrix, bool combine) {
	QGraphicsView::setTransform(matrix, combine);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::centerOn(const QPointF & pos) {
	QGraphicsView::centerOn(pos);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::centerOn(qreal x, qreal y) {
	QGraphicsView::centerOn(x, y);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::centerOn(const QGraphicsItem * item) {
	QGraphicsView::centerOn(item);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::fitInView(const QRectF & rect, Qt::AspectRatioMode aspectRatioMode) {
	QGraphicsView::fitInView(rect, aspectRatioMode);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::fitInView(qreal x, qreal y, qreal w, qreal h, Qt::AspectRatioMode aspectRatioMode) {
	QGraphicsView::fitInView(x, y, w, h, aspectRatioMode);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::fitInView(const QGraphicsItem * item, Qt::AspectRatioMode aspectRatioMode) {
	QGraphicsView::fitInView(item, aspectRatioMode);
	updateNavRect();
	nav_target = _nav();
}


void BlockView::fitInView() {
	QRectF r = _nav();
	QGraphicsView::fitInView(itemsBoundingRect(), Qt::KeepAspectRatio);
	updateNavRect();
	QRectF t = _nav();
	QGraphicsView::fitInView(r, Qt::KeepAspectRatio);
	updateNavRect();
	animateNav(t);
}


QRectF BlockView::itemsBoundingRect() const {
	QList<QGraphicsItem *> gi = scene_->items();
	if (gi.isEmpty()) return QRectF();
	bool f = true;
	QRectF ret;
	foreach(QGraphicsItem * i, gi)
		if (i->isVisible() && (i != &tmp_bus) && !tmp_buses.contains((BlockBusItem *)i)) {
			if (!(i->data(bvidItemSelection).toBool()) && !i->data(bvidTmpItem).toBool()) {
				QRectF br = i->mapRectToScene(i->boundingRect());
				if (br.width() <= 1 || br.height() <= 1) continue;
				if (f)
					ret = br;
				else
					ret |= br;
				f = false;
			}
		}
	return ret;
}


void BlockView::restoreSelState() {
	// qDebug() << "restoreSelState";
	foreach(QGraphicsItem * i, sel_items) {
		i->setPos(i->data(bvidItemPos).toPointF());
	}
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if (i->data(bvidType).toInt() == bvitBus) {
			BlockBusItem * bi = qgraphicsitem_cast<BlockBusItem *>(i);
			bi->pol           = bi->bpol;
			bi->prepareGeometryChange();
		}
}


void BlockView::saveSelState() {
	// qDebug() << "saveSelState";
	QList<QGraphicsItem *> gi = scene_->items();
	sel_items                 = scene_->selectedItems();
	foreach(QGraphicsItem * i, gi) {
		i->setData(bvidSelected, i->isSelected());
		i->setData(bvidItemPos, i->pos());
		if (i->data(bvidType).toInt() == bvitBus) qgraphicsitem_cast<BlockBusItem *>(i)->bpol = qgraphicsitem_cast<BlockBusItem *>(i)->pol;
	}
}


void BlockView::saveBusesState() {
	QList<BlockBusItem *> bl = buses();
	foreach(BlockBusItem * b, bl)
		b->saveState();
}


void BlockView::restoreBusesState() {
	QList<BlockBusItem *> bl = buses();
	foreach(BlockBusItem * b, bl)
		b->restoreState();
}


void BlockView::applySelRect(QGraphicsSceneMouseEvent * me) {
	QList<QGraphicsItem *> ci  = sel_rect.collidingItems(Qt::IntersectsItemBoundingRect);
	QList<QGraphicsItem *> gi  = scene_->items();
	bool add                   = me->modifiers().testFlag(Qt::ControlModifier);
	QList<QGraphicsItem *> sil = scene_->selectedItems();
	block_emit_selection       = true;
	if (!add)
		clearSelection();
	else {
		foreach(QGraphicsItem * i, gi)
			i->setSelected(i->data(bvidSelected).toBool());
	}
	foreach(QGraphicsItem * i, ci) {
		i->setSelected(!i->isSelected());
	}
	block_emit_selection = false;
	if (sil != scene_->selectedItems()) emit selectionChanged();
}


void BlockView::applyGridStep() {
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if (i->type() == QGraphicsItem::UserType + 2) qgraphicsitem_cast<BlockBusItem *>(i)->setGridStep(grid_step);
}


void BlockView::trace(QPointF scene_pos_from, QPointF scene_pos_to, BlockBusItem * bus, bool primary) {
	if (primary) {
		if (hpin) scene_pos_to = hpin->scenePos();
		last_trace_from = scene_pos_from;
		trace_to        = scene_pos_to;
	}
	QRect sr = scene_->sceneRect().toRect();
	int dx = sr.left() / grid_step, dy = sr.top() / grid_step;
	QPoint dp(-dx, -dy), qpt           = quantize(scene_pos_to, grid_step).toPoint() / grid_step + dp;
	QElapsedTimer tm;
	tm.restart();
	wavetrace.resize(sr.size() / grid_step);
	wavetrace.fill(BlockViewWavetrace::Empty);
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if (i->data(bvidType).toInt() == bvitBlock) {
			QRect ir = i->mapRectToScene(i->boundingRect()).toRect().normalized();
			wavetrace.fill(QRect(ir.topLeft() / grid_step + dp, ir.bottomRight() / grid_step + dp), BlockViewWavetrace::Blocked);
			QVector<BlockItemPin *> pins = qgraphicsitem_cast<BlockItem *>(i)->pins();
			foreach(BlockItemPin * p, pins) {
				if (p->busType() == bus->busType())
					wavetrace.fill(quantize(p->scenePos(), grid_step).toPoint() / grid_step + dp, BlockViewWavetrace::Empty);
				if (!m_pin_mc && !p->connectedBuses().isEmpty())
					wavetrace.fill(quantize(p->scenePos(), grid_step).toPoint() / grid_step + dp, BlockViewWavetrace::Blocked);
			}
		}
	if (m_trace_with_buses) {
		foreach(QGraphicsItem * i, gi)
			if (i->data(bvidType).toInt() == bvitBus) {
				BlockBusItem * b = qgraphicsitem_cast<BlockBusItem *>(i);
				if (!b) continue;
				for (int s = 0; s < b->segments.size(); ++s) {
					QPointF p0 = b->pol[b->segments[s].first], p1 = b->pol[b->segments[s].second], cp = p0;
					double sx(0.), sy(0.), dx = qAbs(p1.x() - p0.x()), dy = qAbs(p1.y() - p0.y());
					double signx = (p1.x() >= p0.x() ? 1. : -1.), signy = (p1.y() >= p0.y() ? 1. : -1.);
					int steps(0);
					if ((dx + dy) < grid_step) continue;
					BlockViewWavetrace::CellState cs = BlockViewWavetrace::Blocked;
					if (dx >= dy) { // by x
						sx    = grid_step;
						sy    = sx * dy / dx;
						steps = qRound(dx / grid_step);
						cs    = BlockViewWavetrace::HorizontalBus;
					} else {
						sy    = grid_step;
						sx    = sy * dx / dy;
						steps = qRound(dy / grid_step);
						cs    = BlockViewWavetrace::VerticalBus;
					}
					sx *= signx;
					sy *= signy;
					// qDebug() << "fill" << p0 << "->" << p1 << "in" << steps << sx << sy;
					for (int j = 0; j <= steps; ++j) {
						QPoint tp = quantize(cp, grid_step).toPoint() / grid_step + dp;
						if (tp != qpt) wavetrace.fill(tp, (j > 0 && j < steps) ? cs : BlockViewWavetrace::Blocked);
						// qDebug() << " set" << cp << ((j > 0 && j < steps) ? cs : BlockViewWavetrace::Blocked);
						cp += QPointF(sx, sy);
					}
				}
			}
	}
	bus->clear();
	if (wavetrace.trace(quantize(scene_pos_from, grid_step).toPoint() / grid_step + dp, qpt)) {
		wavetrace.gatherPath();
		foreach(const QPoint & p, wavetrace.path()) {
			bus->appendPoint((p - dp) * grid_step);
		}
	}
	// qDebug() << quantize(scene_pos_from, grid_step).toPoint() / grid_step + dp << qpt;
	scene_->update();
}


void BlockView::clearBusStates() {
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if (i->data(bvidType).toInt() == bvitBus) {
			BlockBusItem * b = qgraphicsitem_cast<BlockBusItem *>(i);
			b->clearBusState();
		}
}


void BlockView::matchBus() {
	match_bus                 = 0;
	bool bv                   = tmp_bus.isVisible();
	QList<QGraphicsItem *> gi = scene_->items();
	QList<BlockBusItem *> buses;
	QList<BlockItem *> blockl;
	int sp = -1, ss = -1;
	QPointF point;
	iconnect = false;
	if (!tmp_bus.pol.isEmpty()) point = tmp_bus.pol.back();
	foreach(QGraphicsItem * i, gi) {
		if (i != bus_from) {
			if (i->data(bvidType).toInt() == bvitBus) buses << qgraphicsitem_cast<BlockBusItem *>(i);
			if (i->data(bvidType).toInt() == bvitBlock) blockl << qgraphicsitem_cast<BlockItem *>(i);
		}
	}
	foreach(BlockBusItem * b, buses) {
		b->clearBusState();
		b->selPoint = b->selSegment = -1;
	}
	if (!bv) return;
	BlockBusItem * b(0);
	if (m_pin_mc) {
		foreach(BlockItem * b_, blockl)
			foreach(BlockItemPin * p_, b_->pins())
				if (p_->scenePos() == point) {
					return;
				}
	}
	// qDebug() << "1" << buses.size() << tmp_bus.pol;
	for (int i = 0; i < buses.size(); ++i) {
		b = buses[i];
		b->testPoint(point, &sp, &ss, true);
		// qDebug() << i << sp << ss;
		if (sp >= 0 || ss >= 0) break;
	}
	if ((sp < 0 && ss < 0) || b == 0) return;
	// qDebug("2");
	match_bus = b;
	if ((b->busType() != tmp_bus.busType()) || b->connections_.value(sp, 0) != 0) {
		b->setBusState(false);
	} else {
		if (b->max_ep >= 2) {
			if (bus_from == 0) {
				if (ss >= 0) {
					if (b->endpointCount() >= b->max_ep) {
						b->setBusState(false);
						return;
					}
				}
				if (sp >= 0) {
					if (b->endpointCount() + b->pointSegmentsCount(sp) - 2 >= b->max_ep) {
						b->setBusState(false);
						return;
					}
				}
			} else {
				int sep = b->endpointCount() + bus_from->endpointCount();
				if (b->pointSegmentsCount(sp) == 1) sep--;
				if (bus_from->selPoint >= 0)
					if (bus_from->pointSegmentsCount(bus_from->selPoint) == 1) sep--;
				if (sep > b->max_ep) {
					b->setBusState(false);
					return;
				}
			}
		}
		iconnect = true;
		b->setBusState(true);
		b->selPoint   = sp;
		b->selSegment = ss;
	}
}


bool BlockView::connectTmpToBus(BlockBusItem * bus) {
	if (bus == 0) return false;
	if (!bus->busState()) return false;
	if (tmp_bus.pol.size() < 2) return false;
	int np = bus->selPoint;
	if (np < 0) np = bus->addPoint(tmp_bus.pol.back());
	if (np < 0) return false;
	tmp_bus.pol.pop_back();
	int lp = bus->pol.size();
	bus->pol << tmp_bus.pol;
	for (int i = 0; i < tmp_bus.pol.size() - 1; ++i)
		bus->segments << QPair<int, int>(lp + i, lp + i + 1);
	bus->segments << QPair<int, int>(np, lp + tmp_bus.pol.size() - 1);
	bus->updateGeometry();
	tmp_bus.clear();
	return true;
}


void BlockView::markPins(int bus_type) {
	unhoverPins();
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi) {
		if (i->data(bvidType).toInt() == bvitPin) {
			BlockItemPin * p = qgraphicsitem_cast<BlockItemPin *>(i);
			p->saveState();
			if (m_pin_mc) {
				if (p->busType() == bus_type) p->setState(BlockItemPin::Accept);
			} else {
				if (p->busType() == bus_type) {
					if (p->state() == BlockItemPin::Disconnected) p->setState(BlockItemPin::Accept);
				}
			}
		}
	}
}


void BlockView::unmarkPins(bool to_normal) {
	unhoverPins();
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi) {
		if (i->data(bvidType).toInt() == bvitPin) {
			qgraphicsitem_cast<BlockItemPin *>(i)->restoreState();
			if (to_normal)
				while (qgraphicsitem_cast<BlockItemPin *>(i)->restoreState())
					;
		}
	}
}


void BlockView::hoverAcceptedPin(BlockItemPin * pin, bool hover) {
	if (!pin) return;
	pin->enlargePin(hover);
}


void BlockView::unhoverPins(BlockItemPin * excl_pin) {
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi) {
		if (excl_pin == ((BlockItemPin *)i)) continue;
		if (i->data(bvidType).toInt() == bvitPin) {
			((BlockItemPin *)i)->enlargePin(false);
		}
	}
}


void BlockView::simplifyBuses() {
	QList<BlockBusItem *> bl = buses();
	foreach(BlockBusItem * b, bl)
		b->simplify();
}


bool BlockView::moveBuses(const QList<QGraphicsItem *> & items, QPointF dp) {
	if (dp.isNull()) return true;
	QList<QGraphicsItem *> gi = scene_->items();
	QVector<BlockItemPin *> pins;
	QList<BlockBusItem *> buses_m_part, buses_m_all;
	QMap<BlockBusItem *, QPolygonF> saved_pols;
	// qDebug() << "move" << dp;
	for (auto * i: items)
		if ((i->data(bvidType).toInt() == bvitBlock) && i->flags().testFlag(QGraphicsItem::ItemIsMovable))
			pins << qgraphicsitem_cast<BlockItem *>(i)->pins();
	for (auto * i: gi) {
		if (i->data(bvidType).toInt() == bvitBus) {
			auto * b                    = qgraphicsitem_cast<BlockBusItem *>(i);
			QList<BlockItemPin *> bpins = b->connectedPins();
			if (bpins.isEmpty()) continue;
			bool affect = false;
			for (auto * i: pins) {
				if (bpins.contains(i)) {
					affect = true;
					break;
				}
			}
			if (!affect) continue;
			for (auto * p: pins)
				bpins.removeAll(p);
			if (bpins.isEmpty())
				buses_m_all << b;
			else {
				buses_m_part << b;
				saved_pols[b] = b->pol;
			}
		}
	}
	bool incorrect_move = false;
	for (auto * b: buses_m_part) {
		for (auto * p: pins) {
			QList<int> ends = b->connections_.keys(p);
			for (int i = 0; i < ends.size(); ++i) {
				QPointF pdp = dp;
				double ang  = 0.;
				switch (p->alignment()) {
				case Qt::AlignRight:
					pdp.setX(0.);
					ang = 0.;
					break;
				case Qt::AlignTop:
					pdp.setY(0.);
					ang = 90.;
					break;
				case Qt::AlignLeft:
					pdp.setX(0.);
					ang = 180.;
					break;
				case Qt::AlignBottom:
					pdp.setY(0.);
					ang = 270.;
					break;
				default: break;
				}
				QVector<int> epl = b->endpointLine(ends[i], ang);
				for (int e: epl)
					b->movePoint(e, pdp);
				b->movePoint(ends[i], dp);
				if (b->hasNullSegment()) {
					incorrect_move = true;
					break;
				}
			}
			if (incorrect_move) break;
		}
		if (incorrect_move) break;
	}
	if (incorrect_move) {
		for (auto * b: buses_m_part)
			b->pol = saved_pols.value(b);
		return false;
	}
	for (auto * b: buses_m_all)
		b->movePolyline(dp);
	return true;
}


QList<BlockBusItem *> BlockView::internalBuses(const QList<BlockItem *> & items) {
	QList<BlockBusItem *> ret;
	if (items.isEmpty()) return ret;
	QList<BlockBusItem *> sbl = buses();
	QSet<BlockItem *> sis     = QList2QSet(items);
	foreach(BlockBusItem * bi, sbl) {
		if (bi->connectedBlocks().isEmpty()) continue;
		QSet<BlockItem *> bis = QList2QSet(bi->connectedBlocks());
		if ((bis - sis).isEmpty()) ret << bi;
	}
	return ret;
}


QList<BlockItemPin *> BlockView::nearPins(BlockItemPin * pin, Qt::KeyboardModifiers km) {
	QList<BlockItemPin *> ret;
	bool up   = km.testFlag(Qt::ShiftModifier);
	bool down = km.testFlag(Qt::ControlModifier);
	bool ab   = km.testFlag(Qt::AltModifier);
	// qDebug() << "nearPins" << km;
	if (!pin || (!up && !down)) return ret;
	const BlockItem * src = pin->parent();
	if (!src) return ret;
	bool dirs[2] = {up, down};
	double dy[2] = {-1, 1};
	for (int i = 0; i < 2; ++i) {
		if (!dirs[i]) continue;
		const BlockItem * cb = src;
		BlockItemPin * cpin  = pin;
		// qDebug() << "dir" << dirs[i];
		while (cpin) {
			// qDebug() << "cur" << cpin;
			QList<QGraphicsItem *> il = scene_->items(cpin->scenePos() + QPointF(0., dy[i] * cb->pinsMargin()));
			BlockItemPin * np         = getPin(il);
			if (np == cpin) break;
			cpin = np;
			if (cpin) {
				if (((cb != cpin->parent()) && !ab) || (cpin->state() != BlockItemPin::Disconnected)) {
					break;
				}
				cb = cpin->parent();
				ret << cpin;
			}
		}
	}
	return ret;
}


BlockItemPin * BlockView::getPin(const QList<QGraphicsItem *> & list) const {
	foreach(QGraphicsItem * i, list) {
		if (i->data(bvidType).toInt() == bvitPin) return qgraphicsitem_cast<BlockItemPin *>(i);
	}
	return 0;
}


void BlockView::highlightNearPins(BlockItemPin * pin, Qt::KeyboardModifiers km) {
	// qDebug() << "restore for" << last_multiconnect_pl.size();
	foreach(BlockItemPin * p, last_multiconnect_pl)
		p->restoreState();
	QList<BlockItemPin *> pl = nearPins(pin, km);
	foreach(BlockItemPin * p, pl) {
		p->saveState();
		p->setState(BlockItemPin::Hover);
	}
	last_multiconnect_pl = pl;
}


void BlockView::hideTmpBuses(bool clear) {
	tmp_bus.hide();
	if (clear) {
		tmp_bus.clear();
		qDeleteAll(tmp_buses);
		tmp_buses.clear();
	}
}


QList<BlockItem *> BlockView::selectedBlocks() const {
	QList<BlockItem *> ret;
	QList<QGraphicsItem *> sil = scene()->selectedItems();
	foreach(QGraphicsItem * b, sil)
		if ((b->data(bvidType).toInt() == bvitBlock) && !b->data(bvidTmpItem).toBool()) ret << qgraphicsitem_cast<BlockItem *>(b);
	return ret;
}


QList<QGraphicsItem *> BlockView::selectedDecors() const {
	QList<QGraphicsItem *> ret, sil = decors();
	foreach(QGraphicsItem * b, sil)
		if (b->isSelected()) ret << b;
	return ret;
}


void BlockView::animateNav(QRectF d, double scl) {
	nav_target = d;
	if (is_nav_anim) {
		if (qAbs<double>(scl - 1.) <= 0.1 && (nav_anim.state() != QAbstractAnimation::Running)) {
			_setNav(d);
			return;
		}
		if (nav_anim.state() != QAbstractAnimation::Running) {
			nav_prev_aa   = renderHints().testFlag(QPainter::Antialiasing);
			nav_prev_imaa = renderHints().testFlag(QPainter::SmoothPixmapTransform);
			nav_prev_grid = isGridVisible();
			setRenderHint(QPainter::Antialiasing, false);
			setRenderHint(QPainter::SmoothPixmapTransform, false);
			setGridVisible(false);
		}
		nav_anim.stop();
		nav_anim.setStartValue(_nav());
		nav_anim.setEndValue(d);
		nav_anim.start();
	} else
		_setNav(d);
}


void BlockView::adjustThumb() {
	if (!scene()) return;
	QSizeF sr = sceneRect().size(), tr;
	double scl;
	if (sr.width() >= sr.height())
		scl = thumb_size.width() / sr.width();
	else
		scl = thumb_size.height() / sr.height();
	tr     = sr * scl;
	int sx = 0, sy = 0;
	if (verticalScrollBar()->isVisible() && (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)) sx = verticalScrollBar()->width();
	if (horizontalScrollBar()->isVisible() && (horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)) sy = horizontalScrollBar()->height();
	widget_thumb.setGeometry(QRect(QPoint(width() - tr.width() - 10 - sx, height() - tr.height() - 10 - sy), tr.toSize()));
}


void BlockView::newBranch(BlockBusItem * item) {
	bus_from = item;
	prev_tcb = m_trace_with_buses;
	startNewBus(item->busType());
	new_branch = true;
	tmp_bus.setBusType(item->busType());
	if (item->selSegment >= 0) {
		QPointF ds(item->pol[item->segments[item->selSegment].first] - item->pol[item->segments[item->selSegment].second]);
		if (ds.x() == 0.) wavetrace.setPreferredDirection(BlockViewWavetrace::Horizontal);
		if (ds.y() == 0.) wavetrace.setPreferredDirection(BlockViewWavetrace::Vertical);
	}
}


void BlockView::startBusPointMove(int bus_type) {
	move_bus_point = true;
	prev_tcb       = m_trace_with_buses;
	startNewBus(bus_type);
}


void BlockView::pinHoverInOut(BlockItemPin * pin) {
	// qDebug() << "pinHoverInOut" << pin << pin->state();
	highlightNearPins(pin, QApplication::keyboardModifiers());
}


void BlockView::checkPaste(bool queued) {
	const QMimeData * mime = QApplication::clipboard()->mimeData();
	bool ret               = false;
	if (mime) ret = mime->hasFormat(_BlockView_Mime_);
	if (queued)
		QMetaObject::invokeMethod(
			this,
			[this, ret]() { pasteEnabledChanged(ret); },
			Qt::QueuedConnection);
	else
		emit pasteEnabledChanged(ret);
}


void BlockView::setBusSquareNodes(bool yes) {
	square_node               = yes;
	QList<BlockBusItem *> sbl = buses();
	foreach(BlockBusItem * b, sbl) {
		b->setSquareNodes(square_node);
	}
}


void BlockView::newBranchTrace(BlockBusItem * item, QPointF to) {
	trace(item->press_pos, to, &tmp_bus);
	tmp_bus.show();
}


void BlockView::newBranchAccept(BlockBusItem * item) {
	unmarkPins();
	if (!new_branch) return;
	new_branch = false;
	tmp_bus.hide();
	if (tmp_bus.pol.size() < 2) return;
	tmp_bus.pol.pop_front();
	if ((tmp_bus.pol.size() == 1) && (tmp_bus.pol[0] == item->press_pos)) return;
	int np = item->addPoint(item->press_pos);
	if (np < 0) return;
	if (match_bus) {
		if (iconnect)
			tmp_bus.pol.pop_back();
		else
			return;
	}
	if (item == match_bus) return;
	int snp = np;
	int lp  = item->pol.size();
	if (!tmp_bus.pol.isEmpty()) {
		item->pol << tmp_bus.pol;
		item->segments << QPair<int, int>(np, lp);
		for (int i = 0; i < tmp_bus.pol.size() - 1; ++i)
			item->segments << QPair<int, int>(lp + i, lp + i + 1);
	}
	if (match_bus != 0) {
		if (!iconnect) return;
		np = match_bus->selPoint;
		if (np < 0) np = match_bus->addPoint(trace_to);
		if (np < 0) return;
		lp = item->pol.size();
		item->pol << match_bus->pol;
		for (int i = 0; i < match_bus->segments.size(); ++i)
			item->segments << QPair<int, int>(match_bus->segments[i].first + lp, match_bus->segments[i].second + lp);
		if (tmp_bus.pol.isEmpty())
			item->segments << QPair<int, int>(lp + np, snp);
		else
			item->segments << QPair<int, int>(lp + np, lp - 1);
		match_bus->setProperty("_nodelete_", true);
		match_bus->deleteLater();
		match_bus = 0;
	}
	item->updateGeometry();
	newBusCreated(item);
	tmp_bus.clear();
}


void BlockView::newBranchCancel() {
	unmarkPins();
	// qDebug() << "cancel";
	new_branch = false;
	hideTmpBuses();
}


void BlockView::removedBus(QObject * o) {
	mm_ci = 0;
	reconnectAll();
	BlockBusItem * bus = (BlockBusItem *)o;
	if (bus->property("_nodelete_").toBool()) return;
	emitActionEvent(BlockItemBase::BusRemove, QList<QGraphicsItem *>() << bus);
	emit connectionsChanged();
}


void BlockView::removedBlock(QObject * o) {
	if (o == ghost_) return;
	emit blockRemoved((BlockItem *)o);
	emitActionEvent(BlockItemBase::BlockRemove, QList<QGraphicsItem *>() << qgraphicsitem_cast<QGraphicsItem *>((BlockItem *)o));
}


void BlockView::removeJunk() {
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi) {
		if (i->data(bvidType).toInt() != bvitBus) continue;
		BlockBusItem * b = qgraphicsitem_cast<BlockBusItem *>(i);
		if (b->pol.size() <= 1) {
			b->deleteLater();
		}
	}
}


void BlockView::sceneSelectionChanged() {
	bool ie = scene()->selectedItems().isEmpty();
	emit copyEnabledChanged(!ie);
	if (!block_emit_selection) emit selectionChanged();
}


void BlockView::_setThumb(double v) {
	_talpha     = v;
	QWidget * w = &widget_thumb;
	QMetaObject::invokeMethod(
		this,
		[w]() { w->update(); },
		Qt::QueuedConnection);
	if (_talpha <= 0.01)
		widget_thumb.hide();
	else
		widget_thumb.show();
}


void BlockView::_setNav(QRectF v) {
	double vw = viewport()->width(), vh = viewport()->height();
	if (vw < 1. || vh < 1. || v.isEmpty()) return;
	QTransform matrix;
	double scl  = qMin(vw / v.width(), vh / v.height());
	double ascl = appScale(this);
	if (scl > 0.02 * ascl && scl < 50.0 * ascl) {
		matrix.scale(scl, scl);
		nav_rect = v;
		QGraphicsView::setTransform(matrix);
		QGraphicsView::centerOn(v.center());
	}
}


QRectF BlockView::_nav() const {
	return nav_rect;
}


void BlockView::_navFinished() {
	setRenderHint(QPainter::Antialiasing, nav_prev_aa);
	setRenderHint(QPainter::SmoothPixmapTransform, nav_prev_imaa);
	setGridVisible(nav_prev_grid);
}


void BlockView::reconnectAll() {
	// qDebug() << "reconnect";
	removeJunk();
	QList<QGraphicsItem *> gi = scene_->items();
	QList<BlockItemPin *> pins;
	QList<BlockBusItem *> buses;
	foreach(QGraphicsItem * i, gi) {
		if (i->data(bvidType).toInt() == bvitPin) pins << qgraphicsitem_cast<BlockItemPin *>(i);
		if (i->data(bvidType).toInt() == bvitBus) buses << qgraphicsitem_cast<BlockBusItem *>(i);
	}
	foreach(BlockItemPin * p, pins) {
		p->clearStateStack();
		p->setState(BlockItemPin::Disconnected);
		p->buses_.clear();
	}
	foreach(BlockBusItem * b, buses) {
		b->connections_.clear();
		QVector<int> conns(b->endpoints());
		for (int c = 0; c < conns.size(); ++c) {
			QPointF cp = b->pol[conns[c]];
			for (int j = 0; j < pins.size(); ++j) {
				BlockItemPin * pin = pins[j];
				if (!pin->isVisible()) continue;
				QPointF pp = pin->scenePos();
				if ((cp - pp).manhattanLength() <= (grid_step / 2.)) {
					// qDebug() << "found";
					if (b->busType() == pin->busType()) {
						b->connections_[conns[c]] = pin;
						if (!pin->buses_.contains(b)) pin->buses_ << b;
						if (m_pin_mc) {
							pin->setState(BlockItemPin::Connected);
						} else {
							pin->setState(pin->buses_.size() == 1 ? BlockItemPin::Connected : BlockItemPin::Reject);
						}
					} else
						pin->setState(BlockItemPin::Reject);
					break;
				}
			}
		}
	}
	// qDebug() << pins.size() << buses.size();
}


void BlockView::zoom(double factor) {
	if (!is_nav_anim || (nav_anim.state() != QPropertyAnimation::Running)) nav_target = _nav();
	QRectF r = nav_target;
	QPoint mp;
	if (underMouse())
		mp = mapFromGlobal(QCursor::pos());
	else
		mp = QPoint(viewport()->width() / 2, viewport()->height() / 2);
	double cx = double(mp.x()) / viewport()->width(), cy = double(mp.y()) / viewport()->height();
	double pw = r.width(), ph = r.height();
	r.setWidth(r.width() / factor);
	r.setHeight(r.height() / factor);
	r.translate(cx * (pw - r.width()), cy * (ph - r.height()));
	animateNav(r);
}


void BlockView::zoomReset() {
	cur_scl = qSqrt(transform().determinant());
	zoom(prev_app_scale / cur_scl);
}


void BlockView::copyToClipboard() {
	QList<BlockItem *> bll     = selectedBlocks();
	QList<QGraphicsItem *> del = selectedDecors();
	// qDebug() << "copy" << bll.size() << del.size();
	if (bll.isEmpty() && del.isEmpty()) return;
	QList<BlockBusItem *> bul = internalBuses(bll);
	QByteArray ba;
	QDataStream s(&ba, QIODevice::ReadWrite);
	s << uint(0x89abcdef) << bll << bul << del;
	QMimeData * mime = new QMimeData();
	mime->setData(_BlockView_Mime_, ba);
	QApplication::clipboard()->setMimeData(mime);
}


void BlockView::pasteFromClipboard() {
	const QMimeData * mime = QApplication::clipboard()->mimeData();
	if (!mime) return;
	if (!mime->hasFormat(_BlockView_Mime_)) return;
	QByteArray ba = mime->data(_BlockView_Mime_);
	// qDebug() << "paste" << ba.size();
	if (ba.size() < 4) return;
	QList<BlockItem *> bll;
	QList<BlockBusItem *> bul;
	QList<QGraphicsItem *> del, gl;
	uint hdr = 0;
	QDataStream s(ba);
	s >> hdr;
	if (hdr != 0x89abcdef) return;
	s >> bll >> bul >> del;
	int all = bll.size() + bul.size() + del.size();
	if (all == 0) return;
	QRectF br;
	foreach(BlockItem * b, bll) {
		br |= b->boundingRect().translated(b->pos());
		gl << b;
	}
	foreach(BlockBusItem * b, bul)
		gl << b;
	foreach(QGraphicsItem * b, del)
		br |= b->boundingRect().translated(b->pos());
	gl << del;
	QPointF copy_dp;
	if (underMouse())
		copy_dp = mapToScene(mapFromGlobal(QCursor::pos()));
	else
		copy_dp = mapToScene(rect().center());
	copy_dp -= br.center();
	copy_dp    = quantize(copy_dp, grid_step);
	ae_enabled = false;
	if (!bll.isEmpty()) copyBlocks(bll, copy_dp);
	if (!bul.isEmpty()) copyBuses(bul, copy_dp);
	foreach(QGraphicsItem * i, del)
		i->setPos(i->pos() + copy_dp);
	addItems(del);
	ae_enabled = true;
	emitActionEvent(BlockItemBase::Paste, gl);
}


void BlockView::selectNone() {
	bool pb                   = block_emit_selection;
	block_emit_selection      = true;
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		i->setSelected(false);
	block_emit_selection = pb;
	if (!block_emit_selection) emit selectionChanged();
}


void BlockView::selectAll() {
	bool pb                   = block_emit_selection;
	block_emit_selection      = true;
	QList<QGraphicsItem *> gi = scene_->items();
	foreach(QGraphicsItem * i, gi)
		if (i->flags().testFlag(QGraphicsItem::ItemIsSelectable)) i->setSelected(true);
	block_emit_selection = pb;
	if (!block_emit_selection) emit selectionChanged();
}


void BlockView::removeSelected() {
	QList<QGraphicsItem *> gi = scene_->selectedItems(), ai;
	blockSignals(true);
	QList<BlockBusItem *> sbuses = buses(), dbuses, wbuses = wrongConnectedBuses();
	foreach(BlockBusItem * i, sbuses)
		if (i->connectedBlocks().isEmpty()) dbuses << i;
	foreach(QGraphicsItem * i, gi) {
		if (i->data(bvidTmpItem).toBool()) continue;
		if (i->data(bvidType).toInt() == bvitBlock) ai << qgraphicsitem_cast<QGraphicsItem *>(i);
		if ((i->data(bvidType).toInt() == bvitBlock) || (i->data(bvidType).toInt() == bvitBus) ||
		    (i->data(bvidType).toInt() == bvitDecor)) {
			scene_->sendEvent(i, new QGraphicsSceneEvent(QEvent::Close));
			delete i;
		}
	}
	reconnectAll();
	foreach(BlockBusItem * i, sbuses)
		if (i->connectedBlocks().isEmpty())
			if (!dbuses.contains(i)) delete i;

	blockSignals(false);
	foreach(QGraphicsItem * i, ai)
		emit blockRemoved((BlockItem *)i);
	emitActionEvent(BlockItemBase::BlockRemove, ai);
}


void BlockView::removeAll() {
	last_multiconnect_pl.clear();
	QList<QGraphicsItem *> gi = scene_->items(), ai;
	blockSignals(true);
	match_bus = bus_from = cur_bus = nullptr;
	mm_ci                          = nullptr;
	hpin                           = nullptr;
	ghost_                         = nullptr;
	foreach(QGraphicsItem * i, gi) {
		if (i->data(bvidTmpItem).toBool()) continue;
		if (i->data(bvidType).toInt() == bvitBlock) ai << qgraphicsitem_cast<QGraphicsItem *>(i);
		if ((i->data(bvidType).toInt() == bvitBlock) || (i->data(bvidType).toInt() == bvitBus) ||
		    (i->data(bvidType).toInt() == bvitDecor)) {
			if ((i != &sel_rect) && (i != &tmp_bus) && (i->parentItem() == 0) && !(i->data(bvidItemSelection).toBool())) {
				// qDebug() << "delete" << i->data(1005).toInt();
				scene_->sendEvent(i, new QGraphicsSceneEvent(QEvent::Close));
				delete i;
			}
		}
	}
	blockSignals(false);
	foreach(QGraphicsItem * i, ai)
		emit blockRemoved((BlockItem *)i);
}
