Skip to content

Commit eaa39b9

Browse files
Add Shift+Scroll horizontal scrolling (#1090)
* Add Shift+Scroll horizontal scrolling * Refactor code --------- Co-authored-by: dail8859 <dail8859@yahoo.com>
1 parent e8ea71a commit eaa39b9

8 files changed

Lines changed: 198 additions & 2 deletions

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ qt_add_executable(NotepadNext
183183
ScintillaSorter.h ScintillaSorter.cpp
184184
widgets/TabsQuickActionsBar.cpp
185185
widgets/TabsQuickActionsBar.h
186+
ShiftMiddleClickBlocker.h ShiftMiddleClickBlocker.cpp
187+
ShiftWheelToHorizontalScrollFilter.h ShiftWheelToHorizontalScrollFilter.cpp
186188
)
187189

188190
set_target_properties(NotepadNext PROPERTIES

src/ShiftMiddleClickBlocker.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2026 Justin Dailey
4+
*
5+
* Notepad Next 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 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "ShiftMiddleClickBlocker.h"
20+
21+
#include <QEvent>
22+
#include <QMouseEvent>
23+
24+
ShiftMiddleClickBlocker::ShiftMiddleClickBlocker(QObject *parent)
25+
: QObject(parent)
26+
{
27+
}
28+
29+
bool ShiftMiddleClickBlocker::eventFilter(QObject *obj, QEvent *event)
30+
{
31+
if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonDblClick) {
32+
return QObject::eventFilter(obj, event);
33+
}
34+
35+
auto *mouseEvent = static_cast<QMouseEvent *>(event);
36+
37+
if (mouseEvent->button() == Qt::MiddleButton && (mouseEvent->modifiers() & Qt::ShiftModifier)) {
38+
return true;
39+
}
40+
41+
return QObject::eventFilter(obj, event);
42+
}

src/ShiftMiddleClickBlocker.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2026 Justin Dailey
4+
*
5+
* Notepad Next 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 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <QObject>
22+
23+
class ShiftMiddleClickBlocker : public QObject
24+
{
25+
Q_OBJECT
26+
27+
public:
28+
explicit ShiftMiddleClickBlocker(QObject *parent = nullptr);
29+
30+
protected:
31+
bool eventFilter(QObject *obj, QEvent *event) override;
32+
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2026 Justin Dailey
4+
*
5+
* Notepad Next 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 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "ShiftWheelToHorizontalScrollFilter.h"
20+
21+
#include <QApplication>
22+
#include <QEvent>
23+
#include <QWheelEvent>
24+
25+
26+
bool isHorizontalWheelEvent(QWheelEvent *event)
27+
{
28+
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
29+
return event->angleDelta().y() == 0;
30+
#else
31+
return event->orientation() == Qt::Horizontal;
32+
#endif
33+
}
34+
35+
ShiftWheelToHorizontalScrollFilter::ShiftWheelToHorizontalScrollFilter(QObject *parent)
36+
: QObject(parent)
37+
{
38+
}
39+
40+
bool ShiftWheelToHorizontalScrollFilter::eventFilter(QObject *obj, QEvent *event)
41+
{
42+
if (event->type() != QEvent::Wheel) {
43+
return QObject::eventFilter(obj, event);
44+
}
45+
46+
auto *wheelEvent = static_cast<QWheelEvent *>(event);
47+
48+
if (!(wheelEvent->modifiers() & Qt::ShiftModifier)
49+
|| (wheelEvent->modifiers() & Qt::ControlModifier)
50+
|| isHorizontalWheelEvent(wheelEvent)) {
51+
return QObject::eventFilter(obj, event);
52+
}
53+
54+
const QPoint angleDelta(wheelEvent->angleDelta().y(), wheelEvent->angleDelta().x());
55+
56+
const QPoint pixelDelta(wheelEvent->pixelDelta().y(), wheelEvent->pixelDelta().x());
57+
58+
QWheelEvent horizontalEvent(
59+
wheelEvent->position(),
60+
wheelEvent->globalPosition(),
61+
pixelDelta,
62+
angleDelta,
63+
wheelEvent->buttons(),
64+
wheelEvent->modifiers() & ~Qt::ShiftModifier,
65+
wheelEvent->phase(),
66+
wheelEvent->inverted(),
67+
wheelEvent->source(),
68+
wheelEvent->pointingDevice());
69+
70+
QApplication::sendEvent(obj, &horizontalEvent);
71+
return true;
72+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* This file is part of Notepad Next.
3+
* Copyright 2026 Justin Dailey
4+
*
5+
* Notepad Next 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 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* Notepad Next is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with Notepad Next. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <QObject>
22+
23+
class ShiftWheelToHorizontalScrollFilter : public QObject
24+
{
25+
Q_OBJECT
26+
27+
public:
28+
explicit ShiftWheelToHorizontalScrollFilter(QObject *parent = nullptr);
29+
30+
protected:
31+
bool eventFilter(QObject *obj, QEvent *event) override;
32+
};

src/ZoomEventWatcher.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,4 @@ bool ZoomEventWatcher::eventFilter(QObject *obj, QEvent *event)
6464
}
6565

6666
return QObject::eventFilter(obj, event);
67-
}
67+
}

