* shader editor: support files with crlf line feeds in linux

preserve line feeds on saving
			indicate 'modified' state by save button and window name
			view shaders from packs too (read only)
			refactor
This commit is contained in:
Garux 2019-11-09 15:13:59 +03:00
parent bad1a965c0
commit 34e9b522b7
6 changed files with 189 additions and 361 deletions

View File

@ -727,7 +727,6 @@ $(INSTALLDIR)/radiant.$(EXE): \
radiant/selection.o \
radiant/select.o \
radiant/server.o \
radiant/shaders.o \
radiant/sockets.o \
radiant/stacktrace.o \
radiant/surfacedialog.o \

View File

@ -746,66 +746,23 @@ EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
return ret;
}
// =============================================================================
// Text Editor dialog
// master window widget
static GtkWidget *text_editor = 0;
static GtkWidget *text_widget; // slave, text widget from the gtk editor
static GtkTextBuffer* text_buffer_;
class TextEditor
{
GtkWidget *m_window = 0;
GtkWidget *m_textView; // slave, text widget from the gtk editor
GtkTextBuffer* m_textBuffer;
GtkWidget *m_button; // save button
CopiedString m_filename;
static gint editor_delete( GtkWidget *widget, gpointer data ){
/* if ( gtk_MessageBox( widget, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION ) == eIDNO ) {
return TRUE;
}
*/
gtk_widget_hide( text_editor );
void construct(){
GtkWidget *vbox, *hbox, *scr;
return TRUE;
}
static void editor_save( GtkWidget *widget, gpointer data ){
FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
//gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
if ( f == 0 ) {
gtk_MessageBox( GTK_WIDGET( data ), "Error saving file !" );
return;
}
/* Obtain iters for the start and end of points of the buffer */
GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_start_iter (text_buffer_, &start);
gtk_text_buffer_get_end_iter (text_buffer_, &end);
/* Get the entire buffer text. */
char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
//char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
fwrite( str, 1, strlen( str ), f );
fclose( f );
g_free (str);
}
static void editor_close( GtkWidget *widget, gpointer data ){
/* if ( gtk_MessageBox( text_editor, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION ) == eIDNO ) {
return;
}
*/
gtk_widget_hide( text_editor );
}
static void CreateGtkTextEditor(){
GtkWidget *dlg;
GtkWidget *vbox, *hbox, *button, *scr, *text;
GtkWindow* dlg_wnd = create_dialog_window( MainFrame_getWindow(), "", G_CALLBACK( editor_delete ), 0, 400, 600 );
dlg = GTK_WIDGET( dlg_wnd );
m_window = GTK_WIDGET( create_dialog_window( MainFrame_getWindow(), "", G_CALLBACK( gtk_widget_hide_on_delete ), 0, 400, 600 ) );
vbox = gtk_vbox_new( FALSE, 5 );
gtk_widget_show( vbox );
gtk_container_add( GTK_CONTAINER( dlg ), GTK_WIDGET( vbox ) );
gtk_container_add( GTK_CONTAINER( m_window ), GTK_WIDGET( vbox ) );
gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
scr = gtk_scrolled_window_new( 0, 0 );
@ -814,96 +771,101 @@ static void CreateGtkTextEditor(){
gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
text = gtk_text_view_new();
gtk_container_add( GTK_CONTAINER( scr ), text );
gtk_widget_show( text );
g_object_set_data( G_OBJECT( dlg ), "text", text );
gtk_text_view_set_editable( GTK_TEXT_VIEW( text ), TRUE );
m_textView = gtk_text_view_new();
gtk_container_add( GTK_CONTAINER( scr ), m_textView );
gtk_widget_show( m_textView );
m_textBuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( m_textView ) );
g_signal_connect( G_OBJECT( m_textBuffer ), "modified-changed",
G_CALLBACK( modified_changed ), this );
hbox = gtk_hbox_new( FALSE, 5 );
gtk_widget_show( hbox );
gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, TRUE, 0 );
button = gtk_button_new_with_label( "Close" );
gtk_widget_show( button );
gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
g_signal_connect( G_OBJECT( button ), "clicked",
G_CALLBACK( editor_close ), dlg );
gtk_widget_set_usize( button, 60, -2 );
button = gtk_button_new_with_label( "Save" );
gtk_widget_show( button );
gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
g_signal_connect( G_OBJECT( button ), "clicked",
G_CALLBACK( editor_save ), dlg );
gtk_widget_set_usize( button, 60, -2 );
text_editor = dlg;
text_widget = text;
}
static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
if ( !text_editor ) {
CreateGtkTextEditor(); // build it the first time we need it
m_button = gtk_button_new_with_label( "Close" );
gtk_widget_show( m_button );
gtk_box_pack_end( GTK_BOX( hbox ), m_button, FALSE, FALSE, 0 );
g_signal_connect( G_OBJECT( m_button ), "clicked",
G_CALLBACK( editor_close ), this );
gtk_widget_set_usize( m_button, 60, -2 );
m_button = gtk_button_new_with_label( "Save" );
gtk_widget_show( m_button );
gtk_box_pack_end( GTK_BOX( hbox ), m_button, FALSE, FALSE, 0 );
g_signal_connect( G_OBJECT( m_button ), "clicked",
G_CALLBACK( editor_save ), this );
gtk_widget_set_usize( m_button, 60, -2 );
}
// Load file
FILE *f = fopen( filename, "r" );
static void editor_close( GtkWidget *widget, TextEditor* self ){
gtk_widget_hide( self->m_window );
}
static void editor_save( GtkWidget *widget, TextEditor* self ){
FILE *f = fopen( self->m_filename.c_str(), "wb" ); //write in binary mode to preserve line feeds
if ( f == 0 ) {
globalWarningStream() << "Unable to load file " << filename << " in shader editor.\n";
gtk_widget_hide( text_editor );
gtk_MessageBox( self->m_window, "Error saving file !" );
return;
}
else
{
fseek( f, 0, SEEK_END );
int len = ftell( f );
void *buf = malloc( len );
void *old_filename;
rewind( f );
fread( buf, 1, len, f );
/* Obtain iters for the start and end of points of the buffer */
GtkTextIter start, end;
gtk_text_buffer_get_bounds( self->m_textBuffer, &start, &end );
gtk_window_set_title( GTK_WINDOW( text_editor ), filename );
/* Get the entire buffer text. */
char *str = gtk_text_buffer_get_text( self->m_textBuffer, &start, &end, FALSE );
GtkTextBuffer* text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( text_widget ) );
gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
fwrite( str, 1, strlen( str ), f );
fclose( f );
g_free ( str );
old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
if ( old_filename ) {
free( old_filename );
gtk_text_buffer_set_modified( self->m_textBuffer, FALSE );
}
g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
static void modified_changed( GtkTextBuffer *textbuffer, TextEditor* self ){
const gboolean modified = gtk_text_buffer_get_modified( textbuffer );
gtk_widget_set_sensitive( self->m_button, modified );
StringOutputStream str( 256 );
str << ( modified? "*" : "" ) << self->m_filename.c_str();
gtk_window_set_title( GTK_WINDOW( self->m_window ), str.c_str() );
}
public:
void DoGtkTextEditor( const char* text, std::size_t size, std::size_t offset, const char* filename, const bool editable ){
if ( !m_window ) {
construct(); // build it the first time we need it
}
m_filename = filename;
gtk_text_buffer_set_text( m_textBuffer, text, size );
gtk_text_buffer_set_modified( m_textBuffer, FALSE );
gtk_text_view_set_editable( GTK_TEXT_VIEW( m_textView ), editable );
// trying to show later
gtk_widget_show( text_editor );
gtk_window_present( GTK_WINDOW( text_editor ) );
gtk_widget_show( m_window );
gtk_window_present( GTK_WINDOW( m_window ) );
//#ifdef WIN32
//#ifdef WIN32
process_gui();
//#endif
//#endif
// only move the cursor if it's not exceeding the size..
// NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
// len is the max size in bytes, not in characters either, but the character count is below that limit..
// thinking .. the difference between character count and byte count would be only because of CR/LF?
{
GtkTextIter text_iter;
// character offset, not byte offset
gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
gtk_text_buffer_place_cursor( text_buffer, &text_iter );
gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
gtk_text_buffer_get_iter_at_offset( m_textBuffer, &text_iter, offset );
gtk_text_buffer_place_cursor( m_textBuffer, &text_iter );
gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( m_textView ), &text_iter, 0, TRUE, 0, 0);
}
//#ifdef WIN32
gtk_widget_queue_draw( text_widget );
//#endif
text_buffer_ = text_buffer;
free( buf );
fclose( f );
//#ifdef WIN32
gtk_widget_queue_draw( m_textView );
//#endif
}
}
};
static TextEditor g_textEditor;
// =============================================================================
// Light Intensity dialog
@ -1106,76 +1068,102 @@ EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const
CopiedString g_TextEditor_editorCommand( "" );
#include "ifilesystem.h"
#include "iarchive.h"
#include "idatastream.h"
#ifdef WIN32
#include <gdk/gdkwin32.h>
#endif
CopiedString g_TextEditor_editorCommand( "" );
void DoShaderView( const char *shaderFileName, const char *shaderName, bool external_editor ){
const char* pathRoot = GlobalFileSystem().findFile( shaderFileName );
const bool pathEmpty = string_empty( pathRoot );
const bool pathIsDir = !pathEmpty && file_is_directory( pathRoot );
void DoTextEditor( const char* filename, int cursorpos, int length, bool external_editor ){
//StringOutputStream paths[4]( 256 );
StringOutputStream paths[4] = { StringOutputStream(256) };
StringOutputStream* goodpath = 0;
StringOutputStream pathFull( 256 );
pathFull << pathRoot << ( pathIsDir? "" : "::" ) << shaderFileName;
const char* gamename = GlobalRadiant().getGameName();
const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
const char* enginePath = GlobalRadiant().getEnginePath();
const char* homeEnginePath = g_qeglobals.m_userEnginePath.c_str();
paths[0] << homeEnginePath << gamename << '/' << filename;
paths[1] << enginePath << gamename << '/' << filename;
paths[2] << homeEnginePath << basegame << '/' << filename;
paths[3] << enginePath << basegame << '/' << filename;
for ( std::size_t i = 0; i < 4; ++i ){
if ( file_exists( paths[i].c_str() ) ){
goodpath = &paths[i];
break;
if( pathEmpty ){
globalErrorStream() << "Failed to load shader file " << shaderFileName << "\n";
}
}
if( goodpath ){
globalOutputStream() << "opening file '" << goodpath->c_str() << "' (line " << cursorpos << " info ignored)\n";
if( external_editor ){
else if( external_editor && pathIsDir ){
if( g_TextEditor_editorCommand.empty() ){
#ifdef WIN32
ShellExecute( (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window ), 0, goodpath->c_str(), 0, 0, SW_SHOWNORMAL );
// SHELLEXECUTEINFO ShExecInfo;
// ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
// ShExecInfo.fMask = 0;
// ShExecInfo.hwnd = (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window );
// ShExecInfo.lpVerb = NULL;
// ShExecInfo.lpFile = goodpath->c_str();
// ShExecInfo.lpParameters = NULL;
// ShExecInfo.lpDirectory = NULL;
// ShExecInfo.nShow = SW_SHOWNORMAL;
// ShExecInfo.hInstApp = NULL;
// ShellExecuteEx(&ShExecInfo);
ShellExecute( (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window ), 0, pathFull.c_str(), 0, 0, SW_SHOWNORMAL );
#else
globalWarningStream() << "Failed to open '" << goodpath->c_str() << "'\nSet Shader Editor Command in preferences\n";
globalWarningStream() << "Failed to open '" << pathFull.c_str() << "'\nSet Shader Editor Command in preferences\n";
#endif
}
else{
StringOutputStream strEditCommand( 256 );
strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << goodpath->c_str() << "\"";
globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
StringOutputStream command( 256 );
command << g_TextEditor_editorCommand.c_str() << " \"" << pathFull.c_str() << "\"";
globalOutputStream() << "Launching: " << command.c_str() << "\n";
// note: linux does not return false if the command failed so it will assume success
if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
globalErrorStream() << "Failed to execute " << strEditCommand.c_str() << "\n";
}
else
{
// the command (appeared) to run successfully, no need to do anything more
return;
if ( !Q_Exec( 0, const_cast<char*>( command.c_str() ), 0, true, false ) )
globalErrorStream() << "Failed to execute " << command.c_str() << "\n";
}
}
else if( ArchiveFile* file = GlobalFileSystem().openFile( shaderFileName ) ){
const std::size_t size = file->size();
char* text = ( char* )malloc( size + 1 );
file->getInputStream().read( ( InputStream::byte_type* )text, size );
text[size] = 0;
file->release();
// look for the shader declaration
std::size_t offset = 0;
bool startOK = false;
bool endOK = false;
while ( !startOK || !endOK ){
const char* found = string_in_string_nocase( text + offset, shaderName );
if ( found == 0 ) {
break;
}
else{
DoGtkTextEditor( goodpath->c_str(), cursorpos, length );
}
offset = found - text;
//validate found one...
startOK = endOK = false;
if ( offset == 0 ){
startOK = true;
}
else{
globalWarningStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
for ( const char* i = found - 1; i >= text; --i ){
if( *i == '\t' || *i == ' ' ){
startOK = true;
continue;
}
else if( *i == '\n' || *i == '\r' || *i == '}' ){
startOK = true;
break;
}
else{
startOK = false;
break;
}
}
}
for ( const char* i = found + strlen( shaderName ); i < text + size; ++i ){
if( *i == '\t' || *i == ' ' ){
endOK = true;
continue;
}
else if( *i == '\n' || *i == '\r' || *i == '{' || string_equal_nocase_n( i, ":q3map", 6 ) ){
endOK = true;
break;
}
else{
endOK = false;
break;
}
}
if( !startOK || !endOK ){
++offset;
}
}
}
g_textEditor.DoGtkTextEditor( text, size, offset, pathFull.c_str(), pathIsDir );
free( text );
}
}

View File

@ -38,7 +38,7 @@ EMessageBoxReturn DoLightIntensityDlg( int *intensity );
EMessageBoxReturn DoShaderTagDlg( CopiedString *tag, const char* title );
EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title );
EMessageBoxReturn DoTextureLayout( float *fx, float *fy );
void DoTextEditor( const char* filename, int cursorpos, int length, bool external_editor );
void DoShaderView( const char *shaderFileName, const char *shaderName, bool external_editor );
void DoProjectSettings();

View File

@ -1,131 +0,0 @@
/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "shaders.h"
#include "ifilesystem.h"
#include "stream/stringstream.h"
#include "gtkdlgs.h"
void ViewShader( const char *pFile, const char *pName, bool external_editor ){
char* pBuff = 0;
//int nSize =
vfsLoadFile( pFile, reinterpret_cast<void**>( &pBuff ) );
if ( pBuff == 0 ) {
globalErrorStream() << "Failed to load shader file " << pFile << "\n";
return;
}
// look for the shader declaration
StringOutputStream strFind( string_length( pName ) );
strFind << LowerCase( pName );
StringOutputStream strLook( string_length( pBuff ) );
strLook << LowerCase( pBuff );
// offset used when jumping over commented out definitions
int length = string_length( pBuff );
std::size_t nOffset = 0;
bool startOK = false;
bool endOK = false;
while ( !startOK || !endOK ){
const char* substr = strstr( strLook.c_str() + nOffset, strFind.c_str() );
if ( substr == 0 ) {
break;
}
std::size_t nStart = substr - strLook.c_str();
startOK = endOK = false;
if ( nStart == 0 ){
startOK = true;
}
//validate found one...
for ( const char* i = substr - 1; i > strLook.c_str(); i-- ){
if( (strncmp( i, "\t", 1 ) == 0) || (strncmp( i, " ", 1 ) == 0) ){
startOK = true;
continue;
}
else if ( (strncmp( i, "\n", 1 ) == 0) || (strncmp( i, "\r", 1 ) == 0) ){
startOK = true;
break;
}
else{
startOK = false;
break;
}
}
const char* b = strLook.c_str() + strlen( strLook.c_str() );
for ( const char* i = substr + strlen( strFind.c_str() ); i < b; i++ ){
if( (strncmp( i, "\t", 1 ) == 0) || (strncmp( i, " ", 1 ) == 0) ){
endOK = true;
continue;
}
else if ( (strncmp( i, "\n", 1 ) == 0) || (strncmp( i, "\r", 1 ) == 0) || (strncmp( i, "{", 1 ) == 0) || (strncmp( i, ":q3map", 6 ) == 0) ){
endOK = true;
break;
}
else{
endOK = false;
break;
}
}
if( !startOK || !endOK ){
nOffset = nStart + 1;
}
else{
//globalErrorStream() << "Validated successfully" << "\n";
nOffset = nStart;
#ifdef WIN32
//fix cr+lf
for ( const char* i = strLook.c_str(); i < substr ; i++ ){
if ( (strncmp( i, "\r\n", 2 ) == 0) ){
nOffset--;
}
}
#endif
}
/*// we have found something, maybe it's a commented out shader name?
char *strCheck = new char[string_length( strLook.c_str() ) + 1];
strcpy( strCheck, strLook.c_str() );
strCheck[nStart] = 0;
char *pCheck = strrchr( strCheck, '\n' );
// if there's a commentary sign in-between we'll continue
if ( pCheck && strstr( pCheck, "//" ) ) {
delete[] strCheck;
nOffset = nStart + 1;
continue;
}
delete[] strCheck;
nOffset = nStart;
break;*/
}
#ifdef WIN32
//fix up length
const char* b = strLook.c_str() + strlen( strLook.c_str() ) - 1;
for ( const char* i = strLook.c_str(); i < b; i++ ){
if ( (strncmp( i, "\r\n", 2 ) == 0) ){
length--;
}
}
#endif
// now close the file
vfsFreeFile( pBuff );
DoTextEditor( pFile, static_cast<int>( nOffset ), length, external_editor );
}

View File

@ -1,27 +0,0 @@
/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant 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.
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if !defined( INCLUDED_SHADERS_H )
#define INCLUDED_SHADERS_H
void ViewShader( const char* file, const char* shader, bool external_editor );
#endif

View File

@ -86,7 +86,6 @@
#include "patchdialog.h"
#include "groupdialog.h"
#include "preferences.h"
#include "shaders.h"
#include "commands.h"
void TextureBrowser_queueDraw( TextureBrowser& textureBrowser );
@ -1052,7 +1051,7 @@ void TextureBrowser_Selection_MouseUp( TextureBrowser& textureBrowser, guint32 f
globalWarningStream() << shader->getName() << " is not a shader, it's a texture.\n";
}
else{
ViewShader( shader->getShaderFileName(), shader->getName(), ( flags & GDK_CONTROL_MASK ) != 0 );
DoShaderView( shader->getShaderFileName(), shader->getName(), ( flags & GDK_CONTROL_MASK ) != 0 );
}
}
}