Skip to content

Commit 3a1c75f

Browse files
committed
Added Insert Date / Time functionality (Settings -> Edit -> Insert Date, plus commands for each option).
Fixed a use after free bug in UICodeEditorSplitter.
1 parent fba521d commit 3a1c75f

12 files changed

Lines changed: 323 additions & 37 deletions

include/eepp/ui/tools/uicodeeditorsplitter.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ class EE_API UICodeEditorSplitter {
435435
Float mVisualSplitEdgePercent{ 0.1 };
436436
TabTryCloseCallback mTabTryCloseCb;
437437
std::function<bool( SplitDirection direction, UIWidget* widget )> mCanCreateSplitFn;
438+
std::unordered_map<Node*, std::vector<Uint32>> mEventCbs;
438439

439440
UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode,
440441
std::shared_ptr<ThreadPool> threadPool,

src/eepp/ui/iconmanager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ UIIconTheme* IconManager::init( const std::string& iconThemeName, FontTrueType*
119119
{ "input-field", 0xF47A },
120120
{ "sun", 0xF1BC },
121121
{ "moon", 0xEF72 },
122+
{ "calendar-2", 0xEB21 },
122123
} ) {
123124
iconTheme->add( UIGlyphIcon::New( icon.first, remixIconFont, icon.second ) );
124125
}

src/eepp/ui/tools/uicodeeditorsplitter.cpp

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,42 @@ UICodeEditorSplitter::UICodeEditorSplitter( UICodeEditorSplitter::Client* client
110110
}
111111
}
112112

113-
UICodeEditorSplitter::~UICodeEditorSplitter() {}
113+
UICodeEditorSplitter::~UICodeEditorSplitter() {
114+
std::vector<UITabWidget*> tabWidgets;
115+
{
116+
Lock l( mTabWidgetMutex );
117+
tabWidgets = mTabWidgets;
118+
mTabWidgets.clear();
119+
}
120+
121+
for ( UITabWidget* tabWidget : tabWidgets ) {
122+
if ( nullptr == tabWidget )
123+
continue;
124+
125+
tabWidget->setTabTryCloseCallback( nullptr );
126+
tabWidget->setSplitFunction( nullptr, mVisualSplitEdgePercent );
127+
auto tabWidgetCbsIt = mEventCbs.find( tabWidget );
128+
if ( tabWidgetCbsIt != mEventCbs.end() )
129+
tabWidget->removeEventListener( tabWidgetCbsIt->second );
130+
131+
for ( size_t i = 0; i < tabWidget->getTabCount(); ++i ) {
132+
UITab* tab = tabWidget->getTab( i );
133+
if ( nullptr == tab || nullptr == tab->getOwnedWidget() ||
134+
!tab->getOwnedWidget()->isWidget() )
135+
continue;
136+
137+
UIWidget* widget = tab->getOwnedWidget()->asType<UIWidget>();
138+
auto widgetCbsIt = mEventCbs.find( widget );
139+
if ( widgetCbsIt != mEventCbs.end() )
140+
widget->removeEventListener( widgetCbsIt->second );
141+
}
142+
}
143+
144+
mEventCbs.clear();
145+
mCurEditor = nullptr;
146+
mCurWidget = nullptr;
147+
mClient = nullptr;
148+
}
114149

