viewarea.cpp 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  1. #include "viewarea.h"
  2. #include <QShortcut>
  3. #include <QLabel>
  4. #include <QLayout>
  5. #include <QSize>
  6. #include <QSplitter>
  7. #include <QCoreApplication>
  8. #include <QDragEnterEvent>
  9. #include <QDropEvent>
  10. #include <QTimer>
  11. #include <QApplication>
  12. #include <QSet>
  13. #include <QHash>
  14. #include "viewwindow.h"
  15. #include "mainwindow.h"
  16. #include "events.h"
  17. #include <utils/widgetutils.h>
  18. #include <utils/docsutils.h>
  19. #include <utils/urldragdroputils.h>
  20. #include <core/vnotex.h>
  21. #include <core/configmgr.h>
  22. #include <core/coreconfig.h>
  23. #include <core/sessionconfig.h>
  24. #include <core/fileopenparameters.h>
  25. #include <notebook/node.h>
  26. #include <notebook/notebook.h>
  27. using namespace vnotex;
  28. ViewArea::ViewArea(QWidget *p_parent)
  29. : QWidget(p_parent),
  30. NavigationMode(NavigationMode::Type::DoubleKeys, this)
  31. {
  32. setupUI();
  33. setAcceptDrops(true);
  34. setupShortcuts();
  35. connect(this, &ViewArea::viewSplitsCountChanged,
  36. this, &ViewArea::handleViewSplitsCountChange);
  37. auto mainWindow = VNoteX::getInst().getMainWindow();
  38. connect(mainWindow, &MainWindow::mainWindowClosed,
  39. this, [this](const QSharedPointer<Event> &p_event) {
  40. if (p_event->m_handled) {
  41. return;
  42. }
  43. if (ConfigMgr::getInst().getCoreConfig().isRecoverLastSessionOnStartEnabled()) {
  44. saveSession();
  45. }
  46. bool ret = close(false);
  47. if (!ret) {
  48. p_event->m_response = false;
  49. p_event->m_handled = true;
  50. }
  51. });
  52. connect(mainWindow, &MainWindow::mainWindowClosedOnQuit,
  53. this, [this]() {
  54. close(true);
  55. });
  56. if (ConfigMgr::getInst().getCoreConfig().isRecoverLastSessionOnStartEnabled()) {
  57. connect(mainWindow, &MainWindow::mainWindowStarted,
  58. this, &ViewArea::loadSession);
  59. }
  60. connect(&VNoteX::getInst(), &VNoteX::nodeAboutToMove,
  61. this, &ViewArea::handleNodeChange);
  62. connect(&VNoteX::getInst(), &VNoteX::nodeAboutToRemove,
  63. this, &ViewArea::handleNodeChange);
  64. connect(&VNoteX::getInst(), &VNoteX::nodeAboutToRename,
  65. this, &ViewArea::handleNodeChange);
  66. connect(&VNoteX::getInst(), &VNoteX::nodeAboutToReload,
  67. this, &ViewArea::handleNodeChange);
  68. auto &configMgr = ConfigMgr::getInst();
  69. connect(&configMgr, &ConfigMgr::editorConfigChanged,
  70. this, [this]() {
  71. forEachViewWindow([](ViewWindow *p_win) {
  72. p_win->handleEditorConfigChange();
  73. return true;
  74. });
  75. });
  76. m_fileCheckTimer = new QTimer(this);
  77. m_fileCheckTimer->setSingleShot(false);
  78. m_fileCheckTimer->setInterval(2000);
  79. connect(m_fileCheckTimer, &QTimer::timeout,
  80. this, [this]() {
  81. auto win = getCurrentViewWindow();
  82. if (win) {
  83. win->checkFileMissingOrChangedOutsidePeriodically();
  84. }
  85. });
  86. connect(qApp, &QApplication::focusChanged,
  87. this, [this](QWidget *p_old, QWidget *p_now) {
  88. if (!p_now) {
  89. m_fileCheckTimer->stop();
  90. } else if (!p_old && m_currentSplit) {
  91. m_fileCheckTimer->start();
  92. }
  93. });
  94. }
  95. ViewArea::~ViewArea()
  96. {
  97. // All splits/workspaces/windows should be released during close() before destruction.
  98. Q_ASSERT(m_splits.isEmpty() && m_currentSplit == nullptr);
  99. Q_ASSERT(m_workspaces.isEmpty());
  100. }
  101. void ViewArea::handleNodeChange(Node *p_node, const QSharedPointer<Event> &p_event)
  102. {
  103. if (p_event->m_handled) {
  104. return;
  105. }
  106. bool ret = close(p_node, false);
  107. p_event->m_response = ret;
  108. p_event->m_handled = !ret;
  109. }
  110. void ViewArea::setupUI()
  111. {
  112. setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  113. m_mainLayout = new QVBoxLayout(this);
  114. m_mainLayout->setContentsMargins(0, 0, 0, 0);
  115. }
  116. QSize ViewArea::sizeHint() const
  117. {
  118. const QSize preferredSize(400, 300);
  119. auto sz = QWidget::sizeHint();
  120. if (sz.width() < preferredSize.width()) {
  121. sz = preferredSize;
  122. }
  123. return sz;
  124. }
  125. void ViewArea::openBuffer(Buffer *p_buffer, const QSharedPointer<FileOpenParameters> &p_paras)
  126. {
  127. // We allow multiple ViewWindows of the same buffer in different workspaces by default.
  128. QVector<ViewWindow *> wins;
  129. if (!p_paras->m_alwaysNewWindow) {
  130. wins = findBufferInViewSplits(p_buffer);
  131. }
  132. if (wins.isEmpty()) {
  133. if (!m_currentSplit) {
  134. addFirstViewSplit();
  135. }
  136. Q_ASSERT(m_currentSplit);
  137. // Create a ViewWindow from @p_buffer.
  138. auto window = p_buffer->createViewWindow(p_paras, nullptr);
  139. m_currentSplit->addViewWindow(window);
  140. setCurrentViewWindow(window);
  141. } else {
  142. auto selectedWin = wins.first();
  143. for (auto win : wins) {
  144. // Prefer window in current split.
  145. if (win->getViewSplit() == m_currentSplit) {
  146. selectedWin = win;
  147. break;
  148. }
  149. }
  150. selectedWin->openTwice(p_paras);
  151. setCurrentViewWindow(selectedWin);
  152. }
  153. if (p_paras->m_focus) {
  154. auto win = getCurrentViewWindow();
  155. if (win) {
  156. win->setFocus(Qt::OtherFocusReason);
  157. }
  158. }
  159. }
  160. QVector<ViewWindow *> ViewArea::findBufferInViewSplits(const Buffer *p_buffer) const
  161. {
  162. QVector<ViewWindow *> wins;
  163. for (auto split : m_splits) {
  164. auto winsInSplit = split->findBuffer(p_buffer);
  165. if (!winsInSplit.isEmpty()) {
  166. wins.append(winsInSplit);
  167. }
  168. }
  169. return wins;
  170. }
  171. ViewSplit *ViewArea::createViewSplit(QWidget *p_parent, ID p_viewSplitId)
  172. {
  173. auto workspace = createWorkspace();
  174. m_workspaces.push_back(workspace);
  175. ID id = p_viewSplitId;
  176. if (id == InvalidViewSplitId) {
  177. id = m_nextViewSplitId++;
  178. } else {
  179. Q_ASSERT(p_viewSplitId >= m_nextViewSplitId);
  180. m_nextViewSplitId = id + 1;
  181. }
  182. auto split = new ViewSplit(m_workspaces, workspace, id, p_parent);
  183. connect(split, &ViewSplit::viewWindowCloseRequested,
  184. this, [this](ViewWindow *p_win) {
  185. closeViewWindow(p_win, false, true);
  186. });
  187. connect(split, &ViewSplit::verticalSplitRequested,
  188. this, [this](ViewSplit *p_split) {
  189. splitViewSplit(p_split, SplitType::Vertical);
  190. });
  191. connect(split, &ViewSplit::horizontalSplitRequested,
  192. this, [this](ViewSplit *p_split) {
  193. splitViewSplit(p_split, SplitType::Horizontal);
  194. });
  195. connect(split, &ViewSplit::maximizeSplitRequested,
  196. this, &ViewArea::maximizeViewSplit);
  197. connect(split, &ViewSplit::distributeSplitsRequested,
  198. this, &ViewArea::distributeViewSplits);
  199. connect(split, &ViewSplit::removeSplitRequested,
  200. this, [this](ViewSplit *p_split) {
  201. removeViewSplit(p_split, false);
  202. });
  203. connect(split, &ViewSplit::removeSplitAndWorkspaceRequested,
  204. this, [this](ViewSplit *p_split) {
  205. removeViewSplit(p_split, true);
  206. });
  207. connect(split, &ViewSplit::newWorkspaceRequested,
  208. this, &ViewArea::newWorkspaceInViewSplit);
  209. connect(split, &ViewSplit::removeWorkspaceRequested,
  210. this, [this](ViewSplit *p_split) {
  211. removeWorkspaceInViewSplit(p_split, true);
  212. });
  213. connect(split, &ViewSplit::focused,
  214. this, [this](ViewSplit *p_split) {
  215. setCurrentViewSplit(p_split, false);
  216. checkCurrentViewWindowChange();
  217. });
  218. connect(split, &ViewSplit::currentViewWindowChanged,
  219. this, [this](ViewWindow *p_win) {
  220. checkCurrentViewWindowChange();
  221. if (shouldUseGlobalStatusWidget()) {
  222. if (p_win) {
  223. p_win->setStatusWidgetVisible(false);
  224. }
  225. m_currentStatusWidget = p_win ? p_win->statusWidget() : nullptr;
  226. emit statusWidgetChanged(m_currentStatusWidget.get());
  227. } else {
  228. Q_ASSERT(!m_currentStatusWidget);
  229. if (p_win) {
  230. p_win->setStatusWidgetVisible(true);
  231. }
  232. }
  233. });
  234. return split;
  235. }
  236. void ViewArea::showSceneWidget()
  237. {
  238. Q_ASSERT(!m_sceneWidget);
  239. Q_ASSERT(m_splits.isEmpty());
  240. auto text = DocsUtils::getDocText(QStringLiteral("get_started.txt"));
  241. // TODO: a more informative widget, such as adding workspace list and LRU files.
  242. m_sceneWidget = new QLabel(text, this);
  243. m_mainLayout->addWidget(m_sceneWidget);
  244. }
  245. void ViewArea::hideSceneWidget()
  246. {
  247. Q_ASSERT(m_sceneWidget);
  248. m_mainLayout->removeWidget(m_sceneWidget);
  249. delete m_sceneWidget;
  250. m_sceneWidget = nullptr;
  251. }
  252. void ViewArea::addFirstViewSplit()
  253. {
  254. Q_ASSERT(!m_currentSplit && m_splits.isEmpty());
  255. auto split = createViewSplit(this);
  256. m_splits.push_back(split);
  257. hideSceneWidget();
  258. m_mainLayout->addWidget(split);
  259. postFirstViewSplit();
  260. }
  261. void ViewArea::postFirstViewSplit()
  262. {
  263. Q_ASSERT(!m_splits.isEmpty());
  264. setCurrentViewSplit(m_splits.first(), false);
  265. emit viewSplitsCountChanged();
  266. checkCurrentViewWindowChange();
  267. m_fileCheckTimer->start();
  268. }
  269. static ViewSplit *fetchFirstChildViewSplit(const QSplitter *p_splitter)
  270. {
  271. if (p_splitter->count() == 0) {
  272. return nullptr;
  273. }
  274. auto child = p_splitter->widget(0);
  275. auto split = dynamic_cast<ViewSplit *>(child);
  276. if (split) {
  277. return split;
  278. }
  279. auto childSplitter = dynamic_cast<QSplitter *>(child);
  280. Q_ASSERT(childSplitter);
  281. return fetchFirstChildViewSplit(childSplitter);
  282. }
  283. void ViewArea::removeViewSplit(ViewSplit *p_split, bool p_removeWorkspace)
  284. {
  285. if (p_removeWorkspace) {
  286. // Remove workspace.
  287. bool ret = removeWorkspaceInViewSplit(p_split, false);
  288. if (!ret) {
  289. return;
  290. }
  291. } else {
  292. // Detach workspace.
  293. p_split->setWorkspace(nullptr);
  294. }
  295. // Remove split.
  296. disconnect(p_split, 0, this, 0);
  297. disconnect(this, 0, p_split, 0);
  298. m_splits.removeAll(p_split);
  299. // Get new current split.
  300. ViewSplit *newCurrentSplit = nullptr;
  301. auto splitter = tryGetParentSplitter(p_split);
  302. if (splitter) {
  303. Q_ASSERT(splitter->count() >= 2);
  304. p_split->hide();
  305. p_split->setParent(this);
  306. newCurrentSplit = fetchFirstChildViewSplit(splitter);
  307. if (splitter->count() == 1) {
  308. // Remove the splitter if there is only one child in it after the removal.
  309. unwrapSplitter(splitter);
  310. }
  311. } else {
  312. Q_ASSERT(m_splits.isEmpty());
  313. m_mainLayout->removeWidget(p_split);
  314. if (!m_splits.isEmpty()) {
  315. newCurrentSplit = m_splits.first();
  316. }
  317. }
  318. p_split->deleteLater();
  319. // Show scene widget and update current split.
  320. if (m_splits.isEmpty()) {
  321. Q_ASSERT(newCurrentSplit == nullptr);
  322. setCurrentViewSplit(newCurrentSplit, false);
  323. showSceneWidget();
  324. m_fileCheckTimer->stop();
  325. } else if (m_currentSplit == p_split) {
  326. setCurrentViewSplit(newCurrentSplit, true);
  327. }
  328. emit viewSplitsCountChanged();
  329. checkCurrentViewWindowChange();
  330. }
  331. ViewWindow *ViewArea::getCurrentViewWindow() const
  332. {
  333. auto split = getCurrentViewSplit();
  334. if (split) {
  335. return split->getCurrentViewWindow();
  336. }
  337. return nullptr;
  338. }
  339. void ViewArea::setCurrentViewWindow(ViewWindow *p_win)
  340. {
  341. auto split = p_win->getViewSplit();
  342. Q_ASSERT(split);
  343. split->setCurrentViewWindow(p_win);
  344. setCurrentViewSplit(split, false);
  345. checkCurrentViewWindowChange();
  346. }
  347. ViewSplit *ViewArea::getCurrentViewSplit() const
  348. {
  349. return m_currentSplit;
  350. }
  351. void ViewArea::setCurrentViewSplit(ViewSplit *p_split, bool p_focus)
  352. {
  353. Q_ASSERT(!p_split || m_splits.contains(p_split));
  354. if (p_split == m_currentSplit) {
  355. return;
  356. }
  357. if (m_currentSplit) {
  358. m_currentSplit->setActive(false);
  359. }
  360. m_currentSplit = p_split;
  361. if (m_currentSplit) {
  362. m_currentSplit->setActive(true);
  363. if (p_focus) {
  364. m_currentSplit->focus();
  365. }
  366. }
  367. }
  368. bool ViewArea::closeViewWindow(ViewWindow *p_win, bool p_force, bool p_removeSplitIfEmpty)
  369. {
  370. Q_ASSERT(p_win && p_win->getViewSplit());
  371. // Make it current ViewWindow.
  372. setCurrentViewWindow(p_win);
  373. if (!p_win->aboutToClose(p_force)) {
  374. return false;
  375. }
  376. // Remove the status widget.
  377. if (m_currentStatusWidget && p_win == getCurrentViewWindow()) {
  378. Q_ASSERT(m_currentStatusWidget == p_win->statusWidget());
  379. emit statusWidgetChanged(nullptr);
  380. }
  381. auto split = p_win->getViewSplit();
  382. split->takeViewWindow(p_win);
  383. delete p_win;
  384. if (p_removeSplitIfEmpty && split->getViewWindowCount() == 0) {
  385. // Remove this split and workspace.
  386. removeViewSplit(split, true);
  387. }
  388. return true;
  389. }
  390. QSharedPointer<ViewWorkspace> ViewArea::createWorkspace()
  391. {
  392. // Get the id of the workspace.
  393. ID id = 1;
  394. QSet<ID> usedIds;
  395. for (auto ws : m_workspaces) {
  396. usedIds.insert(ws->m_id);
  397. }
  398. while (true) {
  399. if (usedIds.contains(id)) {
  400. ++id;
  401. } else {
  402. break;
  403. }
  404. }
  405. return QSharedPointer<ViewWorkspace>::create(id);
  406. }
  407. void ViewArea::splitViewSplit(ViewSplit *p_split, SplitType p_type)
  408. {
  409. Q_ASSERT(p_split);
  410. // Create the new split.
  411. auto newSplit = createViewSplit(this);
  412. // Clone a ViewWindow for the same buffer to display in the new split.
  413. {
  414. auto win = p_split->getCurrentViewWindow();
  415. if (win) {
  416. auto buffer = win->getBuffer();
  417. auto newWindow = buffer->createViewWindow(QSharedPointer<FileOpenParameters>::create(), newSplit);
  418. newSplit->addViewWindow(newWindow);
  419. }
  420. }
  421. // Obey Vim's practice, which is the opposite of Qt.
  422. auto orientation = p_type == SplitType::Vertical ? Qt::Horizontal : Qt::Vertical;
  423. auto splitter = tryGetParentSplitter(p_split);
  424. if (splitter) {
  425. int idx = splitter->indexOf(p_split);
  426. if (splitter->orientation() == orientation) {
  427. // Same orientation.
  428. splitter->insertWidget(idx + 1, newSplit);
  429. } else {
  430. // Split it further.
  431. auto newSplitter = createSplitter(orientation, this);
  432. splitter->replaceWidget(idx, newSplitter);
  433. newSplitter->addWidget(p_split);
  434. newSplitter->addWidget(newSplit);
  435. }
  436. } else {
  437. Q_ASSERT(p_split->parent() == this);
  438. m_mainLayout->removeWidget(p_split);
  439. auto newSplitter = createSplitter(orientation, this);
  440. newSplitter->addWidget(p_split);
  441. newSplitter->addWidget(newSplit);
  442. m_mainLayout->addWidget(newSplitter);
  443. }
  444. m_splits.push_back(newSplit);
  445. setCurrentViewSplit(newSplit, true);
  446. // Let Qt decide the size of splitter first.
  447. QCoreApplication::sendPostedEvents();
  448. distributeViewSplitsOfSplitter(tryGetParentSplitter(newSplit));
  449. emit viewSplitsCountChanged();
  450. checkCurrentViewWindowChange();
  451. }
  452. QSplitter *ViewArea::createSplitter(Qt::Orientation p_orientation, QWidget *p_parent) const
  453. {
  454. auto splitter = new QSplitter(p_orientation, p_parent);
  455. splitter->setChildrenCollapsible(false);
  456. return splitter;
  457. }
  458. QSplitter *ViewArea::tryGetParentSplitter(const QWidget *p_widget) const
  459. {
  460. return dynamic_cast<QSplitter *>(p_widget->parent());
  461. }
  462. void ViewArea::distributeViewSplitsOfSplitter(QSplitter *p_splitter)
  463. {
  464. if (!p_splitter || p_splitter->count() <= 1) {
  465. return;
  466. }
  467. // Distribute the direct children of splitter.
  468. {
  469. auto sizes = p_splitter->sizes();
  470. int totalWidth = 0;
  471. for (auto sz : sizes) {
  472. totalWidth += sz;
  473. }
  474. int newWidth = totalWidth / sizes.size();
  475. if (newWidth <= 0) {
  476. return;
  477. }
  478. for (int i = 0; i < sizes.size(); ++i) {
  479. sizes[i] = newWidth;
  480. }
  481. p_splitter->setSizes(sizes);
  482. }
  483. // Distribute child splitter.
  484. for (int i = 0; i < p_splitter->count(); ++i) {
  485. auto childSplitter = dynamic_cast<QSplitter *>(p_splitter->widget(i));
  486. if (childSplitter) {
  487. distributeViewSplitsOfSplitter(childSplitter);
  488. }
  489. }
  490. return;
  491. }
  492. void ViewArea::unwrapSplitter(QSplitter *p_splitter)
  493. {
  494. Q_ASSERT(p_splitter->count() == 1);
  495. auto paSplitter = tryGetParentSplitter(p_splitter);
  496. if (paSplitter) {
  497. Q_ASSERT(paSplitter->count() >= 2);
  498. int idx = paSplitter->indexOf(p_splitter);
  499. auto child = p_splitter->widget(0);
  500. child->setParent(this);
  501. paSplitter->replaceWidget(idx, child);
  502. } else {
  503. // This is the top child of ViewArea.
  504. Q_ASSERT(p_splitter->parent() == this);
  505. m_mainLayout->removeWidget(p_splitter);
  506. // Maybe another splitter or ViewSplit.
  507. auto child = p_splitter->widget(0);
  508. child->setParent(this);
  509. m_mainLayout->addWidget(child);
  510. }
  511. delete p_splitter;
  512. }
  513. void ViewArea::maximizeViewSplit(ViewSplit *p_split)
  514. {
  515. QWidget *widget = p_split;
  516. while (widget && widget != this) {
  517. maximizeWidgetOfSplitter(widget);
  518. widget = dynamic_cast<QWidget *>(widget->parent());
  519. }
  520. }
  521. void ViewArea::maximizeWidgetOfSplitter(QWidget *p_widget)
  522. {
  523. auto splitter = tryGetParentSplitter(p_widget);
  524. if (!splitter || splitter->count() <= 1) {
  525. return;
  526. }
  527. const int minSplitWidth = 20 * WidgetUtils::calculateScaleFactor();
  528. auto sizes = splitter->sizes();
  529. int totalWidth = 0;
  530. for (auto sz : sizes) {
  531. totalWidth += sz;
  532. }
  533. int newWidth = totalWidth - minSplitWidth * (sizes.size() - 1);
  534. if (newWidth <= 0) {
  535. return;
  536. }
  537. int idx = splitter->indexOf(p_widget);
  538. for (int i = 0; i < sizes.size(); ++i) {
  539. sizes[i] = (i == idx) ? newWidth : minSplitWidth;
  540. }
  541. splitter->setSizes(sizes);
  542. }
  543. void ViewArea::distributeViewSplits()
  544. {
  545. // Get the top splitter if there is any.
  546. auto splitter = dynamic_cast<QSplitter *>(m_mainLayout->itemAt(0)->widget());
  547. if (!splitter) {
  548. return;
  549. }
  550. distributeViewSplitsOfSplitter(splitter);
  551. }
  552. void ViewArea::removeWorkspace(QSharedPointer<ViewWorkspace> p_workspace)
  553. {
  554. if (!p_workspace) {
  555. return;
  556. }
  557. Q_ASSERT(!p_workspace->m_visible && p_workspace->m_viewWindows.isEmpty());
  558. p_workspace->clear();
  559. m_workspaces.removeAll(p_workspace);
  560. }
  561. void ViewArea::newWorkspaceInViewSplit(ViewSplit *p_split)
  562. {
  563. auto workspace = createWorkspace();
  564. m_workspaces.push_back(workspace);
  565. p_split->setWorkspace(workspace);
  566. setCurrentViewSplit(p_split, true);
  567. }
  568. bool ViewArea::removeWorkspaceInViewSplit(ViewSplit *p_split, bool p_insertNew)
  569. {
  570. // Close all the ViewWindows.
  571. setCurrentViewSplit(p_split, true);
  572. auto wins = getAllViewWindows(p_split);
  573. for (const auto win : wins) {
  574. if (!closeViewWindow(win, false, false)) {
  575. return false;
  576. }
  577. }
  578. Q_ASSERT(p_split->getViewWindowCount() == 0);
  579. auto workspace = p_split->getWorkspace();
  580. p_split->setWorkspace(nullptr);
  581. removeWorkspace(workspace);
  582. if (p_insertNew) {
  583. // Find an invisible workspace.
  584. bool found = false;
  585. for (auto &ws : m_workspaces) {
  586. if (!ws->m_visible) {
  587. p_split->setWorkspace(ws);
  588. found = true;
  589. break;
  590. }
  591. }
  592. // No invisible workspace. Create a new empty workspace.
  593. if (!found) {
  594. newWorkspaceInViewSplit(p_split);
  595. }
  596. }
  597. return true;
  598. }
  599. bool ViewArea::shouldUseGlobalStatusWidget() const
  600. {
  601. return m_splits.size() <= 1;
  602. }
  603. void ViewArea::handleViewSplitsCountChange()
  604. {
  605. if (shouldUseGlobalStatusWidget()) {
  606. // Hide the status widget for all ViewWindows.
  607. forEachViewWindow([](ViewWindow *p_win) {
  608. p_win->setStatusWidgetVisible(false);
  609. return true;
  610. });
  611. // Show global status widget for current ViewWindow.
  612. auto win = getCurrentViewWindow();
  613. m_currentStatusWidget = win ? win->statusWidget() : nullptr;
  614. emit statusWidgetChanged(m_currentStatusWidget.get());
  615. } else {
  616. // Show standalone status widget for all ViewWindows.
  617. emit statusWidgetChanged(nullptr);
  618. m_currentStatusWidget = nullptr;
  619. forEachViewWindow([](ViewWindow *p_win) {
  620. p_win->setStatusWidgetVisible(true);
  621. return true;
  622. });
  623. }
  624. }
  625. void ViewArea::forEachViewWindow(const ViewSplit::ViewWindowSelector &p_func)
  626. {
  627. for (auto split : m_splits) {
  628. if (!split->forEachViewWindow(p_func)) {
  629. return;
  630. }
  631. }
  632. for (auto &ws : m_workspaces) {
  633. if (!ws->m_visible) {
  634. for (auto win : ws->m_viewWindows) {
  635. if (!p_func(win)) {
  636. return;
  637. }
  638. }
  639. }
  640. }
  641. }
  642. bool ViewArea::close(bool p_force)
  643. {
  644. return closeIf(p_force, [](ViewWindow *p_win) {
  645. Q_UNUSED(p_win);
  646. return true;
  647. }, true);
  648. }
  649. void ViewArea::setupShortcuts()
  650. {
  651. const auto &coreConfig = ConfigMgr::getInst().getCoreConfig();
  652. // CloseTab.
  653. {
  654. auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::CloseTab), this);
  655. if (shortcut) {
  656. connect(shortcut, &QShortcut::activated,
  657. this, [this]() {
  658. auto win = getCurrentViewWindow();
  659. if (win) {
  660. closeViewWindow(win, false, true);
  661. }
  662. });
  663. }
  664. }
  665. // LocateNode.
  666. {
  667. auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::LocateNode), this);
  668. if (shortcut) {
  669. connect(shortcut, &QShortcut::activated,
  670. this, [this]() {
  671. auto win = getCurrentViewWindow();
  672. if (win) {
  673. auto node = win->getBuffer()->getNode();
  674. if (node) {
  675. emit VNoteX::getInst().locateNodeRequested(node);
  676. }
  677. }
  678. });
  679. }
  680. }
  681. }
  682. bool ViewArea::close(Node *p_node, bool p_force)
  683. {
  684. return closeIf(p_force, [p_node](ViewWindow *p_win) {
  685. auto buffer = p_win->getBuffer();
  686. return buffer->match(p_node) || buffer->isChildOf(p_node);
  687. }, false);
  688. }
  689. bool ViewArea::close(const Notebook *p_notebook, bool p_force)
  690. {
  691. return close(p_notebook->getRootNode().data(), p_force);
  692. }
  693. void ViewArea::checkCurrentViewWindowChange()
  694. {
  695. auto win = getCurrentViewWindow();
  696. if (win == m_currentWindow) {
  697. return;
  698. }
  699. m_currentWindow = win;
  700. emit currentViewWindowChanged();
  701. }
  702. bool ViewArea::closeIf(bool p_force, const ViewSplit::ViewWindowSelector &p_func, bool p_closeEmptySplit)
  703. {
  704. // Go through all hidden workspace. Use current split to show the workspace.
  705. if (m_workspaces.size() > m_splits.size()) {
  706. if (!m_currentSplit) {
  707. // Create at least one split.
  708. addFirstViewSplit();
  709. }
  710. // Need to restore it.
  711. auto currentWorkspace = m_currentSplit->getWorkspace();
  712. QVector<QSharedPointer<ViewWorkspace>> hiddenWorkspaces;
  713. for (auto &ws : m_workspaces) {
  714. if (!ws->m_visible) {
  715. Q_ASSERT(ws != currentWorkspace);
  716. hiddenWorkspaces.push_back(ws);
  717. }
  718. }
  719. Q_ASSERT(!hiddenWorkspaces.isEmpty());
  720. for (auto &ws : hiddenWorkspaces) {
  721. m_currentSplit->setWorkspace(ws);
  722. // Go through this split.
  723. auto wins = getAllViewWindows(m_currentSplit, p_func);
  724. for (const auto win : wins) {
  725. // Do not remove the split even if it is empty.
  726. bool ret = closeViewWindow(win, p_force, false);
  727. if (!ret) {
  728. // User cancels the close of one ViewWindow. No need to restore the workspace.
  729. return false;
  730. }
  731. }
  732. m_currentSplit->setWorkspace(nullptr);
  733. // Remove this workspace if it is empty.
  734. if (ws->m_viewWindows.isEmpty()) {
  735. removeWorkspace(ws);
  736. }
  737. }
  738. // Restore.
  739. m_currentSplit->setWorkspace(currentWorkspace);
  740. }
  741. // Go through all splits.
  742. // Collect the ViewWindows first. Collect empty splits.
  743. QVector<ViewWindow *> wins;
  744. QVector<ViewSplit *> emptySplits;
  745. for (auto split : m_splits) {
  746. if (p_closeEmptySplit && split->getViewWindowCount() == 0) {
  747. emptySplits.push_back(split);
  748. continue;
  749. }
  750. wins.append(getAllViewWindows(split, p_func));
  751. }
  752. if (!emptySplits.isEmpty()) {
  753. // Remove empty splits.
  754. for (auto split : emptySplits) {
  755. removeViewSplit(split, true);
  756. }
  757. }
  758. if (wins.isEmpty()) {
  759. return true;
  760. }
  761. // Close the ViewWindow.
  762. for (auto win : wins) {
  763. bool ret = closeViewWindow(win, p_force, true);
  764. if (!ret) {
  765. return false;
  766. }
  767. }
  768. return true;
  769. }
  770. void ViewArea::focus()
  771. {
  772. auto split = getCurrentViewSplit();
  773. if (split) {
  774. split->focus();
  775. }
  776. }
  777. QVector<void *> ViewArea::getVisibleNavigationItems()
  778. {
  779. QVector<void *> items;
  780. m_navigationItems.clear();
  781. int idx = 0;
  782. for (auto split : m_splits) {
  783. if (split->getViewWindowCount() == 0) {
  784. continue;
  785. }
  786. if (idx >= NavigationMode::c_maxNumOfNavigationItems) {
  787. break;
  788. }
  789. auto info = split->getNavigationModeInfo();
  790. for (int i = 0; i < info.size() && idx < NavigationMode::c_maxNumOfNavigationItems; ++i, ++idx) {
  791. items.push_back(info[i].m_viewWindow);
  792. m_navigationItems.push_back(info[i]);
  793. }
  794. }
  795. return items;
  796. }
  797. void ViewArea::placeNavigationLabel(int p_idx, void *p_item, QLabel *p_label)
  798. {
  799. Q_UNUSED(p_item);
  800. Q_ASSERT(p_idx > -1);
  801. p_label->setParent(m_navigationItems[p_idx].m_viewWindow->getViewSplit());
  802. p_label->move(m_navigationItems[p_idx].m_topLeft);
  803. }
  804. void ViewArea::handleTargetHit(void *p_item)
  805. {
  806. if (p_item) {
  807. setCurrentViewWindow(static_cast<ViewWindow *>(p_item));
  808. focus();
  809. }
  810. }
  811. void ViewArea::clearNavigation()
  812. {
  813. NavigationMode::clearNavigation();
  814. m_navigationItems.clear();
  815. }
  816. void ViewArea::dragEnterEvent(QDragEnterEvent *p_event)
  817. {
  818. if (UrlDragDropUtils::handleDragEnterEvent(p_event)) {
  819. return;
  820. }
  821. QWidget::dragEnterEvent(p_event);
  822. }
  823. void ViewArea::dropEvent(QDropEvent *p_event)
  824. {
  825. if (UrlDragDropUtils::handleDropEvent(p_event, [](const QStringList &p_files) {
  826. for (const auto &file : p_files) {
  827. emit VNoteX::getInst().openFileRequested(file, QSharedPointer<FileOpenParameters>::create());
  828. }
  829. })) {
  830. return;
  831. }
  832. QWidget::dropEvent(p_event);
  833. }
  834. QVector<ViewWindow *> ViewArea::getAllViewWindows(ViewSplit *p_split, const ViewSplit::ViewWindowSelector &p_func) const
  835. {
  836. QVector<ViewWindow *> wins;
  837. p_split->forEachViewWindow([p_func, &wins](ViewWindow *p_win) {
  838. if (p_func(p_win)) {
  839. wins.push_back(p_win);
  840. }
  841. return true;
  842. });
  843. return wins;
  844. }
  845. QVector<ViewWindow *> ViewArea::getAllViewWindows(ViewSplit *p_split) const
  846. {
  847. return getAllViewWindows(p_split, [](ViewWindow *) {
  848. return true;
  849. });
  850. }
  851. QList<Buffer *> ViewArea::getAllBuffersInViewSplits() const
  852. {
  853. QSet<Buffer *> bufferSet;
  854. for (auto split : m_splits) {
  855. auto wins = getAllViewWindows(split);
  856. for (auto win : wins) {
  857. bufferSet.insert(win->getBuffer());
  858. }
  859. }
  860. return bufferSet.values();
  861. }
  862. void ViewArea::loadSession()
  863. {
  864. auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
  865. auto sessionData = sessionConfig.getViewAreaSessionAndClear();
  866. auto session = ViewAreaSession::deserialize(sessionData);
  867. // Load widgets layout.
  868. if (session.m_root.isEmpty()) {
  869. showSceneWidget();
  870. } else {
  871. Q_ASSERT(m_splits.isEmpty());
  872. if (session.m_root.m_type == ViewAreaSession::Node::Type::Splitter) {
  873. // Splitter.
  874. auto splitter = createSplitter(session.m_root.m_orientation, this);
  875. m_mainLayout->addWidget(splitter);
  876. loadSplitterFromSession(session.m_root, splitter);
  877. } else {
  878. // Just only one ViewSplit.
  879. Q_ASSERT(session.m_root.m_type == ViewAreaSession::Node::Type::ViewSplit);
  880. auto split = createViewSplit(this, session.m_root.m_viewSplitId);
  881. m_splits.push_back(split);
  882. m_mainLayout->addWidget(split);
  883. }
  884. QHash<ID, int> viewSplitToWorkspace;
  885. setCurrentViewSplit(m_splits.first(), false);
  886. // Load invisible workspace.
  887. for (int i = 0; i < session.m_workspaces.size(); ++i) {
  888. const auto &ws = session.m_workspaces[i];
  889. if (ws.m_viewSplitId != InvalidViewSplitId) {
  890. viewSplitToWorkspace.insert(ws.m_viewSplitId, i);
  891. continue;
  892. }
  893. for (const auto &winSession : ws.m_viewWindows) {
  894. openViewWindowFromSession(winSession);
  895. }
  896. // Check if there is any window.
  897. if (m_currentSplit->getViewWindowCount() > 0) {
  898. m_currentSplit->setCurrentViewWindow(ws.m_currentViewWindowIndex);
  899. // New another workspace.
  900. auto newWs = createWorkspace();
  901. m_workspaces.push_back(newWs);
  902. m_currentSplit->setWorkspace(newWs);
  903. }
  904. }
  905. // Load visible workspace.
  906. for (auto split : m_splits) {
  907. setCurrentViewSplit(split, false);
  908. auto it = viewSplitToWorkspace.find(split->getId());
  909. Q_ASSERT(it != viewSplitToWorkspace.end());
  910. const auto &ws = session.m_workspaces[it.value()];
  911. for (const auto &winSession : ws.m_viewWindows) {
  912. openViewWindowFromSession(winSession);
  913. }
  914. if (m_currentSplit->getViewWindowCount() > 0) {
  915. m_currentSplit->setCurrentViewWindow(ws.m_currentViewWindowIndex);
  916. }
  917. }
  918. postFirstViewSplit();
  919. distributeViewSplits();
  920. }
  921. }
  922. void ViewArea::saveSession() const
  923. {
  924. ViewAreaSession session;
  925. takeSnapshot(session);
  926. auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
  927. sessionConfig.setViewAreaSession(session.serialize());
  928. }
  929. static void takeSnapshotOfWidgetNodes(ViewAreaSession::Node &p_node, const QWidget *p_widget, QHash<ID, ID> &p_workspaceToViewSplit)
  930. {
  931. p_node.clear();
  932. // Splitter.
  933. auto splitter = dynamic_cast<const QSplitter *>(p_widget);
  934. if (splitter) {
  935. p_node.m_type = ViewAreaSession::Node::Type::Splitter;
  936. p_node.m_orientation = splitter->orientation();
  937. p_node.m_children.resize(splitter->count());
  938. for (int i = 0; i < p_node.m_children.size(); ++i) {
  939. takeSnapshotOfWidgetNodes(p_node.m_children[i], splitter->widget(i), p_workspaceToViewSplit);
  940. }
  941. return;
  942. }
  943. // ViewSplit.
  944. auto viewSplit = dynamic_cast<const ViewSplit *>(p_widget);
  945. Q_ASSERT(viewSplit);
  946. p_node.m_type = ViewAreaSession::Node::Type::ViewSplit;
  947. p_node.m_viewSplitId = viewSplit->getId();
  948. auto ws = viewSplit->getWorkspace();
  949. if (ws) {
  950. viewSplit->updateStateToWorkspace();
  951. p_workspaceToViewSplit.insert(ws->m_id, viewSplit->getId());
  952. }
  953. }
  954. void ViewArea::takeSnapshot(ViewAreaSession &p_session) const
  955. {
  956. QHash<ID, ID> workspaceToViewSplit;
  957. // Widget hirarchy.
  958. p_session.m_root.clear();
  959. if (!m_splits.isEmpty()) {
  960. auto topWidget = m_mainLayout->itemAt(0)->widget();
  961. takeSnapshotOfWidgetNodes(p_session.m_root, topWidget, workspaceToViewSplit);
  962. }
  963. // Workspaces.
  964. p_session.m_workspaces.clear();
  965. p_session.m_workspaces.reserve(m_workspaces.size());
  966. for (const auto &ws : m_workspaces) {
  967. p_session.m_workspaces.push_back(ViewAreaSession::Workspace());
  968. auto &wsSnap = p_session.m_workspaces.last();
  969. if (ws->m_visible) {
  970. auto it = workspaceToViewSplit.find(ws->m_id);
  971. Q_ASSERT(it != workspaceToViewSplit.end());
  972. wsSnap.m_viewSplitId = it.value();
  973. }
  974. wsSnap.m_currentViewWindowIndex = ws->m_currentViewWindowIndex;
  975. for (auto win : ws->m_viewWindows) {
  976. wsSnap.m_viewWindows.push_back(win->saveSession());
  977. }
  978. }
  979. }
  980. void ViewArea::loadSplitterFromSession(const ViewAreaSession::Node &p_node, QSplitter *p_splitter)
  981. {
  982. // @p_splitter is the splitter corresponding to @p_node.
  983. Q_ASSERT(p_node.m_type == ViewAreaSession::Node::Type::Splitter);
  984. for (const auto &child : p_node.m_children) {
  985. if (child.m_type == ViewAreaSession::Node::Type::Splitter) {
  986. auto childSplitter = createSplitter(child.m_orientation, p_splitter);
  987. p_splitter->addWidget(childSplitter);
  988. loadSplitterFromSession(child, childSplitter);
  989. } else {
  990. Q_ASSERT(child.m_type == ViewAreaSession::Node::Type::ViewSplit);
  991. auto childSplit = createViewSplit(this, child.m_viewSplitId);
  992. m_splits.push_back(childSplit);
  993. p_splitter->addWidget(childSplit);
  994. }
  995. }
  996. }
  997. void ViewArea::openViewWindowFromSession(const ViewWindowSession &p_session)
  998. {
  999. if (p_session.m_bufferPath.isEmpty()) {
  1000. return;
  1001. }
  1002. auto paras = QSharedPointer<FileOpenParameters>::create();
  1003. paras->m_mode = p_session.m_viewWindowMode;
  1004. paras->m_readOnly = p_session.m_readOnly;
  1005. paras->m_lineNumber = p_session.m_lineNumber;
  1006. paras->m_alwaysNewWindow = true;
  1007. emit VNoteX::getInst().openFileRequested(p_session.m_bufferPath, paras);
  1008. }