tray icon: added translation and added language setting

Dieser Commit ist enthalten in:
Reinder Feenstra 2024-11-27 22:54:28 +01:00
Ursprung 27fecd8700
Commit 8187b08881
4 geänderte Dateien mit 236 neuen und 25 gelöschten Zeilen

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -22,7 +22,10 @@
#include "trayicon.hpp"
#include <cassert>
#include <regex>
#include <version.hpp>
#include <traintastic/locale/locale.hpp>
#include <traintastic/utils/standardpaths.hpp>
#include "consolewindow.hpp"
#include "registry.hpp"
#include "../../core/eventloop.hpp"
@ -31,12 +34,31 @@
#include "../../traintastic/traintastic.hpp"
#include "../../utils/setthreadname.hpp"
namespace {
std::wstring utf8ToWString(std::string_view text)
{
std::wstring out;
out.resize(text.size() * 2);
const int r = MultiByteToWideChar(CP_THREAD_ACP, 0, text.data(), text.size(), out.data(), out.size());
if(r >= 0)
{
out.resize(static_cast<size_t>(r));
return out;
}
return {};
}
}
namespace Windows {
std::unique_ptr<std::thread> TrayIcon::s_thread;
HWND TrayIcon::s_window = nullptr;
HMENU TrayIcon::s_menu = nullptr;
HMENU TrayIcon::s_menuSettings = nullptr;
HMENU TrayIcon::s_menuLanguage = nullptr;
std::vector<std::string> TrayIcon::s_languages;
void TrayIcon::add(bool isRestart)
{
@ -80,21 +102,41 @@ void TrayIcon::run(bool isRestart)
// create menu:
s_menu = CreatePopupMenu();
menuAddItem(s_menu, MenuItem::ShowHideConsole, "Show/hide console", hasConsoleWindow());
menuAddItem(s_menu, MenuItem::ShowHideConsole, Locale::tr("tray_icon.menu:show_hide_console"), hasConsoleWindow());
menuAddSeperator(s_menu);
s_menuSettings = menuAddSubMenu(s_menu, "Settings");
menuAddItem(s_menuSettings, MenuItem::AllowClientServerRestart, "Allow client to restart server");
menuAddItem(s_menuSettings, MenuItem::AllowClientServerShutdown, "Allow client to shutdown server");
s_menuSettings = menuAddSubMenu(s_menu, Locale::tr("tray_icon.menu:settings"));
s_menuLanguage = menuAddSubMenu(s_menuSettings, Locale::tr("tray_icon.menu:language"));
{
s_languages.clear();
std::regex re("^[a-z]{2}-[a-z]{2}\\.lang$");
const auto localePath = getLocalePath();
for (auto const& dir_entry : std::filesystem::directory_iterator{localePath})
{
auto filename = dir_entry.path().filename().string();
if (std::regex_match(filename, re))
{
filename.resize(filename.size() - 5); // remove .lang
const auto id = menuItemLanguage(s_languages.size());
Locale locale{dir_entry.path()};
const std::string language{locale.translate("language:" + filename)};
menuAddItem(s_menuLanguage, id, language.c_str());
s_languages.emplace_back(std::move(filename));
}
}
}
menuAddSeperator(s_menuSettings);
menuAddItem(s_menuSettings, MenuItem::StartAutomaticallyAtLogon, "Start automatically at logon");
menuAddItem(s_menuSettings, MenuItem::AllowClientServerRestart, Locale::tr("tray_icon.menu:allow_client_to_restart_server"));
menuAddItem(s_menuSettings, MenuItem::AllowClientServerShutdown, Locale::tr("tray_icon.menu:allow_client_to_shutdown_server"));
menuAddSeperator(s_menuSettings);
menuAddItem(s_menuSettings, MenuItem::StartAutomaticallyAtLogon, Locale::tr("tray_icon.menu:start_automatically_at_logon"));
HMENU menuAdvanced = menuAddSubMenu(s_menu, "Advanced");
menuAddItem(menuAdvanced, MenuItem::OpenDataDirectory, "Open data directory");
HMENU menuAdvanced = menuAddSubMenu(s_menu, Locale::tr("tray_icon.menu:advanced"));
menuAddItem(menuAdvanced, MenuItem::OpenDataDirectory, Locale::tr("tray_icon.menu:open_data_directory"));
menuAddSeperator(s_menu);
menuAddItem(s_menu, MenuItem::Restart, "Restart");
menuAddItem(s_menu, MenuItem::Shutdown, "Shutdown");
menuAddItem(s_menu, MenuItem::Restart, Locale::tr("tray_icon.menu:restart"));
menuAddItem(s_menu, MenuItem::Shutdown, Locale::tr("tray_icon.menu:shutdown"));
bool startUpApproved = false;
Registry::getStartUpApproved(startUpApproved);
@ -113,8 +155,8 @@ void TrayIcon::run(bool isRestart)
std::memcpy(notifyIconData.szTip, toolTip.data(), std::min(toolTip.size(), sizeof(notifyIconData.szTip) - 1));
const std::string_view infoTitle{"Traintastic server"};
const std::string_view infoMessage{"Traintastic server is running in the system tray."};
const std::string_view infoMessageRestarted{"Traintastic server restarted"};
const std::string_view infoMessage = Locale::tr("tray_icon.notify:message_running");
const std::string_view infoMessageRestarted = Locale::tr("tray_icon.notify:message_restarting");
std::memcpy(notifyIconData.szInfoTitle, infoTitle.data(), std::min(infoTitle.size(), sizeof(notifyIconData.szInfoTitle) - 1));
if(isRestart)
std::memcpy(notifyIconData.szInfo, infoMessageRestarted.data(), std::min(infoMessageRestarted.size(), sizeof(notifyIconData.szInfo) - 1));
@ -174,7 +216,7 @@ LRESULT CALLBACK TrayIcon::windowProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARA
break;
case WM_COMMAND:
switch(static_cast<MenuItem>(wParam))
switch(static_cast<MenuItem>(wParam & 0xFF))
{
case MenuItem::Shutdown:
EventLoop::call(
@ -242,6 +284,25 @@ LRESULT CALLBACK TrayIcon::windowProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARA
ShellExecuteA(nullptr, "open", dataDir.c_str(), nullptr, nullptr, SW_SHOWDEFAULT);
break;
}
case MenuItem::Language:
{
const auto index = (wParam >> 8) & 0xFF;
std::string value = s_languages[index];
{
std::lock_guard lock{s_settings.mutex};
if(value == s_settings.language)
{
break;
}
}
EventLoop::call(
[value]()
{
Traintastic::instance->settings->language = value;
PostMessage(s_window, WM_TRAINTASTIC_LANGUAGE_CHANGED, 0, 0);
});
break;
}
}
break;
@ -250,25 +311,45 @@ LRESULT CALLBACK TrayIcon::windowProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARA
std::lock_guard lock{s_settings.mutex};
menuSetItemChecked(s_menuSettings, MenuItem::AllowClientServerRestart, s_settings.allowClientServerRestart);
menuSetItemChecked(s_menuSettings, MenuItem::AllowClientServerShutdown, s_settings.allowClientServerShutdown);
for(size_t i = 0; i < s_languages.size(); ++i)
{
menuSetItemChecked(s_menuLanguage, menuItemLanguage(i), s_languages[i] == s_settings.language);
}
break;
}
case WM_TRAINTASTIC_LANGUAGE_CHANGED:
{
const auto text = utf8ToWString(Locale::tr("tray_icon.language_changed_message_box:text"));
const auto caption = utf8ToWString(Locale::tr("tray_icon.language_changed_message_box:caption"));
const int button = MessageBoxExW(hWnd, text.c_str(), caption.c_str(), MB_YESNO | MB_ICONINFORMATION, 0);
if(button == IDYES)
{
EventLoop::call(
[]()
{
Traintastic::instance->restart();
});
}
break;
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
void TrayIcon::menuAddItem(HMENU menu, MenuItem id, const LPCSTR text, bool enabled)
void TrayIcon::menuAddItem(HMENU menu, MenuItem id, std::string_view text, bool enabled)
{
assert(menu);
MENUITEMINFO item;
const auto textW = utf8ToWString(text);
MENUITEMINFOW item;
memset(&item, 0, sizeof(item));
item.cbSize = sizeof(item);
item.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
item.fType = MFT_STRING;
item.fState = enabled ? MFS_ENABLED : MFS_DISABLED;
item.wID = static_cast<UINT>(id);
item.dwTypeData = const_cast<LPSTR>(text);
item.dwTypeData = const_cast<LPWSTR>(textW.c_str());
int n = GetMenuItemCount(menu);
InsertMenuItem(menu, n, TRUE, &item);
InsertMenuItemW(menu, n, TRUE, &item);
}
void TrayIcon::menuAddSeperator(HMENU menu)
@ -282,19 +363,20 @@ void TrayIcon::menuAddSeperator(HMENU menu)
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &item);
}
HMENU TrayIcon::menuAddSubMenu(HMENU menu, const LPCSTR text)
HMENU TrayIcon::menuAddSubMenu(HMENU menu, std::string_view text)
{
assert(menu);
const auto textW = utf8ToWString(text);
HMENU subMenu = CreatePopupMenu();
MENUITEMINFO item;
MENUITEMINFOW item;
memset(&item, 0, sizeof(item));
item.cbSize = sizeof(item);
item.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_SUBMENU;
item.fType = MFT_STRING;
item.fState = MFS_ENABLED;
item.hSubMenu = subMenu;
item.dwTypeData = const_cast<LPSTR>(text);
InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &item);
item.dwTypeData = const_cast<LPWSTR>(textW.c_str());
InsertMenuItemW(menu, GetMenuItemCount(menu), TRUE, &item);
return subMenu;
}
@ -318,6 +400,7 @@ void TrayIcon::getSettings()
std::lock_guard lock{s_settings.mutex};
s_settings.allowClientServerRestart = settings.allowClientServerRestart;
s_settings.allowClientServerShutdown = settings.allowClientServerShutdown;
s_settings.language = settings.language;
}
PostMessage(s_window, WM_TRAINTASTIC_SETTINGS, 0, 0);
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -27,6 +27,7 @@
#include <thread>
#include <mutex>
#include <string>
#include <vector>
#define _WINSOCKAPI_ // prevent windows.h including winsock.h
#include <windows.h>
#undef _WINSOCKAPI_
@ -48,6 +49,7 @@ class TrayIcon
AllowClientServerShutdown = 5,
StartAutomaticallyAtLogon = 6,
OpenDataDirectory = 7,
Language = 8, // bit 8..15 are used for language index
};
struct TraintasticSettings
@ -55,23 +57,37 @@ class TrayIcon
std::mutex mutex;
bool allowClientServerRestart;
bool allowClientServerShutdown;
std::string language;
};
static constexpr UINT WM_NOTIFYICON_CALLBACK = WM_USER + 1;
static constexpr UINT WM_TRAINTASTIC_SETTINGS = WM_USER + 2;
static constexpr UINT WM_TRAINTASTIC_LANGUAGE_CHANGED = WM_USER + 3;
static std::unique_ptr<std::thread> s_thread;
static HWND s_window;
static HMENU s_menu;
static HMENU s_menuSettings;
inline static TraintasticSettings s_settings = {{}, false, false};
static HMENU s_menuLanguage;
static std::vector<std::string> s_languages;
inline static TraintasticSettings s_settings = {
{},
false,
false,
"en-us",
};
static constexpr MenuItem menuItemLanguage(uint16_t index)
{
return static_cast<MenuItem>(static_cast<UINT>(MenuItem::Language) | (static_cast<UINT>(index) << 8));
}
static void run(bool isRestart);
static LRESULT CALLBACK windowProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
static void menuAddItem(HMENU menu, MenuItem id, const LPCSTR text, bool enabled = true);
static void menuAddItem(HMENU menu, MenuItem id, std::string_view text, bool enabled = true);
static void menuAddSeperator(HMENU menu);
static HMENU menuAddSubMenu(HMENU menu, const LPCSTR text);
static HMENU menuAddSubMenu(HMENU menu, std::string_view text);
static bool menuGetItemChecked(HMENU menu, MenuItem id);
static void menuSetItemChecked(HMENU menu, MenuItem id, bool checked);

