Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Utilities
Kate
Commits
0f249ec6
Commit
0f249ec6
authored
Jul 30, 2022
by
Pablo Rauzy
Committed by
Christoph Cullmann
Aug 14, 2022
Browse files
KeyboardMacros Plugin v1.0
parent
6bb69ed2
Changes
4
Hide whitespace changes
Inline
Side-by-side
addons/keyboardmacros/keyboardmacrosplugin.cpp
View file @
0f249ec6
...
...
@@ -6,7 +6,9 @@
#include
"keyboardmacrosplugin.h"
#include
<QAction>
#include
<QApplication>
#include
<QCoreApplication>
#include
<QDateTime>
#include
<QKeyEvent>
#include
<QKeySequence>
#include
<QList>
...
...
@@ -21,24 +23,19 @@
#include
<KPluginFactory>
#include
<KXMLGUIFactory>
#include
<iostream>
#include
<qevent.h>
K_PLUGIN_FACTORY_WITH_JSON
(
KeyboardMacrosPluginFactory
,
"keyboardmacrosplugin.json"
,
registerPlugin
<
KeyboardMacrosPlugin
>
();)
KeyboardMacrosPlugin
::
KeyboardMacrosPlugin
(
QObject
*
parent
,
const
QList
<
QVariant
>
&
)
:
KTextEditor
::
Plugin
(
parent
)
{
// register "recmac" and "runmac" commands
m_recCommand
=
new
KeyboardMacrosPluginRecordCommand
(
this
);
m_runCommand
=
new
KeyboardMacrosPluginRunCommand
(
this
);
}
KeyboardMacrosPlugin
::~
KeyboardMacrosPlugin
()
{
delete
m_recCommand
;
delete
m_runCommand
;
reset
();
qDeleteAll
(
m_tape
.
begin
(),
m_tape
.
end
());
m_tape
.
clear
();
qDeleteAll
(
m_macro
.
begin
(),
m_macro
.
end
());
m_macro
.
clear
();
}
QObject
*
KeyboardMacrosPlugin
::
createView
(
KTextEditor
::
MainWindow
*
mainWindow
)
...
...
@@ -47,112 +44,128 @@ QObject *KeyboardMacrosPlugin::createView(KTextEditor::MainWindow *mainWindow)
return
new
KeyboardMacrosPluginView
(
this
,
mainWindow
);
}
// https://doc.qt.io/qt-6/eventsandfilters.html
// https://doc.qt.io/qt-6/qobject.html#installEventFilter
// https://stackoverflow.com/questions/41631011/my-qt-eventfilter-doesnt-stop-events-as-it-should
// file:///usr/share/qt5/doc/qtcore/qobject.html#installEventFilter
// file:///usr/share/qt5/doc/qtcore/qcoreapplication.html#sendEvent
// also see postEvent, sendPostedEvents, etc
void
KeyboardMacrosPlugin
::
sendMessage
(
const
QString
&
text
,
bool
error
)
{
QVariantMap
genericMessage
;
genericMessage
.
insert
(
QStringLiteral
(
"type"
),
error
?
QStringLiteral
(
"Error"
)
:
QStringLiteral
(
"Info"
));
genericMessage
.
insert
(
QStringLiteral
(
"category"
),
i18n
(
"Macros"
));
genericMessage
.
insert
(
QStringLiteral
(
"categoryIcon"
),
QIcon
::
fromTheme
(
QStringLiteral
(
"input-keyboard"
)));
genericMessage
.
insert
(
QStringLiteral
(
"text"
),
text
);
Q_EMIT
message
(
genericMessage
);
}
bool
KeyboardMacrosPlugin
::
eventFilter
(
QObject
*
obj
,
QEvent
*
event
)
{
if
(
event
->
type
()
==
QEvent
::
KeyPress
||
event
->
type
()
==
QEvent
::
ShortcutOverride
)
{
QKeyEvent
*
keyEvent
=
new
QKeyEvent
(
*
static_cast
<
QKeyEvent
*>
(
event
));
QKeySequence
s
(
keyEvent
->
key
()
|
keyEvent
->
modifiers
());
qDebug
(
"KeySeq: %s"
,
s
.
toString
().
toUtf8
().
data
());
m_keyEvents
.
append
(
keyEvent
);
return
true
;
// FIXME: this should let the event pass through by returning false
// but also capture keypress only once and only the relevant ones
// (e.g., if pressing ctrl then c before releasing only capture ctrl+c)
// Update which widget we filter events from if the focus has changed
m_focusWidget
->
removeEventFilter
(
this
);
m_focusWidget
=
qApp
->
focusWidget
();
m_focusWidget
->
installEventFilter
(
this
);
// We only spy on keyboard events so we only need to check ShortcutOverride and return false
if
(
event
->
type
()
==
QEvent
::
ShortcutOverride
)
{
QKeyEvent
*
keyEvent
=
static_cast
<
QKeyEvent
*>
(
event
);
// if only modifiers are pressed, we don't care
switch
(
keyEvent
->
key
())
{
case
Qt
::
Key_Shift
:
case
Qt
::
Key_Control
:
case
Qt
::
Key_Alt
:
case
Qt
::
Key_Meta
:
case
Qt
::
Key_AltGr
:
return
false
;
}
// we don't want to record the shortcut for recording
if
(
m_recordAction
->
shortcut
().
matches
(
QKeySequence
(
keyEvent
->
key
()
|
keyEvent
->
modifiers
()))
==
QKeySequence
::
ExactMatch
)
{
return
false
;
}
// otherwise we add the keyboard event to the macro
m_tape
.
append
(
new
QKeyEvent
(
QEvent
::
KeyPress
,
keyEvent
->
key
(),
keyEvent
->
modifiers
(),
keyEvent
->
text
()));
m_tape
.
append
(
new
QKeyEvent
(
QEvent
::
KeyRelease
,
keyEvent
->
key
(),
keyEvent
->
modifiers
(),
keyEvent
->
text
()));
return
false
;
}
else
{
return
QObject
::
eventFilter
(
obj
,
event
);
}
}
void
KeyboardMacrosPlugin
::
re
set
()
void
KeyboardMacrosPlugin
::
re
cord
()
{
qDeleteAll
(
m_keyEvents
.
begin
(),
m_keyEvents
.
end
());
m_keyEvents
.
clear
();
// start recording
qDebug
(
"[KeyboardMacrosPlugin] start recording"
);
m_focusWidget
=
qApp
->
focusWidget
();
m_focusWidget
->
installEventFilter
(
this
);
m_recording
=
true
;
m_recordAction
->
setText
(
i18n
(
"End Macro &Recording"
));
m_cancelAction
->
setEnabled
(
true
);
}
void
KeyboardMacrosPlugin
::
stop
(
bool
save
)
{
// stop recording
qDebug
(
"[KeyboardMacrosPlugin] %s recording"
,
save
?
"end"
:
"cancel"
);
m_focusWidget
->
removeEventFilter
(
this
);
m_recording
=
false
;
if
(
save
)
{
// delete current macro
qDeleteAll
(
m_macro
.
begin
(),
m_macro
.
end
());
m_macro
.
clear
();
// replace it with the tape
m_macro
.
swap
(
m_tape
);
// clear tape
m_tape
.
clear
();
m_playAction
->
setEnabled
(
!
m_macro
.
isEmpty
());
}
else
{
// cancel
// delete tape
qDeleteAll
(
m_tape
.
begin
(),
m_tape
.
end
());
m_tape
.
clear
();
}
m_recordAction
->
setText
(
i18n
(
"&Record Macro..."
));
m_cancelAction
->
setEnabled
(
false
);
}
bool
KeyboardMacrosPlugin
::
record
(
KTextEditor
::
View
*
)
void
KeyboardMacrosPlugin
::
cancel
(
)
{
if
(
m_recording
)
{
// end recording
// KTextEditor::Editor::instance()->application()->activeMainWindow()->window()->removeEventFilter(this);
QCoreApplication
::
instance
()
->
removeEventFilter
(
this
);
std
::
cerr
<<
"stop recording"
<<
std
::
endl
;
m_recording
=
false
;
return
true
;
// if success
}
// first reset ...
reset
();
// TODO (after first working release):
// either allow to record multiple macros with names (to pass to the runmac command)
// and/or have at least two slots to be able to cancel a recording and get the previously
// recorded macro back as the current one.
// ... then start recording
std
::
cerr
<<
"start recording"
<<
std
::
endl
;
QCoreApplication
::
instance
()
->
installEventFilter
(
this
);
m_recording
=
true
;
return
true
;
stop
(
false
);
}
bool
KeyboardMacrosPlugin
::
run
(
KTextEditor
::
View
*
view
)
bool
KeyboardMacrosPlugin
::
play
(
)
{
if
(
m_recording
)
{
// end recording before running macro
record
(
view
);
if
(
m_macro
.
isEmpty
())
{
return
false
;
}
if
(
!
m_keyEvents
.
isEmpty
())
{
QList
<
QKeyEvent
*>::
ConstIterator
it
;
for
(
it
=
m_keyEvents
.
constBegin
();
it
!=
m_keyEvents
.
constEnd
();
it
++
)
{
QKeyEvent
*
keyEvent
=
*
it
;
QKeySequence
s
(
keyEvent
->
key
()
|
keyEvent
->
modifiers
());
qDebug
(
"KeySeq: %s"
,
s
.
toString
().
toUtf8
().
data
());
QCoreApplication
::
sendEvent
(
QCoreApplication
::
instance
(),
keyEvent
);
// FIXME: the above doesn't work
}
Macro
::
Iterator
it
;
for
(
it
=
m_macro
.
begin
();
it
!=
m_macro
.
end
();
it
++
)
{
QKeyEvent
*
keyEvent
=
*
it
;
QKeySequence
s
(
keyEvent
->
key
()
|
keyEvent
->
modifiers
());
keyEvent
->
setAccepted
(
false
);
qApp
->
sendEvent
(
qApp
->
focusWidget
(),
keyEvent
);
}
return
true
;
}
bool
KeyboardMacrosPlugin
::
i
sRecord
ing
()
void
KeyboardMacrosPlugin
::
s
lot
Record
()
{
return
m_recording
;
if
(
m_recording
)
{
stop
(
true
);
}
else
{
record
();
}
}
void
KeyboardMacrosPlugin
::
slot
Record
()
void
KeyboardMacrosPlugin
::
slot
Play
()
{
if
(
!
KTextEditor
::
Editor
::
instance
()
->
application
()
->
activeMainWindow
()
)
{
return
;
if
(
m_recording
)
{
stop
(
true
)
;
}
KTextEditor
::
View
*
view
(
KTextEditor
::
Editor
::
instance
()
->
application
()
->
activeMainWindow
()
->
activeView
());
if
(
!
view
)
{
return
;
if
(
!
play
())
{
sendMessage
(
i18n
(
"Macro is empty."
),
false
);
}
record
(
view
);
}
void
KeyboardMacrosPlugin
::
slot
Run
()
void
KeyboardMacrosPlugin
::
slot
Cancel
()
{
if
(
!
KTextEditor
::
Editor
::
instance
()
->
application
()
->
activeMainWindow
())
{
return
;
}
KTextEditor
::
View
*
view
(
KTextEditor
::
Editor
::
instance
()
->
application
()
->
activeMainWindow
()
->
activeView
());
if
(
!
view
)
{
if
(
!
m_recording
)
{
return
;
}
run
(
view
);
cancel
();
}
// BEGIN Plugin view to add our actions to the gui
...
...
@@ -166,18 +179,26 @@ KeyboardMacrosPluginView::KeyboardMacrosPluginView(KeyboardMacrosPlugin *plugin,
setXMLFile
(
QStringLiteral
(
"ui.rc"
));
// create record action
QAction
*
rec
=
actionCollection
()
->
addAction
(
QStringLiteral
(
"
reco
rd
_
macro"
));
rec
->
setText
(
i18n
(
"Record
&
Macro..."
));
QAction
*
rec
=
actionCollection
()
->
addAction
(
QStringLiteral
(
"
keyboa
rdmacro
s_record
"
));
rec
->
setText
(
i18n
(
"
&
Record Macro..."
));
actionCollection
()
->
setDefaultShortcut
(
rec
,
Qt
::
CTRL
|
Qt
::
SHIFT
|
Qt
::
Key_K
);
connect
(
rec
,
&
QAction
::
triggered
,
plugin
,
&
KeyboardMacrosPlugin
::
slotRecord
);
// create run action
QAction
*
run
=
actionCollection
()
->
addAction
(
QStringLiteral
(
"run_macro"
));
run
->
setText
(
i18n
(
"&Run Macro"
));
actionCollection
()
->
setDefaultShortcut
(
run
,
Qt
::
CTRL
|
Qt
::
ALT
|
Qt
::
Key_K
);
connect
(
run
,
&
QAction
::
triggered
,
plugin
,
&
KeyboardMacrosPlugin
::
slotRun
);
// TODO: make an entire "Keyboard Macros" submenu with "record", "run", "save as", "run saved"
plugin
->
m_recordAction
=
rec
;
// create cancel action
QAction
*
cancel
=
actionCollection
()
->
addAction
(
QStringLiteral
(
"keyboardmacros_cancel"
));
cancel
->
setText
(
i18n
(
"&Cancel Macro Recording"
));
cancel
->
setEnabled
(
false
);
connect
(
cancel
,
&
QAction
::
triggered
,
plugin
,
&
KeyboardMacrosPlugin
::
slotCancel
);
plugin
->
m_cancelAction
=
cancel
;
// create play action
QAction
*
play
=
actionCollection
()
->
addAction
(
QStringLiteral
(
"keyboardmacros_play"
));
play
->
setText
(
i18n
(
"&Play Macro"
));
actionCollection
()
->
setDefaultShortcut
(
play
,
Qt
::
CTRL
|
Qt
::
ALT
|
Qt
::
Key_K
);
play
->
setEnabled
(
false
);
connect
(
play
,
&
QAction
::
triggered
,
plugin
,
&
KeyboardMacrosPlugin
::
slotPlay
);
plugin
->
m_playAction
=
play
;
// register our gui elements
mainwindow
->
guiFactory
()
->
addClient
(
this
);
...
...
@@ -191,55 +212,5 @@ KeyboardMacrosPluginView::~KeyboardMacrosPluginView()
// END
// BEGIN commands
KeyboardMacrosPluginRecordCommand
::
KeyboardMacrosPluginRecordCommand
(
KeyboardMacrosPlugin
*
plugin
)
:
KTextEditor
::
Command
(
QStringList
()
<<
QStringLiteral
(
"recmac"
),
plugin
)
,
m_plugin
(
plugin
)
{
}
bool
KeyboardMacrosPluginRecordCommand
::
exec
(
KTextEditor
::
View
*
view
,
const
QString
&
,
QString
&
,
const
KTextEditor
::
Range
&
)
{
if
(
m_plugin
->
isRecording
())
{
// remove from the recording the call to this command…
}
if
(
!
m_plugin
->
record
(
view
))
{
// display fail in toolview
}
return
true
;
}
bool
KeyboardMacrosPluginRecordCommand
::
help
(
KTextEditor
::
View
*
,
const
QString
&
,
QString
&
msg
)
{
msg
=
i18n
(
"<qt><p>Usage: <code>recmac</code></p><p>Start/stop recording a keyboard macro.</p></qt>"
);
return
true
;
}
KeyboardMacrosPluginRunCommand
::
KeyboardMacrosPluginRunCommand
(
KeyboardMacrosPlugin
*
plugin
)
:
KTextEditor
::
Command
(
QStringList
()
<<
QStringLiteral
(
"runmac"
),
plugin
)
,
m_plugin
(
plugin
)
{
}
bool
KeyboardMacrosPluginRunCommand
::
exec
(
KTextEditor
::
View
*
view
,
const
QString
&
,
QString
&
,
const
KTextEditor
::
Range
&
)
{
if
(
!
m_plugin
->
run
(
view
))
{
// display fail in toolview
}
return
true
;
// TODO: allow the command to take a name as an argument to run a saved macro (default to the last recorded one)
}
bool
KeyboardMacrosPluginRunCommand
::
help
(
KTextEditor
::
View
*
,
const
QString
&
,
QString
&
msg
)
{
msg
=
i18n
(
"<qt><p>Usage: <code>runmac</code></p><p>Run recorded keyboard macro.</p></qt>"
);
return
true
;
}
// TODO: add a new "savemac" command
// END
// required for KeyboardMacrosPluginFactory vtable
#include
"keyboardmacrosplugin.moc"
addons/keyboardmacros/keyboardmacrosplugin.h
View file @
0f249ec6
...
...
@@ -15,13 +15,16 @@
#include
<KTextEditor/Plugin>
#include
<KTextEditor/View>
class
KeyboardMacrosPluginRecordCommand
;
class
KeyboardMacrosPluginRunCommand
;
class
KeyboardMacrosPluginView
;
typedef
QList
<
QKeyEvent
*>
Macro
;
class
KeyboardMacrosPlugin
:
public
KTextEditor
::
Plugin
{
Q_OBJECT
friend
KeyboardMacrosPluginView
;
public:
explicit
KeyboardMacrosPlugin
(
QObject
*
parent
=
nullptr
,
const
QList
<
QVariant
>
&
=
QList
<
QVariant
>
());
...
...
@@ -29,25 +32,35 @@ public:
QObject
*
createView
(
KTextEditor
::
MainWindow
*
mainWindow
)
override
;
bool
eventFilter
(
QObject
*
obj
,
QEvent
*
event
)
override
;
void
sendMessage
(
const
QString
&
text
,
bool
error
)
;
void
reset
();
bool
record
(
KTextEditor
::
View
*
view
);
bool
run
(
KTextEditor
::
View
*
view
);
bool
isRecording
();
Q_SIGNALS:
void
message
(
const
QVariantMap
&
message
);
public:
bool
eventFilter
(
QObject
*
obj
,
QEvent
*
event
)
override
;
private:
KTextEditor
::
MainWindow
*
m_mainWindow
;
QWidget
*
m_focusWidget
;
QAction
*
m_recordAction
;
QAction
*
m_cancelAction
;
QAction
*
m_playAction
;
bool
m_recording
=
false
;
QList
<
QKeyEvent
*>
m_keyEvents
;
Macro
m_tape
;
Macro
m_macro
;
KeyboardMacrosPluginRecordCommand
*
m_recCommand
;
KeyboardMacrosPluginRunCommand
*
m_runCommand
;
void
record
();
void
stop
(
bool
save
);
void
cancel
();
bool
play
();
public
Q_SLOTS
:
void
slotRecord
();
void
slotRun
();
void
slotCancel
();
void
slotPlay
();
};
/**
...
...
@@ -65,36 +78,4 @@ private:
KTextEditor
::
MainWindow
*
m_mainWindow
;
};
/**
* recmac command
*/
class
KeyboardMacrosPluginRecordCommand
:
public
KTextEditor
::
Command
{
Q_OBJECT
public:
KeyboardMacrosPluginRecordCommand
(
KeyboardMacrosPlugin
*
plugin
);
bool
exec
(
KTextEditor
::
View
*
view
,
const
QString
&
,
QString
&
,
const
KTextEditor
::
Range
&
=
KTextEditor
::
Range
::
invalid
())
override
;
bool
help
(
KTextEditor
::
View
*
view
,
const
QString
&
,
QString
&
msg
)
override
;
private:
KeyboardMacrosPlugin
*
m_plugin
;
};
/**
* runmac command
*/
class
KeyboardMacrosPluginRunCommand
:
public
KTextEditor
::
Command
{
Q_OBJECT
public:
KeyboardMacrosPluginRunCommand
(
KeyboardMacrosPlugin
*
plugin
);
bool
exec
(
KTextEditor
::
View
*
view
,
const
QString
&
,
QString
&
,
const
KTextEditor
::
Range
&
=
KTextEditor
::
Range
::
invalid
())
override
;
bool
help
(
KTextEditor
::
View
*
view
,
const
QString
&
,
QString
&
msg
)
override
;
private:
KeyboardMacrosPlugin
*
m_plugin
;
};
#endif
addons/keyboardmacros/keyboardmacrosplugin.json
View file @
0f249ec6
{
"KPlugin"
:
{
"Description"
:
"Record and
run
keyboard action sequences"
,
"Description"
:
"Record and
play
keyboard action sequences"
,
"Name"
:
"Keyboard Macros"
,
"ServiceTypes"
:
[
"KTextEditor/Plugin"
...
...
addons/keyboardmacros/ui.rc
View file @
0f249ec6
...
...
@@ -4,8 +4,13 @@
<MenuBar>
<Menu
name=
"tools"
>
<text>
&
Tools
</text>
<Action
name=
"record_macro"
group=
"tools_snippets"
/>
<Action
name=
"run_macro"
group=
"tools_snippets"
/>
<Separator/>
<Menu
name=
"keyboardmacros"
noMerge=
"1"
>
<text>
&
Keyboard Macros
</text>
<Action
name=
"keyboardmacros_record"
/>
<Action
name=
"keyboardmacros_cancel"
/>
<Action
name=
"keyboardmacros_play"
/>
</Menu>
</Menu>
</MenuBar>
</gui>
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment