miércoles, 6 de marzo de 2013

PHP Y LOS TEXTOS EN ESPAÑOL


El lenguaje PHP no gestiona bien los caracteres con tilde que usamos en español (castellano).
Me refiero a los caracteres á é í ó ú Á É Í Ó Ú. Y también por extensión a ñ Ñ y otros similares.


Por ejemplo, la funcion stripos($pajar,$aguja) debería buscar la aguja en el pajar ignorando mayúsculas y minúsculas. Y efectivamente lo hace, pero con los caracteres ordinarios del idioma inglés, que no incluyen los caracteres españoles.

Así que la línea
$pos=stripos($pajar,$aguja);
no funciona cuando el pajar y la aguja contienen caracteres españoles.

La primera idea para adaptar el código al español podría ser
$pos=stripos(strtolower($pajar),strtolower($aguja));
pero esto sigue fallando, porque la funcion strtolower() no convierte a minúsculas los caracteres españoles. Tampoco la función strtoupper() convierte a mayúsculas el texto en español.

Hay mucha documentación de PHP que remite al uso de una declaración del idioma en uso para estas funciones. Dice que se incluya una línea con el contenido siguiente:
setlocale(LC_ALL,'es_ES');
Bien, ésto sencillamente no funciona: ver http://www.rojasdelgado.com/kkk/setlocale

así que finalmente la solución pasa por currárselo uno mismo:

function SINTILDES($texto)
{
$r=$texto;
$listatildes = array(
      'a' => array('á','à','â','ä'),
      'e' => array('é','è','ê','ë'),
      'i' => array('í','ì','î','ï'),
      'o' => array('ó','ò','ô','ö'),
      'u' => array('ú','ù','û','ü'),
 'A' => array('Á','À','Â','Ä'),
 'E' => array('É','È','Ê','Ë'),
 'I' => array('Í','Ì','Î','Ï'),
 'O' => array('Ó','Ò','Ô','Ö'),
 'U' => array('Ú','Ù','Û','Ü')
);
foreach($listatildes as $sintilde=>$contilde)
{
    $r = str_replace($contilde,$sintilde, $r);
}
return $r;
}

function MAYUSCULAS($texto)
{
$r=$texto;
$listacambios = array(
 'Á' => 'á',
 'É' => 'é',
 'Í' => 'í',
 'Ó' => 'ó',
 'Ú' => 'ú',
 'Ñ' => 'ñ'
);
foreach($listacambios as $mayusculas=>$minusculas)
{
    $r = str_replace($minusculas,$mayusculas, $r);
}
return strtoupper($r);
}

function MINUSCULAS($texto)
{
$r=$texto;
$listacambios = array(
 'Á' => 'á',
 'É' => 'é',
 'Í' => 'í',
 'Ó' => 'ó',
 'Ú' => 'ú',
 'Ñ' => 'ñ'
);
foreach($listacambios as $mayusculas=>$minusculas)
{
    $r = str_replace($mayusculas,$minusculas, $r);
}
return strtolower($r);
}

miércoles, 20 de febrero de 2013

CSS - EVITAR QUE UNA DIVISION QUEDE CORTADA AL SALTAR DE PAGINA IMPRESA

No es habitual que se imprima una página web, al menos en papel.
Pero sí que es habitual que se considere importante la versión impresa de una página web, si otra cosa no, porque es mejor prevenir que curar, y la optimización de la versión impresa
¡  AHORRA MEDIO AMBIENTE  !
 
Es apreciable y deseable que se ahorren páginas de impresión (se ahorra papel), pero a veces es mejor considerar el esfuerzo de lectura posterior. Por ejemplo, si una división, en general un conjunto de párrafos con un título de apartado, se imprimiera a caballo entre dos páginas, este esfuerzo de lectura se verá incrementado. En ese caso ¿cómo se puede evitar que el contenido entre <div> y </div> se quede cortado?
 
CSS tiene la solución: basta con incluir estas lineas en el código css de tu página: 
 
@media print
{
     div
     {
        page-break-inside: avoid;
     }
}

domingo, 17 de febrero de 2013

EL REGISTRO DE WINDOWS - ELIMINAR ENTRADAS DESHABILITADAS CON MSCONFIG

Hacer cambios en el Registro de Windows no es una tarea fácil. No en vano en el Registro se guarda toda (o casi toda) la configuración del sistema operativo y del resto de los programas.
Para acceder al Registro basta con ejecutar REGEDIT.EXE, pero insisto, no es recomendable para usuarios sin experiencia. Léete primero algun libro. Lo agradecerás. De lo contrario puede que dejes tu sistema en un estado no utilizable.