Datei anzeigen

@ -6631,5 +6631,61 @@
"reference": "",
"comment": "",
"fuzzy": 0
},
{
"term": "tray_icon.menu:show_hide_console",
"definition": "Show/hide console"
},
{
"term": "tray_icon.menu:settings",
"definition": "Settings"
},
{
"term": "tray_icon.menu:language",
"definition": "Language"
},
{
"term": "tray_icon.menu:allow_client_to_restart_server",
"definition": "Allow client to restart server"
},
{
"term": "tray_icon.menu:allow_client_to_shutdown_server",
"definition": "Allow client to shutdown server"
},
{
"term": "tray_icon.menu:start_automatically_at_logon",
"definition": "Start automatically at logon"
},
{
"term": "tray_icon.menu:advanced",
"definition": "Advanced"
},
{
"term": "tray_icon.menu:open_data_directory",
"definition": "Open data directory"
},
{
"term": "tray_icon.menu:restart",
"definition": "Restart"
},
{
"term": "tray_icon.menu:shutdown",
"definition": "Shutdown"
},
{
"term": "tray_icon.notify:message_running",
"definition": "Traintastic server is running in the background."
},
{
"term": "tray_icon.notify:message_restarting",
"definition": "Traintastic server restarted"
},
{
"term": "tray_icon.language_changed_message_box:caption",
"definition": "Language changed"
},
{
"term": "tray_icon.language_changed_message_box:text",
"definition": "Traintastic server must be restarted for the language change to take effect. Restart now?"
}
]