src/dialogs/MainWindow.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
#include "MacroEditorDialog.h"
8282

8383
#include "ZoomEventWatcher.h"
84+
#include "ShiftMiddleClickBlocker.h"
85+
#include "ShiftWheelToHorizontalScrollFilter.h"
86+
8487
#include "FileDialogHelpers.h"
8588

8689
#include "HtmlConverter.h"
@@ -94,7 +97,9 @@
9497
MainWindow::MainWindow(NotepadNextApplication *app) :
9598
ui(new Ui::MainWindow),
9699
app(app),
97-
zoomEventWatcher(new ZoomEventWatcher(this))
100+
zoomEventWatcher(new ZoomEventWatcher(this)),
101+
shiftMiddleClickBlocker(new ShiftMiddleClickBlocker(this)),
102+
shiftWheelToHorizontalScrollFilter(new ShiftWheelToHorizontalScrollFilter(this))
98103
{
99104
qInfo(Q_FUNC_INFO);
100105

@@ -2095,6 +2100,13 @@ void MainWindow::addEditor(ScintillaNext *editor)
20952100
connect(editor, &ScintillaNext::renamed, this, [=, this]() { updateFileStatusBasedUi(editor); });
20962101
connect(editor, &ScintillaNext::updateUi, this, &MainWindow::updateDocumentBasedUi);
20972102

2103+
// Scintilla pastes the primary selection on middle-click. Suppress an
2104+
// accidental wheel-button press while Shift is held for horizontal scrolling.
2105+
editor->viewport()->installEventFilter(shiftMiddleClickBlocker);
2106+
2107+
// Translate shift+vertical wheel to vertical
2108+
editor->viewport()->installEventFilter(shiftWheelToHorizontalScrollFilter);
2109+
20982110
// Watch for any zoom events (Ctrl+Scroll or pinch-to-zoom (Qt translates it as Ctrl+Scroll)) so that the event
20992111
// can be handled before the ScintillaEditBase widget, so that it can be applied to all editors to keep zoom level equal.
21002112
// NOTE: Need to install this on the scroll area's viewport, not on the editor widget itself...that was painful to learn

src/dialogs/MainWindow.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class Macro;
4040
class Settings;
4141
class QuickFindWidget;
4242
class ZoomEventWatcher;
43+
class ShiftMiddleClickBlocker;
44+
class ShiftWheelToHorizontalScrollFilter;
4345
class Converter;
4446
class DefaultDirectoryManager;
4547
class TabsQuickActionsBar;
@@ -196,6 +198,8 @@ private slots:
196198
DefaultDirectoryManager *defaultDirectoryManager;
197199

198200
ZoomEventWatcher *zoomEventWatcher;
201+
ShiftMiddleClickBlocker *shiftMiddleClickBlocker;
202+
ShiftWheelToHorizontalScrollFilter *shiftWheelToHorizontalScrollFilter;
199203
int zoomLevel = 0;
200204
int contextMenuPos = 0;
201205
QMenu *buildMenu(QStringList actionNames);

0 commit comments

Comments
 (0)