File indexing completed on 2024-05-19 13:33:12

0001 /***************************************************************************
0002  *   Copyright (C) 2005 by David Saxton                                    *
0003  *   david@bluehaze.org                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include <KXMLGUIClient>
0012 
0013 #include "asmformatter.h"
0014 #include "config.h"
0015 #include "filemetainfo.h"
0016 #include "gpsimprocessor.h"
0017 #include "ktechlab.h"
0018 #include "symbolviewer.h"
0019 #include "textdocument.h"
0020 #include "textview.h"
0021 #include "variablelabel.h"
0022 #include "viewiface.h"
0023 
0024 //#include <ktexteditor/editinterface.h> // ?
0025 #include <KTextEditor/TextHintInterface>
0026 
0027 // #include "kateview.h"
0028 #include <KActionCollection>
0029 #include <KLocalizedString>
0030 // #include <k3popupmenu.h>
0031 #include <KToolBarPopupAction>
0032 #include <KXMLGUIFactory>
0033 
0034 #include <QActionGroup>
0035 #include <QApplication>
0036 #include <QCursor>
0037 #include <QVBoxLayout>
0038 //#include <qobjectlist.h>
0039 #include <QClipboard>
0040 #include <QFocusEvent>
0041 #include <QMenu>
0042 #include <QStandardPaths>
0043 #include <QTimer>
0044 
0045 #include <ktechlab_debug.h>
0046 
0047 // BEGIN class TextView
0048 TextView::TextView(TextDocument *textDocument, ViewContainer *viewContainer, uint viewAreaId)
0049     : View(textDocument, viewContainer, viewAreaId)
0050 {
0051     m_view = textDocument->createKateView(this);
0052     m_view->insertChildClient(this);
0053 
0054     KActionCollection *ac = actionCollection();
0055 
0056     // BEGIN Convert To * Actions
0057     // KToolBarPopupAction * pa = new KToolBarPopupAction( i18n("Convert to"), "fork", 0, 0, 0, ac, "program_convert" );
0058     KToolBarPopupAction *pa = new KToolBarPopupAction(QIcon::fromTheme("fork"), i18n("Convert To"), ac);
0059     pa->setObjectName("program_convert");
0060     pa->setDelayed(false);
0061     ac->addAction(pa->objectName(), pa);
0062 
0063     QMenu *m = pa->menu();
0064 
0065     m->setTitle(i18n("Convert To"));
0066     QAction *actToMicrobe = m->addAction(QIcon::fromTheme("convert_to_microbe"), i18n("Microbe"));
0067     actToMicrobe->setData(TextDocument::MicrobeOutput);
0068     m->addAction(QIcon::fromTheme("convert_to_assembly"), i18n("Assembly"))->setData(TextDocument::AssemblyOutput);
0069     m->addAction(QIcon::fromTheme("convert_to_hex"), i18n("Hex"))->setData(TextDocument::HexOutput);
0070     m->addAction(QIcon::fromTheme("convert_to_pic"), i18n("PIC (upload)"))->setData(TextDocument::PICOutput);
0071     connect(m, &QMenu::triggered, textDocument, &TextDocument::slotConvertTo);
0072 
0073     // m->setItemEnabled( TextDocument::MicrobeOutput, false ); // 2018.12.02
0074     actToMicrobe->setEnabled(false);
0075     ac->addAction(pa->objectName(), pa);
0076     // END Convert To * Actions
0077 
0078     {
0079         // new QAction( i18n("Format Assembly Code"), "", Qt::Key_F12, textDocument, SLOT(formatAssembly()), ac, "format_asm" );
0080         QAction *action = new QAction(i18n("Format Assembly Code"), ac);
0081         action->setObjectName("format_asm");
0082         action->setShortcut(Qt::Key_F12);
0083         connect(action, &QAction::triggered, textDocument, &TextDocument::formatAssembly);
0084         ac->addAction(action->objectName(), action);
0085     }
0086 
0087 #ifndef NO_GPSIM
0088     // BEGIN Debug Actions
0089     {
0090         // new QAction( i18n("Set &Breakpoint"), 0, 0, this, SLOT(toggleBreakpoint()), ac, "debug_toggle_breakpoint" );
0091         QAction *action = new QAction(i18n("Set &Breakpoint"), ac);
0092         action->setObjectName("debug_toggle_breakpoint");
0093         connect(action, &QAction::triggered, this, &TextView::toggleBreakpoint);
0094         ac->addAction(action->objectName(), action);
0095     }
0096     {
0097         // new QAction( i18n("Run"), "debug-run", 0, textDocument, SLOT(debugRun()), ac, "debug_run" );
0098         QAction *action = new QAction(QIcon::fromTheme("debug-run"), i18n("Run"), ac);
0099         action->setObjectName("debug_run");
0100         connect(action, &QAction::triggered, textDocument, &TextDocument::debugRun);
0101         ac->addAction(action->objectName(), action);
0102     }
0103     {
0104         // new QAction( i18n("Interrupt"), "media-playback-pause", 0, textDocument, SLOT(debugInterrupt()), ac, "debug_interrupt" );
0105         QAction *action = new QAction(QIcon::fromTheme("media-playback-pause"), i18n("Interrupt"), ac);
0106         action->setObjectName("debug_interrupt");
0107         connect(action, &QAction::triggered, textDocument, &TextDocument::debugInterrupt);
0108         ac->addAction(action->objectName(), action);
0109     }
0110     {
0111         // new QAction( i18n("Stop"), "process-stop", 0, textDocument, SLOT(debugStop()), ac, "debug_stop" );
0112         QAction *action = new QAction(QIcon::fromTheme("process-stop"), i18n("Stop"), ac);
0113         action->setObjectName("debug_stop");
0114         connect(action, &QAction::triggered, textDocument, &TextDocument::debugStop);
0115         ac->addAction(action->objectName(), action);
0116     }
0117     {
0118         // new QAction( i18n("Step"), "debug-step-instruction", Qt::CTRL|Qt::ALT|Qt::Key_Right, textDocument, SLOT(debugStep()), ac, "debug_step" );
0119         QAction *action = new QAction(QIcon::fromTheme("debug-step-instruction"), i18n("Step"), ac);
0120         action->setObjectName("debug_step");
0121         action->setShortcut(Qt::CTRL | Qt::ALT | Qt::Key_Right);
0122         connect(action, &QAction::triggered, textDocument, &TextDocument::debugStep);
0123         ac->addAction(action->objectName(), action);
0124     }
0125     {
0126         // new QAction( i18n("Step Over"), "debug-step-over", 0, textDocument, SLOT(debugStepOver()), ac, "debug_step_over" );
0127         QAction *action = new QAction(QIcon::fromTheme("debug-step-over"), i18n("Step Over"), ac);
0128         action->setObjectName("debug_step_over");
0129         connect(action, &QAction::triggered, textDocument, &TextDocument::debugStepOver);
0130         ac->addAction(action->objectName(), action);
0131     }
0132     {
0133         // new QAction( i18n("Step Out"), "debug-step-out", 0, textDocument, SLOT(debugStepOut()), ac, "debug_step_out" );
0134         QAction *action = new QAction(QIcon::fromTheme("debug-step-out"), i18n("Step Out"), ac);
0135         action->setObjectName("debug_step_out");
0136         connect(action, &QAction::triggered, textDocument, &TextDocument::debugStepOut);
0137         ac->addAction(action->objectName(), action);
0138     }
0139     // END Debug Actions
0140 #endif
0141 
0142     setXMLFile("ktechlabtextui.rc");
0143 
0144     // m_view->setXMLFile("ktechlabkateui.rc") is protected, replace it with code below
0145     {
0146         // see https://github.com/KDE/kxmlgui/blob/master/src/kxmlguiclient.cpp#L219
0147         QString _file = "ktechlabkateui.rc";
0148         QStringList allFiles;
0149 
0150         const QString filter = componentName() + QLatin1Char('/') + _file;
0151 
0152         // files on filesystem
0153         allFiles << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("kxmlgui5/") + filter); // KF >= 5.1
0154 
0155         // KF >= 5.4 (resource file)
0156         const QString qrcFile(QLatin1String(":/kxmlgui5/") + filter);
0157         if (QFile::exists(qrcFile)) {
0158             allFiles << qrcFile;
0159         }
0160 
0161         // then compat locations
0162         const QStringList compatFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, filter) + // kdelibs4, KF 5.0
0163             QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, _file); // kdelibs4, KF 5.0, caller passes component name
0164 
0165         if (allFiles.isEmpty() && !compatFiles.isEmpty()) {
0166             qCWarning(KTL_LOG) << "KXMLGUI file found at deprecated location" << compatFiles
0167                                      << "-- please use ${KDE_INSTALL_KXMLGUI5DIR} to install this file instead.";
0168         }
0169         allFiles += compatFiles;
0170 
0171         QString doc;
0172         if (!allFiles.isEmpty()) {
0173             QString fileFound = findMostRecentXMLFile(allFiles, doc);
0174             qCInfo(KTL_LOG) << "KXMLGUI file found at : " << fileFound;
0175             m_view->replaceXMLFile(fileFound, QString(), false);
0176         } else {
0177             qCWarning(KTL_LOG) << "KXMLGUI not found for " << _file;
0178         }
0179     }
0180 
0181     m_savedCursorLine = 0;
0182     m_savedCursorColumn = 0;
0183     m_pViewIface = new TextViewIface(this);
0184 
0185     setAcceptDrops(true);
0186 
0187     // m_view->installPopup( static_cast<Q3PopupMenu*>( KTechlab::self()->factory()->container( "ktexteditor_popup", KTechlab::self() ) ) );
0188     m_view->setContextMenu(static_cast<QMenu *>(KTechlab::self()->factory()->container("ktexteditor_popup", KTechlab::self())));
0189 
0190     // TODO this ought to not use internal widgets of the ktexteditor view,
0191     //      and only use KTextEditor::TextHintInterface for the hint instead
0192     //      of the own popup logic
0193     // QWidget *internalView = m_view->findChild<QWidget *>("KateViewInternal");
0194 
0195     connect(m_view, &KTextEditor::View::cursorPositionChanged, this, &TextView::slotCursorPositionChanged);
0196     connect(m_view, &KTextEditor::View::selectionChanged, this, &TextView::slotSelectionmChanged);
0197 
0198     // setFocusWidget(internalView);
0199     connect(this, &TextView::focused, this, &TextView::gotFocus);
0200 
0201     m_layout->insertWidget(0, m_view);
0202 
0203     slotCursorPositionChanged();
0204     slotInitDebugActions();
0205     initCodeActions();
0206 
0207 #ifndef NO_GPSIM
0208     m_pTextViewLabel = new VariableLabel(this);
0209     m_pTextViewLabel->hide();
0210 
0211     TextViewEventFilter *eventFilter = new TextViewEventFilter(this);
0212     connect(eventFilter, &TextViewEventFilter::wordHoveredOver, this, &TextView::slotWordHoveredOver);
0213     connect(eventFilter, &TextViewEventFilter::wordUnhovered, this, &TextView::slotWordUnhovered);
0214 
0215     // internalView->installEventFilter(eventFilter);
0216 #endif
0217 
0218     // TODO HACK disable some actions which collide with ktechlab's actions.
0219     //  the proper solution would be to move the actions from KTechLab object level to document level for
0220     //  all types of documents
0221     for (QAction *act : actionCollection()->actions()) {
0222         qCDebug(KTL_LOG) << "act: " << act->text() << " shortcut " << act->shortcut() << ":" << act;
0223 
0224         if (((act->objectName()) == QLatin1String("file_save")) || ((act->objectName()) == QLatin1String("file_save_as")) || ((act->objectName()) == QLatin1String("file_print")) || ((act->objectName()) == QLatin1String("edit_undo")) ||
0225             ((act->objectName()) == QLatin1String("edit_redo")) || ((act->objectName()) == QLatin1String("edit_cut")) || ((act->objectName()) == QLatin1String("edit_copy")) || ((act->objectName()) == QLatin1String("edit_paste"))) {
0226             act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0227             // act->setShortcutConfigurable(true);
0228             act->setShortcut(Qt::Key_unknown);
0229             qCDebug(KTL_LOG) << "action " << act << " disabled";
0230         }
0231     }
0232 }
0233 
0234 TextView::~TextView()
0235 {
0236     if (KTechlab::self()) {
0237         // 2017.01.09: do not crash on document close. factory has its clients removed in TextDocument::~TextDocument()
0238         // if ( KXMLGUIFactory * f = m_view->factory() )
0239         //  f->removeClient( m_view );
0240         KTechlab::self()->addNoRemoveGUIClient(m_view);
0241     }
0242 
0243     delete m_pViewIface;
0244 }
0245 
0246 bool TextView::closeView()
0247 {
0248     if (textDocument()) {
0249         const QUrl url = textDocument()->url();
0250         if (!url.isEmpty())
0251             fileMetaInfo()->grabMetaInfo(url, this);
0252     }
0253 
0254     bool doClose = View::closeView();
0255     if (doClose)
0256         KTechlab::self()->factory()->removeClient(m_view);
0257     return View::closeView();
0258 }
0259 
0260 bool TextView::gotoLine(const int line)
0261 {
0262     // return m_view->setCursorPosition( line, 0/*m_view->cursorColumn()*/ );
0263     return m_view->setCursorPosition(KTextEditor::Cursor(line, 0 /*m_view->cursorColumn()*/));
0264 }
0265 
0266 TextDocument *TextView::textDocument() const
0267 {
0268     return static_cast<TextDocument *>(document());
0269 }
0270 void TextView::undo()
0271 {
0272     qCDebug(KTL_LOG);
0273     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
0274     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
0275     QAction *action = actionByName("edit_undo");
0276     if (action) {
0277         action->trigger();
0278         return;
0279     }
0280     qCWarning(KTL_LOG) << "no edit_undo action in text view! no action taken";
0281 }
0282 void TextView::redo()
0283 {
0284     qCDebug(KTL_LOG);
0285     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
0286     QAction *action = actionByName("edit_redo");
0287     if (action) {
0288         action->trigger();
0289         return;
0290     }
0291     qCWarning(KTL_LOG) << "no edit_redo action in text view! no action taken";
0292 }
0293 
0294 void TextView::cut()
0295 {
0296     // m_view-> cut();
0297     if (!m_view->selection())
0298         return;
0299     QClipboard *clipboard = QApplication::clipboard();
0300     clipboard->setText(m_view->document()->text(m_view->selectionRange()));
0301     m_view->document()->removeText(m_view->selectionRange());
0302 }
0303 
0304 void TextView::copy()
0305 {
0306     // m_view->copy();
0307     if (!m_view->selection())
0308         return;
0309     QClipboard *clipboard = QApplication::clipboard();
0310     clipboard->setText(m_view->document()->text(m_view->selectionRange()));
0311 }
0312 
0313 void TextView::paste()
0314 {
0315     // m_view->paste();
0316     QClipboard *clipboard = QApplication::clipboard();
0317     m_view->document()->insertText(m_view->cursorPosition(), clipboard->text());
0318 }
0319 
0320 void TextView::disableActions()
0321 {
0322     QMenu *tb = (dynamic_cast<KToolBarPopupAction *>(actionByName("program_convert")))->menu();
0323 
0324     const QList<QAction *> actions = tb->actions();
0325     for (QAction *a : actions) {
0326         switch (a->data().toInt()) {
0327         case TextDocument::AssemblyOutput:
0328         case TextDocument::HexOutput:
0329         case TextDocument::PICOutput:
0330             a->setEnabled(false);
0331             break;
0332         default:
0333             qCDebug(KTL_LOG) << " skip action: " << a;
0334         }
0335     }
0336     // tb->setItemEnabled( TextDocument::AssemblyOutput, false );    // 2018.12.02
0337     // tb->setItemEnabled( TextDocument::HexOutput, false );
0338     // tb->setItemEnabled( TextDocument::PICOutput, false );
0339     actionByName("format_asm")->setEnabled(false);
0340 
0341 #ifndef NO_GPSIM
0342     actionByName("debug_toggle_breakpoint")->setEnabled(false);
0343 #endif
0344 }
0345 
0346 // KTextEditor::View::saveResult TextView::save() { return m_view->save(); }
0347 bool TextView::save()
0348 {
0349     return (m_view->document()->documentSave());
0350 }
0351 
0352 // KTextEditor::View::saveResult TextView::saveAs() { return m_view->saveAs(); }
0353 bool TextView::saveAs()
0354 {
0355     return m_view->document()->documentSaveAs();
0356 }
0357 void TextView::print()
0358 {
0359     qCDebug(KTL_LOG);
0360     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
0361     QAction *action = actionByName("file_print");
0362     if (action) {
0363         action->trigger();
0364         return;
0365     }
0366     qCWarning(KTL_LOG) << "no file_print action in text view! no action taken";
0367 }
0368 
0369 void TextView::gotFocus()
0370 {
0371 #ifndef NO_GPSIM
0372     GpsimDebugger *debugger = textDocument()->debugger();
0373     if (!debugger || !debugger->gpsim())
0374         return;
0375 
0376     SymbolViewer::self()->setContext(debugger->gpsim());
0377 #endif
0378 }
0379 
0380 void TextView::slotSelectionmChanged()
0381 {
0382     KTechlab::self()->actionByName("edit_cut")->setEnabled(m_view->selection());
0383     KTechlab::self()->actionByName("edit_copy")->setEnabled(m_view->selection());
0384 }
0385 
0386 void TextView::initCodeActions()
0387 {
0388     disableActions();
0389 
0390     QMenu *tb = (dynamic_cast<KToolBarPopupAction *>(actionByName("program_convert")))->menu();
0391 
0392     QAction *actHexOut = nullptr;
0393     QAction *actPicOut = nullptr;
0394     QAction *actAsmOut = nullptr;
0395     const QList<QAction *> actions = tb->actions();
0396     for (QAction *a : actions) {
0397         switch (a->data().toInt()) {
0398         case TextDocument::AssemblyOutput:
0399             actAsmOut = a;
0400             break;
0401         case TextDocument::HexOutput:
0402             actHexOut = a;
0403             break;
0404         case TextDocument::PICOutput:
0405             actPicOut = a;
0406             break;
0407         default:
0408             qCDebug(KTL_LOG) << " skip action: " << a;
0409         }
0410     }
0411 
0412     switch (textDocument()->guessedCodeType()) {
0413     case TextDocument::ct_asm: {
0414         // tb->setItemEnabled( TextDocument::HexOutput, true );  // 2018.12.02
0415         // tb->setItemEnabled( TextDocument::PICOutput, true );
0416         actHexOut->setEnabled(true);
0417         actPicOut->setEnabled(true);
0418         actionByName("format_asm")->setEnabled(true);
0419 #ifndef NO_GPSIM
0420         actionByName("debug_toggle_breakpoint")->setEnabled(true);
0421         slotInitDebugActions();
0422 #endif
0423         break;
0424     }
0425     case TextDocument::ct_c: {
0426         // tb->setItemEnabled( TextDocument::AssemblyOutput, true );
0427         // tb->setItemEnabled( TextDocument::HexOutput, true );
0428         // tb->setItemEnabled( TextDocument::PICOutput, true );
0429         actAsmOut->setEnabled(true);
0430         actHexOut->setEnabled(true);
0431         actPicOut->setEnabled(true);
0432         break;
0433     }
0434     case TextDocument::ct_hex: {
0435         // tb->setItemEnabled( TextDocument::AssemblyOutput, true );
0436         // tb->setItemEnabled( TextDocument::PICOutput, true );
0437         actAsmOut->setEnabled(true);
0438         actPicOut->setEnabled(true);
0439         break;
0440     }
0441     case TextDocument::ct_microbe: {
0442         // tb->setItemEnabled( TextDocument::AssemblyOutput, true );
0443         // tb->setItemEnabled( TextDocument::HexOutput, true );
0444         // tb->setItemEnabled( TextDocument::PICOutput, true );
0445         actAsmOut->setEnabled(true);
0446         actHexOut->setEnabled(true);
0447         actPicOut->setEnabled(true);
0448         break;
0449     }
0450     case TextDocument::ct_unknown: {
0451         break;
0452     }
0453     }
0454 }
0455 
0456 void TextView::setCursorPosition(uint line, uint col)
0457 {
0458     // m_view->setCursorPosition( line, col );
0459     m_view->setCursorPosition(KTextEditor::Cursor(line, col));
0460 }
0461 
0462 unsigned TextView::currentLine()
0463 {
0464     // unsigned l,c ;
0465     KTextEditor::Cursor curs = m_view->cursorPosition();
0466     return curs.line();
0467 }
0468 unsigned TextView::currentColumn()
0469 {
0470     // unsigned l,c ;
0471     KTextEditor::Cursor curs = m_view->cursorPosition(); // &l, &c );
0472     return curs.column();
0473 }
0474 
0475 void TextView::saveCursorPosition()
0476 {
0477     KTextEditor::Cursor curs = m_view->cursorPosition(); // &m_savedCursorLine, &m_savedCursorColumn );
0478     m_savedCursorLine = curs.line();
0479     m_savedCursorColumn = curs.column();
0480 }
0481 
0482 void TextView::restoreCursorPosition()
0483 {
0484     m_view->setCursorPosition(KTextEditor::Cursor(m_savedCursorLine, m_savedCursorColumn));
0485 }
0486 
0487 void TextView::slotCursorPositionChanged()
0488 {
0489     uint line, column;
0490     KTextEditor::Cursor curs = m_view->cursorPosition(); //&line, &column );
0491     line = curs.line();
0492     column = curs.column();
0493 
0494     m_statusBar->setStatusText(i18n(" Line: %1 Col: %2 ", QString::number(line + 1), QString::number(column + 1)));
0495 
0496     slotUpdateMarksInfo();
0497 }
0498 
0499 void TextView::slotUpdateMarksInfo()
0500 {
0501 #ifndef NO_GPSIM
0502     uint l;
0503     //int c;
0504     KTextEditor::Cursor curs = m_view->cursorPosition(); // &l, &c );
0505     l = curs.line();
0506     //c = curs.column();
0507 
0508     KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(m_view->document());
0509     // if ( m_view->getDoc()->mark(l) & TextDocument::Breakpoint )
0510     if (iface->mark(l) & TextDocument::Breakpoint)
0511         actionByName("debug_toggle_breakpoint")->setText(i18n("Clear &Breakpoint"));
0512     else
0513         actionByName("debug_toggle_breakpoint")->setText(i18n("Set &Breakpoint"));
0514 #endif
0515 }
0516 
0517 void TextView::slotInitDebugActions()
0518 {
0519 #ifndef NO_GPSIM
0520     bool isRunning = textDocument()->debuggerIsRunning();
0521     bool isStepping = textDocument()->debuggerIsStepping();
0522     bool ownDebugger = textDocument()->ownDebugger();
0523 
0524     actionByName("debug_run")->setEnabled(!isRunning || isStepping);
0525     actionByName("debug_interrupt")->setEnabled(isRunning && !isStepping);
0526     actionByName("debug_stop")->setEnabled(isRunning && ownDebugger);
0527     actionByName("debug_step")->setEnabled(isRunning && isStepping);
0528     actionByName("debug_step_over")->setEnabled(isRunning && isStepping);
0529     actionByName("debug_step_out")->setEnabled(isRunning && isStepping);
0530 #endif // !NO_GPSIM
0531 }
0532 
0533 void TextView::toggleBreakpoint()
0534 {
0535 #ifndef NO_GPSIM
0536     uint l;
0537     //int c;
0538     KTextEditor::Cursor curs = m_view->cursorPosition(); // &l, &c );
0539     l = curs.line();
0540     //c = curs.column();
0541     // const bool isBreakpoint = m_view->getDoc()->mark(l) & TextDocument::Breakpoint;
0542     KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface *>(m_view->document());
0543     if (!iface)
0544         return;
0545     const bool isBreakpoint = iface->mark(l) & TextDocument::Breakpoint;
0546     // textDocument()->setBreakpoint( l, !(m_view->getDoc()->mark(l) & TextDocument::Breakpoint) );
0547     textDocument()->setBreakpoint(l, !isBreakpoint);
0548 #endif // !NO_GPSIM
0549 }
0550 
0551 void TextView::slotWordHoveredOver(const QString &word, int line, int /*col*/)
0552 {
0553 #ifndef NO_GPSIM
0554     // We're only interested in popping something up if we currently have a debugger running
0555     GpsimProcessor *gpsim = textDocument()->debugger() ? textDocument()->debugger()->gpsim() : nullptr;
0556     if (!gpsim) {
0557         m_pTextViewLabel->hide();
0558         return;
0559     }
0560 
0561     // Find out if the word that we are hovering over is the operand data
0562     // KTextEditor::EditInterface * e = (KTextEditor::EditInterface*)textDocument()->kateDocument()->qt_cast("KTextEditor::EditInterface");
0563     // InstructionParts parts( e->textLine( unsigned(line) ) );
0564     InstructionParts parts(textDocument()->kateDocument()->line(line));
0565     if (!parts.operandData().contains(word))
0566         return;
0567 
0568     if (RegisterInfo *info = gpsim->registerMemory()->fromName(word))
0569         m_pTextViewLabel->setRegister(info, info->name());
0570 
0571     else {
0572         int operandAddress = textDocument()->debugger()->programAddress(textDocument()->debugFile(), line);
0573         if (operandAddress == -1) {
0574             m_pTextViewLabel->hide();
0575             return;
0576         }
0577 
0578         int regAddress = gpsim->operandRegister(operandAddress);
0579 
0580         if (regAddress != -1)
0581             m_pTextViewLabel->setRegister(gpsim->registerMemory()->fromAddress(regAddress), word);
0582 
0583         else {
0584             m_pTextViewLabel->hide();
0585             return;
0586         }
0587     }
0588 
0589     m_pTextViewLabel->move(mapFromGlobal(QCursor::pos()) + QPoint(0, 20));
0590     m_pTextViewLabel->show();
0591 #else
0592     Q_UNUSED(word);
0593     Q_UNUSED(line);
0594 #endif // !NO_GPSIM
0595 }
0596 
0597 void TextView::slotWordUnhovered()
0598 {
0599 #ifndef NO_GPSIM
0600     m_pTextViewLabel->hide();
0601 #endif // !NO_GPSIM
0602 }
0603 // END class TextView
0604 
0605 // BEGIN class TextViewEventFilter
0606 TextViewEventFilter::TextViewEventFilter(TextView *textView)
0607 {
0608     m_hoverStatus = Sleeping;
0609     m_pTextView = textView;
0610     m_lastLine = m_lastCol = -1;
0611 
0612     //((KTextEditor::TextHintInterface*)textView->kateView()->qt_cast("KTextEditor::TextHintInterface"))->enableTextHints(0);
0613     {
0614         KTextEditor::View *view = textView->kateView();
0615         KTextEditor::TextHintInterface *iface = qobject_cast<KTextEditor::TextHintInterface *>(view);
0616         if (iface) {
0617             // iface->enableTextHints(0);
0618             iface->registerTextHintProvider(this);
0619             // connect( textView->kateView(), SIGNAL(needTextHint(int, int, QString &)), this, SLOT(slotNeedTextHint( int, int, QString& )) );
0620             // 2020.09.10 - no such signal
0621             // connect( view, SIGNAL(needTextHint(const KTextEditor::Cursor &, QString &)),
0622             //         this, SLOT(slotNeedTextHint(const KTextEditor::Cursor &, QString &)) );
0623         } else {
0624             qCWarning(KTL_LOG) << "KTextEditor::View does not implement TextHintInterface for " << view;
0625         }
0626     }
0627 
0628     m_pHoverTimer = new QTimer(this);
0629     connect(m_pHoverTimer, &QTimer::timeout, this, &TextViewEventFilter::hoverTimeout);
0630 
0631     m_pSleepTimer = new QTimer(this);
0632     connect(m_pSleepTimer, &QTimer::timeout, this, &TextViewEventFilter::gotoSleep);
0633 
0634     m_pNoWordTimer = new QTimer(this);
0635     connect(m_pNoWordTimer, &QTimer::timeout, this, &TextViewEventFilter::slotNoWordTimeout);
0636 }
0637 TextViewEventFilter::~TextViewEventFilter()
0638 {
0639     KTextEditor::View *view = m_pTextView->kateView();
0640     KTextEditor::TextHintInterface *iface = qobject_cast<KTextEditor::TextHintInterface *>(view);
0641     if (iface) {
0642         iface->unregisterTextHintProvider(this);
0643     }
0644 }
0645 
0646 QString TextViewEventFilter::textHint(KTextEditor::View * /*view*/, const KTextEditor::Cursor &position)
0647 {
0648     qCDebug(KTL_LOG) << "TextViewEventFilter::textHint: position=" << position.toString();
0649     QString str;
0650     slotNeedTextHint(position, str);
0651     return QString();
0652 }
0653 
0654 bool TextViewEventFilter::eventFilter(QObject *, QEvent *e)
0655 {
0656     //  qCDebug(KTL_LOG) << "e->type() = " << e->type();
0657 
0658     if (e->type() == QEvent::MouseMove) {
0659         if (!m_pNoWordTimer->isActive())
0660             m_pNoWordTimer->start(10);
0661         return false;
0662     }
0663 
0664     if (e->type() == QEvent::FocusOut || e->type() == QEvent::FocusIn || e->type() == QEvent::MouseButtonPress || e->type() == QEvent::Leave || e->type() == QEvent::Wheel) {
0665         // user moved focus somewhere - hide the tip and sleep
0666         if ((static_cast<QFocusEvent *>(e))->reason() != Qt::PopupFocusReason)
0667             updateHovering(nullptr, -1, -1);
0668     }
0669 
0670     return false;
0671 }
0672 
0673 void TextViewEventFilter::hoverTimeout()
0674 {
0675     m_pSleepTimer->stop();
0676     m_hoverStatus = Active;
0677     emit wordHoveredOver(m_lastWord, m_lastLine, m_lastCol);
0678 }
0679 
0680 void TextViewEventFilter::gotoSleep()
0681 {
0682     m_hoverStatus = Sleeping;
0683     m_lastWord = QString();
0684     emit wordUnhovered();
0685     m_pHoverTimer->stop();
0686 }
0687 
0688 void TextViewEventFilter::slotNoWordTimeout()
0689 {
0690     updateHovering(nullptr, -1, -1);
0691 }
0692 
0693 void TextViewEventFilter::updateHovering(const QString &currentWord, int line, int col)
0694 {
0695     if ((currentWord == m_lastWord) && (line == m_lastLine))
0696         return;
0697 
0698     m_lastWord = currentWord;
0699     m_lastLine = line;
0700     m_lastCol = col;
0701 
0702     if (currentWord.isEmpty()) {
0703         if (m_hoverStatus == Active)
0704             m_hoverStatus = Hidden;
0705 
0706         emit wordUnhovered();
0707         if (!m_pSleepTimer->isActive()) {
0708             m_pSleepTimer->setSingleShot(true);
0709             m_pSleepTimer->start(2000 /*, true */);
0710         }
0711         return;
0712     }
0713 
0714     if (m_hoverStatus != Sleeping) {
0715         emit wordHoveredOver(currentWord, line, col);
0716     } else {
0717         m_pHoverTimer->setSingleShot(true);
0718         m_pHoverTimer->start(700 /*, true */);
0719     }
0720 }
0721 
0722 static inline bool isWordLetter(const QString &s)
0723 {
0724     return (s.length() == 1) && (s[0].isLetterOrNumber() || s[0] == '_');
0725 }
0726 
0727 void TextViewEventFilter::slotNeedTextHint(const KTextEditor::Cursor &position, QString & /*text*/)
0728 {
0729     int line = position.line();
0730     int col = position.column();
0731     m_pNoWordTimer->stop();
0732 
0733     // KTextEditor::EditInterface * e = (KTextEditor::EditInterface*)m_pTextView->textDocument()->kateDocument()->qt_cast("KTextEditor::EditInterface");
0734     KTextEditor::Document *d = m_pTextView->textDocument()->kateDocument();
0735 
0736     // Return if we aren't currently in a word
0737     if (!isWordLetter(d->text(KTextEditor::Range(line, col, line, col + 1)))) {
0738         updateHovering(QString(), line, col);
0739         return;
0740     }
0741 
0742     // Find the start of the word
0743     int wordStart = col;
0744     do
0745         wordStart--;
0746     while (wordStart > 0 && isWordLetter(d->text(KTextEditor::Range(line, wordStart, line, wordStart + 1))));
0747     wordStart++;
0748 
0749     // Find the end of the word
0750     int wordEnd = col;
0751     do
0752         wordEnd++;
0753     while (isWordLetter(d->text(KTextEditor::Range(line, wordEnd, line, wordEnd + 1))));
0754 
0755     QString t = d->text(KTextEditor::Range(line, wordStart, line, wordEnd));
0756 
0757     updateHovering(t, line, col);
0758 }
0759 // END class TextViewEventFilter
0760 
0761 #include "moc_textview.cpp"