Datei anzeigen

@ -6307,5 +6307,61 @@
"reference": "",
"comment": "",
"fuzzy": 0
},
{
"term": "tray_icon.menu:show_hide_console",
"definition": "Console weergeven/verbergen"
},
{
"term": "tray_icon.menu:settings",
"definition": "Instellingen"
},
{
"term": "tray_icon.menu:language",
"definition": "Taal"
},
{
"term": "tray_icon.menu:allow_client_to_restart_server",
"definition": "Client toestaan de server opnieuw te starten"
},
{
"term": "tray_icon.menu:allow_client_to_shutdown_server",
"definition": "Client toestaan de server af te sluiten"
},
{
"term": "tray_icon.menu:start_automatically_at_logon",
"definition": "Automatische starten bij inloggen"
},
{
"term": "tray_icon.menu:advanced",
"definition": "Geavanceerd"
},
{
"term": "tray_icon.menu:open_data_directory",
"definition": "Open data map"
},
{
"term": "tray_icon.menu:restart",
"definition": "Herstart"
},
{
"term": "tray_icon.menu:shutdown",
"definition": "Afsluiten"
},
{
"term": "tray_icon.notify:message_running",
"definition": "Traintastic server draait op de achtergrond."
},
{
"term": "tray_icon.notify:message_restarting",
"definition": "Traintastic server is geherstart"
},
{
"term": "tray_icon.language_changed_message_box:caption",
"definition": "Taal gewijzigd"
},
{
"term": "tray_icon.language_changed_message_box:text",
"definition": "Traintastic server moet opnieuw worden opgestart voordat de nieuwe taal actief wordt. Nu herstarten?"
}
]