From f91499499395cc3dbbb10d8f24fb88c0d92a0846 Mon Sep 17 00:00:00 2001 From: royqh1979 Date: Thu, 1 Jul 2021 20:24:47 +0800 Subject: [PATCH] * work save --- RedPandaIDE/RedPandaIDE.pro | 4 + RedPandaIDE/qsynedit/highlighter/asm.cpp | 40 + RedPandaIDE/qsynedit/highlighter/asm.h | 41 + RedPandaIDE/widgets/qconsole.cpp | 1173 ++++++++++++++++++++++ RedPandaIDE/widgets/qconsole.h | 217 ++++ 5 files changed, 1475 insertions(+) create mode 100644 RedPandaIDE/qsynedit/highlighter/asm.cpp create mode 100644 RedPandaIDE/qsynedit/highlighter/asm.h create mode 100644 RedPandaIDE/widgets/qconsole.cpp create mode 100644 RedPandaIDE/widgets/qconsole.h diff --git a/RedPandaIDE/RedPandaIDE.pro b/RedPandaIDE/RedPandaIDE.pro index d2a8fc16..bb2be3c9 100644 --- a/RedPandaIDE/RedPandaIDE.pro +++ b/RedPandaIDE/RedPandaIDE.pro @@ -32,6 +32,7 @@ SOURCES += \ qsynedit/exporter/synexporter.cpp \ qsynedit/exporter/synhtmlexporter.cpp \ qsynedit/exporter/synrtfexporter.cpp \ + qsynedit/highlighter/asm.cpp \ qsynedit/highlighter/base.cpp \ qsynedit/highlighter/composition.cpp \ qsynedit/highlighter/cpp.cpp \ @@ -53,6 +54,7 @@ SOURCES += \ widgets/coloredit.cpp \ widgets/consolewidget.cpp \ widgets/issuestable.cpp \ + widgets/qconsole.cpp \ widgets/qpatchedcombobox.cpp HEADERS += \ @@ -79,6 +81,7 @@ HEADERS += \ qsynedit/exporter/synexporter.h \ qsynedit/exporter/synhtmlexporter.h \ qsynedit/exporter/synrtfexporter.h \ + qsynedit/highlighter/asm.h \ qsynedit/highlighter/base.h \ qsynedit/highlighter/composition.h \ qsynedit/highlighter/cpp.h \ @@ -101,6 +104,7 @@ HEADERS += \ widgets/coloredit.h \ widgets/consolewidget.h \ widgets/issuestable.h \ + widgets/qconsole.h \ widgets/qpatchedcombobox.h FORMS += \ diff --git a/RedPandaIDE/qsynedit/highlighter/asm.cpp b/RedPandaIDE/qsynedit/highlighter/asm.cpp new file mode 100644 index 00000000..f554cb59 --- /dev/null +++ b/RedPandaIDE/qsynedit/highlighter/asm.cpp @@ -0,0 +1,40 @@ +#include "asm.h" + +const QSet SynEditASMHighlighter::Keywords { + "aaa","aad","aam","adc","add","and","arpl","bound","bsf","bsr","bswap","bt","btc","btr","bts", + "call","cbw","cdq","clc","cld","cli","clts","cmc","cmp","cmps","cmpsb","cmpsd","cmpsw", + "cmpxchg","cwd","cwde","daa","das","dec","div","emms","enter","f2xm1","fabs","fadd","faddp","fbld", + "fbstp","fchs","fclex","fcmovb","fcmovbe","fcmove","fcmovnb","fcmovnbe","fcmovne","fcmovnu", + "fcmovu","fcom","fcomi","fcomip","fcomp","fcompp","fcos","fdecstp","fdiv","fdivp","fdivr", + "fdivrp","femms","ffree","fiadd","ficom","ficomp","fidiv","fidivr","fild","fimul","fincstp", + "finit","fist","fistp","fisub","fisubr","fld","fld1","fldcw","fldenv","fldl2e","fldl2t","fldlg2", + "fldln2","fldpi","fldz","fmul","fmulp","fnclex","fninit","fnop","fnsave","fnstcw","fnstenv", + "fnstsw","fpatan","fprem1","fptan","frndint","frstor","fsave","fscale","fsin","fsincos", + "fsqrt","fst","fstcw","fstenv","fstp","fstsw","fsub","fsubp","fsubr","fsubrp","ftst", + "fucom","fucomi","fucomip","fucomp","fucompp","fwait","fxch","fxtract","fyl2xp1","hlt","idiv", + "imul","in","inc","ins","insb","insd","insw","int","into","invd","invlpg","iret","iretd","iretw", + "ja","jae","jb","jbe","jc","jcxz","je","jecxz","jg","jge","jl","jle","jmp","jna","jnae","jnb","jnbe","jnc", + "jne","jng","jnge","jnl","jnle","jno","jnp","jns","jnz","jo","jp","jpe","jpo","js","jz","lahf","lar","lds", + "lea","leave","les","lfs","lgdt","lgs","lidt","lldt","lmsw","lock","lods","lodsb","lodsd","lodsw", + "loop","loope","loopne","loopnz","loopz","lsl","lss","ltr","mov","movd","movq"," movs","movsb", + "movsd","movsw","movsx","movzx","mul","neg","nop","not","or","out","outs","outsb","outsd","outsw", + "packssdw","packsswb","packuswb","paddb","paddd","paddsb","paddsw","paddusb","paddusw", + "paddw","pand","pandn","pavgusb","pcmpeqb","pcmpeqd","pcmpeqw","pcmpgtb","pcmpgtd","pcmpgtw", + "pf2id","pfacc","pfadd","pfcmpeq","pfcmpge","pfcmpgt","pfmax","pfmin","pfmul","pfrcp", + "pfrcpit1","pfrcpit2","pfrsqit1","pfrsqrt","pfsub","pfsubr","pi2fd","pmaddwd","pmulhrw", + "pmulhw","pmullw","pop","popa","popad","popaw","popf","popfd","popfw","por","prefetch","prefetchw", + "pslld","psllq","psllw","psrad","psraw","psrld","psrlq","psrlw","psubb","psubd","psubsb", + "psubsw","psubusb","psubusw","psubw","punpckhbw","punpckhdq","punpckhwd","punpcklbw", + "punpckldq","punpcklwd","push","pusha","pushad","pushaw","pushf","pushfd","pushfw","pxor", + "rcl","rcr","rep","repe","repne","repnz","repz","ret","rol","ror","sahf","sal","sar","sbb","scas", + "scasb","scasd","scasw","seta","setae","setb","setbe","setc","sete","setg","setge","setl","setle", + "setna","setnae","setnb","setnbe","setnc","setne","setng","setnge","setnl","setnle","setno", + "setnp","setns","setnz","seto","setp","setpo","sets","setz","sgdt","shl","shld","shr","shrd","sidt", + "sldt","smsw","stc","std","sti","stos","stosb","stosd","stosw","str","sub","test","verr","verw", + "wait","wbinvd","xadd","xchg","xlat","xlatb","xor" +}; + +SynEditASMHighlighter::SynEditASMHighlighter() +{ + +} diff --git a/RedPandaIDE/qsynedit/highlighter/asm.h b/RedPandaIDE/qsynedit/highlighter/asm.h new file mode 100644 index 00000000..17a86761 --- /dev/null +++ b/RedPandaIDE/qsynedit/highlighter/asm.h @@ -0,0 +1,41 @@ +#ifndef SYNEDITASMHIGHLIGHTER_H +#define SYNEDITASMHIGHLIGHTER_H + +#include "base.h" + + +class SynEditASMHighlighter : public SynHighlighter +{ + enum TokenKind { + Comment, + Identifier, + Key, + Null, + Number, + Space, + String, + Symbol, + Unknown + }; +public: + SynEditASMHighlighter(); + + static const QSet Keywords; +private: + QChar* mLine; + int mLineNumber; + int mRun; + int mStringLen; + QChar mToIdent; + int mTokenPos; + SynTokenKind mTokenID; + SynHighlighterAttribute mCommentAttri; + SynHighlighterAttribute mIdentifierAttri; + SynHighlighterAttribute mKeyAttri; + SynHighlighterAttribute mNumberAttri; + SynHighlighterAttribute mSpaceAttri; + SynHighlighterAttribute mStringAttri; + SynHighlighterAttribute mSymbolAttri; +}; + +#endif // SYNEDITASMHIGHLIGHTER_H diff --git a/RedPandaIDE/widgets/qconsole.cpp b/RedPandaIDE/widgets/qconsole.cpp new file mode 100644 index 00000000..b11b9d5f --- /dev/null +++ b/RedPandaIDE/widgets/qconsole.cpp @@ -0,0 +1,1173 @@ +#include "qconsole.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QConsole::QConsole(QWidget *parent): + QAbstractScrollArea(parent), + mContents(this), + mContentImage() +{ + mHistorySize = 500; + mHistoryIndex = -1; + mCommand = ""; + mCurrentEditableLine = ""; + mRowHeight = 0; + mTopRow = 1; + mRowsInWindow = 0; + mColumnsPerRow = 0; + mColumnWidth = 0; + mReadonly = false; + mSelectionBegin = {0,0}; + mSelectionEnd = {0,0}; + mCaretChar = 0; + mBackground = palette().color(QPalette::Base); + mForeground = palette().color(QPalette::Text); + mSelectionBackground = palette().color(QPalette::Highlight); + mSelectionForeground = palette().color(QPalette::HighlightedText); + mInactiveSelectionBackground = palette().color(QPalette::Inactive,QPalette::Highlight); + mInactiveSelectionForeground = palette().color(QPalette::Inactive,QPalette::HighlightedText); + mTabSize = 4; + mBlinkTimerId = 0; + mBlinkStatus = 0; + //enable input method + setAttribute(Qt::WA_InputMethodEnabled); + setMouseTracking(false); + recalcCharExtent(); + mScrollTimer = new QTimer(this); + mScrollTimer->setInterval(100); + connect(mScrollTimer,&QTimer::timeout,this, &QConsole::scrollTimerHandler); + connect(&mContents,&ConsoleLines::layoutFinished,this, &QConsole::contentsLayouted); + connect(&mContents,&ConsoleLines::rowsAdded,this, &QConsole::contentsRowsAdded); + connect(&mContents,&ConsoleLines::lastRowsChanged,this, &QConsole::contentsLastRowsChanged); + connect(&mContents,&ConsoleLines::lastRowsRemoved,this, &QConsole::contentsLastRowsRemoved); + connect(verticalScrollBar(),&QScrollBar::valueChanged, + this, &QConsole::doScrolled); + +} + +int QConsole::historySize() const +{ + return mHistorySize; +} + +void QConsole::setHistorySize(int historySize) +{ + mHistorySize = historySize; +} + +int QConsole::tabSize() const +{ + return mTabSize; +} + +int QConsole::columnsPerRow() const +{ + return mColumnsPerRow; +} + +int QConsole::rowsInWindow() const +{ + return mRowsInWindow; +} + +int QConsole::charColumns(QChar ch, int columnsBefore) const +{ + if (ch == '\t') { + return mTabSize - (columnsBefore % mTabSize); + } + if (ch == ' ') + return 1; + return std::ceil((int)(fontMetrics().horizontalAdvance(ch)) / (double) mColumnWidth); +} + +void QConsole::invalidate() +{ + viewport()->update(); +} + +void QConsole::invalidateRows(int startRow, int endRow) +{ + if (!isVisible()) + return; + if (startRow == -1 && endRow == -1) { + invalidate(); + } else { + startRow = std::max(startRow, 1); + endRow = std::max(endRow, 1); + // find the visible lines first + if (startRow > endRow) + std::swap(startRow, endRow); + + if (endRow >= mContents.rows()) + endRow = INT_MAX; // paint empty space beyond last line + + // mTopLine is in display coordinates, so FirstLine and LastLine must be + // converted previously. + startRow = std::max(startRow, mTopRow); + endRow = std::min(endRow, mTopRow + mRowsInWindow-1); + + // any line visible? + if (endRow >= startRow) { + QRect rcInval = { + 0, + mRowHeight * (startRow - mTopRow), + clientWidth(), mRowHeight * (endRow - mTopRow + 1) + }; + invalidateRect(rcInval); + } + } + +} + +void QConsole::invalidateRect(const QRect &rect) +{ + viewport()->update(rect); +} + +void QConsole::addLine(const QString &line) +{ + mCurrentEditableLine = ""; + mCaretChar=0; + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + mContents.addLine(line); +} + +void QConsole::removeLastLine() +{ + mCurrentEditableLine = ""; + mCaretChar=0; + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + mContents.RemoveLastLine(); +} + +void QConsole::changeLastLine(const QString &line) +{ + mContents.changeLastLine(line); +} + +QString QConsole::getLastLine() +{ + return mContents.getLastLine(); +} + +void QConsole::recalcCharExtent() { + mRowHeight = fontMetrics().lineSpacing(); + mColumnWidth = fontMetrics().horizontalAdvance("M"); +} + +void QConsole::sizeOrFontChanged(bool) +{ + if (mColumnWidth != 0) { + mColumnsPerRow = std::max(clientWidth()-2,0) / mColumnWidth; + mRowsInWindow = clientHeight() / mRowHeight; + mContents.layout(); + } + +} + +int QConsole::clientWidth() +{ + return viewport()->size().width(); +} + +int QConsole::clientHeight() +{ + return viewport()->size().height(); +} + +void QConsole::setTopRow(int value) +{ + value = std::min(value,maxScrollHeight()); + value = std::max(value, 1); + if (value != mTopRow) { + verticalScrollBar()->setValue(value); + } +} + +int QConsole::maxScrollHeight() +{ + return std::max(mContents.rows(),1); +} + +void QConsole::updateScrollbars() +{ + setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + int nMaxScroll = maxScrollHeight(); + int nMin = 1; + int nMax = std::max(1, nMaxScroll); + int nPage = mRowsInWindow; + int nPos = std::min(std::max(mTopRow,nMin),nMax); + verticalScrollBar()->setMinimum(nMin); + verticalScrollBar()->setMaximum(nMax); + verticalScrollBar()->setPageStep(nPage); + verticalScrollBar()->setSingleStep(1); + if (nPos != verticalScrollBar()->value()) + verticalScrollBar()->setValue(nPos); + else + invalidate(); +} + +void QConsole::paintRows(QPainter &painter, int row1, int row2) +{ + if (row1>row2) + return; + QRect rect(0,(row1-mTopRow)*mRowHeight,clientWidth(),(row2-row1+1)*mRowHeight); + painter.fillRect(rect,mBackground); + QStringList lst = mContents.getRows(row1,row2); + int startRow = row1-mTopRow; + painter.setPen(mForeground); + RowColumn selBeginRC = mContents.lineCharToRowColumn(selectionBegin()); + RowColumn selEndRC = mContents.lineCharToRowColumn(selectionEnd()); + LineChar editBegin = { + .ch = mContents.getLastLine().length() - mCurrentEditableLine.length(), + .line = mContents.lines()-1 + }; + RowColumn editBeginRC = mContents.lineCharToRowColumn(editBegin); + bool isSelection = false; + painter.setPen(mForeground); + for (int i=0; i< lst.size(); i++) { + int currentRow = i+row1-1; + int left=2; + int top = (startRow+i) * mRowHeight; + int baseLine = (startRow+i+1)*mRowHeight - painter.fontMetrics().descent(); + QString s = lst[i]; + int columnsBefore = 0; + for (QChar ch:s) { + int charCol = charColumns(ch,columnsBefore); + int width = charCol * mColumnWidth; + if ((currentRow > selBeginRC.row || + (currentRow == selBeginRC.row && columnsBefore>=selBeginRC.column)) + && + (currentRow < selEndRC.row || + (currentRow == selEndRC.row && columnsBefore+charCol<=selEndRC.column))) { + if (!isSelection) { + isSelection = true; + } + if (!mReadonly &&(currentRow>editBeginRC.row || + columnsBefore >= editBeginRC.column)) { + painter.setPen(mSelectionForeground); + painter.fillRect(left,top,width,mRowHeight,mSelectionBackground); + } else { + painter.setPen(mInactiveSelectionForeground); + painter.fillRect(left,top,width,mRowHeight,mInactiveSelectionBackground); + } + } else { + if (isSelection) { + isSelection = false; + painter.setPen(mForeground); + } + } + painter.drawText(left,baseLine,ch); + left+= width; + columnsBefore += charCol; + } + } +} + +void QConsole::ensureCaretVisible() +{ + int caretRow = mContents.rows(); + if (caretRow < mTopRow) { + mTopRow = caretRow; + return; + } + if (caretRow >= mTopRow + mRowsInWindow) { + mTopRow = caretRow + 1 -mRowsInWindow; + } +} + +void QConsole::showCaret() +{ + if (mBlinkTimerId==0) + mBlinkTimerId = startTimer(500); +} + +void QConsole::hideCaret() +{ + if (mBlinkTimerId!=0) { + killTimer(mBlinkTimerId); + mBlinkTimerId = 0; + mBlinkStatus = 0; + updateCaret(); + } +} + +void QConsole::updateCaret() +{ + QRect rcCaret = getCaretRect(); + invalidateRect(rcCaret); +} + +LineChar QConsole::caretPos() +{ + QString lastLine = mContents.getLastLine(); + int line = std::max(mContents.lines()-1,0); + int charIndex = 0; + if (mCaretChar>=mCurrentEditableLine.length()) { + charIndex = lastLine.length(); + } else { + charIndex = lastLine.length()-mCurrentEditableLine.length()+mCaretChar; + } + return {charIndex,line}; +} + +RowColumn QConsole::caretRowColumn() +{ + return mContents.lineCharToRowColumn(caretPos()); +} + +QPoint QConsole::rowColumnToPixels(const RowColumn &rowColumn) +{ + /* + mTopRow is 1-based; rowColumn.row is 0-based + */ + int row =rowColumn.row+1 - mTopRow; + int col =rowColumn.column; + return QPoint(2+col*mColumnWidth, row*mRowHeight); +} + +QRect QConsole::getCaretRect() +{ + LineChar caret = caretPos(); + QChar caretChar = mContents.getChar(caret); + RowColumn caretRC = mContents.lineCharToRowColumn(caret); + QPoint caretPos = rowColumnToPixels(caretRC); + int caretWidth=mColumnWidth; + //qDebug()<<"caret"<value(); + invalidate(); +} + +void QConsole::contentsLayouted() +{ + updateScrollbars(); +} + +void QConsole::contentsRowsAdded(int rowCount) +{ + ensureCaretVisible(); + updateScrollbars(); +} + +void QConsole::contentsLastRowsRemoved(int rowCount) +{ + ensureCaretVisible(); + updateScrollbars(); +} + +void QConsole::contentsLastRowsChanged(int rowCount) +{ + ensureCaretVisible(); + invalidateRows(mContents.rows()-rowCount+1,mContents.rows()); +} + +void QConsole::scrollTimerHandler() + +{ + QPoint iMousePos; + int X, Y; + + iMousePos = QCursor::pos(); + iMousePos = mapFromGlobal(iMousePos); + RowColumn mousePosRC = pixelsToNearestRowColumn(iMousePos.x(),iMousePos.y()); + + if (mScrollDeltaY != 0) { + qDebug()<<"scroll timer"< 0) // scrolling down? + row+=mRowsInWindow - 1; + mousePosRC.row = row - 1; + qDebug()<button(); + int X=event->pos().x(); + int Y=event->pos().y(); + + QAbstractScrollArea::mousePressEvent(event); + + //fKbdHandler.ExecuteMouseDown(Self, Button, Shift, X, Y); + + if (button == Qt::LeftButton) { + setMouseTracking(true); + RowColumn mousePosRC = pixelsToNearestRowColumn(X,Y); + LineChar mousePos = mContents.rowColumnToLineChar(mousePosRC); + //I couldn't track down why, but sometimes (and definitely not all the time) + //the block positioning is lost. This makes sure that the block is + //maintained in case they started a drag operation on the block + int oldStartRow = mContents.lineCharToRowColumn(selectionBegin()).row+1; + int oldEndRow = mContents.lineCharToRowColumn(selectionEnd()).row+1; + invalidateRows(oldStartRow,oldEndRow); + mSelectionBegin = mousePos; + mSelectionEnd = mousePos; + } +} + +void QConsole::mouseReleaseEvent(QMouseEvent *event) +{ + QAbstractScrollArea::mouseReleaseEvent(event); + mScrollTimer->stop(); + setMouseTracking(false); + +} + +void QConsole::mouseMoveEvent(QMouseEvent *event) +{ + QAbstractScrollArea::mouseMoveEvent(event); + Qt::MouseButtons buttons = event->buttons(); + int X=event->pos().x(); + int Y=event->pos().y(); + + if ((buttons == Qt::LeftButton)) { + // should we begin scrolling? + computeScrollY(Y); + RowColumn mousePosRC = pixelsToNearestRowColumn(X, Y); + LineChar mousePos = mContents.rowColumnToLineChar(mousePosRC); + if (mScrollDeltaY == 0) { + int oldStartRow = mContents.lineCharToRowColumn(selectionBegin()).row+1; + int oldEndRow = mContents.lineCharToRowColumn(selectionEnd()).row+1; + invalidateRows(oldStartRow,oldEndRow); + mSelectionEnd = mousePos; + int row = mContents.lineCharToRowColumn(mSelectionEnd).row+1; + invalidateRows(row,row); + } + } +} + +void QConsole::keyPressEvent(QKeyEvent *event) +{ + switch(event->key()) { + case Qt::Key_Return: + event->accept(); + if (mReadonly) + return; + emit commandInput(mCommand); + if (mHistorySize>0) { + if (mCommandHistory.size()==mHistorySize) { + mCommandHistory.pop_front(); + mHistoryIndex--; + } + if (mCommandHistory.size()==0 || mHistoryIndex<0 || mHistoryIndex == mCommandHistory.size()-1) { + mHistoryIndex=mCommandHistory.size(); + } + mCommandHistory.append(mCommand); + } + mCommand=""; + addLine(""); + return; + case Qt::Key_Up: + event->accept(); + if (mHistorySize>0 && mHistoryIndex>0) { + mHistoryIndex--; + loadCommandFromHistory(); + } + return; + case Qt::Key_Down: + event->accept(); + if (mHistorySize>0 && mHistoryIndexaccept(); + if (mReadonly) + return; + if (mCaretChar>0 && mCaretChar<=mCurrentEditableLine.size()) { + setCaretChar(mCaretChar-1, !(event->modifiers() & Qt::ShiftModifier)); + } + return; + case Qt::Key_Right: + event->accept(); + if (mReadonly) + return; + if (mCaretCharmodifiers() & Qt::ShiftModifier)); + } + return; + case Qt::Key_Home: + event->accept(); + if (mReadonly) + return; + if (mCaretChar>0 && mCaretChar<=mCurrentEditableLine.size()) { + setCaretChar(0, !(event->modifiers() & Qt::ShiftModifier)); + } + return; + case Qt::Key_End: + event->accept(); + if (mReadonly) + return; + if (mCaretCharmodifiers() & Qt::ShiftModifier)); + } + return; + case Qt::Key_Backspace: + event->accept(); + if (mReadonly) + return; + if (!mCurrentEditableLine.isEmpty() && (mCaretChar-1)=0)) { + QString lastLine; + if (caretInSelection()) { + lastLine = removeSelection(); + } else { + lastLine = mContents.getLastLine(); + int len=mCurrentEditableLine.length(); + mCaretChar--; + mCommand.remove(mCommand.length()-len+mCaretChar,1); + lastLine.remove(lastLine.length()-len+mCaretChar,1); + mCurrentEditableLine.remove(mCaretChar,1); + } + mContents.changeLastLine(lastLine); + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + } else { + if (hasSelection()) { + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + invalidate(); + } + } + return; + case Qt::Key_Delete: + event->accept(); + if (mReadonly) + return; + if (!mCurrentEditableLine.isEmpty() && (mCaretChar)=0)) { + QString lastLine; + if (caretInSelection()) { + lastLine = removeSelection(); + } else { + lastLine = mContents.getLastLine(); + int len=mCurrentEditableLine.length(); + mCommand.remove(mCommand.length()-len+mCaretChar,1); + lastLine.remove(lastLine.length()-len+mCaretChar,1); + mCurrentEditableLine.remove(mCaretChar,1); + } + mContents.changeLastLine(lastLine); + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + } else { + if (hasSelection()) { + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + invalidate(); + } + } + return; + default: + if (!event->text().isEmpty()) { + event->accept(); + if (mReadonly) + return; + textInputed(event->text()); + return; + } + } + QAbstractScrollArea::keyPressEvent(event); +} + +void QConsole::focusInEvent(QFocusEvent *event) +{ + showCaret(); +} + +void QConsole::focusOutEvent(QFocusEvent *event) +{ + hideCaret(); +} + +void QConsole::paintEvent(QPaintEvent *event) +{ + if (mRowHeight==0) + return; + // Now paint everything while the caret is hidden. + QPainter painter(viewport()); + //Get the invalidated rect. + QRect rcClip = event->rect(); + QRect rcCaret= getCaretRect(); + if (rcCaret == rcClip) { + // only update caret + painter.drawImage(rcCaret,*mContentImage,rcCaret); + } else { + int nL1, nL2; + // Compute the invalid area in lines + // lines + nL1 = std::min(std::max(mTopRow + rcClip.top() / mRowHeight, mTopRow), maxScrollHeight() + mRowsInWindow - 1 ); + nL2 = std::min(std::max(mTopRow + (rcClip.bottom() + mRowHeight - 1) / mRowHeight, 1), maxScrollHeight() + mRowsInWindow - 1); + QPainter cachePainter(mContentImage.get()); + cachePainter.setFont(font()); + paintRows(cachePainter,nL1,nL2); + painter.drawImage(rcClip,*mContentImage,rcClip); + } + paintCaret(painter, rcCaret); +} + +void QConsole::paintCaret(QPainter &painter, const QRect rcClip) +{ + if (mBlinkStatus!=1) + return; + painter.setClipRect(rcClip); + ConsoleCaretType ct = ConsoleCaretType::ctHorizontalLine; + QColor caretColor = mForeground; + switch(ct) { + case ConsoleCaretType::ctVerticalLine: + painter.fillRect(rcClip.left()+1,rcClip.top(),rcClip.left()+2,rcClip.bottom(),caretColor); + break; + case ConsoleCaretType::ctHorizontalLine: + painter.fillRect(rcClip.left(),rcClip.bottom()-2,rcClip.right(),rcClip.bottom()-1,caretColor); + break; + case ConsoleCaretType::ctBlock: + painter.fillRect(rcClip, caretColor); + break; + case ConsoleCaretType::ctHalfBlock: + QRect rc=rcClip; + rc.setTop(rcClip.top()+rcClip.height() / 2); + painter.fillRect(rcClip, caretColor); + break; + } +} + +void QConsole::textInputed(const QString &text) +{ + if (mContents.rows()<=0) { + mContents.addLine(""); + } + QString lastLine; + if (caretInSelection()) { + lastLine = removeSelection(); + } else { + lastLine = mContents.getLastLine(); + } + if (mCaretChar>=mCurrentEditableLine.size()) { + mCommand += text; + mCurrentEditableLine += text; + mCaretChar=mCurrentEditableLine.size(); + mContents.changeLastLine(lastLine+text); + } else { + int len=mCurrentEditableLine.length(); + mCommand.insert(mCommand.length()-len+mCaretChar,text); + lastLine.insert(lastLine.length()-len+mCaretChar,text); + mCurrentEditableLine.insert(mCaretChar,text); + mContents.changeLastLine(lastLine); + mCaretChar+=text.length(); + } + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); +} + +void QConsole::loadCommandFromHistory() +{ + if (mHistorySize<=0) + return; + mCommand = mCommandHistory[mHistoryIndex]; + QString lastLine = mContents.getLastLine(); + int len=mCurrentEditableLine.length(); + lastLine.remove(lastLine.length()-len,INT_MAX); + mCurrentEditableLine=mCommand; + mCaretChar = mCurrentEditableLine.length(); + mSelectionBegin = caretPos(); + mSelectionEnd = caretPos(); + mContents.changeLastLine(lastLine + mCommand); +} + +LineChar QConsole::selectionBegin() +{ + if (mSelectionBegin.line < mSelectionEnd.line || + (mSelectionBegin.line == mSelectionEnd.line && + mSelectionBegin.ch < mSelectionEnd.ch)) + return mSelectionBegin; + return mSelectionEnd; +} + +LineChar QConsole::selectionEnd() +{ + if (mSelectionBegin.line < mSelectionEnd.line || + (mSelectionBegin.line == mSelectionEnd.line && + mSelectionBegin.ch < mSelectionEnd.ch)) + return mSelectionEnd; + return mSelectionBegin; +} + +void QConsole::setCaretChar(int newCaretChar, bool resetSelection) +{ + RowColumn oldPosRC = caretRowColumn(); + RowColumn oldSelBegin = mContents.lineCharToRowColumn(selectionBegin()); + RowColumn oldSelEnd = mContents.lineCharToRowColumn(selectionEnd()); + int oldStartRow = std::min(std::min(oldPosRC.row,oldSelBegin.row),oldSelEnd.row); + int oldEndRow = std::max(std::max(oldPosRC.row,oldSelBegin.row),oldSelEnd.row); + mCaretChar = newCaretChar; + LineChar newPos = caretPos(); + RowColumn newPosRC = mContents.lineCharToRowColumn(newPos); + if (resetSelection) + mSelectionBegin = newPos; + mSelectionEnd = newPos; + + int startRow = std::min(newPosRC.row, oldStartRow)+1; + int endRow = std::max(newPosRC.row, oldEndRow)+1; + invalidateRows(startRow, endRow); +} + +bool QConsole::caretInSelection() +{ + if (!hasSelection()) + return false; + LineChar selBegin = selectionBegin(); + LineChar selEnd = selectionEnd(); + QString lastline = mContents.getLastLine(); + int editBeginChar = lastline.length() - mCurrentEditableLine.length(); + if (selEnd.line == mContents.lines()-1 && selEnd.ch > editBeginChar ) { + return true; + } + return false; +} + +QString QConsole::removeSelection() +{ + + QString lastLine = mContents.getLastLine(); + int len=mCurrentEditableLine.length(); + LineChar selBegin = selectionBegin(); + LineChar selEnd = selectionEnd(); + int selLen = selEnd.ch -selBegin.ch; + int ch = selBegin.ch -(lastLine.length()-len); + if (selBegin.line < mContents.lines()-1) { + mCaretChar = 0; + selLen = selEnd.ch - (lastLine.length()-len); + } else if (ch<0) { + mCaretChar = 0; + selLen = selLen + ch; + } else { + mCaretChar=ch; + } + mCommand.remove(mCommand.length()-len+mCaretChar,selLen); + lastLine.remove(lastLine.length()-len+mCaretChar,selLen); + mCurrentEditableLine.remove(mCaretChar,selLen); + return lastLine; +} + +bool QConsole::hasSelection() +{ + return (mSelectionBegin.line != mSelectionEnd.line) + || (mSelectionBegin.ch != mSelectionEnd.ch); +} + +int QConsole::computeScrollY(int Y) +{ + QRect iScrollBounds = viewport()->rect(); + if (Y < iScrollBounds.top()) + mScrollDeltaY = (Y - iScrollBounds.top()) / mRowHeight - 1; + else if (Y >= iScrollBounds.bottom()) + mScrollDeltaY = (Y - iScrollBounds.bottom()) / mRowHeight + 1; + else + mScrollDeltaY = 0; + + if (mScrollDeltaY) + mScrollTimer->start(); + return mScrollDeltaY; +} + +RowColumn QConsole::pixelsToNearestRowColumn(int x, int y) +{ + // Result is in display coordinates + // don't return a partially visible last line + if (y >= mRowsInWindow * mRowHeight) { + y = mRowsInWindow * mRowHeight - 1; + if (y < 0) + y = 0; + } + return { + .column = std::max(0, (x - 2) / mColumnWidth), + .row = mTopRow + (y / mRowHeight)-1 + }; +} + + +void QConsole::fontChanged() +{ + recalcCharExtent(); + sizeOrFontChanged(true); +} + +bool QConsole::event(QEvent *event) +{ + switch(event->type()) { + case QEvent::FontChange: + fontChanged(); + break; + case QEvent::PaletteChange: + mBackground = palette().color(QPalette::Base); + mForeground = palette().color(QPalette::Text); + mSelectionBackground = palette().color(QPalette::Highlight); + mSelectionForeground = palette().color(QPalette::HighlightedText); + mInactiveSelectionBackground = palette().color(QPalette::Inactive,QPalette::Highlight); + mInactiveSelectionForeground = palette().color(QPalette::Inactive,QPalette::HighlightedText); + break; + } + return QAbstractScrollArea::event(event); +} + +void QConsole::resizeEvent(QResizeEvent *) +{ + //resize the cache image + std::shared_ptr image = std::make_shared(clientWidth(),clientHeight(), + QImage::Format_ARGB32); + if (mContentImage) { + QRect newRect = image->rect().intersected(mContentImage->rect()); + + QPainter painter(image.get()); + painter.fillRect(viewport()->rect(),mBackground); +// painter.drawImage(newRect,*mContentImage); + } + + mContentImage = image; + + sizeOrFontChanged(false); +} + +void QConsole::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == mBlinkTimerId) { + mBlinkStatus = 1- mBlinkStatus; + updateCaret(); + } +} + +void QConsole::inputMethodEvent(QInputMethodEvent *event) +{ + if (mReadonly) + return; + QString s=event->commitString(); + if (!s.isEmpty()) + textInputed(s); +} + +void QConsole::wheelEvent(QWheelEvent *event) +{ + if (event->angleDelta().y()>0) { + verticalScrollBar()->setValue(verticalScrollBar()->value()-1); + event->accept(); + return; + } else if (event->angleDelta().y()<0) { + verticalScrollBar()->setValue(verticalScrollBar()->value()+1); + event->accept(); + return; + } +} + +int ConsoleLines::rows() const +{ + return mRows; +} + +int ConsoleLines::lines() const +{ + return mLines.count(); +} + +void ConsoleLines::layout() +{ + if (!mConsole) + return; + if (mLayouting) { + mNeedRelayout = true; + return; + } + mLayouting = true; + mNeedRelayout = false; + emit layoutStarted(); + mRows = 0; + bool forceUpdate = (mOldTabSize!=mConsole->tabSize()); + for (PConsoleLine consoleLine: mLines) { + if (forceUpdate || consoleLine->maxColumns > mConsole->columnsPerRow()) { + consoleLine->maxColumns = breakLine(consoleLine->text,consoleLine->fragments); + } + mRows+=consoleLine->fragments.count(); + } + emit layoutFinished(); + mLayouting = false; + if (mNeedRelayout) + emit needRelayout(); +} + +ConsoleLines::ConsoleLines(QConsole *console) +{ + mConsole = console; + mRows = 0; + mLayouting = false; + mNeedRelayout = false; + mOldTabSize = -1; + mMaxLines = 1000; + connect(this,&ConsoleLines::needRelayout,this,&ConsoleLines::layout); +} + +void ConsoleLines::addLine(const QString &line) +{ + PConsoleLine consoleLine=std::make_shared(); + consoleLine->text = line; + consoleLine->maxColumns = breakLine(line,consoleLine->fragments); + if (mLines.count()fragments.count(); + emit rowsAdded(consoleLine->fragments.count()); + } else { + PConsoleLine firstLine = mLines[0]; + mLines.pop_front(); + mRows -= firstLine->fragments.count(); + mLines.append(consoleLine); + mRows += consoleLine->fragments.count(); + emit layoutStarted(); + emit layoutFinished(); + } +} + +void ConsoleLines::RemoveLastLine() +{ + if (mLines.count()<=0) + return; + PConsoleLine consoleLine = mLines[mLines.count()-1]; + mLines.pop_back(); + mRows -= consoleLine->fragments.count(); + emit lastRowsRemoved(consoleLine->fragments.count()); +} + +void ConsoleLines::changeLastLine(const QString &newLine) +{ + if (mLines.count()<=0) { + return; + } + PConsoleLine consoleLine = mLines[mLines.count()-1]; + int oldRows = consoleLine->fragments.count(); + consoleLine->text = newLine; + breakLine(newLine,consoleLine->fragments); + int newRows = consoleLine->fragments.count(); + if (newRows == oldRows) { + emit lastRowsChanged(oldRows); + return ; + } else { + mRows -= oldRows; + mRows += newRows; + emit layoutStarted(); + emit layoutFinished(); + } +} + +QString ConsoleLines::getLastLine() +{ + if (mLines.count()<=0) + return ""; + return mLines[mLines.count()-1]->text; +} + +QString ConsoleLines::getLine(int line) +{ + if (line>=0 && line < mLines.count()) { + return mLines[line]->text; + } + return ""; +} + +QChar ConsoleLines::getChar(int line, int ch) +{ + QString s = getLine(line); + if (ch>=0 && chmRows) + return QStringList(); + if (startRow > endRow) + return QStringList(); + QStringList lst; + int row = 0; + for (PConsoleLine line:mLines) { + for (const QString& s:line->fragments) { + row+=1; + if (row>endRow) { + return lst; + } + if (row>=startRow) { + lst.append(s); + } + } + } + return lst; +} + +LineChar ConsoleLines::rowColumnToLineChar(const RowColumn &rowColumn) +{ + return rowColumnToLineChar(rowColumn.row,rowColumn.column); +} + +LineChar ConsoleLines::rowColumnToLineChar(int row, int column) +{ + LineChar result{column,mLines.size()-1}; + int rows=0; + for (int i=0;i= rows && rowfragments.size()) { + int r=row - rows; + QString fragment = line->fragments[r]; + int columnsBefore = 0; + for (int j=0;jcharColumns(ch, columnsBefore); + if (column>=columnsBefore && columnfragments.size(); + } + return result; +} + +RowColumn ConsoleLines::lineCharToRowColumn(const LineChar &lineChar) +{ + return lineCharToRowColumn(lineChar.line,lineChar.ch); +} + +RowColumn ConsoleLines::lineCharToRowColumn(int line, int ch) +{ + RowColumn result{ch,std::max(0,mRows-1)}; + int rowsBefore = 0; + if (line>=0 && line < mLines.size()) { + for (int i=0;ifragments.size(); + rowsBefore += rows; + } + PConsoleLine consoleLine = mLines[line]; + int charsBefore = 0; + for (int r=0;rfragments.size();r++) { + int chars = consoleLine->fragments[r].size(); + if (r==consoleLine->fragments.size()-1 || (ch>=charsBefore && chfragments[r]; + int columnsBefore = 0; + int len = std::min(ch-charsBefore,fragment.size()); + for (int j=0;jcharColumns(ch,columnsBefore); + columnsBefore += charColumns; + } + result.column=columnsBefore; + result.row = rowsBefore + r; + break; + } + charsBefore += chars; + } + } + return result; +} + +bool ConsoleLines::layouting() const +{ + return mLayouting; +} + +int ConsoleLines::breakLine(const QString &line, QStringList &fragments) +{ + fragments.clear(); + QString s = ""; + int maxColLen = 0; + int columnsBefore = 0; + for (QChar ch:line) { + int charColumn = mConsole->charColumns(ch,columnsBefore); + if (charColumn + columnsBefore > mConsole->columnsPerRow()) { + if (ch == '\t') { + if (columnsBefore != mConsole->columnsPerRow()) { + charColumn = 0; + } else + charColumn = mConsole->tabSize(); + } + fragments.append(s); + if (columnsBefore > maxColLen) { + maxColLen = columnsBefore; + } + s = ""; + columnsBefore = 0; + } + if (charColumn > 0) { + columnsBefore += charColumn; + s += ch; + } + } + if (fragments.count() == 0 || !s.isEmpty()) { + fragments.append(s); + if (columnsBefore > maxColLen) { + maxColLen = columnsBefore; + } + } + return maxColLen; +} + +int ConsoleLines::getMaxLines() const +{ + return mMaxLines; +} + +int ConsoleLines::maxLines() const +{ + return mMaxLines; +} + +void ConsoleLines::setMaxLines(int maxLines) +{ + mMaxLines = maxLines; + if (mMaxLines > 0) { + while (mLines.count()>mMaxLines) { + mLines.pop_front(); + } + } +} diff --git a/RedPandaIDE/widgets/qconsole.h b/RedPandaIDE/widgets/qconsole.h new file mode 100644 index 00000000..fd00212e --- /dev/null +++ b/RedPandaIDE/widgets/qconsole.h @@ -0,0 +1,217 @@ +#ifndef QCONSOLE_H +#define QCONSOLE_H + +#include +#include + +struct ConsoleLine { + QString text; + QStringList fragments; + int maxColumns; +}; + +enum class ConsoleCaretType { + ctVerticalLine,ctHorizontalLine,ctBlock,ctHalfBlock +}; + +using PConsoleLine = std::shared_ptr; + +using ConsoleLineList = QVector; + +/** + * @brief The RowColumn struct + * column and row are 0-based + */ +struct RowColumn { + int column; + int row; +}; + +/** + * @brief The LineChar struct + * line and ch are 0-based + */ +struct LineChar { + int ch; + int line; +}; + +class QConsole; +class ConsoleLines : public QObject{ + Q_OBJECT +public: + explicit ConsoleLines(QConsole* console); + void addLine(const QString& line); + void RemoveLastLine(); + void changeLastLine(const QString& newLine); + QString getLastLine(); + QString getLine(int line); + QChar getChar(int line,int ch); + QChar getChar(const LineChar& lineChar); + /** + * @brief getRows + * @param startRow 1-based + * @param endRow 1-based + * @return + */ + QStringList getRows(int startRow, int endRow); + + LineChar rowColumnToLineChar(const RowColumn& rowColumn); + LineChar rowColumnToLineChar(int row ,int column); + RowColumn lineCharToRowColumn(const LineChar& lineChar); + RowColumn lineCharToRowColumn(int line, int ch); + int rows() const; + int lines() const; + bool layouting() const; + int maxLines() const; + void setMaxLines(int maxLines); + + int getMaxLines() const; +public slots: + void layout(); +signals: + void layoutStarted(); + void layoutFinished(); + void needRelayout(); + void rowsAdded(int rowCount); + void lastRowsRemoved(int rowCount); + void lastRowsChanged(int rowCount); +private: + int breakLine(const QString& line, QStringList& fragments); +private: + ConsoleLineList mLines; + int mRows; + bool mLayouting; + bool mNeedRelayout; + int mOldTabSize; + QConsole* mConsole; + int mMaxLines; +}; + + +class QConsole : public QAbstractScrollArea +{ + Q_OBJECT +public: + explicit QConsole(QWidget* parent = nullptr); + int historySize() const; + void setHistorySize(int historySize); + + int tabSize() const; + + int columnsPerRow() const; + + int rowsInWindow() const; + int charColumns(QChar ch, int columnsBefore) const; + + void invalidate(); + void invalidateRows(int startRow,int endRow); + void invalidateRect(const QRect& rect); + + void addLine(const QString& line); + void removeLastLine(); + void changeLastLine(const QString& line); + QString getLastLine(); + +signals: + void commandInput(const QString& command); +private: + ConsoleLines mContents; + QStringList mCommandHistory; + int mHistorySize; + int mHistoryIndex; + QString mCommand; + QString mCurrentEditableLine; +// bool mIndex; + int mRowHeight; + int mTopRow; // 1-based + int mRowsInWindow; + int mColumnsPerRow; + int mColumnWidth; + bool mReadonly; + LineChar mSelectionBegin; + LineChar mSelectionEnd; + int mCaretChar; + QColor mBackground; + QColor mForeground; + QColor mSelectionBackground; + QColor mSelectionForeground; + QColor mInactiveSelectionBackground; + QColor mInactiveSelectionForeground; + int mTabSize; + std::shared_ptr mContentImage; + int mBlinkTimerId; + int mBlinkStatus; + QTimer* mScrollTimer; + int mScrollDeltaY; +private: + void fontChanged(); + void recalcCharExtent(); + void sizeOrFontChanged(bool bFont); + int clientWidth(); + int clientHeight(); + /** + * @brief setTopRow + * @param value 1-based + */ + void setTopRow(int value); + int maxScrollHeight(); + void updateScrollbars(); + void paintRows(QPainter& painter, int row1,int row2); + void ensureCaretVisible(); + void showCaret(); + void hideCaret(); + void updateCaret(); + LineChar caretPos(); + RowColumn caretRowColumn(); + QPoint rowColumnToPixels(const RowColumn& rowColumn); + QRect getCaretRect(); + void paintCaret(QPainter &painter, const QRect rcClip); + void textInputed(const QString& text); + void loadCommandFromHistory(); + LineChar selectionBegin(); + LineChar selectionEnd(); + void setCaretChar(int newCaretChar, bool resetSelection); + bool caretInSelection(); + QString removeSelection(); + bool hasSelection(); + int computeScrollY(int Y); + RowColumn pixelsToNearestRowColumn(int x,int y); + + +private slots: + void doScrolled(); + void contentsLayouted(); + void contentsRowsAdded(int rowCount); + void contentsLastRowsRemoved(int rowCount); + void contentsLastRowsChanged(int rowCount); + void scrollTimerHandler(); + + // QWidget interface +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void paintEvent(QPaintEvent *event) override; + bool event(QEvent *event) override; + + + // QWidget interface +protected: + void resizeEvent(QResizeEvent *event) override; + + // QObject interface +protected: + void timerEvent(QTimerEvent *event) override; + + // QWidget interface +protected: + void inputMethodEvent(QInputMethodEvent *event) override; + void wheelEvent(QWheelEvent *event) override; +}; + + +#endif // QCONSOLE_H