1 /***************************************************************************
2  *   Copyright (C) 2005 by David Saxton                                    *
3  *   david@bluehaze.org                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10 
11 #define protected public
12 #include <KXMLGUIClient>
13 #undef protected
14 
15 #include "asmformatter.h"
16 #include "config.h"
17 #include "filemetainfo.h"
18 #include "gpsimprocessor.h"
19 #include "ktechlab.h"
20 #include "symbolviewer.h"
21 #include "textdocument.h"
22 #include "textview.h"
23 #include "variablelabel.h"
24 #include "viewiface.h"
25 #include "ktlfindqobjectchild.h"
26 
27 //#include <ktexteditor/editinterface.h> // ?
28 #include <KTextEditor/TextHintInterface>
29 
30 // #include "kateview.h"
31 #include <KLocalizedString>
32 #include <KActionCollection>
33 // #include <k3popupmenu.h>
34 #include <KToolBarPopupAction>
35 #include <KXMLGUIFactory>
36 
37 #include <QDebug>
38 #include <QActionGroup>
39 #include <QApplication>
40 #include <QVBoxLayout>
41 #include <QCursor>
42 //#include <qobjectlist.h>
43 #include <QTimer>
44 #include <QClipboard>
45 #include <QMenu>
46 #include <QFocusEvent>
47 
48 
49 //BEGIN class TextView
TextView(TextDocument * textDocument,ViewContainer * viewContainer,uint viewAreaId,const char * name)50 TextView::TextView( TextDocument * textDocument, ViewContainer *viewContainer, uint viewAreaId, const char *name )
51 	: View( textDocument, viewContainer, viewAreaId, name )
52 {
53 	m_view = textDocument->createKateView(this);
54 	m_view->insertChildClient(this);
55 
56     KActionCollection * ac = actionCollection();
57 
58 	//BEGIN Convert To * Actions
59 	//KToolBarPopupAction * pa = new KToolBarPopupAction( i18n("Convert to"), "fork", 0, 0, 0, ac, "program_convert" );
60     KToolBarPopupAction * pa = new KToolBarPopupAction( QIcon::fromTheme("fork"), i18n("Convert To"), ac);
61     pa->setObjectName("program_convert");
62 	pa->setDelayed(false);
63     ac->addAction(pa->objectName(), pa);
64 
65 	QMenu * m = pa->menu();
66 
67     m->setTitle( i18n("Convert To") );
68     QAction *actToMicrobe = m->addAction( QIcon::fromTheme( "convert_to_microbe" ), i18n("Microbe"));
69     actToMicrobe->setData( TextDocument::MicrobeOutput );
70 	m->addAction( QIcon::fromTheme( "convert_to_assembly" ), i18n("Assembly"))->setData( TextDocument::AssemblyOutput );
71 	m->addAction( QIcon::fromTheme( "convert_to_hex" ), i18n("Hex"))->setData( TextDocument::HexOutput );
72 	m->addAction( QIcon::fromTheme( "convert_to_pic" ), i18n("PIC (upload)"))->setData( TextDocument::PICOutput );
73 	connect( m, SIGNAL(triggered(QAction*)), textDocument, SLOT(slotConvertTo(QAction*)) );
74 
75 	//m->setItemEnabled( TextDocument::MicrobeOutput, false ); // 2018.12.02
76     actToMicrobe->setEnabled(false);
77     ac->addAction(pa->objectName(), pa);
78 	//END Convert To * Actions
79 
80     {
81 	//new QAction( i18n("Format Assembly Code"), "", Qt::Key_F12, textDocument, SLOT(formatAssembly()), ac, "format_asm" );
82         QAction *action = new QAction( i18n("Format Assembly Code"), ac);
83         action->setObjectName("format_asm");
84         action->setShortcut(Qt::Key_F12);
85         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(formatAssembly()));
86         ac->addAction(action->objectName(), action);
87     }
88 
89 
90 #ifndef NO_GPSIM
91 	//BEGIN Debug Actions
92 	{
93 	//new QAction( i18n("Set &Breakpoint"), 0, 0, this, SLOT(toggleBreakpoint()), ac, "debug_toggle_breakpoint" );
94         QAction *action = new QAction(i18n("Set &Breakpoint"), ac);
95         action->setObjectName("debug_toggle_breakpoint");
96         connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleBreakpoint()));
97         ac->addAction(action->objectName(), action);
98     }
99     {
100 	//new QAction( i18n("Run"), "debug-run", 0, textDocument, SLOT(debugRun()), ac, "debug_run" );
101         QAction *action = new QAction( QIcon::fromTheme("debug-run"), i18n("Run"), ac);
102         action->setObjectName("debug_run");
103         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(debugRun()));
104         ac->addAction(action->objectName(), action);
105     }
106     {
107 	//new QAction( i18n("Interrupt"), "media-playback-pause", 0, textDocument, SLOT(debugInterrupt()), ac, "debug_interrupt" );
108         QAction *action = new QAction( QIcon::fromTheme("media-playback-pause"), i18n("Interrupt"), ac);
109         action->setObjectName("debug_interrupt");
110         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(debugInterrupt()));
111         ac->addAction(action->objectName(), action);
112     }
113     {
114 	//new QAction( i18n("Stop"), "process-stop", 0, textDocument, SLOT(debugStop()), ac, "debug_stop" );
115         QAction *action = new QAction( QIcon::fromTheme("process-stop"), i18n("Stop"), ac);
116         action->setObjectName("debug_stop");
117         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(debugStop()));
118         ac->addAction(action->objectName(), action);
119     }
120     {
121 	//new QAction( i18n("Step"), "debug-step-instruction", Qt::CTRL|Qt::ALT|Qt::Key_Right, textDocument, SLOT(debugStep()), ac, "debug_step" );
122         QAction *action = new QAction( QIcon::fromTheme("debug-step-instruction"), i18n("Step"), ac);
123         action->setObjectName("debug_step");
124         action->setShortcut(Qt::CTRL|Qt::ALT|Qt::Key_Right);
125         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(debugStep()));
126         ac->addAction(action->objectName(), action);
127     }
128     {
129 	//new QAction( i18n("Step Over"), "debug-step-over", 0, textDocument, SLOT(debugStepOver()), ac, "debug_step_over" );
130         QAction *action = new QAction( QIcon::fromTheme("debug-step-over"), i18n("Step Over"), ac);
131         action->setObjectName("debug_step_over");
132         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(debugStepOver()));
133         ac->addAction(action->objectName(), action);
134     }
135     {
136 	//new QAction( i18n("Step Out"), "debug-step-out", 0, textDocument, SLOT(debugStepOut()), ac, "debug_step_out" );
137         QAction *action = new QAction( QIcon::fromTheme("debug-step-out"), i18n("Step Out"), ac);
138         action->setObjectName("debug_step_out");
139         connect(action, SIGNAL(triggered(bool)), textDocument, SLOT(debugStepOut()));
140         ac->addAction(action->objectName(), action);
141     }
142 	//END Debug Actions
143 #endif
144 
145 
146 	setXMLFile( "ktechlabtextui.rc" );
147 	m_view->setXMLFile( "ktechlabkateui.rc" );
148 
149 	m_savedCursorLine = 0;
150 	m_savedCursorColumn = 0;
151 	m_pViewIface = new TextViewIface(this);
152 
153 	setAcceptDrops(true);
154 
155 	//m_view->installPopup( static_cast<Q3PopupMenu*>( KTechlab::self()->factory()->container( "ktexteditor_popup", KTechlab::self() ) ) );
156     m_view->setContextMenu( static_cast<QMenu*>( KTechlab::self()->factory()->container( "ktexteditor_popup", KTechlab::self() ) ) );
157 
158 	//QWidget * internalView = static_cast<QWidget*>( m_view->child( 0, "KateViewInternal" ) ); // 2018.12.02
159     QWidget * internalView = static_cast<QWidget*>( ktlFindQObjectChild( m_view, "KateViewInternal" ) );
160 
161 	connect( m_view, SIGNAL(cursorPositionChanged(KTextEditor::View *, const KTextEditor::Cursor &)),	this, SLOT(slotCursorPositionChanged()) );
162 	connect( m_view, SIGNAL(selectionChanged(KTextEditor::View *)), this, SLOT(slotSelectionmChanged()) );
163 
164 	setFocusWidget( internalView );
165 	connect( this, SIGNAL(focused( View* )), this, SLOT(gotFocus()) );
166 
167 	m_layout->insertWidget( 0, m_view );
168 
169 	slotCursorPositionChanged();
170 	slotInitDebugActions();
171 	initCodeActions();
172 
173 #ifndef NO_GPSIM
174 	m_pTextViewLabel = new VariableLabel( this );
175 	m_pTextViewLabel->hide();
176 
177 	TextViewEventFilter * eventFilter = new TextViewEventFilter( this );
178 	connect( eventFilter, SIGNAL(wordHoveredOver( const QString&, int, int )), this, SLOT(slotWordHoveredOver( const QString&, int, int )) );
179 	connect( eventFilter, SIGNAL(wordUnhovered()), this, SLOT(slotWordUnhovered()) );
180 
181 	internalView->installEventFilter( eventFilter );
182 #endif
183 
184     // TODO HACK disable some actions which collide with ktechlab's actions.
185     //  the proper solution would be to move the actions from KTechLab object level to document level for
186     //  all types of documents
187     for (QAction *act: actionCollection()->actions()) {
188         qDebug() << Q_FUNC_INFO << "act: " << act->text() << " shortcut " << act->shortcut() << ":" << act ;
189 
190         if ( ((act->objectName()) == QLatin1String("file_save"))
191             || ((act->objectName()) == QLatin1String("file_save_as"))
192             || ((act->objectName()) == QLatin1String("file_print"))
193             || ((act->objectName()) == QLatin1String("edit_undo"))
194             || ((act->objectName()) == QLatin1String("edit_redo"))
195             || ((act->objectName()) == QLatin1String("edit_cut"))
196             || ((act->objectName()) == QLatin1String("edit_copy"))
197             || ((act->objectName()) == QLatin1String("edit_paste"))
198         ) {
199             act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
200             //act->setShortcutConfigurable(true);
201             act->setShortcut(Qt::Key_unknown);
202             qDebug() << Q_FUNC_INFO << "action " << act << " disabled";
203         }
204     }
205 }
206 
207 
~TextView()208 TextView::~TextView()
209 {
210 	if ( KTechlab::self() ) {
211         // 2017.01.09: do not crash on document close. factory has its clients removed in TextDocument::~TextDocument()
212 		//if ( KXMLGUIFactory * f = m_view->factory() )
213 		//	f->removeClient( m_view );
214 		KTechlab::self()->addNoRemoveGUIClient( m_view );
215 	}
216 
217 	delete m_pViewIface;
218 }
219 
220 
closeView()221 bool TextView::closeView()
222 {
223 	if ( textDocument() )
224 	{
225 		const QUrl url = textDocument()->url();
226 		if ( !url.isEmpty() )
227 			fileMetaInfo()->grabMetaInfo(url, this );
228 	}
229 
230 	bool doClose = View::closeView();
231 	if ( doClose )
232 		KTechlab::self()->factory()->removeClient(m_view);
233 	return View::closeView();
234 }
235 
gotoLine(const int line)236 bool TextView::gotoLine( const int line ) {
237     //return m_view->setCursorPosition( line, 0/*m_view->cursorColumn()*/ );
238     return m_view->setCursorPosition( KTextEditor::Cursor( line, 0/*m_view->cursorColumn()*/ ) );
239 }
240 
textDocument() const241 TextDocument *TextView::textDocument() const
242 {
243 	return static_cast<TextDocument*>(document());
244 }
undo()245 void TextView::undo() {
246     qDebug() << Q_FUNC_INFO;
247     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
248     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
249     QAction* action = actionByName("edit_undo");
250     if (action) {
251         action->trigger();
252         return;
253     }
254     qWarning() << Q_FUNC_INFO << "no edit_undo action in text view! no action taken";
255 }
redo()256 void TextView::redo() {
257     qDebug() << Q_FUNC_INFO;
258     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
259     QAction* action = actionByName("edit_redo");
260     if (action) {
261         action->trigger();
262         return;
263     }
264     qWarning() << Q_FUNC_INFO << "no edit_redo action in text view! no action taken";
265 }
266 
cut()267 void TextView::cut() {
268     //m_view-> cut();
269     if (!m_view->selection()) return;
270     QClipboard *clipboard = QApplication::clipboard();
271     clipboard->setText( m_view->document()->text( m_view->selectionRange() ) );
272     m_view->document()->removeText(m_view->selectionRange());
273 }
274 
copy()275 void TextView::copy() {
276     //m_view->copy();
277     if (!m_view->selection()) return;
278     QClipboard *clipboard = QApplication::clipboard();
279     clipboard->setText( m_view->document()->text( m_view->selectionRange() ) );
280 }
281 
paste()282 void TextView::paste() {
283     //m_view->paste();
284     QClipboard *clipboard = QApplication::clipboard();
285     m_view->document()->insertText( m_view->cursorPosition(), clipboard->text());
286 }
287 
288 
disableActions()289 void TextView::disableActions()
290 {
291 	QMenu * tb = (dynamic_cast<KToolBarPopupAction*>(actionByName("program_convert")))->menu();
292 
293     const QList<QAction*> actions = tb->actions();
294     for(QAction *a : actions) {
295         switch (a->data().toInt()) {
296             case TextDocument::AssemblyOutput:
297             case TextDocument::HexOutput:
298             case TextDocument::PICOutput:
299                 a->setEnabled( false );
300                 break;
301             default:
302                 qDebug() << Q_FUNC_INFO << " skip action: " << a;
303         }
304     }
305 	//tb->setItemEnabled( TextDocument::AssemblyOutput, false );    // 2018.12.02
306 	//tb->setItemEnabled( TextDocument::HexOutput, false );
307 	//tb->setItemEnabled( TextDocument::PICOutput, false );
308 	actionByName("format_asm")->setEnabled(false);
309 
310 #ifndef NO_GPSIM
311 	actionByName("debug_toggle_breakpoint")->setEnabled(false);
312 #endif
313 }
314 
315 //KTextEditor::View::saveResult TextView::save() { return m_view->save(); }
save()316 bool TextView::save()
317 {
318     return (m_view->document()->documentSave());
319 }
320 
321 //KTextEditor::View::saveResult TextView::saveAs() { return m_view->saveAs(); }
saveAs()322 bool TextView::saveAs()
323 {
324     return m_view->document()->documentSaveAs();
325 }
print()326 void TextView::print() {
327     qDebug() << Q_FUNC_INFO;
328     // note: quite a hack, but could not find any more decent way of getting to undo/redo interface
329     QAction* action = actionByName("file_print");
330     if (action) {
331         action->trigger();
332         return;
333     }
334     qWarning() << Q_FUNC_INFO << "no file_print action in text view! no action taken";
335 }
336 
gotFocus()337 void TextView::gotFocus()
338 {
339 #ifndef NO_GPSIM
340 	GpsimDebugger * debugger = textDocument()->debugger();
341 	if ( !debugger || !debugger->gpsim() )
342 		return;
343 
344 	SymbolViewer::self()->setContext( debugger->gpsim() );
345 #endif
346 }
347 
slotSelectionmChanged()348 void TextView::slotSelectionmChanged() {
349     KTechlab::self()->actionByName( "edit_cut" )->setEnabled( m_view->selection() );
350     KTechlab::self()->actionByName( "edit_copy" )->setEnabled( m_view->selection() );
351 }
352 
initCodeActions()353 void TextView::initCodeActions()
354 {
355 	disableActions();
356 
357 	QMenu * tb = (dynamic_cast<KToolBarPopupAction*>(actionByName("program_convert")))->menu();
358 
359     QAction *actHexOut = nullptr;
360     QAction *actPicOut = nullptr;
361     QAction *actAsmOut = nullptr;
362     const QList<QAction*> actions = tb->actions();
363     for (QAction *a : actions) {
364         switch (a->data().toInt()) {
365             case TextDocument::AssemblyOutput:  actAsmOut = a; break;
366             case TextDocument::HexOutput:       actHexOut = a; break;
367             case TextDocument::PICOutput:       actPicOut = a; break;
368             default:
369                 qDebug() << Q_FUNC_INFO << " skip action: " << a;
370         }
371     }
372 
373 	switch ( textDocument()->guessedCodeType() )
374 	{
375 		case TextDocument::ct_asm:
376 		{
377 			//tb->setItemEnabled( TextDocument::HexOutput, true );  // 2018.12.02
378 			//tb->setItemEnabled( TextDocument::PICOutput, true );
379             actHexOut->setEnabled( true );
380             actPicOut->setEnabled( true );
381 			actionByName("format_asm")->setEnabled(true);
382 #ifndef NO_GPSIM
383 			actionByName("debug_toggle_breakpoint")->setEnabled(true);
384 			slotInitDebugActions();
385 #endif
386 			break;
387 		}
388 		case TextDocument::ct_c:
389 		{
390 			//tb->setItemEnabled( TextDocument::AssemblyOutput, true );
391 			//tb->setItemEnabled( TextDocument::HexOutput, true );
392 			//tb->setItemEnabled( TextDocument::PICOutput, true );
393             actAsmOut->setEnabled( true );
394             actHexOut->setEnabled( true );
395             actPicOut->setEnabled( true );
396 			break;
397 		}
398 		case TextDocument::ct_hex:
399 		{
400 			//tb->setItemEnabled( TextDocument::AssemblyOutput, true );
401 			//tb->setItemEnabled( TextDocument::PICOutput, true );
402             actAsmOut->setEnabled( true );
403             actPicOut->setEnabled( true );
404 			break;
405 		}
406 		case TextDocument::ct_microbe:
407 		{
408 			//tb->setItemEnabled( TextDocument::AssemblyOutput, true );
409 			//tb->setItemEnabled( TextDocument::HexOutput, true );
410 			//tb->setItemEnabled( TextDocument::PICOutput, true );
411             actAsmOut->setEnabled( true );
412             actHexOut->setEnabled( true );
413             actPicOut->setEnabled( true );
414 			break;
415 		}
416 		case TextDocument::ct_unknown:
417 		{
418 			break;
419 		}
420 	}
421 }
422 
setCursorPosition(uint line,uint col)423 void TextView::setCursorPosition( uint line, uint col ) {
424     //m_view->setCursorPosition( line, col );
425     m_view->setCursorPosition( KTextEditor::Cursor( line, col ) );
426 }
427 
currentLine()428 unsigned TextView::currentLine()
429 {
430 	//unsigned l,c ;
431 	KTextEditor::Cursor curs = m_view->cursorPosition();
432 	return curs.line();
433 }
currentColumn()434 unsigned TextView::currentColumn()
435 {
436 	//unsigned l,c ;
437 	KTextEditor::Cursor curs = m_view->cursorPosition(); // &l, &c );
438 	return curs.column();
439 }
440 
441 
saveCursorPosition()442 void TextView::saveCursorPosition()
443 {
444 	KTextEditor::Cursor curs = m_view->cursorPosition(); // &m_savedCursorLine, &m_savedCursorColumn );
445 	m_savedCursorLine = curs.line();
446     m_savedCursorColumn = curs.column();
447 }
448 
449 
restoreCursorPosition()450 void TextView::restoreCursorPosition()
451 {
452 	m_view->setCursorPosition( KTextEditor::Cursor(  m_savedCursorLine, m_savedCursorColumn ) );
453 }
454 
455 
slotCursorPositionChanged()456 void TextView::slotCursorPositionChanged()
457 {
458 	uint line, column;
459 	KTextEditor::Cursor curs = m_view->cursorPosition(); //&line, &column );
460 	line = curs.line();
461     column = curs.column();
462 
463 	m_statusBar->setStatusText(i18n(" Line: %1 Col: %2 ", QString::number(line+1), QString::number(column+1)));
464 
465 	slotUpdateMarksInfo();
466 }
467 
468 
slotUpdateMarksInfo()469 void TextView::slotUpdateMarksInfo()
470 {
471 #ifndef NO_GPSIM
472 	uint l,c ;
473 	KTextEditor::Cursor curs = m_view->cursorPosition(); // &l, &c );
474 	l = curs.line();
475     c = curs.column();
476 
477     KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface*>( m_view->document() );
478 	// if ( m_view->getDoc()->mark(l) & TextDocument::Breakpoint )
479     if (iface->mark(l) & TextDocument::Breakpoint)
480 		actionByName("debug_toggle_breakpoint")->setText( i18n("Clear &Breakpoint") );
481 	else
482 		actionByName("debug_toggle_breakpoint")->setText( i18n("Set &Breakpoint") );
483 #endif
484 }
485 
486 
slotInitDebugActions()487 void TextView::slotInitDebugActions()
488 {
489 #ifndef NO_GPSIM
490 	bool isRunning = textDocument()->debuggerIsRunning();
491 	bool isStepping = textDocument()->debuggerIsStepping();
492 	bool ownDebugger = textDocument()->ownDebugger();
493 
494 	actionByName("debug_run")->setEnabled( !isRunning || isStepping );
495 	actionByName("debug_interrupt")->setEnabled(isRunning && !isStepping);
496 	actionByName("debug_stop")->setEnabled(isRunning && ownDebugger);
497 	actionByName("debug_step")->setEnabled(isRunning && isStepping);
498 	actionByName("debug_step_over")->setEnabled(isRunning && isStepping);
499 	actionByName("debug_step_out")->setEnabled(isRunning && isStepping);
500 #endif // !NO_GPSIM
501 }
502 
503 
toggleBreakpoint()504 void TextView::toggleBreakpoint()
505 {
506 #ifndef NO_GPSIM
507 	uint l,c ;
508 	KTextEditor::Cursor curs = m_view->cursorPosition(); // &l, &c );
509 	l = curs.line();
510     c = curs.column();
511     //const bool isBreakpoint = m_view->getDoc()->mark(l) & TextDocument::Breakpoint;
512     KTextEditor::MarkInterface *iface = qobject_cast<KTextEditor::MarkInterface*>(m_view->document());
513     if (!iface) return;
514     const bool isBreakpoint = iface->mark(l) & TextDocument::Breakpoint;
515 	//textDocument()->setBreakpoint( l, !(m_view->getDoc()->mark(l) & TextDocument::Breakpoint) );
516     textDocument()->setBreakpoint(l, !isBreakpoint);
517 #endif // !NO_GPSIM
518 }
519 
520 
slotWordHoveredOver(const QString & word,int line,int)521 void TextView::slotWordHoveredOver( const QString & word, int line, int /*col*/ )
522 {
523 #ifndef NO_GPSIM
524 	// We're only interested in popping something up if we currently have a debugger running
525 	GpsimProcessor * gpsim = textDocument()->debugger() ? textDocument()->debugger()->gpsim() : nullptr;
526 	if ( !gpsim )
527 	{
528 		m_pTextViewLabel->hide();
529 		return;
530 	}
531 
532 	// Find out if the word that we are hovering over is the operand data
533 	//KTextEditor::EditInterface * e = (KTextEditor::EditInterface*)textDocument()->kateDocument()->qt_cast("KTextEditor::EditInterface");
534 	//InstructionParts parts( e->textLine( unsigned(line) ) );
535     InstructionParts parts(textDocument()->kateDocument()->line(line));
536 	if ( !parts.operandData().contains( word ) )
537 		return;
538 
539 	if ( RegisterInfo * info = gpsim->registerMemory()->fromName( word ) )
540 		m_pTextViewLabel->setRegister( info, info->name() );
541 
542 	else
543 	{
544 		int operandAddress = textDocument()->debugger()->programAddress( textDocument()->debugFile(), line );
545 		if ( operandAddress == -1 )
546 		{
547 			m_pTextViewLabel->hide();
548 			return;
549 		}
550 
551 		int regAddress = gpsim->operandRegister( operandAddress );
552 
553 		if ( regAddress != -1 )
554 			m_pTextViewLabel->setRegister( gpsim->registerMemory()->fromAddress( regAddress ), word );
555 
556 		else
557 		{
558 			m_pTextViewLabel->hide();
559 			return;
560 		}
561 	}
562 
563 	m_pTextViewLabel->move( mapFromGlobal( QCursor::pos() ) + QPoint( 0, 20 ) );
564 	m_pTextViewLabel->show();
565 #endif // !NO_GPSIM
566 }
567 
568 
slotWordUnhovered()569 void TextView::slotWordUnhovered()
570 {
571 #ifndef NO_GPSIM
572 	m_pTextViewLabel->hide();
573 #endif // !NO_GPSIM
574 }
575 //END class TextView
576 
577 
578 
579 //BEGIN class TextViewEventFilter
TextViewEventFilter(TextView * textView)580 TextViewEventFilter::TextViewEventFilter( TextView * textView )
581 {
582 	m_hoverStatus = Sleeping;
583 	m_pTextView = textView;
584 	m_lastLine = m_lastCol = -1;
585 
586 	//((KTextEditor::TextHintInterface*)textView->kateView()->qt_cast("KTextEditor::TextHintInterface"))->enableTextHints(0);
587     {
588         KTextEditor::View * view = textView->kateView();
589         KTextEditor::TextHintInterface *iface = qobject_cast<KTextEditor::TextHintInterface*>(view);
590         if (iface) {
591             //iface->enableTextHints(0);
592             iface->registerTextHintProvider(this);
593             //connect( textView->kateView(), SIGNAL(needTextHint(int, int, QString &)), this, SLOT(slotNeedTextHint( int, int, QString& )) );
594             // 2020.09.10 - no such signal
595             //connect( view, SIGNAL(needTextHint(const KTextEditor::Cursor &, QString &)),
596             //         this, SLOT(slotNeedTextHint(const KTextEditor::Cursor &, QString &)) );
597         } else {
598             qWarning() << "KTextEditor::View does not implement TextHintInterface for " << view;
599         }
600     }
601 
602 	m_pHoverTimer = new QTimer( this );
603 	connect( m_pHoverTimer, SIGNAL(timeout()), this, SLOT(hoverTimeout()) );
604 
605 	m_pSleepTimer = new QTimer( this );
606 	connect( m_pSleepTimer, SIGNAL(timeout()), this, SLOT(gotoSleep()) );
607 
608 	m_pNoWordTimer = new QTimer( this );
609 	connect( m_pNoWordTimer, SIGNAL(timeout()), this, SLOT(slotNoWordTimeout()) );
610 }
~TextViewEventFilter()611 TextViewEventFilter::~TextViewEventFilter()
612 {
613     KTextEditor::View * view = m_pTextView->kateView();
614     KTextEditor::TextHintInterface *iface = qobject_cast<KTextEditor::TextHintInterface*>(view);
615     if (iface) {
616         iface->unregisterTextHintProvider(this);
617     }
618 }
619 
620 
textHint(KTextEditor::View *,const KTextEditor::Cursor & position)621 QString TextViewEventFilter::textHint(KTextEditor::View * /*view*/, const KTextEditor::Cursor &position) {
622     qDebug() << "TextViewEventFilter::textHint: position=" << position.toString();
623     QString str;
624     slotNeedTextHint(position, str);
625     return QString();
626 }
627 
eventFilter(QObject *,QEvent * e)628 bool TextViewEventFilter::eventFilter( QObject *, QEvent * e )
629 {
630 // 	qDebug() << Q_FUNC_INFO << "e->type() = " << e->type() << endl;
631 
632 	if ( e->type() == QEvent::MouseMove )
633 	{
634 		if ( !m_pNoWordTimer->isActive() )
635 			m_pNoWordTimer->start( 10 );
636 		return false;
637 	}
638 
639 	if ( e->type() == QEvent::FocusOut || e->type() == QEvent::FocusIn || e->type() == QEvent::MouseButtonPress || e->type() == QEvent::Leave || e->type() == QEvent::Wheel )
640 	{
641 		// user moved focus somewhere - hide the tip and sleep
642 		if ( ((QFocusEvent*)e)->reason() != Qt::PopupFocusReason )
643 			updateHovering( nullptr, -1, -1 );
644 	}
645 
646 	return false;
647 }
648 
649 
hoverTimeout()650 void TextViewEventFilter::hoverTimeout()
651 {
652 	m_pSleepTimer->stop();
653 	m_hoverStatus = Active;
654 	emit wordHoveredOver( m_lastWord, m_lastLine, m_lastCol );
655 }
656 
657 
gotoSleep()658 void TextViewEventFilter::gotoSleep()
659 {
660 	m_hoverStatus = Sleeping;
661 	m_lastWord = QString::null;
662 	emit wordUnhovered();
663 	m_pHoverTimer->stop();
664 }
665 
666 
slotNoWordTimeout()667 void TextViewEventFilter::slotNoWordTimeout()
668 {
669 	updateHovering( nullptr, -1, -1 );
670 }
671 
672 
updateHovering(const QString & currentWord,int line,int col)673 void TextViewEventFilter::updateHovering( const QString & currentWord, int line, int col )
674 {
675 	if ( (currentWord == m_lastWord) && (line == m_lastLine) )
676 		return;
677 
678 	m_lastWord = currentWord;
679 	m_lastLine = line;
680 	m_lastCol = col;
681 
682 	if ( currentWord.isEmpty() )
683 	{
684 		if ( m_hoverStatus == Active )
685 			m_hoverStatus = Hidden;
686 
687 		emit wordUnhovered();
688 		if ( !m_pSleepTimer->isActive() ) {
689             m_pSleepTimer->setSingleShot( true );
690 			m_pSleepTimer->start( 2000 /*, true */ );
691         }
692 		return;
693 	}
694 
695 	if ( m_hoverStatus != Sleeping ) {
696 		emit wordHoveredOver( currentWord, line, col );
697     } else {
698         m_pHoverTimer->setSingleShot( true );
699 		m_pHoverTimer->start( 700 /*, true */ );
700     }
701 }
702 
703 
isWordLetter(const QString & s)704 static inline bool isWordLetter( const QString & s ) { return (s.length() == 1) && (s[0].isLetterOrNumber() || s[0] == '_'); }
705 
706 
slotNeedTextHint(const KTextEditor::Cursor & position,QString &)707 void TextViewEventFilter::slotNeedTextHint(const KTextEditor::Cursor &position, QString &  /*text*/)
708 {
709     int line = position.line();
710     int col = position.column();
711 	m_pNoWordTimer->stop();
712 
713 	//KTextEditor::EditInterface * e = (KTextEditor::EditInterface*)m_pTextView->textDocument()->kateDocument()->qt_cast("KTextEditor::EditInterface");
714     KTextEditor::Document *d = m_pTextView->textDocument()->kateDocument();
715 
716 	// Return if we aren't currently in a word
717 	if ( !isWordLetter( d->text( KTextEditor::Range( line, col, line, col+1 ) ) ) )
718 	{
719 		updateHovering( QString::null, line, col );
720 		return;
721 	}
722 
723 	// Find the start of the word
724 	int wordStart = col;
725 	do wordStart--;
726 	while ( wordStart > 0 && isWordLetter( d->text( KTextEditor::Range( line, wordStart, line, wordStart+1 ) ) ) );
727 	wordStart++;
728 
729 	// Find the end of the word
730 	int wordEnd = col;
731 	do wordEnd++;
732 	while ( isWordLetter( d->text( KTextEditor::Range( line, wordEnd, line, wordEnd+1 ) ) ) );
733 
734 	QString t = d->text( KTextEditor::Range( line, wordStart, line, wordEnd ) );
735 
736 	updateHovering( t, line, col );
737 }
738 //END class TextViewEventFilter
739