diff --git a/OpenBoard.pro b/OpenBoard.pro
index 73e52d511..93c8b8c78 100644
--- a/OpenBoard.pro
+++ b/OpenBoard.pro
@@ -84,6 +84,7 @@ UB_ETC.files = resources/etc
UB_I18N.files = resources/i18n/*.qm
UB_LIBRARY.files = resources/library
UB_FONTS.files = resources/fonts
+UB_STARTUP_HINTS.files = resources/startupHints
UB_THIRDPARTY_INTERACTIVE.files = thirdparty/interactive
DEFINES += NO_THIRD_PARTY_WARNINGS
@@ -138,6 +139,7 @@ win32 {
UB_LIBRARY.path = $$DESTDIR
UB_I18N.path = $$DESTDIR/i18n
UB_ETC.path = $$DESTDIR
+ UB_STARTUP_HINTS.path = $$DESTDIR
UB_THIRDPARTY_INTERACTIVE.path = $$DESTDIR/library
system(md $$replace(BUILD_DIR, /, \\))
system(echo "$$VERSION" > $$BUILD_DIR/version)
@@ -217,6 +219,8 @@ macx {
UB_LIBRARY.path = "$$RESOURCES_DIR"
UB_FONTS.files = "resources/fonts"
UB_FONTS.path = "$$RESOURCES_DIR"
+ UB_STARTUP_HINTS.files = "resources/startupHints"
+ UB_STARTUP_HINTS.path = "$$RESOURCES_DIR"
UB_THIRDPARTY_INTERACTIVE.files = $$files($$THIRD_PARTY_PATH/interactive/*)
UB_THIRDPARTY_INTERACTIVE.path = "$$RESOURCES_DIR/library/interactive"
UB_MACX_ICNS.files = $$files(resources/macx/*.icns)
@@ -439,6 +443,7 @@ macx {
QMAKE_BUNDLE_DATA += UB_ETC \
UB_LIBRARY \
UB_FONTS \
+ UB_STARTUP_HINTS \
UB_THIRDPARTY_INTERACTIVE \
UB_MACX_ICNS \
UB_MACX_EXTRAS \
@@ -478,6 +483,7 @@ linux-g++* {
UB_LIBRARY.path = $$DESTDIR
UB_I18N.path = $$DESTDIR/i18n
UB_ETC.path = $$DESTDIR
+ UB_STARTUP_HINTS.path = $$DESTDIR
UB_THIRDPARTY_INTERACTIVE.path = $$DESTDIR/library
system(mkdir -p $$BUILD_DIR)
system(echo "$$VERSION" > $$BUILD_DIR/version)
@@ -527,6 +533,7 @@ TRANSLATIONS = resources/i18n/OpenBoard_ar.ts \
INSTALLS = UB_ETC \
UB_I18N \
UB_LIBRARY \
+ UB_STARTUP_HINTS \
UB_THIRDPARTY_INTERACTIVE
DISTFILES += \
diff --git a/README.md b/README.md
index ffc226523..96fc6f016 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@
OpenBoard is an open-source cross-platform interactive white board application designed primarily for use in schools. It was originally forked from Open-Sankoré, which was itself based on Uniboard.
+
+
### Installing
1.7.7 installers are available for Windows, macOS and Debian on the [Downloads page](https://github.com/OpenBoard-org/OpenBoard/wiki/Downloads).
diff --git a/RUN_LOCAL_WINDOWS.md b/RUN_LOCAL_WINDOWS.md
new file mode 100644
index 000000000..3b034ad89
--- /dev/null
+++ b/RUN_LOCAL_WINDOWS.md
@@ -0,0 +1,86 @@
+# Run This Local Windows Build
+
+This file documents the local Windows setup used to build and run this modified OpenBoard checkout.
+
+## Quick Run
+
+The app has already been built and deployed here:
+
+```powershell
+D:\Ai\product\own\OpenBoard\OpenBoard\build\win32\release\product\OpenBoard.exe
+```
+
+Double-click `OpenBoard.exe`, or run:
+
+```powershell
+cd D:\Ai\product\own\OpenBoard\OpenBoard\build\win32\release\product
+.\OpenBoard.exe
+```
+
+## What Was Installed
+
+This local build uses:
+
+```text
+Qt 6.8.3 MSVC 2022 x64: D:\Ai\Qt\6.8.3\msvc2022_64
+OpenBoard source: D:\Ai\product\own\OpenBoard\OpenBoard
+Third-party libraries: D:\Ai\product\own\OpenBoard\OpenBoard-ThirdParty
+Windows SDK: 10.0.18362
+Visual Studio Build Tools
+```
+
+## Rebuild After Code Changes
+
+Open a normal PowerShell terminal and run:
+
+```powershell
+cd D:\Ai\product\own\OpenBoard\OpenBoard
+
+cmd.exe /d /s /c '"C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\Common7\Tools\VsDevCmd.bat" -arch=amd64 && "D:\Ai\Qt\6.8.3\msvc2022_64\bin\qmake.exe" OpenBoard.pro && nmake'
+```
+
+If `quazip.lib` is missing, rebuild QuaZip first:
+
+```powershell
+cmd.exe /d /s /c '"C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\Common7\Tools\VsDevCmd.bat" -arch=amd64 && cd /d "D:\Ai\product\own\OpenBoard\OpenBoard-ThirdParty\quazip" && "D:\Ai\Qt\6.8.3\msvc2022_64\bin\qmake.exe" quazip.pro && nmake release'
+```
+
+Then rebuild OpenBoard again.
+
+## Deploy Runtime Files
+
+After a successful build, copy OpenBoard resources and deploy Qt dependencies:
+
+```powershell
+cd D:\Ai\product\own\OpenBoard\OpenBoard
+
+cmd.exe /d /s /c '"C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\Common7\Tools\VsDevCmd.bat" -arch=amd64 && cd /d "D:\Ai\product\own\OpenBoard\OpenBoard" && nmake install'
+
+& D:\Ai\Qt\6.8.3\msvc2022_64\bin\windeployqt.exe --release --compiler-runtime --qmldir src build\win32\release\product\OpenBoard.exe
+
+$product = Resolve-Path build\win32\release\product
+Copy-Item ..\OpenBoard-ThirdParty\poppler\bin\*.dll -Destination $product -Force
+Copy-Item ..\OpenBoard-ThirdParty\openssl\openssl-3.0.15-win64\bin\*.dll -Destination $product -Force
+Copy-Item ..\OpenBoard-ThirdParty\zlib\1.2.11\bin\*.dll -Destination $product -Force
+```
+
+## Test The New Changes
+
+After launching OpenBoard:
+
+- Confirm the toolbar shows the new `Minimize` button.
+- Click `Minimize` and confirm OpenBoard goes to the Windows taskbar.
+- Restore OpenBoard from the taskbar.
+- Check the system tray icon menu for `Show OpenBoard`, `Minimize`, and `Exit`.
+- Open `Preferences -> Display`.
+- Enable `Run in windowed mode (shows minimize/maximize/close buttons)`.
+- Restart OpenBoard and confirm the native Windows title bar appears.
+
+Fullscreen mode remains the default when the windowed mode preference is disabled.
+
+## Notes
+
+- This checkout is nested one level below `D:\Ai\product\own\OpenBoard`.
+- `OpenBoard-ThirdParty` must sit next to the `OpenBoard` source folder.
+- If `rc.exe` is missing, install the Windows SDK.
+- If `qmake` resolves to Anaconda Qt 5, use the full Qt 6 qmake path shown above.
diff --git a/image/README/openboard-startup-hints.png b/image/README/openboard-startup-hints.png
new file mode 100644
index 000000000..579d919e1
Binary files /dev/null and b/image/README/openboard-startup-hints.png differ
diff --git a/resources/forms/mainWindow.ui b/resources/forms/mainWindow.ui
index e7c76e60d..73ac224fc 100644
--- a/resources/forms/mainWindow.ui
+++ b/resources/forms/mainWindow.ui
@@ -82,6 +82,7 @@
+
@@ -132,6 +133,7 @@
+
@@ -186,6 +188,7 @@
+
@@ -1849,10 +1852,10 @@
:/images/toolbar/hide.png:/images/toolbar/hide.png
- Hide OpenBoard
+ Minimize
- Hide OpenBoard Application
+ Minimize OpenBoard
Ctrl+H
diff --git a/resources/forms/preferences.ui b/resources/forms/preferences.ui
index 45a2fc180..1d10f8cf9 100644
--- a/resources/forms/preferences.ui
+++ b/resources/forms/preferences.ui
@@ -106,7 +106,7 @@
783
- 765
+ 790
@@ -252,7 +252,7 @@
0
- 620
+ 645
1011
101
@@ -524,7 +524,7 @@
0
540
1011
- 71
+ 96
@@ -578,6 +578,13 @@
+ -
+
+
+ Run in windowed mode (shows minimize/maximize/close buttons)
+
+
+
-
diff --git a/src/core/UBApplication.cpp b/src/core/UBApplication.cpp
index 640fad511..d0973bf9b 100644
--- a/src/core/UBApplication.cpp
+++ b/src/core/UBApplication.cpp
@@ -99,6 +99,7 @@ UBApplication::UBApplication(const QString &id, int &argc, char **argv) : Single
, mPreferencesController(NULL)
, mApplicationTranslator(NULL)
, mQtGuiTranslator(NULL)
+ , mSystemTrayIcon(NULL)
{
Q_UNUSED(id)
staticMemoryCleaner = new QObject(0); // deleted in UBApplication destructor
@@ -165,6 +166,12 @@ UBApplication::~UBApplication()
UBFileSystemUtils::deleteAllTempDirCreatedDuringSession();
+ if (mSystemTrayIcon)
+ {
+ mSystemTrayIcon->hide();
+ mSystemTrayIcon->setContextMenu(nullptr);
+ }
+
delete mainWindow;
mainWindow = 0;
@@ -374,16 +381,13 @@ int UBApplication::exec(const QString& pFileToImport)
connect(mainWindow->actionDesktop, SIGNAL(triggered(bool)), applicationController, SLOT(showDesktop(bool)));
connect(mainWindow->actionDesktop, SIGNAL(triggered(bool)), this, SLOT(stopScript()));
-#if defined(Q_OS_OSX) || defined(Q_OS_LINUX)
connect(mainWindow->actionHideApplication, SIGNAL(triggered()), this, SLOT(showMinimized()));
-#else
- connect(mainWindow->actionHideApplication, SIGNAL(triggered()), mainWindow, SLOT(showMinimized()));
-#endif
mPreferencesController = new UBPreferencesController(mainWindow);
connect(mainWindow->actionPreferences, SIGNAL(triggered()), mPreferencesController, SLOT(show()));
connect(mainWindow->actionCheckUpdate, SIGNAL(triggered()), applicationController, SLOT(checkUpdateRequest()));
+ setupSystemTrayIcon();
toolBarPositionChanged(UBSettings::settings()->appToolBarPositionedAtTop->get());
@@ -441,10 +445,56 @@ void UBApplication::showMinimized()
#elif defined(Q_OS_LINUX)
mainWindow->showMinimized();
bIsMinimized = true;
+#else
+ mainWindow->showMinimized();
#endif
}
+void UBApplication::restoreMainWindow()
+{
+ if (!mainWindow)
+ return;
+
+#if defined(Q_OS_OSX) || defined(Q_OS_LINUX)
+ bIsMinimized = false;
+#endif
+
+ if (UBSettings::settings()->appRunInWindow->get().toBool())
+ mainWindow->showNormal();
+ else
+ UBPlatformUtils::showFullScreen(mainWindow);
+
+ mainWindow->raise();
+ mainWindow->activateWindow();
+}
+
+void UBApplication::setupSystemTrayIcon()
+{
+ if (!QSystemTrayIcon::isSystemTrayAvailable())
+ return;
+
+ mSystemTrayIcon = new QSystemTrayIcon(QIcon(":/images/OpenBoard.png"), this);
+ mSystemTrayIcon->setToolTip(tr("OpenBoard"));
+
+ QMenu* trayMenu = new QMenu(mainWindow);
+ QAction* showAction = trayMenu->addAction(tr("Show OpenBoard"));
+ QAction* minimizeAction = trayMenu->addAction(tr("Minimize"));
+ trayMenu->addSeparator();
+ QAction* exitAction = trayMenu->addAction(tr("Exit"));
+
+ connect(showAction, SIGNAL(triggered()), this, SLOT(restoreMainWindow()));
+ connect(minimizeAction, SIGNAL(triggered()), this, SLOT(showMinimized()));
+ connect(exitAction, SIGNAL(triggered()), this, SLOT(closing()));
+ connect(mSystemTrayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
+ if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick)
+ restoreMainWindow();
+ });
+
+ mSystemTrayIcon->setContextMenu(trayMenu);
+ mSystemTrayIcon->show();
+}
+
void UBApplication::startScript()
{
diff --git a/src/core/UBApplication.h b/src/core/UBApplication.h
index ab13cf832..5d74d3bef 100644
--- a/src/core/UBApplication.h
+++ b/src/core/UBApplication.h
@@ -34,6 +34,7 @@
#include
#include
#include
+#include
#include "singleapplication/singleapplication.h"
@@ -135,10 +136,12 @@ class UBApplication : public SingleApplication
//#ifdef Q_OS_OSX // for some reason this is not compiled if the ifdef is uncommented
void showMinimized();
//#endif
+ void restoreMainWindow();
void onScreenCountChanged(int newCount);
private:
void updateProtoActionsState();
+ void setupSystemTrayIcon();
void setupTranslators(QStringList args);
QList mProtoMenus;
bool mIsVerbose;
@@ -153,6 +156,7 @@ class UBApplication : public SingleApplication
UBPreferencesController* mPreferencesController;
QTranslator* mApplicationTranslator;
QTranslator* mQtGuiTranslator;
+ QSystemTrayIcon* mSystemTrayIcon;
};
diff --git a/src/core/UBPreferencesController.cpp b/src/core/UBPreferencesController.cpp
index ed0e393d8..05de68fce 100644
--- a/src/core/UBPreferencesController.cpp
+++ b/src/core/UBPreferencesController.cpp
@@ -190,6 +190,7 @@ void UBPreferencesController::wire()
settings->boardKeyboardPaletteKeyBtnSize->setString(mPreferencesUI->keyboardPaletteKeyButtonSize->itemText(index));
});
connect(mPreferencesUI->startModeComboBox, SIGNAL(currentIndexChanged(int)), settings->appStartMode, SLOT(setInt(int)));
+ connect(mPreferencesUI->runInWindowCheckBox, SIGNAL(clicked(bool)), settings->appRunInWindow, SLOT(setBool(bool)));
connect(mPreferencesUI->useExternalBrowserCheckBox, SIGNAL(clicked(bool)), settings->webUseExternalBrowser, SLOT(setBool(bool)));
connect(mPreferencesUI->displayBrowserPageCheckBox, SIGNAL(clicked(bool)), settings->webShowPageImmediatelyOnMirroredScreen, SLOT(setBool(bool)));
@@ -320,6 +321,7 @@ void UBPreferencesController::init()
mPreferencesUI->emptyTrashDaysValue->setValue(settings->emptyTrashDaysValue->get().toInt());
mPreferencesUI->startModeComboBox->setCurrentIndex(settings->appStartMode->get().toInt());
+ mPreferencesUI->runInWindowCheckBox->setChecked(settings->appRunInWindow->get().toBool());
mPreferencesUI->useExternalBrowserCheckBox->setChecked(settings->webUseExternalBrowser->get().toBool());
mPreferencesUI->displayBrowserPageCheckBox->setChecked(settings->webShowPageImmediatelyOnMirroredScreen->get().toBool());
@@ -383,6 +385,7 @@ void UBPreferencesController::defaultSettings()
mPreferencesUI->verticalChoice->setChecked(settings->appToolBarOrientationVertical->reset().toBool());
mPreferencesUI->horizontalChoice->setChecked(!settings->appToolBarOrientationVertical->reset().toBool());
mPreferencesUI->startModeComboBox->setCurrentIndex(0);
+ mPreferencesUI->runInWindowCheckBox->setChecked(settings->appRunInWindow->reset().toBool());
mPreferencesUI->useSystemOSKCheckBox->setChecked(settings->useSystemOnScreenKeyboard->reset().toBool());