1 #include "mainwindow.h"
2
3 #include <QCloseEvent>
4 #include <QLayout>
5 #include <QSettings>
6 #include <QToolBar>
7
8 namespace NeovimQt {
9
MainWindow(NeovimConnector * c,ShellOptions opts,QWidget * parent)10 MainWindow::MainWindow(NeovimConnector *c, ShellOptions opts, QWidget *parent)
11 :QMainWindow(parent), m_nvim(0), m_errorWidget(0), m_shell(0),
12 m_delayedShow(DelayedShow::Disabled), m_tabline(0), m_tabline_bar(0),
13 m_shell_options(opts), m_neovim_requested_close(false)
14 {
15 m_errorWidget = new ErrorWidget();
16 m_stack.addWidget(m_errorWidget);
17 connect(m_errorWidget, &ErrorWidget::reconnectNeovim,
18 this, &MainWindow::reconnectNeovim);
19 setCentralWidget(&m_stack);
20
21 init(c);
22 }
23
init(NeovimConnector * c)24 void MainWindow::init(NeovimConnector *c)
25 {
26 if (m_shell) {
27 m_shell->deleteLater();
28 m_stack.removeWidget(m_shell);
29 }
30 if (m_nvim) {
31 m_nvim->deleteLater();
32 }
33
34 m_tabline_bar = addToolBar("tabline");
35 m_tabline_bar->setObjectName("tabline");
36 m_tabline_bar->setAllowedAreas(Qt::TopToolBarArea);
37 m_tabline_bar->setMovable(false);
38 m_tabline_bar->setFloatable(false);
39 // Avoid margins around the tabbar
40 m_tabline_bar->layout()->setContentsMargins(0, 0, 0, 0);
41
42 m_tabline = new QTabBar(m_tabline_bar);
43 m_tabline->setDrawBase(false);
44 m_tabline->setExpanding(false);
45 m_tabline->setDocumentMode(true);
46 m_tabline->setFocusPolicy(Qt::NoFocus);
47 connect(m_tabline, &QTabBar::currentChanged,
48 this, &MainWindow::changeTab);
49
50 m_tabline_bar->addWidget(m_tabline);
51 m_tabline_bar->setVisible(m_shell_options.enable_ext_tabline);
52
53 // Context menu and actions for right-click
54 m_contextMenu = new QMenu();
55 m_actCut = new QAction(QIcon::fromTheme("edit-cut"), QString("Cut"), nullptr /*parent*/);
56 m_actCopy = new QAction(QIcon::fromTheme("edit-copy"), QString("Copy"), nullptr /*parent*/);
57 m_actPaste = new QAction(QIcon::fromTheme("edit-paste"), QString("Paste"), nullptr /*parent*/);
58 m_actSelectAll = new QAction(QIcon::fromTheme("edit-select-all"), QString("Select All"),
59 nullptr /*parent*/);
60 m_contextMenu->addAction(m_actCut);
61 m_contextMenu->addAction(m_actCopy);
62 m_contextMenu->addAction(m_actPaste);
63 m_contextMenu->addSeparator();
64 m_contextMenu->addAction(m_actSelectAll);
65
66 m_nvim = c;
67
68 m_tree = new TreeView(c);
69 m_shell = new Shell(c, m_shell_options);
70
71 // GuiScrollBar
72 m_scrollbar = new ScrollBar{ m_nvim };
73
74 // ShellWidget + GuiScrollBar Layout
75 // QSplitter does not allow layouts directly: QWidget { HLayout { ShellWidget, QScrollBar } }
76 QWidget* shellScrollable{ new QWidget() };
77 QHBoxLayout* layout{ new QHBoxLayout() };
78 layout->setSpacing(0);
79 layout->setMargin(0);
80 layout->addWidget(m_shell);
81 layout->addWidget(m_scrollbar);
82 shellScrollable->setLayout(layout);
83
84 m_window = new QSplitter();
85 m_window->addWidget(m_tree);
86 m_tree->hide();
87 m_window->addWidget(shellScrollable);
88
89 m_stack.insertWidget(1, m_window);
90 m_stack.setCurrentIndex(1);
91
92 connect(m_shell, SIGNAL(neovimAttached(bool)),
93 this, SLOT(neovimAttachmentChanged(bool)));
94 connect(m_shell, SIGNAL(neovimTitleChanged(const QString &)),
95 this, SLOT(neovimSetTitle(const QString &)));
96 connect(m_shell, &Shell::neovimResized,
97 this, &MainWindow::neovimWidgetResized);
98 connect(m_shell, &Shell::neovimMaximized,
99 this, &MainWindow::neovimMaximized);
100 connect(m_shell, &Shell::neovimSuspend,
101 this, &MainWindow::neovimSuspend);
102 connect(m_shell, &Shell::neovimFullScreen,
103 this, &MainWindow::neovimFullScreen);
104 connect(m_shell, &Shell::neovimGuiCloseRequest,
105 this, &MainWindow::neovimGuiCloseRequest);
106 connect(m_nvim, &NeovimConnector::processExited,
107 this, &MainWindow::neovimExited);
108 connect(m_nvim, &NeovimConnector::error,
109 this, &MainWindow::neovimError);
110 connect(m_shell, &Shell::neovimIsUnsupported,
111 this, &MainWindow::neovimIsUnsupported);
112 connect(m_shell, &Shell::neovimExtTablineSet,
113 this, &MainWindow::extTablineSet);
114 connect(m_shell, &Shell::neovimTablineUpdate,
115 this, &MainWindow::neovimTablineUpdate);
116 connect(m_shell, &Shell::neovimShowtablineSet,
117 this, &MainWindow::neovimShowtablineSet);
118 connect(m_shell, &Shell::neovimShowContextMenu,
119 this, &MainWindow::neovimShowContextMenu);
120 connect(m_actCut, &QAction::triggered,
121 this, &MainWindow::neovimSendCut);
122 connect(m_actCopy, &QAction::triggered,
123 this, &MainWindow::neovimSendCopy);
124 connect(m_actPaste, &QAction::triggered,
125 this, &MainWindow::neovimSendPaste);
126 connect(m_actSelectAll, &QAction::triggered,
127 this, &MainWindow::neovimSendSelectAll);
128 m_shell->setFocus(Qt::OtherFocusReason);
129
130 if (m_nvim->errorCause()) {
131 neovimError(m_nvim->errorCause());
132 }
133 }
134
neovimAttached() const135 bool MainWindow::neovimAttached() const
136 {
137 return (m_shell != NULL && m_shell->neovimAttached());
138 }
139
140 /** The Neovim process has exited */
neovimExited(int status)141 void MainWindow::neovimExited(int status)
142 {
143 showIfDelayed();
144
145 if (m_nvim->errorCause() != NeovimConnector::NoError) {
146 m_errorWidget->setText(m_nvim->errorString());
147 m_errorWidget->showReconnect(m_nvim->canReconnect());
148 m_stack.setCurrentIndex(0);
149 } else if (status != 0) {
150 m_errorWidget->setText(QString("Neovim exited with status code (%1)").arg(status));
151 m_errorWidget->showReconnect(m_nvim->canReconnect());
152 m_stack.setCurrentIndex(0);
153 } else {
154 close();
155 }
156 }
neovimError(NeovimConnector::NeovimError err)157 void MainWindow::neovimError(NeovimConnector::NeovimError err)
158 {
159 showIfDelayed();
160
161 switch(err) {
162 case NeovimConnector::FailedToStart:
163 m_errorWidget->setText("Unable to start nvim: " + m_nvim->errorString());
164 break;
165 default:
166 m_errorWidget->setText(m_nvim->errorString());
167 }
168 m_errorWidget->showReconnect(m_nvim->canReconnect());
169 m_stack.setCurrentIndex(0);
170 }
neovimIsUnsupported()171 void MainWindow::neovimIsUnsupported()
172 {
173 showIfDelayed();
174 m_errorWidget->setText(QString("Cannot connect to this Neovim, required API version 1, found [%1-%2]")
175 .arg(m_nvim->apiCompatibility())
176 .arg(m_nvim->apiLevel()));
177 m_errorWidget->showReconnect(m_nvim->canReconnect());
178 m_stack.setCurrentIndex(0);
179 }
180
neovimSetTitle(const QString & title)181 void MainWindow::neovimSetTitle(const QString &title)
182 {
183 this->setWindowTitle(title);
184 }
185
neovimWidgetResized()186 void MainWindow::neovimWidgetResized()
187 {
188 m_shell->resizeNeovim(m_shell->size());
189 }
190
neovimMaximized(bool set)191 void MainWindow::neovimMaximized(bool set)
192 {
193 if (set) {
194 setWindowState(windowState() | Qt::WindowMaximized);
195 } else {
196 setWindowState(windowState() & ~Qt::WindowMaximized);
197 }
198 }
199
neovimSuspend()200 void MainWindow::neovimSuspend()
201 {
202 qDebug() << "Minimizing window";
203 setWindowState(windowState() | Qt::WindowMinimized);
204 }
205
neovimFullScreen(bool set)206 void MainWindow::neovimFullScreen(bool set)
207 {
208 if (set) {
209 setWindowState(windowState() | Qt::WindowFullScreen);
210 } else {
211 setWindowState(windowState() & ~Qt::WindowFullScreen);
212 }
213 }
214
neovimGuiCloseRequest()215 void MainWindow::neovimGuiCloseRequest()
216 {
217 m_neovim_requested_close = true;
218 QMainWindow::close();
219 m_neovim_requested_close = false;
220 }
221
reconnectNeovim()222 void MainWindow::reconnectNeovim()
223 {
224 if (m_nvim->canReconnect()) {
225 init(m_nvim->reconnect());
226 }
227 m_stack.setCurrentIndex(1);
228 }
229
closeEvent(QCloseEvent * ev)230 void MainWindow::closeEvent(QCloseEvent *ev)
231 {
232 // Do not save window geometry in '--fullscreen' mode. If saved, all
233 // subsequent Neovim-Qt sessions would default to fullscreen mode.
234 if (!isFullScreen()) {
235 saveWindowGeometry();
236 }
237
238 if (m_neovim_requested_close) {
239 // If this was requested by nvim, shutdown
240 QWidget::closeEvent(ev);
241 } else if (m_shell->close()) {
242 // otherwise only if the Neovim shell closes too
243 QWidget::closeEvent(ev);
244 } else {
245 ev->ignore();
246 }
247 }
changeEvent(QEvent * ev)248 void MainWindow::changeEvent( QEvent *ev)
249 {
250 if (ev->type() == QEvent::WindowStateChange && isWindow()) {
251 m_shell->updateGuiWindowState(windowState());
252 }
253 QWidget::changeEvent(ev);
254 }
255
256 /// Call show() after a 1s delay or when Neovim attachment
257 /// is complete, whichever comes first
delayedShow(DelayedShow type)258 void MainWindow::delayedShow(DelayedShow type)
259 {
260 m_delayedShow = type;
261 if (m_nvim->errorCause() != NeovimConnector::NoError) {
262 showIfDelayed();
263 return;
264 }
265
266 if (type != DelayedShow::Disabled) {
267 QTimer *t = new QTimer(this);
268 t->setSingleShot(true);
269 t->setInterval(1000);
270 connect(m_shell, &Shell::neovimResized, this, &MainWindow::showIfDelayed);
271 connect(t, &QTimer::timeout, this, &MainWindow::showIfDelayed);
272 t->start();
273 }
274 }
275
showIfDelayed()276 void MainWindow::showIfDelayed()
277 {
278 if (!isVisible()) {
279 if (m_delayedShow == DelayedShow::Normal) {
280 show();
281 } else if (m_delayedShow == DelayedShow::Maximized) {
282 showMaximized();
283 } else if (m_delayedShow == DelayedShow::FullScreen) {
284 showFullScreen();
285 }
286 }
287 m_delayedShow = DelayedShow::Disabled;
288 }
289
neovimAttachmentChanged(bool attached)290 void MainWindow::neovimAttachmentChanged(bool attached)
291 {
292 emit neovimAttached(attached);
293 if (attached) {
294 if (isWindow() && m_shell != NULL) {
295 m_shell->updateGuiWindowState(windowState());
296 }
297 } else {
298 m_tabline->deleteLater();
299 m_tabline_bar->deleteLater();
300 }
301 }
302
shell()303 Shell* MainWindow::shell()
304 {
305 return m_shell;
306 }
307
extTablineSet(bool val)308 void MainWindow::extTablineSet(bool val)
309 {
310 bool old = m_shell_options.enable_ext_tabline;
311 m_shell_options.enable_ext_tabline = val;
312 // redraw if state changed
313 if (old != m_shell_options.enable_ext_tabline) {
314 if (!val) m_tabline_bar->setVisible(false);
315 m_nvim->api0()->vim_command("silent! redraw!");
316 }
317 }
318
neovimShowtablineSet(int val)319 void MainWindow::neovimShowtablineSet(int val)
320 {
321 m_shell_options.nvim_show_tabline = val;
322 }
323
neovimTablineUpdate(int64_t curtab,QList<Tab> tabs)324 void MainWindow::neovimTablineUpdate(int64_t curtab, QList<Tab> tabs)
325 {
326 if (!m_shell_options.enable_ext_tabline) {
327 return;
328 }
329
330 // remove extra tabs
331 for (int index=tabs.size(); index<m_tabline->count(); index++) {
332 m_tabline->removeTab(index);
333 }
334
335 for (int index=0; index<tabs.size(); index++) {
336 // Escape & in tab name otherwise it will be interpreted as
337 // a keyboard shortcut (#357) - escaping is done using &&
338 QString text = tabs[index].name;
339 text.replace("&", "&&");
340
341 if (m_tabline->count() <= index) {
342 m_tabline->addTab(text);
343 } else {
344 m_tabline->setTabText(index, text);
345 }
346
347 m_tabline->setTabToolTip(index, text);
348 m_tabline->setTabData(index, QVariant::fromValue(tabs[index].tab));
349
350 if (curtab == tabs[index].tab) {
351 m_tabline->setCurrentIndex(index);
352 }
353 }
354
355 // hide/show the tabline toolbar
356 if (m_shell_options.nvim_show_tabline==0) {
357 m_tabline_bar->setVisible(false);
358 } else if (m_shell_options.nvim_show_tabline==2) {
359 m_tabline_bar->setVisible(true);
360 } else {
361 m_tabline_bar->setVisible(tabs.size() > 1);
362 }
363
364 Q_ASSERT(tabs.size() == m_tabline->count());
365 }
366
neovimShowContextMenu()367 void MainWindow::neovimShowContextMenu()
368 {
369 m_contextMenu->popup(QCursor::pos());
370 }
371
neovimSendCut()372 void MainWindow::neovimSendCut()
373 {
374 m_nvim->api0()->vim_command_output(R"(normal! "+x)");
375 }
376
377 void MainWindow::neovimSendCopy()
378 {
379 m_nvim->api0()->vim_command(R"(normal! "+y)");
380 }
381
neovimSendPaste()382 void MainWindow::neovimSendPaste()
383 {
384 m_nvim->api0()->vim_command(R"(normal! "+gP)");
385 }
386
387 void MainWindow::neovimSendSelectAll()
388 {
389 m_nvim->api0()->vim_command("normal! ggVG");
390 }
391
392 void MainWindow::changeTab(int index)
393 {
394 if (!m_shell_options.enable_ext_tabline) {
395 return;
396 }
397
398 if (m_nvim->api2() == NULL) {
399 return;
400 }
401
402 int64_t tab = m_tabline->tabData(index).toInt();
403 m_nvim->api2()->nvim_set_current_tabpage(tab);
404 }
405
406 void MainWindow::saveWindowGeometry()
407 {
408 QSettings settings{ "nvim-qt", "window-geometry" };
409 settings.setValue("window_geometry", saveGeometry());
410 settings.setValue("window_state", saveState());
411 }
412
413 void MainWindow::restoreWindowGeometry()
414 {
415 QSettings settings{ "nvim-qt", "window-geometry" };
416 restoreGeometry(settings.value("window_geometry").toByteArray());
417 restoreState(settings.value("window_state").toByteArray());
418 }
419
420 } // namespace NeovimQt
421