From 3bd00f477a1c54331064a106a8bbbc40ed4db3ae Mon Sep 17 00:00:00 2001 From: Yuki Date: Tue, 3 Mar 2026 10:37:34 +0800 Subject: [PATCH 01/11] init --- CONTRIBUTING.md | 1 + TeXmacs/progs/init-research.scm | 1 + TeXmacs/progs/startup-tab/startup-tab.scm | 21 +++++++++++++ devel/216_1.md | 5 ++++ src/Plugins/Qt/qt_startup_tab_widget.cpp | 25 ++++++++++++++++ src/Plugins/Qt/qt_startup_tab_widget.hpp | 36 +++++++++++++++++++++++ src/System/Boot/init_texmacs.cpp | 2 +- src/System/config.h.xmake | 2 ++ src/Texmacs/Data/new_window.cpp | 13 ++++++-- src/Texmacs/Data/new_window.hpp | 2 +- xmake.lua | 7 +++++ 11 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 TeXmacs/progs/startup-tab/startup-tab.scm create mode 100644 devel/216_1.md create mode 100644 src/Plugins/Qt/qt_startup_tab_widget.cpp create mode 100644 src/Plugins/Qt/qt_startup_tab_widget.hpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3ce93ee83..e910495b19 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,3 +26,4 @@ x表示项目编号,y表示任务编号。 | 213 | 文档 | | 214 | 插件协议 | | 215 | 图形和图像处理 | +| 216 | 开始界面 | \ No newline at end of file diff --git a/TeXmacs/progs/init-research.scm b/TeXmacs/progs/init-research.scm index 246428e718..f4a26d6690 100644 --- a/TeXmacs/progs/init-research.scm +++ b/TeXmacs/progs/init-research.scm @@ -130,6 +130,7 @@ (lazy-menu (texmacs menus preferences-widgets) open-preferences) (use-modules (texmacs menus main-menu)) (use-modules (texmacs menus tabpage-menu)) +(use-modules (startup-tab startup-tab)) (lazy-define (texmacs menus file-menu) recent-file-list recent-directory-list) (lazy-define (texmacs menus view-menu) set-bottom-bar test-bottom-bar?) (tm-define (notify-set-attachment name key val) (noop)) diff --git a/TeXmacs/progs/startup-tab/startup-tab.scm b/TeXmacs/progs/startup-tab/startup-tab.scm new file mode 100644 index 0000000000..f0d7bc3188 --- /dev/null +++ b/TeXmacs/progs/startup-tab/startup-tab.scm @@ -0,0 +1,21 @@ + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : startup-tab.scm +;; DESCRIPTION : startup tab helpers +;; COPYRIGHT : (C) 2026 Yuki Lu +;; +;; This software falls under the GNU general public license version 3 or later. +;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE +;; in the root directory or . +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(texmacs-module (startup-tab startup-tab) + (:use (texmacs texmacs tm-files))) + +(tm-define (startup-tab-enabled?) + #t) + +(tm-define (startup-tab-default-entry) + "file") diff --git a/devel/216_1.md b/devel/216_1.md new file mode 100644 index 0000000000..f0439d1d95 --- /dev/null +++ b/devel/216_1.md @@ -0,0 +1,5 @@ +# 216_1 启动标签页骨架 + 入口绑定 + +## 如何测试 + +## 2026/03/02 \ No newline at end of file diff --git a/src/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp new file mode 100644 index 0000000000..b64269bcce --- /dev/null +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -0,0 +1,25 @@ + +/****************************************************************************** + * MODULE : qt_startup_tab_widget.cpp + * DESCRIPTION: Startup tab widget skeleton for Mogan STEM + * COPYRIGHT : (C) 2026 Yuki Lu + ******************************************************************************* + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#include "qt_startup_tab_widget.hpp" + +QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) + : QWidget (parent), currentEntry_ (Entry::File) {} + +QTStartupTabWidget::Entry +QTStartupTabWidget::current_entry () const { + return currentEntry_; +} + +void +QTStartupTabWidget::set_current_entry (Entry entry) { + currentEntry_= entry; +} diff --git a/src/Plugins/Qt/qt_startup_tab_widget.hpp b/src/Plugins/Qt/qt_startup_tab_widget.hpp new file mode 100644 index 0000000000..8b32248c49 --- /dev/null +++ b/src/Plugins/Qt/qt_startup_tab_widget.hpp @@ -0,0 +1,36 @@ + +/****************************************************************************** + * MODULE : qt_startup_tab_widget.hpp + * DESCRIPTION: Startup tab widget skeleton for Mogan STEM + * COPYRIGHT : (C) 2026 Yuki Lu + ******************************************************************************* + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#ifndef QT_STARTUP_TAB_WIDGET_HPP +#define QT_STARTUP_TAB_WIDGET_HPP + +#include + +class QTStartupTabWidget : public QWidget { +public: + enum class Entry { + File, + Template, + Recent, + Settings + }; + +public: + explicit QTStartupTabWidget (QWidget* parent= nullptr); + + Entry current_entry () const; + void set_current_entry (Entry entry); + +private: + Entry currentEntry_; +}; + +#endif diff --git a/src/System/Boot/init_texmacs.cpp b/src/System/Boot/init_texmacs.cpp index 605fb7bdf9..398b182183 100644 --- a/src/System/Boot/init_texmacs.cpp +++ b/src/System/Boot/init_texmacs.cpp @@ -942,7 +942,7 @@ TeXmacs_main (int argc, char** argv) { load_welcome_doc (); } - if (!has_initial_file || !has_initial_welcome) ensure_window (); + if (!has_initial_file) ensure_window (); if (DEBUG_BENCH) lolly::system::bench_print (std_bench); bench_reset ("initialize texmacs"); diff --git a/src/System/config.h.xmake b/src/System/config.h.xmake index 2ab2114dff..ff1f44d9e4 100644 --- a/src/System/config.h.xmake +++ b/src/System/config.h.xmake @@ -89,4 +89,6 @@ ${define USE_PLUGIN_SPARKLE} /* Use MuPDF library */ ${define USE_MUPDF_RENDERER} +${define USE_STARTUP_TAB} + ${define IS_COMMUNITY} diff --git a/src/Texmacs/Data/new_window.cpp b/src/Texmacs/Data/new_window.cpp index 45f50fec12..a192790a77 100644 --- a/src/Texmacs/Data/new_window.cpp +++ b/src/Texmacs/Data/new_window.cpp @@ -84,7 +84,7 @@ class kill_window_command_rep : public command_rep { }; url -new_window (bool map_flag, tree geom) { +new_window (bool map_flag, tree geom, bool force_tab_bar) { int mask= 0; if (get_preference ("header") == "on") mask+= 1; if (get_preference ("main icon bar") == "on") mask+= 2; @@ -96,7 +96,7 @@ new_window (bool map_flag, tree geom) { // if (get_preference ("left tools") == "on") mask += 128; if (get_preference ("bottom tools") == "on") mask+= 256; if (get_preference ("extra tools") == "on") mask+= 512; - if (get_preference ("tab bar") == "on") mask+= 1024; + if (force_tab_bar || get_preference ("tab bar") == "on") mask+= 1024; url* id = tm_new (url_none ()); command quit= tm_new (id); tm_window win = tm_new (texmacs_widget (mask, quit), geom); @@ -296,8 +296,17 @@ open_window (tree geom) { url ensure_window (tree geom) { if (number_buffers () == 0) { +#ifdef USE_STARTUP_TAB + url name= "tmfs://startup-tab"; + if (is_nil (concrete_buffer (name))) create_buffer (name, tree (DOCUMENT)); + url win= new_window (true, geom, true); + window_set_view (win, get_passive_view (name), true); + set_title_buffer (name, "Mogan STEM"); + return win; +#else url name= make_welcome_buffer (); return new_buffer_in_new_window (name, tree (DOCUMENT), geom); +#endif } array all_views = get_all_views (); diff --git a/src/Texmacs/Data/new_window.hpp b/src/Texmacs/Data/new_window.hpp index e492f9816e..b20358503e 100644 --- a/src/Texmacs/Data/new_window.hpp +++ b/src/Texmacs/Data/new_window.hpp @@ -26,7 +26,7 @@ void switch_to_window (url win); void switch_to_parent_window (); url create_buffer (); -url new_window (bool map_flag= true, tree geom= ""); +url new_window (bool map_flag= true, tree geom= "", bool force_tab_bar= false); url open_window (tree geom= ""); url ensure_window (tree geom= ""); void clone_window (); diff --git a/xmake.lua b/xmake.lua index fb4456a5b9..3369fd70c3 100644 --- a/xmake.lua +++ b/xmake.lua @@ -30,6 +30,11 @@ option_end() -- Temporary statement to move into MuPDF set_config("mupdf", true) +option("startup_tab") + set_default(false) + set_description("Enable startup tab with left navigation") +option_end() + -- Adjust community or commercial version option("is_community") set_default(true) @@ -76,6 +81,7 @@ add_configfiles("src/System/config.h.xmake", { USE_FONTCONFIG = true, PDFHUMMUS_NO_TIFF = true, USE_MUPDF_RENDERER = has_config("mupdf"), + USE_STARTUP_TAB = has_config("startup_tab"), IS_COMMUNITY = has_config("is_community"), DEBUG_WITH_TIMESTAMP = has_config("debug_with_timestamp"), } @@ -682,6 +688,7 @@ target("libmogan") do USE_PLUGIN_SPARKLE = false, USE_PLUGIN_HTML = true, USE_MUPDF_RENDERER = has_config("mupdf"), + USE_STARTUP_TAB = has_config("startup_tab"), IS_COMMUNITY = has_config("is_community"), DEBUG_WITH_TIMESTAMP = has_config("debug_with_timestamp"), }}) From f41c37c9e510a8d4f305ac05574879eba652c35b Mon Sep 17 00:00:00 2001 From: Yuki Date: Tue, 3 Mar 2026 11:38:36 +0800 Subject: [PATCH 02/11] tabpage --- src/Plugins/Qt/QTMTabPage.cpp | 96 ++++++++++++++++++++++++++------- src/Texmacs/Data/new_view.cpp | 3 ++ src/Texmacs/Data/new_window.cpp | 1 + 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/Plugins/Qt/QTMTabPage.cpp b/src/Plugins/Qt/QTMTabPage.cpp index dda959f78c..0313a873a3 100644 --- a/src/Plugins/Qt/QTMTabPage.cpp +++ b/src/Plugins/Qt/QTMTabPage.cpp @@ -21,6 +21,7 @@ const int MIN_TAB_PAGE_WIDTH= 150; // The maximum width of a single tab page (in pixels). const int MAX_TAB_PAGE_WIDTH= 200; +const int STARTUP_TAB_MAX_WIDTH= 100; // The horizontal padding for tab container (in pixels). #ifdef Q_OS_MAC @@ -100,6 +101,24 @@ url g_mostRecentlyDraggedTab= url_none (); QTMTabPageContainer* g_mostRecentlyDraggedBar= nullptr; QTMTabPageContainer* g_mostRecentlyEnteredBar= nullptr; +static url +startup_tab_buffer_name () { + return url ("tmfs://startup-tab"); +} + +static bool +is_startup_tab_view (url viewUrl) { + if (is_none (viewUrl)) return false; + return view_to_buffer (viewUrl) == startup_tab_buffer_name (); +} + +static int +startup_tab_index (const QList& tabs) { + for (int i= 0; i < tabs.size (); ++i) + if (tabs[i] != nullptr && is_startup_tab_view (tabs[i]->m_viewUrl)) return i; + return -1; +} + /****************************************************************************** * QTMTabPage ******************************************************************************/ @@ -196,6 +215,9 @@ QTMTabPage::resizeEvent (QResizeEvent* e) { void QTMTabPage::mousePressEvent (QMouseEvent* e) { + if (is_startup_tab_view (m_viewUrl)) { + return QToolButton::mousePressEvent (e); + } if (e->button () == Qt::LeftButton) { g_mostRecentlyDraggedTab= this->m_viewUrl; g_mostRecentlyDraggedBar= @@ -208,6 +230,9 @@ QTMTabPage::mousePressEvent (QMouseEvent* e) { void QTMTabPage::mouseMoveEvent (QMouseEvent* e) { + if (is_startup_tab_view (m_viewUrl)) { + return QToolButton::mouseMoveEvent (e); + } if (!(e->buttons () & Qt::LeftButton)) return QToolButton::mouseMoveEvent (e); if ((e->pos () - m_dragStartPos).manhattanLength () < 3) { // avoid treating small movement(more like a click) as dragging @@ -255,8 +280,7 @@ QTMTabPage::leaveEvent (QEvent* e) { void QTMTabPage::updateCloseButtonVisibility () { - // 始终显示关闭按钮(无论是否选中或悬停) - bool shouldShow= true; + bool shouldShow= !is_startup_tab_view (m_viewUrl); bool wasVisible= m_closeBtn->isVisible (); m_closeBtn->setVisible (shouldShow); @@ -356,6 +380,12 @@ QTMTabPageContainer::extractTabPages (QList* p_src) { // which will be deleted by the parent widget (QTMTabPageBar) when it // is destroyed (by shedule_destruction). } + + int startupIndex= startup_tab_index (m_tabPageList); + if (startupIndex > 0) { + QTMTabPage* startupTab= m_tabPageList.takeAt (startupIndex); + m_tabPageList.prepend (startupTab); + } } void @@ -405,21 +435,25 @@ QTMTabPageContainer::arrangeTabPages () { // Set new positions for all tabs for (int i= 0; i < m_tabPageList.size (); ++i) { QTMTabPage* tab= m_tabPageList[i]; + int currentTabWidth= tabWidth; + if (is_startup_tab_view (tab->m_viewUrl)) { + currentTabWidth= std::min (tabWidth, STARTUP_TAB_MAX_WIDTH); + } if (g_pointingIndex == i) { // construct a dummy rectangle widget for indication of the inser place of // the dragged tab - dummyTabPage->setGeometry (accumWidth, 0, tabWidth, m_rowHeight); + dummyTabPage->setGeometry (accumWidth, 0, currentTabWidth, m_rowHeight); dummyTabPage->show (); - accumWidth+= tabWidth; + accumWidth+= currentTabWidth; } if (g_mostRecentlyClosedTab == tab->m_viewUrl) { tab->hide (); continue; } - tab->setGeometry (accumWidth, 0, tabWidth, m_rowHeight); - accumWidth+= tabWidth; + tab->setGeometry (accumWidth, 0, currentTabWidth, m_rowHeight); + accumWidth+= currentTabWidth; tab->show (); } if (g_pointingIndex >= m_tabPageList.size ()) { @@ -473,26 +507,29 @@ QTMTabPageContainer::setHitTestVisibleForTabPages ( int QTMTabPageContainer::mapToPointing (QDropEvent* e, QPoint& p_indicatorPos) { QPoint pos= e->position ().toPoint (); - // Now we use g_tabWidth to calculate the pointing index, instead of geometry - if (g_tabWidth <= 0) { + if (m_tabPageList.isEmpty ()) { p_indicatorPos= QPoint (0, 0); return 0; } - int index= pos.x () / g_tabWidth; - index = qMax (0, qMin (index, m_tabPageList.size ())); - if (index < m_tabPageList.size ()) { - QRect rect = m_tabPageList[index]->geometry (); + + int index= m_tabPageList.size (); + for (int i= 0; i < m_tabPageList.size (); ++i) { + QTMTabPage* tab= m_tabPageList[i]; + if (!tab || !tab->isVisible ()) continue; + QRect rect = tab->geometry (); int x_mid= rect.x () + rect.width () / 2; - if (pos.x () >= x_mid) { - p_indicatorPos= rect.topRight (); - return std::min (index + 1, int (m_tabPageList.size ())); + if (pos.x () < x_mid) { + index = i; + p_indicatorPos= rect.topLeft (); + break; } - p_indicatorPos= rect.topLeft (); - return index; + index = i + 1; + p_indicatorPos= rect.topRight (); } - // no valid pointing tab, p_indicatorPos should be at the end - p_indicatorPos= m_tabPageList.last ()->geometry ().topRight (); - return m_tabPageList.size (); + + int startupIndex= startup_tab_index (m_tabPageList); + if (startupIndex == 0) index= qMax (1, index); + return std::min (index, static_cast (m_tabPageList.size ())); } void @@ -533,6 +570,17 @@ QTMTabPageContainer::dropEvent (QDropEvent* e) { QTMTabPage* draggingTab = m_tabPageList[m_draggingTabIndex]; int oldIndex = m_draggingTabIndex; int newIndex= pointingIndex > oldIndex ? pointingIndex - 1 : pointingIndex; + int startupIndex= startup_tab_index (m_tabPageList); + if (startupIndex == oldIndex) { + g_mostRecentlyClosedTab= url_none (); + g_pointingIndex = -1; + m_draggingTabIndex = -1; + arrangeTabPages (); + m_indicator->hide (); + dummyTabPage->hide (); + return; + } + if (startupIndex == 0) newIndex= qMax (1, newIndex); g_mostRecentlyClosedTab= url_none (); g_pointingIndex = -1; @@ -553,6 +601,14 @@ QTMTabPageContainer::dropEvent (QDropEvent* e) { QObject* src= e->source (); if (src && src != this) { url dragged_view = g_mostRecentlyDraggedTab; + if (is_startup_tab_view (dragged_view)) { + g_mostRecentlyDraggedTab= url_none (); + g_mostRecentlyDraggedBar= nullptr; + g_pointingIndex = -1; + m_indicator->hide (); + dummyTabPage->hide (); + return; + } tm_window dragged_window= concrete_view (dragged_view)->win_tabpage; url target_view= m_tabPageList[0]->m_viewUrl; // 通过view来获取window tm_window target_window= concrete_view (target_view)->win_tabpage; diff --git a/src/Texmacs/Data/new_view.cpp b/src/Texmacs/Data/new_view.cpp index 5fb1a28b08..354ec722cc 100644 --- a/src/Texmacs/Data/new_view.cpp +++ b/src/Texmacs/Data/new_view.cpp @@ -430,6 +430,9 @@ kill_tabpage (url win_u, url u) { // 在关闭文档,或标签页时都将调用此方法。 tm_view vw= concrete_view (u); if (vw == NULL) return; + if (vw->buf != NULL && vw->buf->buf->name == url ("tmfs://startup-tab")) { + return; + } tm_window win = vw->win; tm_window win_tabpage= vw->win_tabpage; if (win_tabpage == NULL) return; diff --git a/src/Texmacs/Data/new_window.cpp b/src/Texmacs/Data/new_window.cpp index a192790a77..6d52f8b0c8 100644 --- a/src/Texmacs/Data/new_window.cpp +++ b/src/Texmacs/Data/new_window.cpp @@ -338,6 +338,7 @@ clone_window () { void kill_buffer (url name) { + if (name == url ("tmfs://startup-tab")) return; array vs= buffer_to_views (name); for (int i= 0; i < N (vs); i++) if (!is_none (vs[i])) { From 8f82c33bd70248d7ca84d628c001c78fed238fb3 Mon Sep 17 00:00:00 2001 From: Yuki Date: Tue, 17 Mar 2026 09:07:57 +0800 Subject: [PATCH 03/11] wip --- src/Plugins/Qt/qt_startup_tab_widget.cpp | 30 +++++- src/Plugins/Qt/qt_tm_widget.cpp | 117 ++++++++++++++++++++--- src/Plugins/Qt/qt_tm_widget.hpp | 3 + src/System/Boot/init_texmacs.cpp | 6 +- 4 files changed, 136 insertions(+), 20 deletions(-) diff --git a/src/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp index b64269bcce..4790869d39 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.cpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -11,8 +11,36 @@ #include "qt_startup_tab_widget.hpp" +#include +#include + +namespace { +const char* +entry_to_string (QTStartupTabWidget::Entry entry) { + switch (entry) { + case QTStartupTabWidget::Entry::File: + return "File"; + case QTStartupTabWidget::Entry::Template: + return "Template"; + case QTStartupTabWidget::Entry::Recent: + return "Recent"; + case QTStartupTabWidget::Entry::Settings: + return "Settings"; + default: + return "Unknown"; + } +} +} // namespace + QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) - : QWidget (parent), currentEntry_ (Entry::File) {} + : QWidget (parent), currentEntry_ (Entry::File) { + setMinimumSize(400, 300); + setStyleSheet("background-color: #f0f0f0;"); + QLabel* label = new QLabel("Mogan STEM Startup Tab (File/Template/Recent/Settings)", this); + label->setAlignment(Qt::AlignCenter); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(label); +} QTStartupTabWidget::Entry QTStartupTabWidget::current_entry () const { diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index 1c16cf6c35..ec8bd194f5 100644 --- a/src/Plugins/Qt/qt_tm_widget.cpp +++ b/src/Plugins/Qt/qt_tm_widget.cpp @@ -44,6 +44,7 @@ #include "qt_gui.hpp" #include "qt_picture.hpp" #include "qt_renderer.hpp" +#include "qt_startup_tab_widget.hpp" #include "qt_tm_widget.hpp" #include "qt_utilities.hpp" @@ -71,6 +72,18 @@ using moebius::data::scm_quote; int menu_count= 0; // zero if no menu is currently being displayed list waiting_widgets; +static bool +is_startup_tab_file (const string& file) { + return file == "tmfs://startup-tab"; +} + +static bool +is_startup_tab_current_view () { + url view= get_current_view_safe (); + if (is_none (view)) return false; + return view_to_buffer (view) == url ("tmfs://startup-tab"); +} + static void replaceActions (QWidget* dest, QList* src) { // NOTE: the parent hierarchy of the actions is not modified while installing @@ -132,7 +145,7 @@ qt_tm_widget_rep::qt_tm_widget_rep (int mask, command _quit) : qt_window_widget_rep (new QTMWindow (0), "popup", _quit), helper (this), prompt (NULL), full_screen (false), menuToolBarVisibleCache (false), titleBarVisibleCache (false), membershipTitleLabel (nullptr), - m_userId ("") { + m_userId (""), startupContentWidget (nullptr), startupTabMode (false) { type= texmacs_widget; main_widget= concrete (::glue_widget (true, true, 1, 1)); @@ -796,6 +809,66 @@ qt_tm_widget_rep::plain_window_widget (string name, command _quit, int b) { return this; } +void +qt_tm_widget_rep::sync_startup_tab_mode () { + QWidget* editorWidget= main_widget->qwid; + QLayout* layout = centralwidget ()->layout (); + if (!layout) return; + bool hasActiveView= !is_none (get_current_view_safe ()); + + if (!hasActiveView) startupTabMode= true; + if (editorWidget == nullptr) startupTabMode= true; + + if (!startupTabMode && !hasActiveView) { + if (editorWidget) { + editorWidget->hide (); + if (layout->indexOf (editorWidget) >= 0) layout->removeWidget (editorWidget); + } + update_visibility (); // 先更新可见性,确保标签页容器可见 + if (!startupContentWidget) + startupContentWidget= new QTStartupTabWidget (centralwidget ()); + if (layout->indexOf (startupContentWidget) < 0) + layout->addWidget (startupContentWidget); + startupContentWidget->show (); + return; + } + + if (startupTabMode) { + if (editorWidget) { + editorWidget->hide (); + if (layout->indexOf (editorWidget) >= 0) layout->removeWidget (editorWidget); + } + update_visibility (); + if (!startupContentWidget) + startupContentWidget= new QTStartupTabWidget (centralwidget ()); + if (layout->indexOf (startupContentWidget) < 0) + layout->addWidget (startupContentWidget); + startupContentWidget->show (); + return; + } + else { + if (startupContentWidget) { + startupContentWidget->hide (); + if (layout->indexOf (startupContentWidget) >= 0) + layout->removeWidget (startupContentWidget); + } + + if (editorWidget) { + if (layout->indexOf (editorWidget) < 0) layout->addWidget (editorWidget); + editorWidget->show (); + } + } + + update_visibility (); + if (!startupTabMode) { + if (scrollarea ()) + scrollarea ()->surface ()->setSizePolicy (QSizePolicy::Fixed, + QSizePolicy::Fixed); + url currentView= get_current_view_safe (); + if (!is_none (currentView)) send_keyboard_focus (abstract (main_widget)); + } +} + void qt_tm_widget_rep::update_visibility () { #define XOR(exp1, exp2) (((!exp1) && (exp2)) || ((exp1) && (!exp2))) @@ -810,6 +883,7 @@ qt_tm_widget_rep::update_visibility () { bool old_bottomVisibility= bottomTools->isVisible (); bool old_extraVisibility = extraTools->isVisible (); bool old_auxVisibility = auxiliaryWidget->isVisible (); + bool old_tabVisibility = tabPageContainer ? tabPageContainer->isVisible () : false; bool old_statusVisibility= mainwindow ()->statusBar ()->isVisible (); bool old_titleVisibility = windowAgent->titleBar ()->isVisible (); @@ -827,6 +901,22 @@ qt_tm_widget_rep::update_visibility () { bool new_auxVisibility = visibility[11]; bool new_titleVisibility = visibility[0]; + if (startupTabMode) { + new_mainVisibility = false; + new_menuVisibility = false; + new_modeVisibility = false; + new_focusVisibility = false; + new_userVisibility = false; + new_statusVisibility= false; + new_sideVisibility = false; + new_leftVisibility = false; + new_bottomVisibility= false; + new_extraVisibility = false; + new_auxVisibility = false; + new_tabVisibility = true; + new_titleVisibility = true; + } + if (XOR (old_mainVisibility, new_mainVisibility)) mainToolBar->setVisible (new_mainVisibility); if (XOR (old_menuVisibility, new_menuVisibility)) @@ -847,6 +937,8 @@ qt_tm_widget_rep::update_visibility () { extraTools->setVisible (new_extraVisibility); if (XOR (old_auxVisibility, new_auxVisibility)) auxiliaryWidget->setVisible (new_auxVisibility); + if (tabPageContainer && XOR (old_tabVisibility, new_tabVisibility)) + tabPageContainer->setVisible (new_tabVisibility); if (XOR (old_titleVisibility, new_titleVisibility)) windowAgent->titleBar ()->setVisible (new_titleVisibility); if (XOR (old_statusVisibility, new_statusVisibility)) @@ -1074,6 +1166,7 @@ qt_tm_widget_rep::send (slot s, blackbox val) { string file= open_box (val); if (DEBUG_QT_WIDGETS) debug_widgets << "\tFile: " << file << LF; mainwindow ()->setWindowFilePath (utf8_to_qstring (file)); + startupTabMode= is_startup_tab_file (file); } break; case SLOT_POSITION: { check_type (val, s); @@ -1280,23 +1373,19 @@ qt_tm_widget_rep::write (slot s, blackbox index, widget w) { check_type_void (index, s); QWidget* q= main_widget->qwid; - q->hide (); QLayout* l= centralwidget ()->layout (); - l->removeWidget (q); + if (q && l->indexOf (q) >= 0) l->removeWidget (q); q= concrete (w)->as_qwidget (); // force creation of the new QWidget - l->addWidget (q); - /* " When you use a layout, you do not need to pass a parent when - constructing the child widgets. The layout will automatically reparent - the widgets (using QWidget::setParent()) so that they are children of - the widget on which the layout is installed " */ main_widget= concrete (w); - // canvas() now returns the new QTMWidget (or 0) - - if (scrollarea ()) // Fix size to draw margins around. - scrollarea ()->surface ()->setSizePolicy (QSizePolicy::Fixed, - QSizePolicy::Fixed); - send_keyboard_focus (abstract (main_widget)); + url currentView= get_current_view_safe (); + if (is_none (currentView)) { + startupTabMode= true; + } + else { + startupTabMode= is_startup_tab_current_view (); + } + sync_startup_tab_mode (); } break; case SLOT_MAIN_MENU: diff --git a/src/Plugins/Qt/qt_tm_widget.hpp b/src/Plugins/Qt/qt_tm_widget.hpp index 7a489ce41a..6fb068b484 100644 --- a/src/Plugins/Qt/qt_tm_widget.hpp +++ b/src/Plugins/Qt/qt_tm_widget.hpp @@ -138,6 +138,8 @@ class qt_tm_widget_rep : public qt_window_widget_rep { qt_widget tab_bar_widget; qt_widget auxiliary_widget; qt_widget dock_window_widget; // trick to return correct widget position + QWidget* startupContentWidget; + bool startupTabMode; public: qt_tm_widget_rep (int mask, command _quit); @@ -157,6 +159,7 @@ class qt_tm_widget_rep : public qt_window_widget_rep { static void tweak_iconbar_size (QSize& sz); void openRenewalPage (); void checkNetworkAvailable (); + void sync_startup_tab_mode (); friend class QTMInteractiveInputHelper; diff --git a/src/System/Boot/init_texmacs.cpp b/src/System/Boot/init_texmacs.cpp index 398b182183..35d7095008 100644 --- a/src/System/Boot/init_texmacs.cpp +++ b/src/System/Boot/init_texmacs.cpp @@ -936,11 +936,7 @@ TeXmacs_main (int argc, char** argv) { } } - bool has_initial_welcome= false; - if (install_status == 1 || install_status == 2) { - has_initial_welcome= true; - load_welcome_doc (); - } + if (install_status == 1) load_welcome_doc (); if (!has_initial_file) ensure_window (); From a3647781bb85f3c6ef8c8e7591bac012bee01efd Mon Sep 17 00:00:00 2001 From: Saki Date: Tue, 17 Mar 2026 09:22:43 +0800 Subject: [PATCH 04/11] =?UTF-8?q?[216=5F1]=20=E4=BF=AE=E5=A4=8D=20Backstag?= =?UTF-8?q?e=20=E5=90=AF=E5=8A=A8=E9=A1=B5=E9=80=BB=E8=BE=91=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 简化 sync_startup_tab_mode() 函数,消除重复代码 - 修复状态不一致问题:确保 startupTabMode 在需要时正确设置 - 在 SLOT_FILE 处理后调用 sync_startup_tab_mode() 同步界面 - 优化代码结构,提高可维护性 --- src/Plugins/Qt/qt_tm_widget.cpp | 36 ++++++++++++++------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index ec8bd194f5..9b70e0b0e7 100644 --- a/src/Plugins/Qt/qt_tm_widget.cpp +++ b/src/Plugins/Qt/qt_tm_widget.cpp @@ -814,39 +814,33 @@ qt_tm_widget_rep::sync_startup_tab_mode () { QWidget* editorWidget= main_widget->qwid; QLayout* layout = centralwidget ()->layout (); if (!layout) return; - bool hasActiveView= !is_none (get_current_view_safe ()); - if (!hasActiveView) startupTabMode= true; - if (editorWidget == nullptr) startupTabMode= true; + bool hasActiveView= !is_none (get_current_view_safe ()); - if (!startupTabMode && !hasActiveView) { - if (editorWidget) { - editorWidget->hide (); - if (layout->indexOf (editorWidget) >= 0) layout->removeWidget (editorWidget); - } - update_visibility (); // 先更新可见性,确保标签页容器可见 - if (!startupContentWidget) - startupContentWidget= new QTStartupTabWidget (centralwidget ()); - if (layout->indexOf (startupContentWidget) < 0) - layout->addWidget (startupContentWidget); - startupContentWidget->show (); - return; + // Auto-enable startup mode when no active view or no editor widget + if (!hasActiveView || editorWidget == nullptr) { + startupTabMode= true; } if (startupTabMode) { + // Show Backstage/Startup view if (editorWidget) { editorWidget->hide (); if (layout->indexOf (editorWidget) >= 0) layout->removeWidget (editorWidget); } + update_visibility (); - if (!startupContentWidget) + + if (!startupContentWidget) { startupContentWidget= new QTStartupTabWidget (centralwidget ()); - if (layout->indexOf (startupContentWidget) < 0) + } + if (layout->indexOf (startupContentWidget) < 0) { layout->addWidget (startupContentWidget); + } startupContentWidget->show (); - return; } else { + // Show normal editor view if (startupContentWidget) { startupContentWidget->hide (); if (layout->indexOf (startupContentWidget) >= 0) @@ -857,10 +851,9 @@ qt_tm_widget_rep::sync_startup_tab_mode () { if (layout->indexOf (editorWidget) < 0) layout->addWidget (editorWidget); editorWidget->show (); } - } - update_visibility (); - if (!startupTabMode) { + update_visibility (); + if (scrollarea ()) scrollarea ()->surface ()->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -1167,6 +1160,7 @@ qt_tm_widget_rep::send (slot s, blackbox val) { if (DEBUG_QT_WIDGETS) debug_widgets << "\tFile: " << file << LF; mainwindow ()->setWindowFilePath (utf8_to_qstring (file)); startupTabMode= is_startup_tab_file (file); + sync_startup_tab_mode (); } break; case SLOT_POSITION: { check_type (val, s); From bb7d5b7e1ccb5c1a1fc161ea1387ab60e4be1f65 Mon Sep 17 00:00:00 2001 From: Yuki Date: Tue, 17 Mar 2026 10:08:50 +0800 Subject: [PATCH 05/11] wip --- src/Plugins/Qt/QTMTabPage.cpp | 15 ++++++++------- src/Plugins/Qt/qt_startup_tab_widget.cpp | 13 +++++++------ src/Plugins/Qt/qt_startup_tab_widget.hpp | 7 +------ src/Plugins/Qt/qt_tm_widget.cpp | 8 +++++--- src/Texmacs/Data/new_view.cpp | 4 +++- src/Texmacs/Data/new_window.cpp | 10 +++++----- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/Plugins/Qt/QTMTabPage.cpp b/src/Plugins/Qt/QTMTabPage.cpp index 0313a873a3..45c3fe2198 100644 --- a/src/Plugins/Qt/QTMTabPage.cpp +++ b/src/Plugins/Qt/QTMTabPage.cpp @@ -20,7 +20,7 @@ // The minimum width of a single tab page (in pixels). const int MIN_TAB_PAGE_WIDTH= 150; // The maximum width of a single tab page (in pixels). -const int MAX_TAB_PAGE_WIDTH= 200; +const int MAX_TAB_PAGE_WIDTH = 200; const int STARTUP_TAB_MAX_WIDTH= 100; // The horizontal padding for tab container (in pixels). @@ -115,7 +115,8 @@ is_startup_tab_view (url viewUrl) { static int startup_tab_index (const QList& tabs) { for (int i= 0; i < tabs.size (); ++i) - if (tabs[i] != nullptr && is_startup_tab_view (tabs[i]->m_viewUrl)) return i; + if (tabs[i] != nullptr && is_startup_tab_view (tabs[i]->m_viewUrl)) + return i; return -1; } @@ -434,7 +435,7 @@ QTMTabPageContainer::arrangeTabPages () { // Set new positions for all tabs for (int i= 0; i < m_tabPageList.size (); ++i) { - QTMTabPage* tab= m_tabPageList[i]; + QTMTabPage* tab = m_tabPageList[i]; int currentTabWidth= tabWidth; if (is_startup_tab_view (tab->m_viewUrl)) { currentTabWidth= std::min (tabWidth, STARTUP_TAB_MAX_WIDTH); @@ -519,11 +520,11 @@ QTMTabPageContainer::mapToPointing (QDropEvent* e, QPoint& p_indicatorPos) { QRect rect = tab->geometry (); int x_mid= rect.x () + rect.width () / 2; if (pos.x () < x_mid) { - index = i; + index = i; p_indicatorPos= rect.topLeft (); break; } - index = i + 1; + index = i + 1; p_indicatorPos= rect.topRight (); } @@ -600,11 +601,11 @@ QTMTabPageContainer::dropEvent (QDropEvent* e) { // Attach当前标签页到其他窗口 QObject* src= e->source (); if (src && src != this) { - url dragged_view = g_mostRecentlyDraggedTab; + url dragged_view= g_mostRecentlyDraggedTab; if (is_startup_tab_view (dragged_view)) { g_mostRecentlyDraggedTab= url_none (); g_mostRecentlyDraggedBar= nullptr; - g_pointingIndex = -1; + g_pointingIndex = -1; m_indicator->hide (); dummyTabPage->hide (); return; diff --git a/src/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp index 4790869d39..4fbe976c5c 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.cpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -34,12 +34,13 @@ entry_to_string (QTStartupTabWidget::Entry entry) { QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) : QWidget (parent), currentEntry_ (Entry::File) { - setMinimumSize(400, 300); - setStyleSheet("background-color: #f0f0f0;"); - QLabel* label = new QLabel("Mogan STEM Startup Tab (File/Template/Recent/Settings)", this); - label->setAlignment(Qt::AlignCenter); - QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(label); + setMinimumSize (400, 300); + setStyleSheet ("background-color: #f0f0f0;"); + QLabel* label= new QLabel ( + "Mogan STEM Startup Tab (File/Template/Recent/Settings)", this); + label->setAlignment (Qt::AlignCenter); + QVBoxLayout* layout= new QVBoxLayout (this); + layout->addWidget (label); } QTStartupTabWidget::Entry diff --git a/src/Plugins/Qt/qt_startup_tab_widget.hpp b/src/Plugins/Qt/qt_startup_tab_widget.hpp index 8b32248c49..74095285af 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.hpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.hpp @@ -16,12 +16,7 @@ class QTStartupTabWidget : public QWidget { public: - enum class Entry { - File, - Template, - Recent, - Settings - }; + enum class Entry { File, Template, Recent, Settings }; public: explicit QTStartupTabWidget (QWidget* parent= nullptr); diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index 9b70e0b0e7..0ac22c6936 100644 --- a/src/Plugins/Qt/qt_tm_widget.cpp +++ b/src/Plugins/Qt/qt_tm_widget.cpp @@ -826,7 +826,8 @@ qt_tm_widget_rep::sync_startup_tab_mode () { // Show Backstage/Startup view if (editorWidget) { editorWidget->hide (); - if (layout->indexOf (editorWidget) >= 0) layout->removeWidget (editorWidget); + if (layout->indexOf (editorWidget) >= 0) + layout->removeWidget (editorWidget); } update_visibility (); @@ -876,7 +877,8 @@ qt_tm_widget_rep::update_visibility () { bool old_bottomVisibility= bottomTools->isVisible (); bool old_extraVisibility = extraTools->isVisible (); bool old_auxVisibility = auxiliaryWidget->isVisible (); - bool old_tabVisibility = tabPageContainer ? tabPageContainer->isVisible () : false; + bool old_tabVisibility= + tabPageContainer ? tabPageContainer->isVisible () : false; bool old_statusVisibility= mainwindow ()->statusBar ()->isVisible (); bool old_titleVisibility = windowAgent->titleBar ()->isVisible (); @@ -1371,7 +1373,7 @@ qt_tm_widget_rep::write (slot s, blackbox index, widget w) { if (q && l->indexOf (q) >= 0) l->removeWidget (q); q= concrete (w)->as_qwidget (); // force creation of the new QWidget - main_widget= concrete (w); + main_widget = concrete (w); url currentView= get_current_view_safe (); if (is_none (currentView)) { startupTabMode= true; diff --git a/src/Texmacs/Data/new_view.cpp b/src/Texmacs/Data/new_view.cpp index 354ec722cc..2634dce7cc 100644 --- a/src/Texmacs/Data/new_view.cpp +++ b/src/Texmacs/Data/new_view.cpp @@ -541,10 +541,12 @@ attach_view (url win_u, url u) { set_scrollable (wid, vw->ed); vw->ed->cvw= wid.rep; ASSERT (is_attached (wid), "widget should be attached"); + // 先通知 view 被设置,确保 view_history 更新后再调用 resume + // 这样 resume() 中的菜单刷新能获取到正确的 view 列表 + notify_set_view (u); vw->ed->resume (); win->set_window_name (vw->buf->buf->title); win->set_window_url (vw->buf->buf->name); - notify_set_view (u); // cout << "View attached\n"; } diff --git a/src/Texmacs/Data/new_window.cpp b/src/Texmacs/Data/new_window.cpp index 6d52f8b0c8..4dde9cda98 100644 --- a/src/Texmacs/Data/new_window.cpp +++ b/src/Texmacs/Data/new_window.cpp @@ -297,12 +297,12 @@ url ensure_window (tree geom) { if (number_buffers () == 0) { #ifdef USE_STARTUP_TAB - url name= "tmfs://startup-tab"; - if (is_nil (concrete_buffer (name))) create_buffer (name, tree (DOCUMENT)); - url win= new_window (true, geom, true); - window_set_view (win, get_passive_view (name), true); + url name= "tmfs://startup-tab"; + if (is_nil (concrete_buffer (name))) create_buffer (name, tree (DOCUMENT)); + url win= new_window (true, geom, true); + window_set_view (win, get_passive_view (name), true); set_title_buffer (name, "Mogan STEM"); - return win; + return win; #else url name= make_welcome_buffer (); return new_buffer_in_new_window (name, tree (DOCUMENT), geom); From 6d936278578c61dd31fc8ef907d26b704997adb6 Mon Sep 17 00:00:00 2001 From: Saki Date: Tue, 17 Mar 2026 10:11:00 +0800 Subject: [PATCH 06/11] =?UTF-8?q?[216=5F1]=20=E4=BF=AE=E5=A4=8D=20startup-?= =?UTF-8?q?tab=20=E6=A0=87=E7=AD=BE=E9=A1=B5=E6=A0=87=E9=A2=98=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 set_title_buffer 移到 window_set_view 之前 - 确保标签页创建时 buffer 标题已设置为 'Mogan STEM' --- src/Texmacs/Data/new_window.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Texmacs/Data/new_window.cpp b/src/Texmacs/Data/new_window.cpp index 4dde9cda98..40a3813c61 100644 --- a/src/Texmacs/Data/new_window.cpp +++ b/src/Texmacs/Data/new_window.cpp @@ -299,9 +299,10 @@ ensure_window (tree geom) { #ifdef USE_STARTUP_TAB url name= "tmfs://startup-tab"; if (is_nil (concrete_buffer (name))) create_buffer (name, tree (DOCUMENT)); + // 先设置标题,再创建 view,确保标签页显示正确的标题 + set_title_buffer (name, "Mogan STEM"); url win= new_window (true, geom, true); window_set_view (win, get_passive_view (name), true); - set_title_buffer (name, "Mogan STEM"); return win; #else url name= make_welcome_buffer (); From 09e1d6cfdf3bfea683f6c9be76a71dbcadaeba5f Mon Sep 17 00:00:00 2001 From: Yuki Date: Tue, 17 Mar 2026 11:59:47 +0800 Subject: [PATCH 07/11] wip --- src/Plugins/Qt/qt_startup_tab_widget.cpp | 58 ++++++++++++++++++++++-- src/Plugins/Qt/qt_startup_tab_widget.hpp | 8 ++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp index 4fbe976c5c..9fe490c132 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.cpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -13,6 +13,8 @@ #include #include +#include +#include namespace { const char* @@ -33,14 +35,53 @@ entry_to_string (QTStartupTabWidget::Entry entry) { } // namespace QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) - : QWidget (parent), currentEntry_ (Entry::File) { + : QWidget (parent), currentEntry_ (Entry::File), label_ (nullptr) { setMinimumSize (400, 300); setStyleSheet ("background-color: #f0f0f0;"); - QLabel* label= new QLabel ( + setFocusPolicy (Qt::NoFocus); + label_= new QLabel ( "Mogan STEM Startup Tab (File/Template/Recent/Settings)", this); - label->setAlignment (Qt::AlignCenter); + label_->setAlignment (Qt::AlignCenter); + + // Create buttons for each entry + QPushButton* fileButton= new QPushButton ("File", this); + fileButton->setFocusPolicy (Qt::NoFocus); + QPushButton* templateButton= new QPushButton ("Template", this); + templateButton->setFocusPolicy (Qt::NoFocus); + QPushButton* recentButton= new QPushButton ("Recent", this); + recentButton->setFocusPolicy (Qt::NoFocus); + QPushButton* settingsButton= new QPushButton ("Settings", this); + settingsButton->setFocusPolicy (Qt::NoFocus); + + // Connect button clicks to set current entry + connect (fileButton, &QPushButton::clicked, this, [this]() { + set_current_entry (Entry::File); + }); + connect (templateButton, &QPushButton::clicked, this, [this]() { + set_current_entry (Entry::Template); + }); + connect (recentButton, &QPushButton::clicked, this, [this]() { + set_current_entry (Entry::Recent); + }); + connect (settingsButton, &QPushButton::clicked, this, [this]() { + set_current_entry (Entry::Settings); + }); + + // Arrange buttons horizontally + QHBoxLayout* buttonLayout= new QHBoxLayout; + buttonLayout->addWidget (fileButton); + buttonLayout->addWidget (templateButton); + buttonLayout->addWidget (recentButton); + buttonLayout->addWidget (settingsButton); + buttonLayout->addStretch (); + + // Main vertical layout QVBoxLayout* layout= new QVBoxLayout (this); - layout->addWidget (label); + layout->addWidget (label_); + layout->addLayout (buttonLayout); + layout->addStretch (); + + update_label (); } QTStartupTabWidget::Entry @@ -51,4 +92,13 @@ QTStartupTabWidget::current_entry () const { void QTStartupTabWidget::set_current_entry (Entry entry) { currentEntry_= entry; + update_label (); +} + +void +QTStartupTabWidget::update_label () { + if (label_) { + label_->setText (QString ("Mogan STEM Startup Tab - Current: %1") + .arg (entry_to_string (currentEntry_))); + } } diff --git a/src/Plugins/Qt/qt_startup_tab_widget.hpp b/src/Plugins/Qt/qt_startup_tab_widget.hpp index 74095285af..3fa9715a59 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.hpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.hpp @@ -14,7 +14,11 @@ #include +class QLabel; + class QTStartupTabWidget : public QWidget { + Q_OBJECT + public: enum class Entry { File, Template, Recent, Settings }; @@ -24,8 +28,12 @@ class QTStartupTabWidget : public QWidget { Entry current_entry () const; void set_current_entry (Entry entry); +private slots: + void update_label (); + private: Entry currentEntry_; + QLabel* label_; }; #endif From bd79aefefbe536a7cc67feb1620ef59f72242848 Mon Sep 17 00:00:00 2001 From: Saki Date: Tue, 17 Mar 2026 16:01:13 +0800 Subject: [PATCH 08/11] wip --- src/Plugins/Qt/qt_startup_tab_widget.cpp | 30 ++++++++++-------------- src/Plugins/Qt/qt_startup_tab_widget.hpp | 2 +- src/Plugins/Qt/qt_tm_widget.cpp | 17 ++++++-------- src/Texmacs/Data/new_view.cpp | 6 ++++- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp index 9fe490c132..dea7ab48e0 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.cpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -11,10 +11,10 @@ #include "qt_startup_tab_widget.hpp" -#include -#include #include +#include #include +#include namespace { const char* @@ -39,8 +39,8 @@ QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) setMinimumSize (400, 300); setStyleSheet ("background-color: #f0f0f0;"); setFocusPolicy (Qt::NoFocus); - label_= new QLabel ( - "Mogan STEM Startup Tab (File/Template/Recent/Settings)", this); + label_= new QLabel ("Mogan STEM Startup Tab (File/Template/Recent/Settings)", + this); label_->setAlignment (Qt::AlignCenter); // Create buttons for each entry @@ -54,18 +54,14 @@ QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) settingsButton->setFocusPolicy (Qt::NoFocus); // Connect button clicks to set current entry - connect (fileButton, &QPushButton::clicked, this, [this]() { - set_current_entry (Entry::File); - }); - connect (templateButton, &QPushButton::clicked, this, [this]() { - set_current_entry (Entry::Template); - }); - connect (recentButton, &QPushButton::clicked, this, [this]() { - set_current_entry (Entry::Recent); - }); - connect (settingsButton, &QPushButton::clicked, this, [this]() { - set_current_entry (Entry::Settings); - }); + connect (fileButton, &QPushButton::clicked, this, + [this] () { set_current_entry (Entry::File); }); + connect (templateButton, &QPushButton::clicked, this, + [this] () { set_current_entry (Entry::Template); }); + connect (recentButton, &QPushButton::clicked, this, + [this] () { set_current_entry (Entry::Recent); }); + connect (settingsButton, &QPushButton::clicked, this, + [this] () { set_current_entry (Entry::Settings); }); // Arrange buttons horizontally QHBoxLayout* buttonLayout= new QHBoxLayout; @@ -99,6 +95,6 @@ void QTStartupTabWidget::update_label () { if (label_) { label_->setText (QString ("Mogan STEM Startup Tab - Current: %1") - .arg (entry_to_string (currentEntry_))); + .arg (entry_to_string (currentEntry_))); } } diff --git a/src/Plugins/Qt/qt_startup_tab_widget.hpp b/src/Plugins/Qt/qt_startup_tab_widget.hpp index 3fa9715a59..de27711ff5 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.hpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.hpp @@ -32,7 +32,7 @@ private slots: void update_label (); private: - Entry currentEntry_; + Entry currentEntry_; QLabel* label_; }; diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index 0ac22c6936..cb5def32d2 100644 --- a/src/Plugins/Qt/qt_tm_widget.cpp +++ b/src/Plugins/Qt/qt_tm_widget.cpp @@ -1370,18 +1370,15 @@ qt_tm_widget_rep::write (slot s, blackbox index, widget w) { QWidget* q= main_widget->qwid; QLayout* l= centralwidget ()->layout (); - if (q && l->indexOf (q) >= 0) l->removeWidget (q); + if (q && l->indexOf (q) >= 0) { + l->removeWidget (q); + q->hide (); // 隐藏旧的 widget + } q= concrete (w)->as_qwidget (); // force creation of the new QWidget - main_widget = concrete (w); - url currentView= get_current_view_safe (); - if (is_none (currentView)) { - startupTabMode= true; - } - else { - startupTabMode= is_startup_tab_current_view (); - } - sync_startup_tab_mode (); + // SLOT_SCROLLABLE 只更新 main_widget,不设置 startupTabMode + // startupTabMode 的判定和界面更新由 SLOT_FILE 处理 + main_widget= concrete (w); } break; case SLOT_MAIN_MENU: diff --git a/src/Texmacs/Data/new_view.cpp b/src/Texmacs/Data/new_view.cpp index 2634dce7cc..d6e79a80bf 100644 --- a/src/Texmacs/Data/new_view.cpp +++ b/src/Texmacs/Data/new_view.cpp @@ -546,7 +546,8 @@ attach_view (url win_u, url u) { notify_set_view (u); vw->ed->resume (); win->set_window_name (vw->buf->buf->title); - win->set_window_url (vw->buf->buf->name); + // set_window_url 移到 window_set_view 中,在 set_current_view 之后调用 + // win->set_window_url (vw->buf->buf->name); // cout << "View attached\n"; } @@ -585,6 +586,9 @@ window_set_view (url win_u, url new_u, bool focus) { if (!is_none (old_u)) detach_view (old_u); attach_view (win_u, new_u); if (focus || get_current_view () == old_u) set_current_view (new_u); + // 在 set_current_view 之后调用 set_window_url,确保 SLOT_FILE 处理时 current + // view 已更新 + win->set_window_url (new_vw->buf->buf->name); exec_delayed (scheme_cmd ("(make-cursor-visible '" * scm_quote (as_string (new_u)) * ")")); exec_delayed (scheme_cmd ("(when (defined? 'refresh-auxiliary-widget) " From 240c80e0dc0e5537f2d1cf3d692497d471b72545 Mon Sep 17 00:00:00 2001 From: Saki Date: Tue, 17 Mar 2026 16:17:38 +0800 Subject: [PATCH 09/11] =?UTF-8?q?[216=5F1]=20=E6=9B=B4=E6=96=B0=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加详细的开发记录,包括: - 如何测试 - 所有修改的 What/Why/How - 新增和修改的文件说明 --- devel/216_1.md | 106 ++++++++++++++++++++++- src/Plugins/Qt/qt_startup_tab_widget.cpp | 44 ++++------ src/Plugins/Qt/qt_tm_widget.cpp | 47 ++++++---- 3 files changed, 150 insertions(+), 47 deletions(-) diff --git a/devel/216_1.md b/devel/216_1.md index f0439d1d95..e6ec0d6d52 100644 --- a/devel/216_1.md +++ b/devel/216_1.md @@ -1,5 +1,109 @@ # 216_1 启动标签页骨架 + 入口绑定 ## 如何测试 +1. 启动 Mogan STEM,应该显示 "Mogan STEM" 标签页 +2. 点击标签页应该显示 Backstage 界面(灰色背景,有 File/Template/Recent/Settings 按钮) +3. 新建文档(Ctrl+N)应该创建新标签页 +4. 切换标签页时,Mogan STEM 标签页应该始终显示 Backstage,而不是编辑器 +5. 关闭 Mogan STEM 标签页应该无效(受保护) -## 2026/03/02 \ No newline at end of file +## 2026/03/17 启动标签页骨架实现与入口绑定 + +### What +实现 Mogan STEM 启动标签页(Backstage)功能,包括骨架实现、入口绑定、状态同步和代码优化。 + +#### 新增文件: + +**src/Plugins/Qt/qt_startup_tab_widget.hpp** (新增) +- 定义 `QTStartupTabWidget` 类 +- 定义 `Entry` 枚举:File, Template, Recent, Settings + +**src/Plugins/Qt/qt_startup_tab_widget.cpp** (新增) +- 实现 `QTStartupTabWidget` 类 +- 提供 Backstage 界面:灰色背景,File/Template/Recent/Settings 按钮 +- 点击按钮切换当前 entry,更新标签文字 +- 使用循环和数组简化按钮创建和连接 + +**TeXmacs/progs/startup-tab/startup-tab.scm** (新增) +- Scheme 层 startup-tab 支持 + +#### 修改文件: + +**src/Plugins/Qt/qt_tm_widget.hpp** (修改) +- 添加 `startupContentWidget` 和 `startupTabMode` 成员变量 +- 添加 `sync_startup_tab_mode()` 方法声明 + +**src/Plugins/Qt/qt_tm_widget.cpp** (修改) +- 添加 `is_startup_tab_file()` 和 `is_startup_tab_current_view()` 辅助函数 +- 实现 `sync_startup_tab_mode()` 函数: + - 根据 `startupTabMode` 显示/隐藏 Backstage 或编辑器 + - 提取 `show_widget_in_layout` 和 `hide_widget_from_layout` 辅助函数 + - 在析构函数中删除 `startupContentWidget`,修复内存泄漏 +- `SLOT_FILE` 处理:设置 `startupTabMode` 并调用 `sync_startup_tab_mode()` +- `SLOT_SCROLLABLE` 处理:只更新 `main_widget`,隐藏旧 widget,不调用 `sync_startup_tab_mode()` +- `update_visibility()`: 添加对 `startupTabMode` 的支持,隐藏工具栏 + +**src/Texmacs/Data/new_window.hpp** (修改) +- `new_window()`: 添加 `force_tab_bar` 参数 + +**src/Texmacs/Data/new_window.cpp** (修改) +- `ensure_window()`: 添加 `USE_STARTUP_TAB` 支持 + - 创建 `tmfs://startup-tab` buffer + - 将 `set_title_buffer` 移到 `window_set_view` 之前,确保标签页标题正确 + - 调用 `new_window(true, geom, true)` 强制显示标签栏 +- `kill_buffer()`: 防止删除 startup-tab + +**src/Texmacs/Data/new_view.cpp** (修改) +- `attach_view()`: 将 `notify_set_view(u)` 移到 `vw->ed->resume()` 之前 + - 确保 view 先加入 `view_history`,再刷新标签页菜单 +- `window_set_view()`: 将 `set_window_url` 移到 `set_current_view` 之后 + - 确保 `SLOT_FILE` 触发时 `get_current_view_safe()` 返回正确的 view +- `kill_tabpage()`: 防止删除 startup-tab + +**src/Plugins/Qt/qt_window_widget.cpp** (修改) +- 移除不必要的 `qt_tm_widget.hpp` 包含 + +**src/Plugins/Qt/qt_dialogues.cpp** (修改) +- 移除不必要的 `qt_tm_widget.hpp` 包含 + +**src/System/config.h.xmake** (修改) +- 添加 `${define USE_STARTUP_TAB}` 宏 + +### Why +**目标**:为 Mogan 文档编辑器添加类似 Microsoft Word / WPS 的「文件页覆盖(Backstage)」与「模板中心」,在不新增独立启动壳层的前提下提升用户体验,降低新用户学习成本。 + +**问题与解决方案**: + +1. **标签页不显示问题**: + - 原因:`notify_set_view` 在 `resume` 之后调用,标签页菜单刷新时 `view_history` 为空 + - 解决:将 `notify_set_view` 移到 `resume` 之前 + +2. **标签页标题显示问题**: + - 原因:`set_title_buffer` 在创建 view 之后调用,标签页已使用默认 buffer 名 + - 解决:将 `set_title_buffer` 移到 `window_set_view` 之前 + +3. **标签页切换时界面状态不一致问题**: + - 原因:`SLOT_SCROLLABLE` 在 `set_current_view` 之前触发,`get_current_view_safe()` 返回旧 view;旧 widget 被从 layout 移除但没有隐藏 + - 解决:将 `set_window_url` 移到 `set_current_view` 之后;`SLOT_SCROLLABLE` 中隐藏旧 widget;分离 `SLOT_SCROLLABLE` 和 `SLOT_FILE` 的职责 + +4. **内存泄漏问题**: + - 原因:`startupContentWidget` 没有删除 + - 解决:在析构函数中删除 + +### How +**核心逻辑**: + +1. **启动流程**: + - `ensure_window()` 检测无 buffer 时,创建 `tmfs://startup-tab` buffer + - 设置 buffer 标题为 "Mogan STEM" + - 创建窗口并设置 view + +2. **界面切换**: + - `SLOT_SCROLLABLE`(`attach_view` 中):更新 `main_widget`,隐藏旧 widget + - `set_current_view`:更新 current view + - `SLOT_FILE`(`set_window_url` 中):设置 `startupTabMode`,调用 `sync_startup_tab_mode()` + - `sync_startup_tab_mode()`:根据 `startupTabMode` 显示/隐藏 Backstage 或编辑器 + +3. **状态判断**: + - `is_startup_tab_file(file)`:检查文件路径是否为 `tmfs://startup-tab` + - `is_startup_tab_current_view()`:检查 current view 的 buffer 是否为 startup-tab diff --git a/src/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp index dea7ab48e0..c15dfb3f79 100644 --- a/src/Plugins/Qt/qt_startup_tab_widget.cpp +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -44,31 +44,23 @@ QTStartupTabWidget::QTStartupTabWidget (QWidget* parent) label_->setAlignment (Qt::AlignCenter); // Create buttons for each entry - QPushButton* fileButton= new QPushButton ("File", this); - fileButton->setFocusPolicy (Qt::NoFocus); - QPushButton* templateButton= new QPushButton ("Template", this); - templateButton->setFocusPolicy (Qt::NoFocus); - QPushButton* recentButton= new QPushButton ("Recent", this); - recentButton->setFocusPolicy (Qt::NoFocus); - QPushButton* settingsButton= new QPushButton ("Settings", this); - settingsButton->setFocusPolicy (Qt::NoFocus); + struct ButtonInfo { + const char* text; + Entry entry; + }; + ButtonInfo buttons[]= {{"File", Entry::File}, + {"Template", Entry::Template}, + {"Recent", Entry::Recent}, + {"Settings", Entry::Settings}}; - // Connect button clicks to set current entry - connect (fileButton, &QPushButton::clicked, this, - [this] () { set_current_entry (Entry::File); }); - connect (templateButton, &QPushButton::clicked, this, - [this] () { set_current_entry (Entry::Template); }); - connect (recentButton, &QPushButton::clicked, this, - [this] () { set_current_entry (Entry::Recent); }); - connect (settingsButton, &QPushButton::clicked, this, - [this] () { set_current_entry (Entry::Settings); }); - - // Arrange buttons horizontally QHBoxLayout* buttonLayout= new QHBoxLayout; - buttonLayout->addWidget (fileButton); - buttonLayout->addWidget (templateButton); - buttonLayout->addWidget (recentButton); - buttonLayout->addWidget (settingsButton); + for (const auto& info : buttons) { + QPushButton* btn= new QPushButton (info.text, this); + btn->setFocusPolicy (Qt::NoFocus); + connect (btn, &QPushButton::clicked, this, + [this, entry= info.entry] () { set_current_entry (entry); }); + buttonLayout->addWidget (btn); + } buttonLayout->addStretch (); // Main vertical layout @@ -93,8 +85,6 @@ QTStartupTabWidget::set_current_entry (Entry entry) { void QTStartupTabWidget::update_label () { - if (label_) { - label_->setText (QString ("Mogan STEM Startup Tab - Current: %1") - .arg (entry_to_string (currentEntry_))); - } + label_->setText (QString ("Mogan STEM Startup Tab - Current: %1") + .arg (entry_to_string (currentEntry_))); } diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index cb5def32d2..b70725a249 100644 --- a/src/Plugins/Qt/qt_tm_widget.cpp +++ b/src/Plugins/Qt/qt_tm_widget.cpp @@ -767,6 +767,11 @@ qt_tm_widget_rep::~qt_tm_widget_rep () { // clear any residual waiting menu installation waiting_widgets= remove (waiting_widgets, this); + + // delete startup content widget + if (startupContentWidget) { + delete startupContentWidget; + } } void @@ -809,6 +814,25 @@ qt_tm_widget_rep::plain_window_widget (string name, command _quit, int b) { return this; } +// Helper functions to show/hide widgets in layout +static void +show_widget_in_layout (QWidget* widget, QLayout* layout) { + if (!widget || !layout) return; + if (layout->indexOf (widget) < 0) { + layout->addWidget (widget); + } + widget->show (); +} + +static void +hide_widget_from_layout (QWidget* widget, QLayout* layout) { + if (!widget || !layout) return; + widget->hide (); + if (layout->indexOf (widget) >= 0) { + layout->removeWidget (widget); + } +} + void qt_tm_widget_rep::sync_startup_tab_mode () { QWidget* editorWidget= main_widget->qwid; @@ -824,34 +848,19 @@ qt_tm_widget_rep::sync_startup_tab_mode () { if (startupTabMode) { // Show Backstage/Startup view - if (editorWidget) { - editorWidget->hide (); - if (layout->indexOf (editorWidget) >= 0) - layout->removeWidget (editorWidget); - } + hide_widget_from_layout (editorWidget, layout); update_visibility (); if (!startupContentWidget) { startupContentWidget= new QTStartupTabWidget (centralwidget ()); } - if (layout->indexOf (startupContentWidget) < 0) { - layout->addWidget (startupContentWidget); - } - startupContentWidget->show (); + show_widget_in_layout (startupContentWidget, layout); } else { // Show normal editor view - if (startupContentWidget) { - startupContentWidget->hide (); - if (layout->indexOf (startupContentWidget) >= 0) - layout->removeWidget (startupContentWidget); - } - - if (editorWidget) { - if (layout->indexOf (editorWidget) < 0) layout->addWidget (editorWidget); - editorWidget->show (); - } + hide_widget_from_layout (startupContentWidget, layout); + show_widget_in_layout (editorWidget, layout); update_visibility (); From fa7653213198f0f29df186a5f1b2fd9d193c1f7a Mon Sep 17 00:00:00 2001 From: Saki Date: Tue, 17 Mar 2026 17:30:18 +0800 Subject: [PATCH 10/11] devel --- devel/216_1.md | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/devel/216_1.md b/devel/216_1.md index e6ec0d6d52..e79db9f3aa 100644 --- a/devel/216_1.md +++ b/devel/216_1.md @@ -1,11 +1,12 @@ # 216_1 启动标签页骨架 + 入口绑定 ## 如何测试 -1. 启动 Mogan STEM,应该显示 "Mogan STEM" 标签页 -2. 点击标签页应该显示 Backstage 界面(灰色背景,有 File/Template/Recent/Settings 按钮) -3. 新建文档(Ctrl+N)应该创建新标签页 -4. 切换标签页时,Mogan STEM 标签页应该始终显示 Backstage,而不是编辑器 -5. 关闭 Mogan STEM 标签页应该无效(受保护) +- 编译时先输入 `xmake config -vD --startup_tab=true` + 1. 启动 Mogan STEM,应该显示 "Mogan STEM" 标签页 + 2. 点击标签页应该显示 Backstage 界面(灰色背景,有 File/Template/Recent/Settings 按钮) + 3. 新建文档应该创建新标签页 + 4. 切换标签页为 Mogan STEM 标签页时应该始终显示 Backstage,而不是编辑器 + 5. 关闭 Mogan STEM 标签页应该无效(受保护) ## 2026/03/17 启动标签页骨架实现与入口绑定 @@ -72,24 +73,6 @@ ### Why **目标**:为 Mogan 文档编辑器添加类似 Microsoft Word / WPS 的「文件页覆盖(Backstage)」与「模板中心」,在不新增独立启动壳层的前提下提升用户体验,降低新用户学习成本。 -**问题与解决方案**: - -1. **标签页不显示问题**: - - 原因:`notify_set_view` 在 `resume` 之后调用,标签页菜单刷新时 `view_history` 为空 - - 解决:将 `notify_set_view` 移到 `resume` 之前 - -2. **标签页标题显示问题**: - - 原因:`set_title_buffer` 在创建 view 之后调用,标签页已使用默认 buffer 名 - - 解决:将 `set_title_buffer` 移到 `window_set_view` 之前 - -3. **标签页切换时界面状态不一致问题**: - - 原因:`SLOT_SCROLLABLE` 在 `set_current_view` 之前触发,`get_current_view_safe()` 返回旧 view;旧 widget 被从 layout 移除但没有隐藏 - - 解决:将 `set_window_url` 移到 `set_current_view` 之后;`SLOT_SCROLLABLE` 中隐藏旧 widget;分离 `SLOT_SCROLLABLE` 和 `SLOT_FILE` 的职责 - -4. **内存泄漏问题**: - - 原因:`startupContentWidget` 没有删除 - - 解决:在析构函数中删除 - ### How **核心逻辑**: From 6eb829dfb469236a3eef6e7ca3c3782692e8561c Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 18 Mar 2026 08:21:54 +0800 Subject: [PATCH 11/11] devel --- devel/216_1.md | 14 +++++++++----- src/System/Boot/init_texmacs.cpp | 5 ----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/devel/216_1.md b/devel/216_1.md index e79db9f3aa..b002b4bf8b 100644 --- a/devel/216_1.md +++ b/devel/216_1.md @@ -44,6 +44,11 @@ - `SLOT_SCROLLABLE` 处理:只更新 `main_widget`,隐藏旧 widget,不调用 `sync_startup_tab_mode()` - `update_visibility()`: 添加对 `startupTabMode` 的支持,隐藏工具栏 +**src/Plugins/Qt/QTMTabPage.cpp** (修改) +- 启动页 tab 特殊化:固定到最左侧、限制最大宽度(`STARTUP_TAB_MAX_WIDTH`) +- 禁止启动页 tab 拖拽重排/跨窗口拖放 +- 启动页 tab 不显示关闭按钮 + **src/Texmacs/Data/new_window.hpp** (修改) - `new_window()`: 添加 `force_tab_bar` 参数 @@ -61,11 +66,10 @@ - 确保 `SLOT_FILE` 触发时 `get_current_view_safe()` 返回正确的 view - `kill_tabpage()`: 防止删除 startup-tab -**src/Plugins/Qt/qt_window_widget.cpp** (修改) -- 移除不必要的 `qt_tm_widget.hpp` 包含 - -**src/Plugins/Qt/qt_dialogues.cpp** (修改) -- 移除不必要的 `qt_tm_widget.hpp` 包含 +**src/System/Boot/init_texmacs.cpp** (修改) +- 启动阶段欢迎页触发条件调整为仅 `install_status == 1` +- 移除 `install_status == 2`相关代码 +- 移除 `has_initial_welcome` 对 `ensure_window()` 的影响,确保无命令行文件时仍进入统一窗口创建流程 **src/System/config.h.xmake** (修改) - 添加 `${define USE_STARTUP_TAB}` 宏 diff --git a/src/System/Boot/init_texmacs.cpp b/src/System/Boot/init_texmacs.cpp index 35d7095008..394f347377 100644 --- a/src/System/Boot/init_texmacs.cpp +++ b/src/System/Boot/init_texmacs.cpp @@ -616,11 +616,6 @@ load_settings_and_check_version () { install_status= 1; } - if (get_setting ("VERSION") != XMACS_VERSION) { - init_upgrade (); - install_status= 2; - } - return install_status; }