diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index 37f12229a..262635114 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -930,4 +930,20 @@ Für sichtbare Änderung ecode neu starten.
Hereinzoomen
Herauszoomen
Zoom zurücksetzen
+ Bildschirmfoto aufnehmen
+ Bildschirmfoto-Einstellungen
+ Speicherpfad
+ Dateinamensmuster
+ Speicherformat
+ Dateinamensmuster für Bildschirmfotos festlegen
+ Legen Sie das Dateinamensmuster für Bildschirmfotos fest:
+Verwendet strftime-Formatbezeichner. Die Dateierweiterung richtet sich nach dem ausgewählten Bildschirmfotoformat.
+ Speicherformat für Bildschirmfotos festlegen
+ Wählen Sie das Speicherformat für Bildschirmfotos:
+ Das Dateinamensmuster für Bildschirmfotos ist ungültig.
+ Ungültiges Speicherformat für Bildschirmfotos.
+ Das Verzeichnis für Bildschirmfotos konnte nicht erstellt werden.
+ Es konnte kein verfügbarer Dateiname für das Bildschirmfoto gefunden werden.
+ Das Bildschirmfoto konnte nicht gespeichert werden.
+ Bildschirmfoto gespeichert:
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index 63704b28e..3e29de6bc 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -914,4 +914,20 @@ Restart ecode to see the changes.
Zoom In
Zoom Out
Zoom Reset
+ Take Screenshot
+ Screenshot Settings
+ Save Path
+ Filename Pattern
+ Save Format
+ Set Screenshot Filename Pattern
+ Set the screenshot filename pattern:
+Uses strftime format specifiers. The file extension follows the selected screenshot format.
+ Set Screenshot Save Format
+ Select the screenshot save format:
+ The screenshot filename pattern is invalid.
+ Invalid screenshot save format.
+ Couldn't create the screenshot directory.
+ Couldn't find an available screenshot filename.
+ Couldn't save the screenshot.
+ Screenshot saved:
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index fab4e7f9f..449f784cd 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -909,4 +909,20 @@ Redémarrer ecode pour voir les changements.
Ouvrir le lien
Coller dans le terminal
Renommer le terminal
+ Prendre une capture d’écran
+ Paramètres des captures d’écran
+ Dossier d’enregistrement
+ Modèle de nom de fichier
+ Format d’enregistrement
+ Définir le modèle de nom des captures d’écran
+ Définissez le modèle de nom des captures d’écran :
+Utilise les spécificateurs de format strftime. L’extension du fichier correspond au format de capture sélectionné.
+ Définir le format des captures d’écran
+ Sélectionnez le format d’enregistrement des captures d’écran :
+ Le modèle de nom des captures d’écran n’est pas valide.
+ Le format d’enregistrement des captures d’écran n’est pas valide.
+ Impossible de créer le dossier des captures d’écran.
+ Impossible de trouver un nom de fichier disponible pour la capture d’écran.
+ Impossible d’enregistrer la capture d’écran.
+ Capture d’écran enregistrée :
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index 7e61a50a8..4e6ef0d87 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -688,4 +688,20 @@ file in the directory tree.
字小
字大
重设字体大小
+ 截取屏幕截图
+ 屏幕截图设置
+ 保存路径
+ 文件名格式
+ 保存格式
+ 设置屏幕截图文件名格式
+ 设置屏幕截图文件名格式:
+使用 strftime 格式说明符。文件扩展名由所选屏幕截图格式决定。
+ 设置屏幕截图保存格式
+ 选择屏幕截图保存格式:
+ 屏幕截图文件名格式无效。
+ 屏幕截图保存格式无效。
+ 无法创建屏幕截图目录。
+ 无法找到可用的屏幕截图文件名。
+ 无法保存屏幕截图。
+ 屏幕截图已保存:
diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp
index 00a52e1c4..b25caccd4 100644
--- a/src/tools/ecode/appconfig.cpp
+++ b/src/tools/ecode/appconfig.cpp
@@ -127,6 +127,12 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
windowState.lastRunVersion = iniState.getValueU( "editor", "last_run_version", 0 );
windowState.sidePanelTabsOrder =
String::split( iniState.getValue( "ui", "side_panel_tabs_order", "" ), ',' );
+ screenshot.savePath = ini.getValue( "screenshots", "save_path", "" );
+ screenshot.filenamePattern =
+ ini.getValue( "screenshots", "filename_pattern", "ecode-%Y-%m-%d-%H-%M-%S.png" );
+ screenshot.saveFormat = ini.getValue( "screenshots", "save_format", "png" );
+ if ( Image::extensionToSaveType( screenshot.saveFormat ) == Image::SaveType::Unknown )
+ screenshot.saveFormat = "png";
editor.showLineNumbers = ini.getValueB( "editor", "show_line_numbers", true );
editor.showWhiteSpaces = ini.getValueB( "editor", "show_white_spaces", true );
editor.showLineEndings = ini.getValueB( "editor", "show_line_endings", false );
@@ -366,6 +372,9 @@ void AppConfig::save( const std::vector& recentFiles,
ini.setValue( "ui", "font_hinting", FontTrueType::fontHintingToString( ui.fontHinting ) );
ini.setValue( "ui", "font_antialiasing",
FontTrueType::fontAntialiasingToString( ui.fontAntialiasing ) );
+ ini.setValue( "screenshots", "save_path", screenshot.savePath );
+ ini.setValue( "screenshots", "filename_pattern", screenshot.filenamePattern );
+ ini.setValue( "screenshots", "save_format", screenshot.saveFormat );
ini.setValueB( "ui", "editor_font_in_input_fields", ui.editorFontInInputFields );
iniState.setValue( "ui", "side_panel_tabs_order",
String::join( windowState.sidePanelTabsOrder, ',' ) );
diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp
index 1ce071ba4..0cba80b72 100644
--- a/src/tools/ecode/appconfig.hpp
+++ b/src/tools/ecode/appconfig.hpp
@@ -96,6 +96,12 @@ struct WindowStateConfig {
std::vector sidePanelTabsOrder;
};
+struct ScreenshotConfig {
+ std::string savePath;
+ std::string filenamePattern{ "ecode-%Y-%m-%d-%H-%M-%S.png" };
+ std::string saveFormat{ "png" };
+};
+
struct CodeEditorConfig {
std::string colorScheme{ "ecode" };
StyleSheetLength fontSize{ 11, StyleSheetLength::Dp };
@@ -296,6 +302,7 @@ struct TabWidgetCbs {
class AppConfig {
public:
WindowStateConfig windowState;
+ ScreenshotConfig screenshot;
ContextSettings context;
CodeEditorConfig editor;
DocumentConfig doc;
diff --git a/src/tools/ecode/datetimecontroller.hpp b/src/tools/ecode/datetimecontroller.hpp
index e77e5c21f..5086db95e 100644
--- a/src/tools/ecode/datetimecontroller.hpp
+++ b/src/tools/ecode/datetimecontroller.hpp
@@ -20,13 +20,13 @@ class DateTimeController {
void registerCommands( TextDocument& doc );
- private:
- PluginContextProvider* mContext{ nullptr };
-
static bool isValidDateFormat( const std::string& format );
static std::string formatCurrentDate( const std::string& format );
+ private:
+ PluginContextProvider* mContext{ nullptr };
+
void insertDate( const std::string& format );
void setCustomDateFormat();
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index 2964a5cd3..fe963b6cd 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -392,6 +392,14 @@ std::string App::getDefaultFileDialogFolder() const {
return mLastFileFolder.empty() ? getLastUsedFolder() : mLastFileFolder;
}
+std::string App::getDefaultScreenshotPath() const {
+ std::string path( mConfigPath );
+ FileSystem::dirAddSlashAtEnd( path );
+ path += "screenshots";
+ FileSystem::dirAddSlashAtEnd( path );
+ return path;
+}
+
std::string App::getLastUsedFolder() const {
if ( !mCurrentProject.empty() && mCurrentProject != getPlaygroundPath() )
return mCurrentProject;
@@ -2101,6 +2109,7 @@ static Uint32 DefaultSwitchToStatusPanelModifier = KEYMOD_LALT;
std::map App::getLocalKeybindings() {
return {
+ { { KEY_PRINTSCREEN, KEYMOD_NONE }, "take-screenshot" },
{ { KEY_RETURN, KEYMOD_LALT | KEYMOD_LCTRL }, "fullscreen-toggle" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" },
{ { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
@@ -2167,6 +2176,10 @@ std::map App::getMigrateKeybindings() {
std::vector App::getUnlockedCommands() {
return {
+ "take-screenshot",
+ "screenshot-save-path",
+ "screenshot-filename-pattern",
+ "screenshot-save-format",
"create-new",
"create-new-terminal",
"create-new-welcome-tab",
@@ -2852,6 +2865,61 @@ void App::fullscreenToggle() {
mSettings->updateViewMenu();
}
+void App::takeScreenshot() {
+ auto format = Image::extensionToSaveType( mConfig.screenshot.saveFormat );
+ if ( format == Image::SaveType::Unknown )
+ format = Image::SaveType::PNG;
+
+ std::string filename =
+ DateTimeController::formatCurrentDate( mConfig.screenshot.filenamePattern );
+ if ( filename.empty() ) {
+ errorMsgBox( i18n( "invalid_screenshot_filename_pattern",
+ "The screenshot filename pattern is invalid." ) );
+ return;
+ }
+
+ const std::string extension = "." + Image::saveTypeToExtension( format );
+ const std::string configuredExtension = FileSystem::fileExtension( filename );
+ if ( !configuredExtension.empty() )
+ filename.resize( filename.size() - configuredExtension.size() - 1 );
+ filename += extension;
+
+ std::string savePath = mConfig.screenshot.savePath.empty() ? getDefaultScreenshotPath()
+ : mConfig.screenshot.savePath;
+ FileSystem::dirAddSlashAtEnd( savePath );
+ if ( !FileSystem::isDirectory( savePath ) && !FileSystem::makeDir( savePath, true ) ) {
+ errorMsgBox( i18n( "couldnt_create_screenshot_directory",
+ "Couldn't create the screenshot directory." ) );
+ return;
+ }
+
+ std::string filepath = savePath + filename;
+ if ( FileSystem::fileExists( filepath ) ) {
+ const std::string stem = FileSystem::fileRemoveExtension( filename );
+ bool availablePathFound = false;
+ for ( Uint32 suffix = 2; suffix < 10000; ++suffix ) {
+ filepath = savePath + stem + "-" + String::toString( suffix ) + extension;
+ if ( !FileSystem::fileExists( filepath ) ) {
+ availablePathFound = true;
+ break;
+ }
+ }
+ if ( !availablePathFound ) {
+ errorMsgBox( i18n( "couldnt_find_available_screenshot_filename",
+ "Couldn't find an available screenshot filename." ) );
+ return;
+ }
+ }
+
+ if ( mWindow->takeScreenshot( filepath, format ) ) {
+ mNotificationCenter->addInteractiveNotification(
+ i18n( "screenshot_saved", "Screenshot saved:" ) + "\n" + filepath,
+ i18n( "open", "Open" ), [this, filepath] { openFileFromPath( filepath ); } );
+ } else {
+ errorMsgBox( i18n( "couldnt_save_screenshot", "Couldn't save the screenshot." ) );
+ }
+}
+
void App::showGlobalSearch( bool searchAndReplace, std::optional pathFilters ) {
mGlobalSearchController->showGlobalSearch( searchAndReplace, pathFilters );
}
diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp
index 7659266ed..6db3695d7 100644
--- a/src/tools/ecode/ecode.hpp
+++ b/src/tools/ecode/ecode.hpp
@@ -77,6 +77,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
std::string getDefaultFileDialogFolder() const;
+ std::string getDefaultScreenshotPath() const;
+
void openFolderDialog();
void openFontDialog( std::string& fontPath, bool loadingMonoFont, bool terminalFont = false,
@@ -131,6 +133,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
void fullscreenToggle();
+ void takeScreenshot();
+
void downloadFileWebDialog();
void showGlobalSearch( bool searchAndReplace, std::optional pathFilters = {} );
@@ -342,6 +346,13 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
t.setCommand( "about-ecode", [this] { mSettingsActions->aboutEcode(); } );
t.setCommand( "ecode-source", [this] { mSettingsActions->ecodeSource(); } );
t.setCommand( "ui-scale-factor", [this] { mSettingsActions->setUIScaleFactor(); } );
+ t.setCommand( "take-screenshot", [this] { takeScreenshot(); } );
+ t.setCommand( "screenshot-save-path",
+ [this] { mSettingsActions->setScreenshotSavePath(); } );
+ t.setCommand( "screenshot-filename-pattern",
+ [this] { mSettingsActions->setScreenshotFilenamePattern(); } );
+ t.setCommand( "screenshot-save-format",
+ [this] { mSettingsActions->setScreenshotSaveFormat(); } );
t.setCommand( "show-side-panel", [this] { switchSidePanel(); } );
t.setCommand( "toggle-status-bar", [this] { switchStatusBar(); } );
t.setCommand( "toggle-menu-bar", [this] { switchMenuBar(); } );
diff --git a/src/tools/ecode/notificationcenter.cpp b/src/tools/ecode/notificationcenter.cpp
index e7618b5ae..66f0751b3 100644
--- a/src/tools/ecode/notificationcenter.cpp
+++ b/src/tools/ecode/notificationcenter.cpp
@@ -68,7 +68,7 @@ void NotificationCenter::addShowRequest( const String& uri, const String& action
auto action = [this, uri, actionText, delay]() {
static const auto layout = R"xml(
-
+
@@ -103,7 +103,7 @@ void NotificationCenter::addInteractiveNotification( String text, String actionT
allowCopy, onInteraction = std::move( onInteraction )]() {
static const auto layout = R"xml(
-
+
diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp
index 743fe260f..7965036e3 100644
--- a/src/tools/ecode/settingsactions.cpp
+++ b/src/tools/ecode/settingsactions.cpp
@@ -1,4 +1,5 @@
#include "settingsactions.hpp"
+#include "datetimecontroller.hpp"
#include "ecode.hpp"
#include "version.hpp"
@@ -414,6 +415,84 @@ void SettingsActions::setUIPanelFontSize() {
mApp->setFocusEditorOnClose( msgBox );
}
+void SettingsActions::setScreenshotSavePath() {
+ std::string initialPath = mApp->getConfig().screenshot.savePath;
+ if ( !FileSystem::isDirectory( initialPath ) ) {
+ initialPath = mApp->getDefaultScreenshotPath();
+ FileSystem::makeDir( initialPath, true );
+ }
+ UIFileDialog* dialog = UIFileDialog::New(
+ UIFileDialog::DefaultFlags | UIFileDialog::AllowFolderSelect |
+ UIFileDialog::ShowOnlyFolders |
+ ( mApp->getConfig().ui.nativeFileDialogs ? UIFileDialog::UseNativeFileDialog : 0 ),
+ "*", initialPath );
+ dialog->setWindowFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL );
+ dialog->setTitle( i18n( "screenshot_save_path", "Screenshot Save Path" ) );
+ dialog->setCloseShortcut( KEY_ESCAPE );
+ dialog->on( Event::OpenFile, [this]( const Event* event ) {
+ std::string path = event->getNode()->asType()->getFullPath();
+ if ( FileSystem::isDirectory( path ) ) {
+ FileSystem::dirAddSlashAtEnd( path );
+ mApp->getConfig().screenshot.savePath = std::move( path );
+ mApp->saveConfig();
+ }
+ } );
+ dialog->center();
+ dialog->show();
+}
+
+void SettingsActions::setScreenshotFilenamePattern() {
+ UIMessageBox* msgBox = UIMessageBox::New(
+ UIMessageBox::INPUT,
+ i18n( "set_screenshot_filename_pattern_message",
+ "Set the screenshot filename pattern:\nUses strftime format specifiers. The file "
+ "extension follows the selected screenshot format." ) );
+ msgBox->setTitle(
+ i18n( "set_screenshot_filename_pattern", "Set Screenshot Filename Pattern" ) );
+ msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
+ msgBox->getTextInput()->setText( mApp->getConfig().screenshot.filenamePattern );
+ msgBox->showWhenReady();
+ msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
+ std::string pattern = msgBox->getTextInput()->getText().toUtf8();
+ std::string filename = DateTimeController::formatCurrentDate( pattern );
+ if ( !DateTimeController::isValidDateFormat( pattern ) || filename.empty() ||
+ filename.find_first_of( "<>:\"/\\|?*" ) != std::string::npos ) {
+ msgBox->getTextInput()->addClass( "error" );
+ mApp->errorMsgBox( i18n( "invalid_screenshot_filename_pattern",
+ "The screenshot filename pattern is invalid." ) );
+ return;
+ }
+ mApp->getConfig().screenshot.filenamePattern = std::move( pattern );
+ mApp->saveConfig();
+ msgBox->closeWindow();
+ } );
+}
+
+void SettingsActions::setScreenshotSaveFormat() {
+ UIMessageBox* msgBox = UIMessageBox::New(
+ UIMessageBox::DROPDOWNLIST,
+ i18n( "set_screenshot_save_format_message", "Select the screenshot save format:" ) );
+ msgBox->setTitle( i18n( "set_screenshot_save_format", "Set Screenshot Save Format" ) );
+ msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
+ msgBox->getDropDownList()->getListBox()->addListBoxItems(
+ { "PNG", "JPG", "WEBP", "QOI", "BMP", "TGA", "DDS" } );
+ msgBox->getDropDownList()->getListBox()->setSelected(
+ String( mApp->getConfig().screenshot.saveFormat ).toUpper() );
+ msgBox->showWhenReady();
+ msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
+ String formatText( msgBox->getDropDownList()->getText() );
+ std::string format = formatText.toLower().toUtf8();
+ if ( Image::extensionToSaveType( format ) == Image::SaveType::Unknown ) {
+ mApp->errorMsgBox(
+ i18n( "invalid_screenshot_save_format", "Invalid screenshot save format." ) );
+ return;
+ }
+ mApp->getConfig().screenshot.saveFormat = std::move( format );
+ mApp->saveConfig();
+ msgBox->closeWindow();
+ } );
+}
+
String SettingsActions::i18n( const std::string& key, const String& def ) {
return mApp->i18n( key, def );
}
diff --git a/src/tools/ecode/settingsactions.hpp b/src/tools/ecode/settingsactions.hpp
index 9353b24fb..38c04c98c 100644
--- a/src/tools/ecode/settingsactions.hpp
+++ b/src/tools/ecode/settingsactions.hpp
@@ -40,6 +40,12 @@ class SettingsActions {
void setUIPanelFontSize();
+ void setScreenshotSavePath();
+
+ void setScreenshotFilenamePattern();
+
+ void setScreenshotSaveFormat();
+
private:
App* mApp{ nullptr };
diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp
index e42296bbc..8c5eda4a7 100644
--- a/src/tools/ecode/settingsmenu.cpp
+++ b/src/tools/ecode/settingsmenu.cpp
@@ -1732,13 +1732,33 @@ UIMenu* SettingsMenu::createWindowMenu() {
"to choose a language again when opening these files." ) )
->setId( "reset-global-file-associations" );
- mWindowMenu->addSeparator();
-
mWindowMenu
->addCheckBox( i18n( "welcome_screen_enable", "Enable Welcome Screen" ),
mApp->getConfig().ui.welcomeScreen )
->setId( "welcome-screen-enable" );
+ mWindowMenu->addSeparator();
+
+ UIPopUpMenu* screenshotMenu = UIPopUpMenu::New();
+ screenshotMenu->add( i18n( "screenshot_save_path", "Save Path" ) )
+ ->setId( "screenshot-save-path" );
+ screenshotMenu->add( i18n( "screenshot_filename_pattern", "Filename Pattern" ) )
+ ->setId( "screenshot-filename-pattern" );
+ screenshotMenu->add( i18n( "screenshot_save_format", "Save Format" ) )
+ ->setId( "screenshot-save-format" );
+ screenshotMenu->on( Event::OnItemClicked, [this]( const Event* event ) {
+ if ( event->getNode()->isType( UI_TYPE_MENUITEM ) )
+ runCommand( event->getNode()->getId() );
+ } );
+ mWindowMenu->addSubMenu( i18n( "screenshot_settings", "Screenshot Settings" ),
+ findIcon( "image" ), screenshotMenu );
+
+ mWindowMenu->addSeparator();
+ mWindowMenu
+ ->add( i18n( "take_screenshot", "Take Screenshot" ), findIcon( "image" ),
+ getKeybind( "take-screenshot" ) )
+ ->setId( "take-screenshot" );
+
mWindowMenu->on( Event::OnItemClicked, [this]( const Event* event ) {
if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) )
return;