La mayoría de los cambios que se requiere hacer en el Registro son del tipo
  • deshabilitar algún programa al arrancar windows
  • deshabilitar algún servicio al arrancar windows
  • otros...
Estos cambios pueden hacerse más cómodamente desde el Configurador de Microsoft. Para acceder a esta configuración basta con ejecutar MSCONFIG.EXE, aunque debo decir que esta tarea tampoco es recomendable para usuarios sin experiencia ni formación previa. Soy muy pesado pero léete primero algun libro...
Aquí puedes deshabilitar programas y servicios con solo quitar la marca de selección que les corresponda.

El problema aparece cuando, tras algún tiempo, se empieza a llenar esta lista con entradas deshabilitadas. El Configurador de Microsoft no tiene ninguna opcion para eliminar definitivamente las entradas deshabilitadas. Para eliminarlas tenemos que hacer uso de algun programa de utilidad que hay por ahí, o bien ejecutar regedit.exe y eliminar las entradas de las ramas del registro
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\MSConfig\startupfolder
ó
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\MSConfig\startupreg

 
En estas ramas están las entradas deshabilitadas con msconfig.exe
 
Tened cuidado al borrarlas, porque no se pueden recuperar. Os aconsejo hacer primero una copia del registro entero, o mejor de la rama HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\MSConfig entera, así si luego hay algún problema podréis recuperarla.
 


miércoles, 30 de enero de 2013

ANDROID: GUARDANDO PREFERENCIAS


La celebérrima aplicacion holamundo suele saludar al mundo anglosajón con un

Hello, world!

y al mundo hispano parlante con un

¡Hola, mundo!

Pero esa aplicación ya la genera automáticamente el entorno de desarrollo Eclipse cuando empezamos una nueva aplicación para Android.

Es un saludo muy cariñoso que, además, puede ser generalizado mediante una preferencia guardada en el directorio protegido de la aplicación, en la memoria del teléfono, de una manera muy sencilla. Tras dibujar la disposición gráfica que genera un TextView llamado rotulo en medio de la pantalla, se llama a una función  que se encargará de todo:


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
saludarsegunpreferencias();
}


1.- Esa función, saludarsegunpreferencias(), obtiene una instancia al objeto PreferenciasCompartidas mediante la llamada:

SharedPreferences pref=PreferenceManager.getDefaultSharedPreferences(this);

2.- Usando ese objeto se puede obtener una cadena de caracteres llamando a la función getString(nombreCadena, valorPorDefecto), así:

String objetoasaludar=pref.getString("destinodelsaludo", "mundo");

3.- Así ya podemos saludar según las preferencias almacenadas o, en su defecto, usar el valorPorDefecto:


TextView rotulo=(TextView) findViewById(R.id.rotulo);
rotulo.setText("¡Hola, "+objetoasaludar+"!");


4.- Al pulsar en el menú Preferencias se debe lanzar la ejecución de la actividad que guardará las preferencias.


@Override
    public boolean onOptionsItemSelected(MenuItem item)
{
        switch (item.getItemId())
        {
        case R.id.menu_settings:
            Intent intento = new Intent(this, PreferenciasActivity.class);
                startActivityForResult(intento,0);
                break;        }
        return true;
    }

5.- La actividad que guarda las preferencias queda tan sencilla como:

public class PreferenciasActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        addPreferencesFromResource(R.xml.preferencias);
    }
}

que requiere un fichero llamado preferencias.xml en la ruta relativa del proyecto /res/xml/preferencias.xml
Este archivo contiene la especificación gráfica de las preferencias:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <EditTextPreference android:dialogTitle="¿A QUIEN SALUDO?" android:title="¿A QUIEN SALUDO?" android:summary="Escribe a quén tiene que ir dirigido el saludo" android:key="destinodelsaludo"/>

</PreferenceScreen>

y genera ese aspecto visual común a todas las típicas pantallas de configuración de las aplicaciones de Android



OPTIMIZACIÓN NÚMERO 1:

Lo que ocurre es que, tal y como está escrito este código, al aceptar y pulsar el botón Atrás, volvemos a la actividad principal, donde sigue mostrándose el saludo antiguo. El saludo nuevo solo se muestra si cerramos y volvemos a abrir la aplicación.
Para que se repinte el saludo con las preferencias de configuración actuales, hay que añadir el siguiente código a la clase principal:


    @Override
    protected void onResume()
    {
        super.onResume();
        saludarsegunpreferencias();
    }

