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..b002b4bf8b --- /dev/null +++ b/devel/216_1.md @@ -0,0 +1,96 @@ +# 216_1 启动标签页骨架 + 入口绑定 + +## 如何测试 +- 编译时先输入 `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 启动标签页骨架实现与入口绑定 + +### 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/Plugins/Qt/QTMTabPage.cpp** (修改) +- 启动页 tab 特殊化:固定到最左侧、限制最大宽度(`STARTUP_TAB_MAX_WIDTH`) +- 禁止启动页 tab 拖拽重排/跨窗口拖放 +- 启动页 tab 不显示关闭按钮 + +**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/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}` 宏 + +### Why +**目标**:为 Mogan 文档编辑器添加类似 Microsoft Word / WPS 的「文件页覆盖(Backstage)」与「模板中心」,在不新增独立启动壳层的前提下提升用户体验,降低新用户学习成本。 + +### 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/QTMTabPage.cpp b/src/Plugins/Qt/QTMTabPage.cpp index dda959f78c..45c3fe2198 100644 --- a/src/Plugins/Qt/QTMTabPage.cpp +++ b/src/Plugins/Qt/QTMTabPage.cpp @@ -20,7 +20,8 @@ // 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). #ifdef Q_OS_MAC @@ -100,6 +101,25 @@ 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 +216,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 +231,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 +281,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 +381,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 @@ -404,22 +435,26 @@ 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); + } 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 +508,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 +571,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; @@ -552,7 +601,15 @@ 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; + 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/Plugins/Qt/qt_startup_tab_widget.cpp b/src/Plugins/Qt/qt_startup_tab_widget.cpp new file mode 100644 index 0000000000..c15dfb3f79 --- /dev/null +++ b/src/Plugins/Qt/qt_startup_tab_widget.cpp @@ -0,0 +1,90 @@ + +/****************************************************************************** + * 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" + +#include +#include +#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), label_ (nullptr) { + setMinimumSize (400, 300); + setStyleSheet ("background-color: #f0f0f0;"); + setFocusPolicy (Qt::NoFocus); + label_= new QLabel ("Mogan STEM Startup Tab (File/Template/Recent/Settings)", + this); + label_->setAlignment (Qt::AlignCenter); + + // Create buttons for each entry + struct ButtonInfo { + const char* text; + Entry entry; + }; + ButtonInfo buttons[]= {{"File", Entry::File}, + {"Template", Entry::Template}, + {"Recent", Entry::Recent}, + {"Settings", Entry::Settings}}; + + QHBoxLayout* buttonLayout= new QHBoxLayout; + 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 + QVBoxLayout* layout= new QVBoxLayout (this); + layout->addWidget (label_); + layout->addLayout (buttonLayout); + layout->addStretch (); + + update_label (); +} + +QTStartupTabWidget::Entry +QTStartupTabWidget::current_entry () const { + return currentEntry_; +} + +void +QTStartupTabWidget::set_current_entry (Entry entry) { + currentEntry_= entry; + update_label (); +} + +void +QTStartupTabWidget::update_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 new file mode 100644 index 0000000000..de27711ff5 --- /dev/null +++ b/src/Plugins/Qt/qt_startup_tab_widget.hpp @@ -0,0 +1,39 @@ + +/****************************************************************************** + * 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 QLabel; + +class QTStartupTabWidget : public QWidget { + Q_OBJECT + +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 slots: + void update_label (); + +private: + Entry currentEntry_; + QLabel* label_; +}; + +#endif diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index 1c16cf6c35..b70725a249 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)); @@ -754,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 @@ -796,6 +814,64 @@ 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; + QLayout* layout = centralwidget ()->layout (); + if (!layout) return; + + bool hasActiveView= !is_none (get_current_view_safe ()); + + // Auto-enable startup mode when no active view or no editor widget + if (!hasActiveView || editorWidget == nullptr) { + startupTabMode= true; + } + + if (startupTabMode) { + // Show Backstage/Startup view + hide_widget_from_layout (editorWidget, layout); + + update_visibility (); + + if (!startupContentWidget) { + startupContentWidget= new QTStartupTabWidget (centralwidget ()); + } + show_widget_in_layout (startupContentWidget, layout); + } + else { + // Show normal editor view + hide_widget_from_layout (startupContentWidget, layout); + show_widget_in_layout (editorWidget, layout); + + update_visibility (); + + 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 +886,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_statusVisibility= mainwindow ()->statusBar ()->isVisible (); bool old_titleVisibility = windowAgent->titleBar ()->isVisible (); @@ -827,6 +905,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 +941,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 +1170,8 @@ 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); + sync_startup_tab_mode (); } break; case SLOT_POSITION: { check_type (val, s); @@ -1280,23 +1378,16 @@ 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->hide (); // 隐藏旧的 widget + } 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 " */ + // SLOT_SCROLLABLE 只更新 main_widget,不设置 startupTabMode + // startupTabMode 的判定和界面更新由 SLOT_FILE 处理 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)); } 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 605fb7bdf9..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; } @@ -936,13 +931,9 @@ 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 || !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_view.cpp b/src/Texmacs/Data/new_view.cpp index 5fb1a28b08..d6e79a80bf 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; @@ -538,10 +541,13 @@ 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); + // set_window_url 移到 window_set_view 中,在 set_current_view 之后调用 + // win->set_window_url (vw->buf->buf->name); // cout << "View attached\n"; } @@ -580,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) " diff --git a/src/Texmacs/Data/new_window.cpp b/src/Texmacs/Data/new_window.cpp index 45f50fec12..40a3813c61 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,18 @@ 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)); + // 先设置标题,再创建 view,确保标签页显示正确的标题 + set_title_buffer (name, "Mogan STEM"); + url win= new_window (true, geom, true); + window_set_view (win, get_passive_view (name), true); + 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 (); @@ -329,6 +339,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])) { 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"), }})