mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
ecode: add configurable screenshot support
Add an unlocked take-screenshot command and expose it under Settings -> Window with a default Print Screen binding. Allow configuring the screenshot destination, filename pattern, and output format. Save screenshots under the ecode configuration directory by default, notify users after capture, and provide an action to open the result in the integrated image viewer. Addresses a request in SpartanJ/ecode#950.
This commit is contained in:
@@ -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<std::string>& 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, ',' ) );
|
||||
|
||||
@@ -96,6 +96,12 @@ struct WindowStateConfig {
|
||||
std::vector<std::string> 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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<KeyBindings::Shortcut, std::string> 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<std::string, std::string> App::getMigrateKeybindings() {
|
||||
|
||||
std::vector<std::string> 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<std::string> pathFilters ) {
|
||||
mGlobalSearchController->showGlobalSearch( searchAndReplace, pathFilters );
|
||||
}
|
||||
|
||||
@@ -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<std::string> 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(); } );
|
||||
|
||||
@@ -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(
|
||||
<vbox lw="mp" class="notification">
|
||||
<TextView lw="mp" wordwrap="true" />
|
||||
<TextView lw="mp" lh="wc" word-wrap="true" />
|
||||
<hbox lg="right">
|
||||
<PushButton />
|
||||
</hbox>
|
||||
@@ -103,7 +103,7 @@ void NotificationCenter::addInteractiveNotification( String text, String actionT
|
||||
allowCopy, onInteraction = std::move( onInteraction )]() {
|
||||
static const auto layout = R"xml(
|
||||
<vbox lw="mp" class="notification">
|
||||
<TextView lw="mp" wordwrap="true" />
|
||||
<TextView lw="mp" lh="wc" word-wrap="true" />
|
||||
<hbox lg="right">
|
||||
<PushButton />
|
||||
</hbox>
|
||||
|
||||
@@ -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<UIFileDialog>()->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 );
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ class SettingsActions {
|
||||
|
||||
void setUIPanelFontSize();
|
||||
|
||||
void setScreenshotSavePath();
|
||||
|
||||
void setScreenshotFilenamePattern();
|
||||
|
||||
void setScreenshotSaveFormat();
|
||||
|
||||
private:
|
||||
App* mApp{ nullptr };
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user