115150
UITabWidget* UICodeEditorSplitter::tabWidgetFromEditor( UICodeEditor* editor ) const {
116151
if ( editor && editor->getData() != 0 )
@@ -256,7 +291,7 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() {
256291
registerSplitterCommands( doc );
257292
/* Splitter commands */
258293

259-
editor->on( Event::OnFocus, [this]( const Event* event ) {
294+
mEventCbs[editor].push_back( editor->on( Event::OnFocus, [this]( const Event* event ) {
260295
UICodeEditor* editor = event->getNode()->asType<UICodeEditor>();
261296
UICodeEditor* prevEditor = mCurEditor;
262297
if ( mRestoreEditorSelectionOnFocus && prevEditor && prevEditor != editor &&
@@ -265,29 +300,32 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() {
265300
setCurrentWidget( editor );
266301
if ( mRestoreEditorSelectionOnFocus && prevEditor && prevEditor != editor )
267302
restoreEditorSelection( editor );
268-
} );
269-
editor->on( Event::OnFocusLoss, [this]( const Event* event ) {
303+
} ) );
304+
mEventCbs[editor].push_back( editor->on( Event::OnFocusLoss, [this]( const Event* event ) {
270305
if ( mRestoreEditorSelectionOnFocus )
271306
saveEditorSelection( event->getNode()->asType<UICodeEditor>() );
272-
} );
273-
editor->on( Event::OnTextChanged, [this]( const Event* event ) {
307+
} ) );
308+
mEventCbs[editor].push_back( editor->on( Event::OnTextChanged, [this]( const Event* event ) {
274309
mClient->onDocumentModified( event->getNode()->asType<UICodeEditor>(),
275310
event->getNode()->asType<UICodeEditor>()->getDocument() );
276-
} );
277-
editor->on( Event::OnSelectionChanged, [this]( const Event* event ) {
278-
mClient->onDocumentSelectionChange(
279-
event->getNode()->asType<UICodeEditor>(),
280-
event->getNode()->asType<UICodeEditor>()->getDocument() );
281-
} );
282-
editor->on( Event::OnCursorPosChange, [this]( const Event* event ) {
283-
mClient->onDocumentCursorPosChange(
284-
event->getNode()->asType<UICodeEditor>(),
285-
event->getNode()->asType<UICodeEditor>()->getDocument() );
286-
} );
287-
editor->on( Event::OnDocumentUndoRedo, [this]( const Event* event ) {
288-
mClient->onDocumentUndoRedo( event->getNode()->asType<UICodeEditor>(),
289-
event->getNode()->asType<UICodeEditor>()->getDocument() );
290-
} );
311+
} ) );
312+
mEventCbs[editor].push_back(
313+
editor->on( Event::OnSelectionChanged, [this]( const Event* event ) {
314+
mClient->onDocumentSelectionChange(
315+
event->getNode()->asType<UICodeEditor>(),
316+
event->getNode()->asType<UICodeEditor>()->getDocument() );
317+
} ) );
318+
mEventCbs[editor].push_back(
319+
editor->on( Event::OnCursorPosChange, [this]( const Event* event ) {
320+
mClient->onDocumentCursorPosChange(
321+
event->getNode()->asType<UICodeEditor>(),
322+
event->getNode()->asType<UICodeEditor>()->getDocument() );
323+
} ) );
324+
mEventCbs[editor].push_back(
325+
editor->on( Event::OnDocumentUndoRedo, [this]( const Event* event ) {
326+
mClient->onDocumentUndoRedo( event->getNode()->asType<UICodeEditor>(),
327+
event->getNode()->asType<UICodeEditor>()->getDocument() );
328+
} ) );
291329
editor->addKeyBinds( getLocalDefaultKeybindings() );
292330
editor->addUnlockedCommands( getUnlockedCommands() );
293331

@@ -569,11 +607,12 @@ UICodeEditorSplitter::createCodeEditorInTabWidget( UITabWidget* tabWidget ) {
569607
return std::make_pair( (UITab*)nullptr, (UICodeEditor*)nullptr );
570608
UICodeEditor* editor = createCodeEditor();
571609
mAboutToAddEditor = editor;
572-
editor->on( Event::OnDocumentChanged, [this]( const Event* event ) {
573-
UICodeEditor* editor = event->getNode()->asType<UICodeEditor>();
574-
mEditorSelections.erase( editor );
575-
mClient->onDocumentStateChanged( editor, editor->getDocument() );
576-
} );
610+
mEventCbs[editor].push_back(
611+
editor->on( Event::OnDocumentChanged, [this]( const Event* event ) {
612+
UICodeEditor* editor = event->getNode()->asType<UICodeEditor>();
613+
mEditorSelections.erase( editor );
614+
mClient->onDocumentStateChanged( editor, editor->getDocument() );
615+
} ) );
577616
UITab* tab = tabWidget->add( editor->getDocument().getFilename(), editor );
578617
editor->setData( (UintPtr)tab );
579618
DocEvent docEvent( editor, &editor->getDocument(), Event::OnEditorTabReady );
@@ -616,21 +655,21 @@ UICodeEditorSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget*
616655
widget->setData( (UintPtr)tab );
617656
// We use both events because there was an strange behavior that sometimes OnFocusWithin was not
618657
// enough, so this is just in case.
619-
widget->on( Event::OnFocus, [this]( const Event* event ) {
658+
mEventCbs[widget].push_back( widget->on( Event::OnFocus, [this]( const Event* event ) {
620659
setCurrentWidget( event->getNode()->asType<UIWidget>() );
621-
} );
622-
widget->on( Event::OnFocusWithin, [this]( const Event* event ) {
660+
} ) );
661+
mEventCbs[widget].push_back( widget->on( Event::OnFocusWithin, [this]( const Event* event ) {
623662
setCurrentWidget( event->getNode()->asType<UIWidget>() );
624-
} );
625-
widget->on( Event::OnTitleChange, [this]( const Event* event ) {
663+
} ) );
664+
mEventCbs[widget].push_back( widget->on( Event::OnTitleChange, [this]( const Event* event ) {
626665
const TextEvent* tevent = static_cast<const TextEvent*>( event );
627666
UIWidget* widget = event->getNode()->asType<UIWidget>();
628667
UITabWidget* tabWidget = tabWidgetFromWidget( widget );
629668
UITab* tab = tabWidget->getTabFromOwnedWidget( widget );
630669
if ( !tab )
631670
return;
632671
tab->setText( tevent->getText() );
633-
} );
672+
} ) );
634673
if ( focus )
635674
tabWidget->setTabSelected( tab );
636675
mClient->onTabCreated( tab, widget );
@@ -713,7 +752,8 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) {
713752
},
714753
mVisualSplitEdgePercent );
715754
}
716-
tabWidget->on( Event::OnTabSelected, [this]( const Event* event ) {
755+
mEventCbs[tabWidget].push_back( tabWidget->on( Event::OnTabSelected, [this](
756+
const Event* event ) {
717757
UITabWidget* tabWidget = event->getNode()->asType<UITabWidget>();
718758
eeASSERT( nullptr != tabWidget && nullptr != tabWidget->getTabSelected() &&
719759
nullptr != tabWidget->getTabSelected()->getOwnedWidget() );
@@ -727,7 +767,7 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) {
727767
} else {
728768
setCurrentWidget( tabWidget->getTabSelected()->getOwnedWidget()->asType<UIWidget>() );
729769
}
730-
} );
770+
} ) );
731771
tabWidget->setTabTryCloseCallback(
732772
[this]( UITab* tab, UITabWidget::FocusTabBehavior focusTabBehavior ) -> bool {
733773
if ( tab->getOwnedWidget() &&
@@ -737,9 +777,10 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) {
737777
}
738778
return false;
739779
} );
740-
tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) {
741-
onTabClosed( static_cast<const TabEvent*>( event ) );
742-
} );
780+
mEventCbs[tabWidget].push_back(
781+
tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) {
782+
onTabClosed( static_cast<const TabEvent*>( event ) );
783+
} ) );
743784
if ( mOnTabWidgetCreateCb )
744785
mOnTabWidgetCreateCb( tabWidget );
745786
Lock l( mTabWidgetMutex );
@@ -1842,12 +1883,22 @@ void UICodeEditorSplitter::closeSplitter( UISplitter* splitter ) {
18421883
void UICodeEditorSplitter::onTabClosed( const TabEvent* tabEvent ) {
18431884
UIWidget* widget = tabEvent->getTab()->getOwnedWidget()->asType<UIWidget>();
18441885
UITabWidget* tabWidget = tabEvent->getTab()->getTabWidget();
1886+
auto widgetCbsIt = mEventCbs.find( widget );
1887+
if ( widgetCbsIt != mEventCbs.end() ) {
1888+
widget->removeEventListener( widgetCbsIt->second );
1889+
mEventCbs.erase( widgetCbsIt );
1890+
}
18451891
if ( widget && widget->isType( UI_TYPE_CODEEDITOR ) )
18461892
mEditorSelections.erase( widget->asType<UICodeEditor>() );
18471893
if ( tabWidget->getTabCount() == 0 ) {
18481894
UISplitter* splitter = splitterFromWidget( widget );
18491895
if ( splitter ) {
18501896
if ( splitter->isFull() ) {
1897+
auto tabWidgetCbsIt = mEventCbs.find( tabWidget );
1898+
if ( tabWidgetCbsIt != mEventCbs.end() ) {
1899+
tabWidget->removeEventListener( tabWidgetCbsIt->second );
1900+
mEventCbs.erase( tabWidgetCbsIt );
1901+
}
18511902
tabWidget->close();
18521903
auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget );
18531904
if ( itWidget != mTabWidgets.end() ) {

src/tools/ecode/appconfig.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
177177
TextFormat::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) );
178178
editor.newTabPosition =
179179
NewTabPosition::fromString( ini.getValue( "editor", "new_tab_position", "last" ) );
180+
editor.customDateFormat = ini.getValue( "editor", "custom_date_format", "%d.%m.%Y %H:%M:%S" );
180181
// Migrate old data
181182
if ( ini.keyValueExists( "document", "windows_line_endings" ) &&
182183
!ini.keyValueExists( "document", "line_endings" ) &&
@@ -395,6 +396,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
395396
ini.setValue( "editor", "tab_jump_mode",
396397
UITabWidget::tabJumpModeToString( editor.tabJumpMode ) );
397398
ini.setValue( "editor", "new_tab_position", NewTabPosition::toString( editor.newTabPosition ) );
399+
ini.setValue( "editor", "custom_date_format", editor.customDateFormat );
398400
ini.setValueB( "editor", "single_click_tree_navigation", editor.singleClickNavigation );
399401
ini.setValueB( "editor", "sync_project_tree_with_editor", editor.syncProjectTreeWithEditor );
400402
ini.setValueB( "editor", "auto_close_xml_tags", editor.autoCloseXMLTags );

src/tools/ecode/appconfig.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ struct CodeEditorConfig {
121121
bool restoreEditorSelectionOnFocus{ true };
122122
UITabWidget::TabJumpMode tabJumpMode{ UITabWidget::TabJumpMode::Linear };
123123
NewTabPosition::Position newTabPosition{ NewTabPosition::Last };
124+
std::string customDateFormat{ "%d.%m.%Y %H:%M:%S" };
124125

125126
bool singleClickNavigation{ false };
126127
bool syncProjectTreeWithEditor{ true };
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#include "datetimecontroller.hpp"
2+
#include "appconfig.hpp"
3+
#include "plugins/plugincontextprovider.hpp"
4+
#include <cctype>
5+
#include <ctime>
6+
#include <eepp/scene/scenemanager.hpp>
7+
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
8+
#include <eepp/ui/uicodeeditor.hpp>
9+
#include <eepp/ui/uimessagebox.hpp>
10+
#include <eepp/ui/uitextinput.hpp>
11+
#include <string_view>
12+
13+
namespace ecode {
14+
15+
DateTimeController::DateTimeController( PluginContextProvider* context ) : mContext( context ) {}
16+
17+
void DateTimeController::registerCommands( EE::UI::Doc::TextDocument& doc ) {
18+
doc.setCommand( "insert-date-dd-mm-yyyy", [this] { insertDate( "%d.%m.%Y" ); } );
19+
doc.setCommand( "insert-date-mm-dd-yyyy", [this] { insertDate( "%m.%d.%Y" ); } );
20+
doc.setCommand( "insert-date-yyyy-mm-dd", [this] { insertDate( "%Y/%m/%d" ); } );
21+
doc.setCommand( "insert-date-time-dd-mm-yyyy", [this] { insertDate( "%d.%m.%Y %H:%M:%S" ); } );
22+
doc.setCommand( "insert-date-time-mm-dd-yyyy", [this] { insertDate( "%m.%d.%Y %H:%M:%S" ); } );
23+
doc.setCommand( "insert-date-time-yyyy-mm-dd", [this] { insertDate( "%Y/%m/%d %H:%M:%S" ); } );
24+
doc.setCommand( "insert-date-custom",
25+
[this] { insertDate( mContext->getConfig().editor.customDateFormat ); } );
26+
doc.setCommand( "set-custom-date-format", [this] { setCustomDateFormat(); } );
27+
}
28+
29+
bool DateTimeController::isValidDateFormat( const std::string& format ) {
30+
if ( String::trim( format ).empty() )
31+
return false;
32+
33+
for ( size_t i = 0; i < format.size(); ++i ) {
34+
if ( format[i] != '%' )
35+
continue;
36+
37+
if ( ++i >= format.size() )
38+
return false;
39+
40+
while ( i < format.size() && ( format[i] == '_' || format[i] == '-' || format[i] == '0' ||
41+
format[i] == '^' || format[i] == '#' ) )
42+
++i;
43+
44+
while ( i < format.size() && std::isdigit( static_cast<unsigned char>( format[i] ) ) )
45+
++i;
46+
47+
if ( i < format.size() && ( format[i] == 'E' || format[i] == 'O' ) )
48+
++i;
49+
50+
if ( i >= format.size() )
51+
return false;
52+
53+
static constexpr std::string_view VALID_SPECIFIERS =
54+
"aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%";
55+
if ( VALID_SPECIFIERS.find( format[i] ) == std::string_view::npos )
56+
return false;
57+
}
58+
59+
return true;
60+
}
61+
62+
std::string DateTimeController::formatCurrentDate( const std::string& format ) {
63+
std::time_t rawtime;
64+
std::time( &rawtime );
65+
std::tm* timeinfo = std::localtime( &rawtime );
66+
if ( nullptr == timeinfo )
67+
return {};
68+
69+
char buf[256];
70+
if ( std::strftime( buf, sizeof( buf ), format.c_str(), timeinfo ) == 0 )
71+
return {};
72+
return std::string( buf );
73+
}
74+
75+
void DateTimeController::insertDate( const std::string& format ) {
76+
if ( !mContext->getSplitter()->curEditorExistsAndFocused() )
77+
return;
78+
79+
UICodeEditor* editor = mContext->getSplitter()->getCurEditor();
80+
if ( nullptr == editor || editor->isLocked() )
81+
return;
82+
83+
if ( !isValidDateFormat( format ) )
84+
return;
85+
86+
std::string date( formatCurrentDate( format ) );
87+
if ( !date.empty() )
88+
editor->getDocument().textInput( String::fromUtf8( date ) );
89+
}
90+
91+
void DateTimeController::setCustomDateFormat() {
92+
UIMessageBox* msgBox =
93+
UIMessageBox::New( UIMessageBox::INPUT,
94+
mContext
95+
->i18n( "set_custom_date_format_message",
96+
"Set Custom Date Format:\nUses strftime format specifiers." )
97+
.unescape() );
98+
msgBox->setTitle( mContext->i18n( "set_custom_date_format", "Set Custom Date Format" ) );
99+
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
100+
msgBox->getTextInput()->setText( mContext->getConfig().editor.customDateFormat );
101+
msgBox->getTextInput()->on( Event::OnTextChanged, [msgBox]( const Event* ) {
102+
msgBox->getTextInput()->removeClass( "error" );
103+
} );
104+
msgBox->showWhenReady();
105+
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
106+
std::string format( msgBox->getTextInput()->getText().toUtf8() );
107+
if ( !isValidDateFormat( format ) || formatCurrentDate( format ).empty() ) {
108+
msgBox->getTextInput()->addClass( "error" );
109+
mContext->errorMsgBox(
110+
mContext->i18n( "invalid_date_format", "Invalid date format." ) );
111+
return;
112+
}
113+
mContext->getConfig().editor.customDateFormat = std::move( format );
114+
msgBox->closeWindow();
115+
} );
116+
setFocusEditorOnClose( msgBox );
117+
}
118+
119+
void DateTimeController::setFocusEditorOnClose( EE::UI::UIMessageBox* msgBox ) {
120+
UICodeEditor* editor = mContext->getSplitter()->getCurEditor();
121+
msgBox->on( Event::OnWindowClose, [editor]( const Event* ) {
122+
if ( editor && SceneManager::existsSingleton() &&
123+
!SceneManager::instance()->isShuttingDown() )
124+
editor->setFocus();
125+
} );
126+
}
127+
128+
} // namespace ecode

0 commit comments

Comments
 (0)