feat: route search through QML shell

This commit is contained in:
dela
2026-07-15 13:47:20 +08:00
parent b117bbf99e
commit a411fd396a
10 changed files with 159 additions and 8 deletions
+4 -2
View File
@@ -44,7 +44,7 @@
| 2 | 用户首页 | 收藏 `views/user/FavoritesView` | favorites-agent | done | MediaCard/List、收藏模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类、详情、文件夹、人物跳转及返回 | | 2 | 用户首页 | 收藏 `views/user/FavoritesView` | favorites-agent | done | MediaCard/List、收藏模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类、详情、文件夹、人物跳转及返回 |
| 2 | 用户首页 | 分类 `views/user/CategoryView` | category-agent | done | MediaCard/List、筛选/分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类切换、分页、空态、返回状态保持 | | 2 | 用户首页 | 分类 `views/user/CategoryView` | category-agent | done | MediaCard/List、筛选/分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类切换、分页、空态、返回状态保持 |
| 2 | 媒体浏览 | 媒体库/人物/过滤结果(共用 `views/media/LibraryView`) | library-agent | done | MediaCard/Grid、排序筛选菜单、分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;海报/平铺视图、排序筛选、滚动续载、人物页 | | 2 | 媒体浏览 | 媒体库/人物/过滤结果(共用 `views/media/LibraryView`) | library-agent | done | MediaCard/Grid、排序筛选菜单、分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;海报/平铺视图、排序筛选、滚动续载、人物页 |
| 2 | 应用壳 | `MainWindow` 标题栏、全局搜索、HomeView 侧栏与路由栈 | core | in-progress | `NavigationController`、单引擎 `Router.qml`、Home/Favorites singleton 已接入;待 Search/浏览 route、侧栏与标题导航 | `cmake --build cmake-build-debug --target qEmbyNavigationTest qEmbyApp_qmllint qEmbyApp -j2 && ./cmake-build-debug/bin/qEmbyNavigationTest`;登录→首页→收藏→详情→返回→设置→退出全链路,检查窗口按钮与托盘 | | 2 | 应用壳 | `MainWindow` 标题栏、全局搜索、HomeView 侧栏与路由栈 | core | in-progress | `NavigationController`、单引擎 `Router.qml`、Home/Favorites/Search 已接入;待 Category/Library 等浏览 route、侧栏与标题导航 | `cmake --build cmake-build-debug --target qEmbyNavigationTest qEmbyApp_qmllint qEmbyApp -j2 && ./cmake-build-debug/bin/qEmbyNavigationTest`;登录→首页→搜索→返回→收藏→详情→返回→设置→退出全链路,检查窗口按钮与托盘 |
| 3 | 媒体详情 | 详情 `views/media/DetailView` | detail-agent | done | MediaCard、Flow/Tag、动作菜单、编辑/识别弹窗桥接 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;电影/剧集/人物各一项,播放、收藏、版本选择、编辑入口 | | 3 | 媒体详情 | 详情 `views/media/DetailView` | detail-agent | done | MediaCard、Flow/Tag、动作菜单、编辑/识别弹窗桥接 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;电影/剧集/人物各一项,播放、收藏、版本选择、编辑入口 |
| 3 | 媒体详情 | 季 `views/media/SeasonView` | season-agent | done | Episode 列表模型、MediaCard、播放动作 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;切季、剧集续播/播放、已播状态、返回 | | 3 | 媒体详情 | 季 `views/media/SeasonView` | season-agent | done | Episode 列表模型、MediaCard、播放动作 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;切季、剧集续播/播放、已播状态、返回 |
| 3 | 管理 | 媒体库 `PageLibraries` | admin-libraries-agent | done | 管理卡片/Grid、拖拽排序、Library CRUD 对话框 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;新增/编辑/删除/扫描/取消扫描/排序 | | 3 | 管理 | 媒体库 `PageLibraries` | admin-libraries-agent | done | 管理卡片/Grid、拖拽排序、Library CRUD 对话框 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;新增/编辑/删除/扫描/取消扫描/排序 |
@@ -65,7 +65,9 @@
4. **迁移复杂业务页**:详情/季与六个剩余管理页。先提取 C++ ViewModel/`QAbstractListModel`,QML 不直接持有 service 协程或业务缓存。 4. **迁移复杂业务页**:详情/季与六个剩余管理页。先提取 C++ ViewModel/`QAbstractListModel`,QML 不直接持有 service 协程或业务缓存。
5. **最后处理播放与长尾弹窗**:播放器涉及 libmpv/OpenGL/原生窗口和输入焦点,是最高风险项。平台集成可以保留 C++,目标应是“QML 表现层 + C++ 原生能力”,而不是消灭所有 QWidget/C++。 5. **最后处理播放与长尾弹窗**:播放器涉及 libmpv/OpenGL/原生窗口和输入焦点,是最高风险项。平台集成可以保留 C++,目标应是“QML 表现层 + C++ 原生能力”,而不是消灭所有 QWidget/C++。
当前开发批次(2026-07-15):导航 Phase 0/1 已落地,Phase 2 首批完成。新增强类型 `NavigationRoute`、`NavigationController` 和轻量契约测试;Widgets 页面统一先转换为规范 route,再进入旧页面栈。`QuickRouterHost` 以单个 QML engine 承载 `Router.qml`,Home/Favorites 共享该 host 与图片 provider,并由 `HomeQuickRouterView` 协调 ViewModel 生命周期和 BaseView 媒体动作。其他 route 暂时保持旧 Widgets host,切入时 Router 保留底层页面且暂停生命周期,避免过渡闪屏。可用 `QEMBY_USE_LEGACY_HOME_PAGES=1` 临时回退旧 Home/Favorites 双 host。下一批先接 Search,再处理 Category/Library 的参数加载与 LRU ViewModel 所有权。 当前开发批次(2026-07-15):导航 Phase 0/1 已落地,Phase 2 首批完成。新增强类型 `NavigationRoute`、`NavigationController` 和轻量契约测试;Widgets 页面统一先转换为规范 route,再进入旧页面栈。`QuickRouterHost` 以单个 QML engine 承载 `Router.qml`,Home/Favorites 共享该 host 与图片 provider,并由 `HomeQuickRouterView` 协调 ViewModel 生命周期和 BaseView 媒体动作。其他 route 暂时保持旧 Widgets host,切入时 Router 保留底层页面且暂停生命周期,避免过渡闪屏。可用 `QEMBY_USE_LEGACY_HOME_PAGES=1` 临时回退旧 Home/Favorites 双 host。
Phase 2 第二批(2026-07-15):Search 已加入根 Router。`HomeView::triggerSearch()` 现在生成规范 `search` route;同一 Quick host 内的 Home/Favorites/Search 切换只更新 Controller/Loader,进入 legacy 详情页时仍保存兼容历史。`SearchViewModel` 新增 active/generation 生命周期,隐藏或离开搜索时淘汰在途结果,返回时仅按需恢复;旧 `SearchView` 默认 active 行为保持兼容。下一批进入 Category/Library/Person/Filtered,重点解决带参数 route 的 ViewModel 实例所有权与滚动状态。
阶段门禁:每阶段至少通过 QML 静态检查、Debug 构建、明暗主题、窗口缩放、键盘焦点、页面反复进入退出、网络失败/空数据检查;依赖真实服务器的行为必须用 Emby/Jellyfin 各冒烟一次。 阶段门禁:每阶段至少通过 QML 静态检查、Debug 构建、明暗主题、窗口缩放、键盘焦点、页面反复进入退出、网络失败/空数据检查;依赖真实服务器的行为必须用 Emby/Jellyfin 各冒烟一次。
+7
View File
@@ -402,3 +402,10 @@ qemby://search?q=<percent-encoded-query>
- 播放、收藏、详情/媒体库/人物/分类导航、通用菜单及 Dashboard 管理媒体库专用菜单继续复用 `BaseView`/C++ action bridge,避免页面迁移造成功能回退。 - 播放、收藏、详情/媒体库/人物/分类导航、通用菜单及 Dashboard 管理媒体库专用菜单继续复用 `BaseView`/C++ action bridge,避免页面迁移造成功能回退。
- Home/Favorites 单引擎路径默认开启;设置环境变量 `QEMBY_USE_LEGACY_HOME_PAGES=1` 可回退旧双 `QuickPageHost`,用于一个发布周期内的兼容验证。 - Home/Favorites 单引擎路径默认开启;设置环境变量 `QEMBY_USE_LEGACY_HOME_PAGES=1` 可回退旧双 `QuickPageHost`,用于一个发布周期内的兼容验证。
- 首批无服务器 offscreen 冒烟和 legacy fallback 冒烟均可稳定运行至测试超时,未出现 QML runtime error。期间发现并修复 `AdminTranscodingPage.DoubleField` 覆盖 `QQuickItem` final `bottom/top` 属性的问题。 - 首批无服务器 offscreen 冒烟和 legacy fallback 冒烟均可稳定运行至测试超时,未出现 QML runtime error。期间发现并修复 `AdminTranscodingPage.DoubleField` 覆盖 `QQuickItem` final `bottom/top` 属性的问题。
2026-07-15 完成 Phase 2 第二批 Search:
- `Router.qml` 增加 Search 静态组件;Search 与 Home/Favorites 共享根 engine、图片 provider 和 action bridge。
- `HomeView::triggerSearch()` 改为 push 规范 search route;同一 Router widget 内切换也会记录历史,因此 Home → Search → Back 可以恢复 Home,Search → legacy Detail → Back 可以恢复原搜索。
- `SearchViewModel` 增加 active/generation 取消语义;inactive 时只记录待搜索 query,重新激活后按需恢复,避免隐藏页面回写陈旧结果。
- 导航契约测试新增 search key 和返回用例;默认 Router 与 legacy fallback 的无服务器 offscreen 冒烟均无 runtime 输出。
+4 -2
View File
@@ -9,7 +9,7 @@ Item {
property var navigationController: navigation property var navigationController: navigation
// Keep this allow-list deliberately small until each page's context // Keep this allow-list deliberately small until each page's context
// ViewModel has been moved to the root Quick host. // ViewModel has been moved to the root Quick host.
property var enabledRoutes: ["home", "favorites"] property var enabledRoutes: ["home", "favorites", "search"]
property var currentRoute: navigationController property var currentRoute: navigationController
? navigationController.currentRoute ? navigationController.currentRoute
: ({ "name": "navigationError", : ({ "name": "navigationError",
@@ -69,7 +69,7 @@ Item {
} }
function routeImplemented(name) { function routeImplemented(name) {
return name === "home" || name === "favorites"; return name === "home" || name === "favorites" || name === "search";
} }
function routeKnown(name) { function routeKnown(name) {
@@ -102,6 +102,7 @@ Item {
switch (name) { switch (name) {
case "home": return homeComponent; case "home": return homeComponent;
case "favorites": return favoritesComponent; case "favorites": return favoritesComponent;
case "search": return searchComponent;
default: default:
return null; return null;
} }
@@ -219,6 +220,7 @@ Item {
Component { id: homeComponent; DashboardPage { anchors.fill: parent } } Component { id: homeComponent; DashboardPage { anchors.fill: parent } }
Component { id: favoritesComponent; FavoritesPage { anchors.fill: parent } } Component { id: favoritesComponent; FavoritesPage { anchors.fill: parent } }
Component { id: searchComponent; SearchPage { anchors.fill: parent } }
Component { Component {
id: recoveryComponent id: recoveryComponent
@@ -27,6 +27,13 @@ int main(int argc, char **argv)
navigation.enterAuthenticatedSession(false); navigation.enterAuthenticatedSession(false);
ok &= check(navigation.currentRoute().value("name") == "home", "session starts at home"); ok &= check(navigation.currentRoute().value("name") == "home", "session starts at home");
ok &= check(navigation.push({{"name", "search"},
{"params", QVariantMap{{"query", "star wars"}}}}),
"accepts a valid search route");
ok &= check(navigation.currentRoute().value("key") == "search:star%20wars",
"search query participates in the route key");
ok &= check(navigation.back() && navigation.currentRoute().value("name") == "home",
"back returns from search to its session root");
ok &= check(navigation.push({{"name", "library"}, ok &= check(navigation.push({{"name", "library"},
{"params", QVariantMap{{"libraryId", "lib/1"}, {"params", QVariantMap{{"libraryId", "lib/1"},
{"title", "Movies"}}}}), {"title", "Movies"}}}}),
@@ -136,6 +136,28 @@ void SearchViewModel::setCurrentTab(int tab) {
launchCurrentSearch(); launchCurrentSearch();
} }
void SearchViewModel::setActive(bool active) {
if (m_active == active)
return;
m_active = active;
if (!active) {
++m_searchGeneration;
if (m_loading)
m_retryOnActivate = true;
if (m_loading) {
m_loading = false;
emit stateChanged();
}
return;
}
if (m_retryOnActivate && !m_query.isEmpty()) {
m_retryOnActivate = false;
launchCurrentSearch();
}
}
QString SearchViewModel::includeItemTypes(int tab) { QString SearchViewModel::includeItemTypes(int tab) {
switch (tab) { switch (tab) {
case 0: case 0:
@@ -155,6 +177,10 @@ QString SearchViewModel::includeItemTypes(int tab) {
void SearchViewModel::launchCurrentSearch() { void SearchViewModel::launchCurrentSearch() {
if (m_query.isEmpty()) if (m_query.isEmpty())
return; return;
if (!m_active) {
m_retryOnActivate = true;
return;
}
QCoro::connect(search(m_query), this, []() {}); QCoro::connect(search(m_query), this, []() {});
} }
@@ -163,7 +189,16 @@ QCoro::Task<void> SearchViewModel::search(QString query) {
if (query.isEmpty() || !m_core || !m_core->mediaService()) if (query.isEmpty() || !m_core || !m_core->mediaService())
co_return; co_return;
const bool queryChanged = m_query != query;
m_query = query; m_query = query;
if (!m_active) {
m_retryOnActivate = true;
if (queryChanged)
emit stateChanged();
co_return;
}
m_retryOnActivate = false;
const quint64 generation = ++m_searchGeneration; const quint64 generation = ++m_searchGeneration;
const QString types = includeItemTypes(m_currentTab); const QString types = includeItemTypes(m_currentTab);
m_loading = true; m_loading = true;
@@ -64,6 +64,7 @@ public:
void setTileView(bool tileView); void setTileView(bool tileView);
void setCurrentTab(int tab); void setCurrentTab(int tab);
void setActive(bool active);
QCoro::Task<void> search(QString query); QCoro::Task<void> search(QString query);
void updateItem(const MediaItem &item); void updateItem(const MediaItem &item);
@@ -92,6 +93,8 @@ private:
QString m_statsText; QString m_statsText;
QString m_errorText; QString m_errorText;
bool m_loading = false; bool m_loading = false;
bool m_active = true;
bool m_retryOnActivate = false;
bool m_tileView = false; bool m_tileView = false;
int m_currentTab = 0; int m_currentTab = 0;
quint64 m_searchGeneration = 0; quint64 m_searchGeneration = 0;
@@ -8,6 +8,7 @@
#include "../../components/moderntoast.h" #include "../../components/moderntoast.h"
#include "../../quick/navigationcontroller.h" #include "../../quick/navigationcontroller.h"
#include "../../quick/quickrouterhost.h" #include "../../quick/quickrouterhost.h"
#include "../search/searchviewmodel.h"
#include <qembycore.h> #include <qembycore.h>
#include <services/admin/adminservice.h> #include <services/admin/adminservice.h>
@@ -37,11 +38,14 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
m_host = new QuickRouterHost(core, navigation, this); m_host = new QuickRouterHost(core, navigation, this);
m_dashboardViewModel = new UserDashboardViewModel(core, this); m_dashboardViewModel = new UserDashboardViewModel(core, this);
m_favoritesViewModel = new FavoritesViewModel(core, this); m_favoritesViewModel = new FavoritesViewModel(core, this);
m_searchViewModel = new SearchViewModel(core, this);
m_host->setContextProperty(QStringLiteral("dashboardPage"), m_host->setContextProperty(QStringLiteral("dashboardPage"),
m_dashboardViewModel); m_dashboardViewModel);
m_host->setContextProperty(QStringLiteral("favoritesPage"), m_host->setContextProperty(QStringLiteral("favoritesPage"),
m_favoritesViewModel); m_favoritesViewModel);
m_host->setContextProperty(QStringLiteral("searchPage"),
m_searchViewModel);
m_host->loadRouter(); m_host->loadRouter();
layout->addWidget(m_host); layout->addWidget(m_host);
@@ -96,6 +100,18 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
{ {
handleMoreMenuRequested(item, QCursor::pos()); handleMoreMenuRequested(item, QCursor::pos());
}); });
connect(m_searchViewModel, &SearchViewModel::itemActivated,
this, &HomeQuickRouterView::forwardItemActivation);
connect(m_searchViewModel, &SearchViewModel::playRequested,
this, &BaseView::handlePlayRequested);
connect(m_searchViewModel, &SearchViewModel::favoriteRequested,
this, &BaseView::handleFavoriteRequested);
connect(m_searchViewModel, &SearchViewModel::moreRequested,
this, [this](const MediaItem &item, const QPoint &localPos)
{
handleMoreMenuRequested(item, m_host->mapToGlobal(localPos));
});
} }
void HomeQuickRouterView::refreshHome() void HomeQuickRouterView::refreshHome()
@@ -125,6 +141,7 @@ void HomeQuickRouterView::onMediaItemUpdated(const MediaItem &item)
{ {
m_dashboardViewModel->updateItem(item); m_dashboardViewModel->updateItem(item);
m_favoritesViewModel->updateItem(item); m_favoritesViewModel->updateItem(item);
m_searchViewModel->updateItem(item);
} }
void HomeQuickRouterView::onMediaItemRemoved(const QString &itemId) void HomeQuickRouterView::onMediaItemRemoved(const QString &itemId)
@@ -199,21 +216,50 @@ void HomeQuickRouterView::dispatchCardContextMenuRequest(
void HomeQuickRouterView::updateRouteActivity() void HomeQuickRouterView::updateRouteActivity()
{ {
const QString routeName = m_navigation const QVariantMap route = m_navigation
? m_navigation->currentRoute().value(QStringLiteral("name")).toString() ? m_navigation->currentRoute()
: QString(); : QVariantMap{};
const QString routeName = route.value(QStringLiteral("name")).toString();
const bool hostActive = m_host && m_host->isActive(); const bool hostActive = m_host && m_host->isActive();
m_dashboardViewModel->setActive(hostActive && m_dashboardViewModel->setActive(hostActive &&
routeName == QLatin1String("home")); routeName == QLatin1String("home"));
m_favoritesViewModel->setActive(hostActive && m_favoritesViewModel->setActive(hostActive &&
routeName == QLatin1String("favorites")); routeName == QLatin1String("favorites"));
const bool searchActive = hostActive &&
routeName == QLatin1String("search");
if (!searchActive)
{
m_searchViewModel->setActive(false);
return;
}
const QVariantMap params = route.value(QStringLiteral("params")).toMap();
const QString query = params.value(QStringLiteral("query"))
.toString().trimmed();
const QString routeKey = route.value(QStringLiteral("key")).toString();
const bool routeChanged = routeKey != m_searchRouteKey ||
query != m_searchQuery;
if (!query.isEmpty() && routeChanged)
{
m_searchRouteKey = routeKey;
m_searchQuery = query;
// SearchViewModel deliberately remembers a query while inactive.
// Queue the new query before activation so an interrupted previous
// query cannot be restarted immediately before this one.
QCoro::connect(m_searchViewModel->search(query), this, []() {});
}
m_searchViewModel->setActive(true);
} }
void HomeQuickRouterView::deactivateRoutes() void HomeQuickRouterView::deactivateRoutes()
{ {
m_dashboardViewModel->setActive(false); m_dashboardViewModel->setActive(false);
m_favoritesViewModel->setActive(false); m_favoritesViewModel->setActive(false);
m_searchViewModel->setActive(false);
} }
void HomeQuickRouterView::forwardItemActivation(const MediaItem &item) void HomeQuickRouterView::forwardItemActivation(const MediaItem &item)
@@ -5,6 +5,7 @@
class FavoritesViewModel; class FavoritesViewModel;
class NavigationController; class NavigationController;
class QuickRouterHost; class QuickRouterHost;
class SearchViewModel;
class UserDashboardViewModel; class UserDashboardViewModel;
// Bridges the first application-level QML routes to the existing BaseView // Bridges the first application-level QML routes to the existing BaseView
@@ -52,4 +53,7 @@ private:
QuickRouterHost *m_host = nullptr; QuickRouterHost *m_host = nullptr;
UserDashboardViewModel *m_dashboardViewModel = nullptr; UserDashboardViewModel *m_dashboardViewModel = nullptr;
FavoritesViewModel *m_favoritesViewModel = nullptr; FavoritesViewModel *m_favoritesViewModel = nullptr;
SearchViewModel *m_searchViewModel = nullptr;
QString m_searchRouteKey;
QString m_searchQuery;
}; };
+44
View File
@@ -1085,6 +1085,11 @@ void HomeView::triggerSearch(const QString &query)
m_libraryList->clearSelection(); m_libraryList->clearSelection();
const QVariantMap searchRequest{
{QStringLiteral("name"), QStringLiteral("search")},
{QStringLiteral("params"),
QVariantMap{{QStringLiteral("query"), trimmedQuery}}}};
if (!pushQuickRoute(searchRequest))
pushView(createSearchView(trimmedQuery)); pushView(createSearchView(trimmedQuery));
} }
@@ -1263,6 +1268,39 @@ void HomeView::pushView(QWidget *view)
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty()); Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
} }
bool HomeView::pushQuickRoute(const QVariantMap &request)
{
if (!m_useQuickHomeRouter || !m_homeQuickRouterView ||
!m_navigationController || !m_contentSwitcher)
return false;
QWidget *current = m_contentSwitcher->currentWidget();
if (!m_navigationController->push(request))
return false;
if (current)
{
RouteInfo info;
info.widget = current;
info.isDynamic = current->property("isDynamic").toBool();
info.routeType = current->property("routeType").toString();
info.routeId = current->property("routeId").toString();
info.routeTitle = current->property("routeTitle").toString();
info.routeExtraId = current->property("routeExtraId").toString();
m_navStack.push(info);
}
if (current != m_homeQuickRouterView)
{
if (m_contentSwitcher->indexOf(m_homeQuickRouterView) == -1)
m_contentSwitcher->addWidget(m_homeQuickRouterView);
m_contentSwitcher->slideInWgt(m_homeQuickRouterView,
SlidingStackedWidget::RightToLeft);
}
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
return true;
}
void HomeView::navigateBack() void HomeView::navigateBack()
{ {
QWidget *current = m_contentSwitcher->currentWidget(); QWidget *current = m_contentSwitcher->currentWidget();
@@ -1349,6 +1387,12 @@ void HomeView::navigateBack()
if (!targetView) if (!targetView)
targetView = homeRootWidget(); targetView = homeRootWidget();
if (targetView == current)
{
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
return;
}
+1
View File
@@ -116,6 +116,7 @@ private:
void pushView(QWidget* view); void pushView(QWidget* view);
bool pushQuickRoute(const QVariantMap &request);
void resetToView(QWidget* view, const QString &rootRoute = QString()); void resetToView(QWidget* view, const QString &rootRoute = QString());
QWidget* homeRootWidget() const; QWidget* homeRootWidget() const;