OPTIMIZACIÓN NÚMERO 2: (...y la más importante)

Como hemos llegado a la "aparente" ejecución correcta a base de sucesivas mejoras (bueno, en este caso, una mejora) hemos cometido el error de ejecutar dos veces el código saludarsegunpreferencias() al arrancar por primera vez la aplicación:

  1. La primera vez llamándolo desde el método onCreate
  2. La segunda vez llamándolo desde el método onResume
Así que el código siguiente es erróneo:



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
                saludarsegunpreferencias();
}

@Override
    protected void onResume()
    {
        super.onResume();
        saludarsegunpreferencias();
    }




y debería anularse la llamada en el método onCreate, quedando correctamente así:


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
    protected void onResume()
    {
        super.onResume();
        saludarsegunpreferencias();
    }



viernes, 25 de enero de 2013

ANDROID: ARCHIVO_PARA_GUARDAR_ALGO


Lo confieso. Soy un novato en programar android. Me paso las horas viendo códigos escritos por otras personas que saben más que yo. Veo cosas escritas como

File miarchivo=new File(Environment.getExternalStorageDirectory()+"/micarpeta/archivo.txt");
guardartexto("hola que tal", miarchivo);

que seguro que produce un error si el móvil donde se ejecuta no tiene tarjeta de expansión de memoria.
También dará error si el móvil tiene tarjeta pero no está montada, si está montada en modo solo lectura, etc... En estos casos sería lógico usar la memoria del teléfono en lugar de la memoria de la tarjeta de expansión.

Pero, en mi opinión, el problema no es que dé errores de ejecución, el problema es que los que estamos aprendiendo android nos acostumbramos a escribirlo así, tal cual, y luego ya va dando errores por ahí.

Además, el problema se agraba cuando quieres aprender a guardar fotos de la cámara programáticamente, y entonces veo cosas escritas como

File mifoto=new File(Environment.getExternalStorageDirectory()+"/DCIM/Camera/foto.jpg");
guardarfoto(data, mifoto);

¡Qué maravilla de códigos fuente! ¡Qué didáctica! ¡Qué buen modelo de aprendizaje!
Esto produce error cuando el móvil no tiene tarjeta de expansión de memoria, por supuesto, y además cuando el modelo de móvil no guarda las fotos en /DCIM/Camera, por ej. el HTC Desire las guarda en /DCIM/100MEDIA, ya que la carpeta "Camera" no estará creada.

Total, que tienes la captura en byte[] data y no hay manera de conseguir tener un archivo mifoto que funcione en todos los teléfonos y en todas las configuraciones. 

¿acaso a nadie de Google se le ha ocurrido que lo único que necesitamos los novatos aprendices de android es un simple archivoParaGuardarAlgo? Para usarlo así:

File miarchivo=new archivoParaGuardarAlgo("/micarpeta/", "archivo.txt");
guardartexto("hola que tal", miarchivo);

File mifoto=new archivoParaGuardarAlgo("/", "foto.jpg");
guardarfoto(data, mifoto);

Mientras no aparezca esta class mágica, ahí van mis intentos:

     public static File ALMACEN()
    {
        if(Environment.getExternalStorageState()==Environment.MEDIA_MOUNTED)
        {

            //Si hay tarjeta de memoria y está montada para lectura y escritura
            return Environment.getExternalStorageDirectory();
        }
        else
        {
            //Si no hay tarjeta de memoria, devuelve la zona de almacenamiento del telefono
            return Environment.getDataDirectory();
        }
    }


     public static File ArchivoParaGuardarAlgo(String rutarelativa, String nombrearchivo)
    {
        File directorio=new File(ALMACEN()+rutarelativa);
        if(directorio.exists() && directorio.isDirectory())
        {
            File r=new File(ALMACEN()+rutarelativa+nombrearchivo);
            return r;
        }
        else
        {
            boolean rutacreadaok=directorio.mkdirs(); //crea el directorio y todos los subdirectorios que falten
            if(rutacreadaok)
            {
                File r=new File(ALMACEN()+rutarelativa+nombrearchivo);
                return r;
            }
            else
            {
                return null;
            }
        }
    }

Otro día le pongo los try/catch y lo vuelvo a publicar.