// license:BSD-3-Clause // copyright-holders:Andrew Gardner #include "emu.h" #include "debuggerview.h" #include "modules/lib/osdobj_common.h" #include #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) #define horizontalAdvance width #endif namespace osd::debugger::qt { DebuggerView::DebuggerView( debug_view_type type, running_machine &machine, QWidget *parent) : QAbstractScrollArea(parent), m_machine(machine), m_view(nullptr), m_preferBottom(false) { // I like setting the font per-view since it doesn't override the menu fonts. const char *const selectedFont(downcast(m_machine.options()).debugger_font()); const float selectedFontSize(downcast(m_machine.options()).debugger_font_size()); QFont viewFontRequest((!*selectedFont || !strcmp(selectedFont, OSDOPTVAL_AUTO)) ? "Courier New" : selectedFont); viewFontRequest.setFixedPitch(true); viewFontRequest.setStyleHint(QFont::TypeWriter); viewFontRequest.setPointSize((selectedFontSize <= 0) ? 11 : selectedFontSize); setFont(viewFontRequest); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_view = m_machine.debug_view().alloc_view( type, DebuggerView::debuggerViewUpdate, this); connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &DebuggerView::verticalScrollSlot); connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &DebuggerView::horizontalScrollSlot); } DebuggerView::~DebuggerView() { if (m_view) m_machine.debug_view().free_view(*m_view); } int DebuggerView::sourceIndex() const { if (m_view) { debug_view_source const *const source = m_view->source(); if (source) return m_view->source_index(*source); } return -1; } void DebuggerView::paintEvent(QPaintEvent *event) { // Tell the MAME debug view how much real estate is available QFontMetrics actualFont = fontMetrics(); double const fontWidth = actualFont.horizontalAdvance(QString(100, '_')) / 100.; int const fontHeight = std::max(1, actualFont.lineSpacing()); int const contentWidth = width() - verticalScrollBar()->width(); int const lineWidth = contentWidth / fontWidth; bool const fullWidth = lineWidth >= m_view->total_size().x; int const contentHeight = height() - (fullWidth ? 0 : horizontalScrollBar()->height()); m_view->set_visible_size(debug_view_xy(lineWidth, contentHeight / fontHeight)); // Handle the scroll bars int const horizontalScrollCharDiff = m_view->total_size().x - m_view->visible_size().x; horizontalScrollBar()->setRange(0, (std::max)(0, horizontalScrollCharDiff)); horizontalScrollBar()->setPageStep(lineWidth - 1); int const verticalScrollCharDiff = m_view->total_size().y - m_view->visible_size().y; int const verticalScrollSize = (std::max)(0, verticalScrollCharDiff); bool const atEnd = verticalScrollBar()->value() == verticalScrollBar()->maximum(); verticalScrollBar()->setRange(0, verticalScrollSize); verticalScrollBar()->setPageStep((contentHeight / fontHeight) - 1); if (m_preferBottom && atEnd) verticalScrollBar()->setValue(verticalScrollSize); // Draw the viewport widget QPainter painter(viewport()); painter.fillRect(0, 0, width(), height(), QBrush(Qt::white)); painter.setBackgroundMode(Qt::OpaqueMode); painter.setBackground(QColor(255,255,255)); // Background control QBrush bgBrush; bgBrush.setStyle(Qt::SolidPattern); painter.setPen(QPen(QColor(0,0,0))); const debug_view_xy visibleCharDims = m_view->visible_size(); const debug_view_char *viewdata = m_view->viewdata(); for (int y = 0; y < visibleCharDims.y; y++, viewdata += visibleCharDims.x) { int width = 1; for (int x = 0; x < visibleCharDims.x; x += width) { const unsigned char textAttr = viewdata[x].attrib; // Text color handling QColor fgColor(0,0,0); QColor bgColor(255,255,255); if (textAttr & DCA_VISITED) bgColor.setRgb(0xc6, 0xe2, 0xff); if (textAttr & DCA_ANCILLARY) bgColor.setRgb(0xe0, 0xe0, 0xe0); if (textAttr & DCA_SELECTED) bgColor.setRgb(0xff, 0x80, 0x80); if (textAttr & DCA_CURRENT) bgColor.setRgb(0xff, 0xff, 0x00); if ((textAttr & DCA_SELECTED) && (textAttr & DCA_CURRENT)) bgColor.setRgb(0xff,0xc0,0x80); if (textAttr & DCA_CHANGED) fgColor.setRgb(0xff, 0x00, 0x00); if (textAttr & DCA_INVALID) fgColor.setRgb(0x00, 0x00, 0xff); if (textAttr & DCA_DISABLED) { fgColor.setRgb( (fgColor.red() + bgColor.red()) >> 1, (fgColor.green() + bgColor.green()) >> 1, (fgColor.blue() + bgColor.blue()) >> 1); } if (textAttr & DCA_COMMENT) fgColor.setRgb(0x00, 0x80, 0x00); bgBrush.setColor(bgColor); painter.setBackground(bgBrush); painter.setPen(QPen(fgColor)); QString text(QChar(viewdata[x].byte)); for (width = 1; (x + width) < visibleCharDims.x; width++) { if (textAttr != viewdata[x + width].attrib) break; text.append(QChar(viewdata[x + width].byte)); } // Your characters are not guaranteed to take up the entire length x fontWidth x fontHeight, so fill before. painter.fillRect( x * fontWidth, y * fontHeight, ((x + width) < visibleCharDims.x) ? (width * fontWidth) : (contentWidth - (x * fontWidth)), fontHeight, bgBrush); if (((y + 1) == visibleCharDims.y) && (contentHeight > (visibleCharDims.y * fontHeight))) { if (textAttr & DCA_ANCILLARY) bgColor.setRgb(0xe0, 0xe0, 0xe0); else bgColor.setRgb(0xff, 0xff, 0xff); bgBrush.setColor(bgColor); painter.fillRect( x * fontWidth, visibleCharDims.y * fontHeight, ((x + width) < visibleCharDims.x) ? (width * fontWidth) : (contentWidth - (x * fontWidth)), contentHeight - (visibleCharDims.y * fontHeight), bgBrush); } // There is a touchy interplay between font height, drawing difference, visible position, etc // Fonts don't get drawn "down and to the left" like boxes, so some wiggling is needed. painter.drawText(x * fontWidth, (y * fontHeight + (fontHeight * 0.80)), text); } } } void DebuggerView::keyPressEvent(QKeyEvent* event) { if (m_view == nullptr) return QWidget::keyPressEvent(event); Qt::KeyboardModifiers keyMods = QApplication::keyboardModifiers(); const bool ctrlDown = keyMods.testFlag(Qt::ControlModifier); int keyPress = -1; switch (event->key()) { case Qt::Key_Up: keyPress = DCH_UP; break; case Qt::Key_Down: keyPress = DCH_DOWN; break; case Qt::Key_Left: keyPress = DCH_LEFT; if (ctrlDown) keyPress = DCH_CTRLLEFT; break; case Qt::Key_Right: keyPress = DCH_RIGHT; if (ctrlDown) keyPress = DCH_CTRLRIGHT; break; case Qt::Key_PageUp: keyPress = DCH_PUP; break; case Qt::Key_PageDown: keyPress = DCH_PDOWN; break; case Qt::Key_Home: keyPress = DCH_HOME; if (ctrlDown) keyPress = DCH_CTRLHOME; break; case Qt::Key_End: keyPress = DCH_END; if (ctrlDown) keyPress = DCH_CTRLEND; break; case Qt::Key_0: keyPress = '0'; break; case Qt::Key_1: keyPress = '1'; break; case Qt::Key_2: keyPress = '2'; break; case Qt::Key_3: keyPress = '3'; break; case Qt::Key_4: keyPress = '4'; break; case Qt::Key_5: keyPress = '5'; break; case Qt::Key_6: keyPress = '6'; break; case Qt::Key_7: keyPress = '7'; break; case Qt::Key_8: keyPress = '8'; break; case Qt::Key_9: keyPress = '9'; break; case Qt::Key_A: keyPress = 'a'; break; case Qt::Key_B: keyPress = 'b'; break; case Qt::Key_C: keyPress = 'c'; break; case Qt::Key_D: keyPress = 'd'; break; case Qt::Key_E: keyPress = 'e'; break; case Qt::Key_F: keyPress = 'f'; break; default: return QWidget::keyPressEvent(event); } m_view->set_cursor_visible(true); m_view->process_char(keyPress); // Catch the view up with the cursor verticalScrollBar()->setValue(m_view->visible_position().y); viewport()->update(); update(); } void DebuggerView::mousePressEvent(QMouseEvent *event) { if (!m_view) return; QFontMetrics actualFont = fontMetrics(); const double fontWidth = actualFont.horizontalAdvance(QString(100, '_')) / 100.; const int fontHeight = std::max(1, actualFont.lineSpacing()); debug_view_xy const topLeft = m_view->visible_position(); debug_view_xy const visibleCharDims = m_view->visible_size(); debug_view_xy clickViewPosition; clickViewPosition.x = (std::min)(int(topLeft.x + (event->x() / fontWidth)), topLeft.x + visibleCharDims.x - 1); clickViewPosition.y = (std::min)(int(topLeft.y + (event->y() / fontHeight)), topLeft.y + visibleCharDims.y - 1); if (event->button() == Qt::LeftButton) { m_view->process_click(DCK_LEFT_CLICK, clickViewPosition); } else if (event->button() == Qt::MiddleButton) { m_view->process_click(DCK_MIDDLE_CLICK, clickViewPosition); } else if (event->button() == Qt::RightButton) { if (m_view->cursor_supported()) { m_view->set_cursor_position(clickViewPosition); m_view->set_cursor_visible(true); } } viewport()->update(); update(); } void DebuggerView::contextMenuEvent(QContextMenuEvent *event) { QMenu *const menu = new QMenu(this); addItemsToContextMenu(menu); menu->popup(event->globalPos()); } void DebuggerView::addItemsToContextMenu(QMenu *menu) { QAction *const copyAct = new QAction("Copy Visible", menu); QAction *const pasteAct = new QAction("Paste", menu); pasteAct->setEnabled(QApplication::clipboard()->mimeData()->hasText()); connect(copyAct, &QAction::triggered, this, &DebuggerView::copyVisibleSlot); connect(pasteAct, &QAction::triggered, this, &DebuggerView::pasteSlot); menu->addAction(copyAct); menu->addAction(pasteAct); } void DebuggerView::verticalScrollSlot(int value) { m_view->set_visible_position(debug_view_xy(horizontalScrollBar()->value(), value)); } void DebuggerView::horizontalScrollSlot(int value) { m_view->set_visible_position(debug_view_xy(value, verticalScrollBar()->value())); } void DebuggerView::copyVisibleSlot() { // get visible text debug_view_xy const visarea = m_view->visible_size(); debug_view_char const *viewdata = m_view->viewdata(); if (!viewdata) return; // turn into a plain string, trimming trailing whitespace std::string text; for (uint32_t row = 0; row < visarea.y; row++, viewdata += visarea.x) { std::string::size_type const start = text.length(); for (uint32_t col = 0; col < visarea.x; ++col) text += wchar_t(viewdata[col].byte); std::string::size_type const nonblank = text.find_last_not_of("\t\n\v\r "); if (nonblank != std::string::npos) text.resize((std::max)(start, nonblank + 1)); text += "\n"; } // copy to the clipboard QApplication::clipboard()->setText(text.c_str()); } void DebuggerView::pasteSlot() { for (QChar ch : QApplication::clipboard()->text()) { if ((32 <= ch.unicode()) && (127 >= ch.unicode())) m_view->process_char(ch.unicode()); } } void DebuggerView::debuggerViewUpdate(debug_view &debugView, void *osdPrivate) { // Get a handle to the DebuggerView being updated and redraw DebuggerView *dView = reinterpret_cast(osdPrivate); dView->verticalScrollBar()->setValue(dView->view()->visible_position().y); dView->horizontalScrollBar()->setValue(dView->view()->visible_position().x); dView->viewport()->update(); dView->update(); emit dView->updated(); } } // namespace osd::debugger::qt