grafX2/src/readline.c
Yves Rizoud 5fd72996c3 Virtual keyboard for string input
git-svn-id: svn://pulkomandy.tk/GrafX2/trunk@1636 416bcca6-2ee7-4201-b75f-2eb2f807beb1
2010-10-02 19:05:30 +00:00

607 lines
21 KiB
C

/* vim:expandtab:ts=2 sw=2:
*/
/* Grafx2 - The Ultimate 256-color bitmap paint program
Copyright 2008 Yves Rizoud
Copyright 2007 Adrien Destugues
Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud)
Grafx2 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; version 2
of the License.
Grafx2 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 Grafx2; if not, see <http://www.gnu.org/licenses/>
*/
/************************************************************************
* *
* READLINE (procédure permettant de saisir une chaîne de caractères) *
* *
************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "const.h"
#include "struct.h"
#include "global.h"
#include "misc.h"
#include "errors.h"
#include "const.h"
#include "sdlscreen.h"
#include "readline.h"
#include "windows.h"
#include "input.h"
#define TEXT_COLOR MC_Black
#define BACKGROUND_COLOR MC_Light
#define CURSOR_COLOR MC_Black
#define CURSOR_BACKGROUND_COLOR MC_Dark
// Suppresion d'un caractère à une certaine POSITION dans une CHAINE.
void Remove_character(char * str, byte position)
{
for (;str[position]!='\0';position++)
str[position]=str[position+1];
}
void Insert_character(char * str, char letter, byte position)
// Insertion d'une LETTRE à une certaine POSITION
// dans une CHAINE d'une certaine TAILLE.
{
char temp_char;
for (;letter!='\0';position++)
{
// On mémorise le caractère qui se trouve en "position"
temp_char=str[position];
// On splotch la lettre à insérer
str[position]=letter;
// On place le caractère mémorisé dans "letter" comme nouvelle lettre à insérer
letter=temp_char;
}
// On termine la chaine
str[position]='\0';
}
int Valid_character(int c)
{
// Sous Linux: Seul le / est strictement interdit, mais beaucoup
// d'autres poseront des problèmes au shell, alors on évite.
// Sous Windows : c'est moins grave car le fopen() échouerait de toutes façons.
// AmigaOS4: Pas de ':' car utilisé pour les volumes.
#if defined(__WIN32__)
char forbidden_char[] = {'/', '|', '?', '*', '<', '>', ':', '\\'};
#elif defined (__amigaos4__)
char forbidden_char[] = {'/', '|', '?', '*', '<', '>', ':'};
#else
char forbidden_char[] = {'/', '|', '?', '*', '<', '>'};
#endif
int position;
if (c < ' ' || c > 255)
return 0;
for (position=0; position<(long)sizeof(forbidden_char); position++)
if (c == forbidden_char[position])
return 0;
return 1;
}
void Display_whole_string(word x_pos,word y_pos,char * str,byte position)
{
char cursor[2];
Print_general(x_pos,y_pos,str,TEXT_COLOR,BACKGROUND_COLOR);
cursor[0]=str[position] ? str[position] : ' ';
cursor[1]='\0';
Print_general(x_pos+(position<<3)*Menu_factor_X,y_pos,cursor,CURSOR_COLOR,CURSOR_BACKGROUND_COLOR);
}
/****************************************************************************
* Enhanced super scanf deluxe pro plus giga mieux :-) *
****************************************************************************/
byte Readline(word x_pos,word y_pos,char * str,byte visible_size,byte input_type)
// Paramètres:
// x_pos, y_pos : Coordonnées de la saisie dans la fenêtre
// str : Chaîne recevant la saisie (et contenant éventuellement une valeur initiale)
// max_size : Nombre de caractères logeant dans la zone de saisie
// input_type : 0=Chaîne, 1=Nombre, 2=Nom de fichier
// Sortie:
// 0: Sortie par annulation (Esc.) / 1: sortie par acceptation (Return)
{
byte max_size;
// Grosse astuce pour les noms de fichiers: La taille affichée est différente
// de la taille maximum gérée.
if (input_type == 2)
max_size = 255;
else
max_size = visible_size;
return Readline_ex(x_pos,y_pos,str,visible_size,max_size,input_type,0);
}
/****************************************************************************
* Enhanced super scanf deluxe pro plus giga mieux :-) *
****************************************************************************/
byte Readline_ex(word x_pos,word y_pos,char * str,byte visible_size,byte max_size, byte input_type, byte decimal_places)
// Paramètres:
// x_pos, y_pos : Coordonnées de la saisie dans la fenêtre
// str : Chaîne recevant la saisie (et contenant éventuellement une valeur initiale)
// max_size : Nombre de caractères logeant dans la zone de saisie
// input_type : 0=String, 1=Unsigned int, 2=Filename 3=Signed Double
// decimal_places: Number of decimal places for a double
// Sortie:
// 0: Sortie par annulation (Esc.) / 1: sortie par acceptation (Return)
{
char initial_string[256];
char display_string[256];
byte position;
byte size;
word input_key=0;
byte is_authorized;
word window_x=Window_pos_X;
word window_y=Window_pos_Y;
// Virtual keyboard
byte use_virtual_keyboard=0;
static byte caps_lock=0;
word keymapping[] =
{
SDLK_CLEAR,SDLK_BACKSPACE,SDLK_RETURN,KEY_ESC,
'0','1','2','3','4','5','6','7','8','9','.',',',
'Q','W','E','R','T','Y','U','I','O','P',
'A','S','D','F','G','H','J','K','L',
SDLK_CAPSLOCK,'Z','X','C','V','B','N','M',' ',
'-','+','*','/','|','\\',
'(',')','{','}','[',']',
'_','=','<','>','%','@',
':',';','`','\'','"','~',
'!','?','^','&','#','$'
};
byte offset=0; // index du premier caractère affiché
// Si on a commencé à editer par un clic-droit, on vide la chaine.
if (Mouse_K==RIGHT_SIDE)
str[0]='\0';
else if (input_type==INPUT_TYPE_INTEGER && str[0]!='\0')
snprintf(str,10,"%d",atoi(str)); // On tasse la chaine à gauche
else if (input_type==INPUT_TYPE_DECIMAL)
{
// Nothing. The caller should have used Sprint_double, with min_positions
// at zero, so there's no spaces on the left and no useless 0s on the right.
}
else if (input_type==INPUT_TYPE_HEXA)
{
// Nothing. The caller should have initialized a valid hexa number.
}
// Virtual keyboards
if (input_type == INPUT_TYPE_STRING || input_type == INPUT_TYPE_FILENAME )
{
// Full keyboard
const int keyboard_width=320;
const int keyboard_height=87;
int h_pos;
int x,y;
h_pos= Window_pos_X+(keyboard_width-Window_width)/-2;
if (h_pos<0)
h_pos=0;
else if (h_pos+keyboard_width>Screen_width)
h_pos=Screen_width-keyboard_width;
use_virtual_keyboard=1;
Hide_cursor();
Open_popup(h_pos,Window_pos_Y+(y_pos+9)*Menu_factor_Y,keyboard_width,keyboard_height);
Window_rectangle(0,0,Window_width, Window_height-2, MC_Light);
Window_rectangle(2,Window_height-3,Window_width, 2, MC_Black);
// The order is important, see the array
Window_set_normal_button( 7,67,43,15,"Clr", 0,1,KEY_NONE);
Window_set_normal_button( 51,67,43,15,"Del", 0,1,KEY_NONE);
Window_set_normal_button( 95,67,43,15,"OK", 0,1,KEY_NONE);
Window_set_normal_button(139,67,43,15,"Esc", 0,1,KEY_NONE);
Window_display_frame_in(5,65,179,19);
Window_set_normal_button(193,63,17,19,"0", 0,1,KEY_NONE);
Window_set_normal_button(193,43,17,19,"1", 0,1,KEY_NONE);
Window_set_normal_button(211,43,17,19,"2", 0,1,KEY_NONE);
Window_set_normal_button(229,43,17,19,"3", 0,1,KEY_NONE);
Window_set_normal_button(193,23,17,19,"4", 0,1,KEY_NONE);
Window_set_normal_button(211,23,17,19,"5", 0,1,KEY_NONE);
Window_set_normal_button(229,23,17,19,"6", 0,1,KEY_NONE);
Window_set_normal_button(193, 3,17,19,"7", 0,1,KEY_NONE);
Window_set_normal_button(211, 3,17,19,"8", 0,1,KEY_NONE);
Window_set_normal_button(229, 3,17,19,"9", 0,1,KEY_NONE);
Window_set_normal_button(211,63,17,19,".", 0,1,KEY_NONE);
Window_set_normal_button(229,63,17,19,",", 0,1,KEY_NONE);
Window_set_normal_button( 3, 3,18,19,"Q", 0,1,KEY_NONE);
Window_set_normal_button( 22, 3,18,19,"W", 0,1,KEY_NONE);
Window_set_normal_button( 41, 3,18,19,"E", 0,1,KEY_NONE);
Window_set_normal_button( 60, 3,18,19,"R", 0,1,KEY_NONE);
Window_set_normal_button( 79, 3,18,19,"T", 0,1,KEY_NONE);
Window_set_normal_button( 98, 3,18,19,"Y", 0,1,KEY_NONE);
Window_set_normal_button(117, 3,18,19,"U", 0,1,KEY_NONE);
Window_set_normal_button(136, 3,18,19,"I", 0,1,KEY_NONE);
Window_set_normal_button(155, 3,18,19,"O", 0,1,KEY_NONE);
Window_set_normal_button(174, 3,18,19,"P", 0,1,KEY_NONE);
Window_set_normal_button( 12,23,18,19,"A", 0,1,KEY_NONE);
Window_set_normal_button( 31,23,18,19,"S", 0,1,KEY_NONE);
Window_set_normal_button( 50,23,18,19,"D", 0,1,KEY_NONE);
Window_set_normal_button( 69,23,18,19,"F", 0,1,KEY_NONE);
Window_set_normal_button( 88,23,18,19,"G", 0,1,KEY_NONE);
Window_set_normal_button(107,23,18,19,"H", 0,1,KEY_NONE);
Window_set_normal_button(126,23,18,19,"J", 0,1,KEY_NONE);
Window_set_normal_button(145,23,18,19,"K", 0,1,KEY_NONE);
Window_set_normal_button(164,23,18,19,"L", 0,1,KEY_NONE);
Window_set_normal_button( 3,43,18,19,caps_lock?"\036":"\037", 0,1,KEY_NONE);
Window_set_normal_button( 22,43,18,19,"Z", 0,1,KEY_NONE);
Window_set_normal_button( 41,43,18,19,"X", 0,1,KEY_NONE);
Window_set_normal_button( 60,43,18,19,"C", 0,1,KEY_NONE);
Window_set_normal_button( 79,43,18,19,"V", 0,1,KEY_NONE);
Window_set_normal_button( 98,43,18,19,"B", 0,1,KEY_NONE);
Window_set_normal_button(117,43,18,19,"N", 0,1,KEY_NONE);
Window_set_normal_button(136,43,18,19,"M", 0,1,KEY_NONE);
Window_set_normal_button(155,43,18,19," ", 0,1,KEY_NONE);
for (y=0; y<5; y++)
{
for (x=0; x<6; x++)
{
char label[2]=" ";
label[0]=keymapping[x+y*6+44];
Window_set_normal_button(247+x*12, 3+y*16,11,15,label, 0,1,KEY_NONE);
}
}
Update_window_area(0,0,Window_width, Window_height);
Display_cursor();
}
Keyboard_click_allowed = 0;
Hide_cursor();
// Effacement de la chaîne
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
Update_rect(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3));
// Mise à jour des variables se rapportant à la chaîne en fonction de la chaîne initiale
strcpy(initial_string,str);
size=strlen(str);
position=(size<max_size)? size:size-1;
if (position-offset>=visible_size)
offset=position-visible_size+1;
// Formatage d'une partie de la chaine (si trop longue pour tenir)
strncpy(display_string, str + offset, visible_size);
display_string[visible_size]='\0';
if (offset>0)
display_string[0]=LEFT_TRIANGLE_CHARACTER;
if (visible_size + offset + 1 < size )
display_string[visible_size-1]=RIGHT_TRIANGLE_CHARACTER;
Display_whole_string(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),display_string,position - offset);
Update_rect(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3));
Flush_update();
if (Mouse_K)
{
Display_cursor();
Wait_end_of_click();
Hide_cursor();
}
while ((input_key!=SDLK_RETURN) && (input_key!=KEY_ESC))
{
Display_cursor();
if (use_virtual_keyboard)
{
int clicked_button;
clicked_button=Window_clicked_button();
input_key=Key_ANSI;
if (clicked_button==-1)
input_key=SDLK_RETURN;
else if (clicked_button>0)
{
input_key=keymapping[clicked_button-1];
if (input_key==SDLK_CAPSLOCK)
{
// toggle uppercase
caps_lock=!caps_lock;
Hide_cursor();
Print_in_window(8, 49,caps_lock?"\036":"\037", MC_Black,MC_Light);
Display_cursor();
}
else if (input_key>='A' && input_key<='Z' && !caps_lock)
{
input_key+='a'-'A';
}
}
}
else
{
do
{
Get_input(20);
input_key=Key_ANSI;
if (Mouse_K)
input_key=SDLK_RETURN;
} while(input_key==0);
}
Hide_cursor();
switch (input_key)
{
case SDLK_DELETE : // Suppr.
if (position<size)
{
Remove_character(str,position);
size--;
// Effacement de la chaîne
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
goto affichage;
}
break;
case SDLK_LEFT : // Gauche
if (position>0)
{
// Effacement de la chaîne
if (position==size)
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
position--;
if (offset > 0 && (position == 0 || position < (offset + 1)))
offset--;
goto affichage;
}
break;
case SDLK_RIGHT : // Droite
if ((position<size) && (position<max_size-1))
{
position++;
//if (position > visible_size + offset - 2)
//if (offset + visible_size < max_size && (position == size || (position > visible_size + offset - 2)))
if (display_string[position-offset]==RIGHT_TRIANGLE_CHARACTER || position-offset>=visible_size)
offset++;
goto affichage;
}
break;
case SDLK_HOME : // Home
if (position)
{
// Effacement de la chaîne
if (position==size)
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
position = 0;
offset = 0;
goto affichage;
}
break;
case SDLK_END : // End
if ((position<size) && (position<max_size-1))
{
position=(size<max_size)?size:size-1;
if (position-offset>=visible_size)
offset=position-visible_size+1;
goto affichage;
}
break;
case SDLK_BACKSPACE : // Backspace : combinaison de gauche + suppr
if (position)
{
position--;
if (offset > 0 && (position == 0 || position < (offset + 1)))
offset--;
Remove_character(str,position);
size--;
// Effacement de la chaîne
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
goto affichage;
}
break;
case SDLK_CLEAR : // Clear
str[0]='\0';
position=offset=0;
// Effacement de la chaîne
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
goto affichage;
case SDLK_RETURN :
break;
case KEY_ESC :
// On restaure la chaine initiale
strcpy(str,initial_string);
size=strlen(str);
break;
default :
if (size<max_size)
{
// On va regarder si l'utilisateur le droit de se servir de cette touche
is_authorized=0; // On commence par supposer qu'elle est interdite
switch(input_type)
{
case INPUT_TYPE_STRING :
if (input_key>=' ' && input_key<= 255)
is_authorized=1;
break;
case INPUT_TYPE_INTEGER :
if ( (input_key>='0') && (input_key<='9') )
is_authorized=1;
break;
case INPUT_TYPE_DECIMAL:
if ( (input_key>='0') && (input_key<='9') )
is_authorized=1;
else if (input_key=='-' && position==0 && str[0]!='-')
is_authorized=1;
else if (input_key=='.')
is_authorized=1;
break;
case INPUT_TYPE_FILENAME:
// On regarde si la touche est autorisée
if ( Valid_character(input_key))
is_authorized=1;
case INPUT_TYPE_HEXA:
if ( (input_key>='0') && (input_key<='9') )
is_authorized=1;
else if ( (input_key>='A') && (input_key<='F') )
is_authorized=1;
else if ( (input_key>='a') && (input_key<='f') )
is_authorized=1;
break;
} // End du "switch(input_type)"
// Si la touche était autorisée...
if (is_authorized)
{
// ... alors on l'insère ...
Insert_character(str,input_key,position/*,size*/);
// ce qui augmente la taille de la chaine
size++;
// et qui risque de déplacer le curseur vers la droite
if (size<max_size)
{
position++;
if (display_string[position-offset]==RIGHT_TRIANGLE_CHARACTER || position-offset>=visible_size)
offset++;
}
// Enfin, on raffiche la chaine
goto affichage;
} // End du test d'autorisation de touche
} // End du test de place libre
break;
affichage:
size=strlen(str);
// Formatage d'une partie de la chaine (si trop longue pour tenir)
strncpy(display_string, str + offset, visible_size);
display_string[visible_size]='\0';
if (offset>0)
display_string[0]=LEFT_TRIANGLE_CHARACTER;
if (visible_size + offset + 0 < size )
display_string[visible_size-1]=RIGHT_TRIANGLE_CHARACTER;
Display_whole_string(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),display_string,position - offset);
Update_rect(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3));
} // End du "switch(input_key)"
Flush_update();
} // End du "while"
Keyboard_click_allowed = 1;
if (use_virtual_keyboard)
{
byte old_mouse_k = Mouse_K;
Close_popup();
Mouse_K=old_mouse_k;
Input_sticky_control=0;
}
// Effacement de la chaîne
Block(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3),BACKGROUND_COLOR);
// On raffiche la chaine correctement
if (input_type==INPUT_TYPE_INTEGER)
{
if (str[0]=='\0')
{
strcpy(str,"0");
size=1;
}
Print_in_window(x_pos+((max_size-size)<<3),y_pos,str,TEXT_COLOR,BACKGROUND_COLOR);
}
else if (input_type==INPUT_TYPE_DECIMAL)
{
double value;
// Discard extra digits
value = Fround(atof(str), decimal_places);
Sprint_double(str,value,decimal_places,visible_size);
// Recompute updated size
size = strlen(str);
if (size<=visible_size)
Print_in_window(x_pos+((visible_size-size)<<3),y_pos,str,TEXT_COLOR,BACKGROUND_COLOR);
else
Print_in_window_limited(x_pos,y_pos,str,visible_size,TEXT_COLOR,BACKGROUND_COLOR);
}
else
{
Print_in_window_limited(x_pos,y_pos,str,visible_size,TEXT_COLOR,BACKGROUND_COLOR);
}
Update_rect(window_x+(x_pos*Menu_factor_X),window_y+(y_pos*Menu_factor_Y),
visible_size*(Menu_factor_X<<3),(Menu_factor_Y<<3));
return (input_key==SDLK_RETURN);
}
void Sprint_double(char *str, double value, byte decimal_places, byte min_positions)
{
int i;
int length;
sprintf(str,"%.*f",decimal_places, value);
length=strlen(str);
for (i=0; i<length; i++)
{
if (str[i]=='.')
{
// Remove extraneous zeroes
char * decimals = str+i+1;
int j;
for (j=strlen(decimals)-1; j >= 0 && decimals[j]=='0'; j--)
{
decimals[j] = '\0';
}
// If all decimals were removed, remove the dot too
if (str[i+1]=='\0')
str[i]='\0';
// Update string length
length=strlen(str);
// Ends the parent loop
break;
}
}
// Now try add spaces at beginning
if (length<min_positions)
{
int offset = min_positions - length;
// Move the string to the right
for (i=0; i<=length; i++)
{
str[length+offset-i] = str[length-i];
}
// Replace the N first characters by spaces
for (i=0; i<offset; i++)
{
str[i] = ' ';
}
}
}