From 69748d064cd53eb199a57826cafb7b823ee9175c Mon Sep 17 00:00:00 2001 From: karliss Date: Mon, 17 Jun 2019 15:13:34 +0300 Subject: [PATCH] Improve hex selection painting and right click behavior. (#1602) * Improve hex selection painting and right click behavior. * Do not remove selection when right clicking outside selection * Indicate active hex widget side by painting selection differently * Add horizontal padding and indicate partial selection. --- src/widgets/HexWidget.cpp | 141 ++++++++++++++++++++++++++------------ src/widgets/HexWidget.h | 11 +++ 2 files changed, 108 insertions(+), 44 deletions(-) diff --git a/src/widgets/HexWidget.cpp b/src/widgets/HexWidget.cpp index 0abfb321..3c5e80fc 100644 --- a/src/widgets/HexWidget.cpp +++ b/src/widgets/HexWidget.cpp @@ -517,14 +517,25 @@ void HexWidget::keyPressEvent(QKeyEvent *event) void HexWidget::contextMenuEvent(QContextMenuEvent *event) { QPoint pt = event->pos(); + bool mouseOutsideSelection = false; if (event->reason() == QContextMenuEvent::Mouse) { auto mouseAddr = mousePosToAddr(pt).address; - if (selection.isEmpty() || !(mouseAddr >= selection.start() && mouseAddr <= selection.end())) { - cursorOnAscii = asciiArea.contains(pt); + if (asciiArea.contains(pt)) { + cursorOnAscii = true; + } else if (itemArea.contains(pt)) { + cursorOnAscii = false; + } + if (selection.isEmpty()) { seek(mouseAddr); + } else { + mouseOutsideSelection = !selection.contains(mouseAddr); } } + auto disableOutsideSelectionActions = [this](bool disable) { + actionCopyAddress->setDisabled(disable); + }; + QMenu *menu = new QMenu(); QMenu *sizeMenu = menu->addMenu(tr("Item size:")); sizeMenu->addActions(actionsItemSize); @@ -535,9 +546,11 @@ void HexWidget::contextMenuEvent(QContextMenuEvent *event) menu->addAction(actionItemBigEndian); menu->addSeparator(); menu->addAction(actionCopy); + disableOutsideSelectionActions(mouseOutsideSelection); menu->addAction(actionCopyAddress); menu->addActions(this->actions()); menu->exec(mapToGlobal(pt)); + disableOutsideSelectionActions(false); menu->deleteLater(); } @@ -736,8 +749,9 @@ void HexWidget::drawItemArea(QPainter &painter) for (int j = 0; j < itemColumns; ++j) { for (int k = 0; k < itemGroupSize && itemAddr <= data->maxIndex(); ++k, itemAddr += itemByteLen) { itemString = renderItem(itemAddr - startAddress, &itemColor); - if (selection.contains(itemAddr)) + if (selection.contains(itemAddr) && !cursorOnAscii) { itemColor = palette().highlightedText().color(); + } painter.setPen(itemColor); painter.drawText(itemRect, Qt::AlignVCenter, itemString); itemRect.translate(itemWidth(), 0); @@ -771,7 +785,7 @@ void HexWidget::drawAsciiArea(QPainter &painter) charRect.moveLeft(asciiArea.left()); for (int j = 0; j < itemRowByteLen() && address <= data->maxIndex(); ++j, ++address) { ascii = renderAscii(address - startAddress, &color); - if (selection.contains(address)) + if (selection.contains(address) && cursorOnAscii) color = palette().highlightedText().color(); painter.setPen(color); /* Dots look ugly. Use fillRect() instead of drawText(). */ @@ -795,54 +809,85 @@ void HexWidget::drawAsciiArea(QPainter &painter) void HexWidget::fillSelectionBackground(QPainter &painter, bool ascii) { - QRect rect; - const QRect *area = ascii ? &asciiArea : &itemArea; - - int startOffset = -1; - int endOffset = -1; - - if (!selection.intersects(startAddress, lastVisibleAddr())) { + if (selection.isEmpty()) { return; } + const auto parts = rangePolygons(selection.start(), selection.end(), ascii); + for (const auto &shape : qAsConst(parts)) { + QColor highlightColor = palette().color(QPalette::Highlight); + if (ascii == cursorOnAscii) { + painter.setBrush(highlightColor); + painter.drawPolygon(shape); + } else { + painter.setPen(highlightColor); + painter.drawPolyline(shape); + } + } +} + +QVector HexWidget::rangePolygons(RVA start, RVA last, bool ascii) +{ + if (last < startAddress || start > lastVisibleAddr()) { + return {}; + } + + QRectF rect; + const QRectF area = QRectF(ascii ? asciiArea : itemArea); /* Convert absolute values to relative */ - startOffset = std::max(selection.start(), startAddress) - startAddress; - endOffset = std::min(selection.end(), lastVisibleAddr()) - startAddress; + int startOffset = std::max(uint64_t(start), startAddress) - startAddress; + int endOffset = std::min(uint64_t(last), lastVisibleAddr()) - startAddress; - /* Align values */ - int startOffset2 = (startOffset + itemRowByteLen()) & ~(itemRowByteLen() - 1); - int endOffset2 = endOffset & ~(itemRowByteLen() - 1); + QVector parts; - QColor highlightColor = palette().color(QPalette::Highlight); + auto getRectangle = [&](int offset) { + return QRectF(ascii ? asciiRectangle(offset) : itemRectangle(offset)); + }; - /* Fill top/bottom parts */ - if (startOffset2 <= endOffset2) { - /* Fill the top part even if it's a whole line */ - rect = ascii ? asciiRectangle(startOffset) : itemRectangle(startOffset); - rect.setRight(area->right()); - painter.fillRect(rect, highlightColor); - /* Fill the bottom part even if it's a whole line */ - rect = ascii ? asciiRectangle(endOffset) : itemRectangle(endOffset); - rect.setLeft(area->left()); - painter.fillRect(rect, highlightColor); - /* Required for calculating the bottomRight() of the main part */ - --endOffset2; - } else { - startOffset2 = startOffset; - endOffset2 = endOffset; - } - - /* Fill the main part */ - if (startOffset2 <= endOffset2) { - if (ascii) { - rect = asciiRectangle(startOffset2); - rect.setBottomRight(asciiRectangle(endOffset2).bottomRight()); - } else { - rect = itemRectangle(startOffset2); - rect.setBottomRight(itemRectangle(endOffset2).bottomRight()); + auto startRect = getRectangle(startOffset); + auto endRect = getRectangle(endOffset); + if (!ascii) { + if (int startFraction = startOffset % itemByteLen) { + startRect.setLeft(startRect.left() + startFraction * startRect.width() / itemByteLen); + } + if (int endFraction = itemByteLen - 1 - (endOffset % itemByteLen)) { + endRect.setRight(endRect.right() - endFraction * endRect.width() / itemByteLen); } - painter.fillRect(rect, highlightColor); } + if (endOffset - startOffset + 1 <= rowSizeBytes) { + if (startOffset / rowSizeBytes == endOffset / rowSizeBytes) { // single row + rect = startRect; + rect.setRight(endRect.right()); + parts.push_back(QPolygonF(rect)); + } else { + // two seperate rectangles + rect = startRect; + rect.setRight(area.right()); + parts.push_back(QPolygonF(rect)); + rect = endRect; + rect.setLeft(area.left()); + parts.push_back(QPolygonF(rect)); + } + } else { + // single multiline shape + QPolygonF shape; + shape << startRect.topLeft(); + rect = getRectangle(startOffset + rowSizeBytes - 1 - startOffset % rowSizeBytes); + shape << rect.topRight(); + if (endOffset % rowSizeBytes != rowSizeBytes - 1) { + rect = getRectangle(endOffset - endOffset % rowSizeBytes - 1); + shape << rect.bottomRight() << endRect.topRight(); + } + shape << endRect.bottomRight(); + shape << getRectangle(endOffset - endOffset % rowSizeBytes).bottomLeft(); + if (startOffset % rowSizeBytes) { + rect = getRectangle(startOffset - startOffset % rowSizeBytes + rowSizeBytes); + shape << rect.topLeft() << startRect.bottomLeft(); + } + shape << shape.first(); // close the shape + parts.push_back(shape); + } + return parts; } void HexWidget::updateMetrics() @@ -1165,17 +1210,25 @@ QRect HexWidget::itemRectangle(uint offset) int x; int y; + int width = itemWidth(); y = (offset / itemRowByteLen()) * lineHeight; offset %= itemRowByteLen(); x = (offset / itemGroupByteLen()) * columnExWidth(); offset %= itemGroupByteLen(); x += (offset / itemByteLen) * itemWidth(); + if (offset == 0) { + x -= charWidth / 2; + width += charWidth / 2; + } + if (offset == itemGroupByteLen() - 1) { + width += charWidth / 2; + } x += itemArea.x(); y += itemArea.y(); - return QRect(x, y, itemWidth(), lineHeight); + return QRect(x, y, width, lineHeight); } QRect HexWidget::asciiRectangle(uint offset) diff --git a/src/widgets/HexWidget.h b/src/widgets/HexWidget.h index cba04641..d95180c0 100644 --- a/src/widgets/HexWidget.h +++ b/src/widgets/HexWidget.h @@ -314,8 +314,19 @@ private: BasicCursor asciiPosToAddr(const QPoint &point, bool middle = false) const; BasicCursor currentAreaPosToAddr(const QPoint &point, bool middle = false) const; BasicCursor mousePosToAddr(const QPoint &point, bool middle = false) const; + /** + * @brief Rectangle for single item in data area. + * @param offset relative to first byte on screen + * @return + */ QRect itemRectangle(uint offset); + /** + * @brief Rectangle for single item in ascii area. + * @param offset relative to first byte on screen + * @return + */ QRect asciiRectangle(uint offset); + QVector rangePolygons(RVA start, RVA last, bool ascii); void updateWidth(); inline int itemWidth() const