2022-05-03 03:07:22 -07:00
//
// xemu User Interface
//
// Copyright (C) 2020-2022 Matt Borgerson
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "monitor.hh"
#include "imgui.h"
#include "misc.hh"
#include "font-manager.hh"
// Portable helpers
static int Stricmp ( const char * str1 , const char * str2 ) { int d ; while (( d = toupper ( * str2 ) - toupper ( * str1 )) == 0 && * str1 ) { str1 ++ ; str2 ++ ; } return d ; }
static char * Strdup ( const char * str ) { size_t len = strlen ( str ) + 1 ; void * buf = malloc ( len ); IM_ASSERT ( buf ); return ( char * ) memcpy ( buf , ( const void * ) str , len ); }
static void Strtrim ( char * str ) { char * str_end = str + strlen ( str ); while ( str_end > str && str_end [ - 1 ] == ' ' ) str_end -- ; * str_end = 0 ; }
MonitorWindow :: MonitorWindow ()
{
is_open = false ;
memset ( InputBuf , 0 , sizeof ( InputBuf ));
HistoryPos = - 1 ;
AutoScroll = true ;
ScrollToBottom = false ;
}
MonitorWindow ::~ MonitorWindow ()
{
}
void MonitorWindow :: Draw ()
{
if ( ! is_open ) return ;
int style_pop_cnt = PushWindowTransparencySettings ( true ) + 1 ;
ImGui :: PushStyleColor ( ImGuiCol_ChildBg , ImU32 ( ImColor ( 0 , 0 , 0 , 80 )));
ImGuiIO & io = ImGui :: GetIO ();
ImVec2 window_pos = ImVec2 ( 0 , io . DisplaySize . y / 2 );
ImGui :: SetNextWindowPos ( window_pos , ImGuiCond_Appearing );
ImGui :: SetNextWindowSize ( ImVec2 ( io . DisplaySize . x , io . DisplaySize . y / 2 ), ImGuiCond_Appearing );
if ( ImGui :: Begin ( "Monitor" , & is_open , ImGuiWindowFlags_NoCollapse )) {
const float footer_height_to_reserve = ImGui :: GetStyle (). ItemSpacing . y + ImGui :: GetFrameHeightWithSpacing (); // 1 separator, 1 input text
ImGui :: BeginChild ( "ScrollingRegion" , ImVec2 ( 0 , - footer_height_to_reserve ), false , ImGuiWindowFlags_HorizontalScrollbar ); // Leave room for 1 separator + 1 InputText
ImGui :: PushStyleVar ( ImGuiStyleVar_ItemSpacing , ImVec2 ( 4 , 1 )); // Tighten spacing
ImGui :: PushFont ( g_font_mgr . m_fixed_width_font );
2022-06-03 23:11:12 -07:00
// FIXME: Replace scroll to bottom hack when https://github.com/ocornut/imgui/issues/1972 is resolved.
// ImGui does not provide any mechanism to adjust scrolling in an InputTextMultiline and does not
// provide any other widget that allows for selectable text.
char * buffer = xemu_get_monitor_buffer ();
size_t buffer_len = strlen ( buffer );
// Calculating the precise size will cause an unnecessary vertical scrollbar in the InputTextMultiline.
int num_newlines = 2 ;
const char * start = buffer ;
while ( start ) {
start = strchr ( start , '\n' );
if ( start ) {
++ num_newlines ;
++ start ;
}
}
float input_height = fmax ( ImGui :: GetWindowHeight (),
g_font_mgr . m_fixed_width_font -> FontSize * num_newlines );
ImGui :: PushID ( "#MonitorOutput" );
ImGui :: InputTextMultiline ( "" ,
buffer ,
2025-03-08 03:31:20 -07:00
buffer_len + 1 ,
2022-06-03 23:11:12 -07:00
ImVec2 ( - 1.0f , input_height ),
ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_NoUndoRedo );
ImGui :: PopID ();
2022-05-03 03:07:22 -07:00
ImGui :: PopFont ();
if ( ScrollToBottom || ( AutoScroll && ImGui :: GetScrollY () >= ImGui :: GetScrollMaxY ())) {
ImGui :: SetScrollHereY ( 1.0f );
}
ScrollToBottom = false ;
ImGui :: PopStyleVar ();
ImGui :: EndChild ();
ImGui :: Separator ();
// Command-line
bool reclaim_focus = ImGui :: IsWindowAppearing ();
ImGui :: SetNextItemWidth ( - 1 );
ImGui :: PushFont ( g_font_mgr . m_fixed_width_font );
if ( ImGui :: InputText ( "#commandline" , InputBuf , IM_ARRAYSIZE ( InputBuf ), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory , & TextEditCallbackStub , ( void * ) this )) {
char * s = InputBuf ;
Strtrim ( s );
if ( s [ 0 ])
ExecCommand ( s );
strcpy ( s , "" );
reclaim_focus = true ;
}
ImGui :: PopFont ();
// Auto-focus on window apparition
ImGui :: SetItemDefaultFocus ();
if ( reclaim_focus ) {
ImGui :: SetKeyboardFocusHere ( - 1 ); // Auto focus previous widget
}
}
ImGui :: End ();
ImGui :: PopStyleColor ( style_pop_cnt );
}
void MonitorWindow :: ToggleOpen ( void )
{
is_open = ! is_open ;
}
void MonitorWindow :: ExecCommand ( const char * command_line )
{
xemu_run_monitor_command ( command_line );
// Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
HistoryPos = - 1 ;
for ( int i = History . Size - 1 ; i >= 0 ; i -- )
if ( Stricmp ( History [ i ], command_line ) == 0 )
{
free ( History [ i ]);
History . erase ( History . begin () + i );
break ;
}
History . push_back ( Strdup ( command_line ));
// On commad input, we scroll to bottom even if AutoScroll==false
ScrollToBottom = true ;
}
int MonitorWindow :: TextEditCallbackStub ( ImGuiInputTextCallbackData * data ) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
{
MonitorWindow * console = ( MonitorWindow * ) data -> UserData ;
return console -> TextEditCallback ( data );
}
int MonitorWindow :: TextEditCallback ( ImGuiInputTextCallbackData * data )
{
switch ( data -> EventFlag )
{
case ImGuiInputTextFlags_CallbackHistory :
{
// Example of HISTORY
const int prev_history_pos = HistoryPos ;
if ( data -> EventKey == ImGuiKey_UpArrow )
{
if ( HistoryPos == - 1 )
HistoryPos = History . Size - 1 ;
else if ( HistoryPos > 0 )
HistoryPos -- ;
}
else if ( data -> EventKey == ImGuiKey_DownArrow )
{
if ( HistoryPos != - 1 )
if ( ++ HistoryPos >= History . Size )
HistoryPos = - 1 ;
}
// A better implementation would preserve the data on the current input line along with cursor position.
if ( prev_history_pos != HistoryPos )
{
const char * history_str = ( HistoryPos >= 0 ) ? History [ HistoryPos ] : "" ;
data -> DeleteChars ( 0 , data -> BufTextLen );
data -> InsertChars ( 0 , history_str );
}
}
}
return 0 ;
}
MonitorWindow monitor_window ;