Archivos de la categoría accesibilidad

¿Puedes vivir sin tu ratón?

Inténtalo. Desconecta el ratón, o apártalo de tu mano.
Ahora, continúa con lo que estabas haciendo: abre el correo, responde un mail, entra a tu web favorita y examina tus finanzas en el banco. ¡Eh! ¡Que te he visto mirándolo de reojo! ¡No! ¡Concéntrate! ¡no caigas en la tentación!

¿Qué tal? ¿Cuánto has durado antes de volver a conectar el ratón?

En muchas interfaces el ratón es prescindible. Existen muchísimos atajos de teclado que el usuario medio no conoce, aunque de conocerlos y utilizarlos, le agilizarían el día a día de una manera notable.

Sin embargo, otras muchas interfaces son dependientes del ratón: iconos que no se alcanzan con las flechas o el tabulador, menúes que se despliegan al pasar el ratón por encima… Cosas muy monas, pero que excluyen a las personas que como yo, no podemos usar el ratón.

Esta tarde, intentando seleccionar una fila completa en el editor de tablas de SQL server, me di cuenta de que no había (o yo no encontré), ningún atajo de teclado para realizar esa operación. Si hacéis click con el ratón en el encabezado de la fila, se selecciona… y yo tuve que meterme en el navegador de la capa de accesibilidad del sistema operativo, buscar la fila 201, entrar dentro del control y buscar el encabezado. Por suerte, el objeto de accesibilidad permitía ser pinchado , así que pude simular el click de ratón.
Una operación que vosotros hacéis en medio segundo, tardé en hacerla más de 30 segundos (y porque lo mío con el navegador de objetos de UIA es puro vicio)
El siguiente paso podría ser automatizarlo con un script para que mi lector de pantallas, al pulsar una tecla, hiciera ese trabajo por mí y me seleccionara la fila… ¡Fijaros la de tiempo que tendría que perder, cuando lo único que debería haber hecho el desarrollador era poner un dichoso ítem más en el menú contextual!

Y para no alargarme innecesariamente, solo pedirte, amigo desarrollador, que cuando desarrolles alguna interfaz, te pares un momento, la manejes con teclado, y si no eres capaz de llegar a todos los rinconcitos, sabrás que yo tampoco podré hacerlo.

¡Gracias por tenerlo en cuenta a partir de ahora! ¡Si no lo haces, te remorderá la conciencia! 😉

¡A descansar!

Entrada visitada 151 veces

Rotar imágenes según sus metadatos de orientación

¡Hola!

Hay herramientas que se te ocurren pensando en qué cosas podrían ser útiles; y otras que surgen cuando te topas con un problema y dices: ¡joder, seguro que hay alguna solución para esto!

El otro día, mi mujer, @amaterasu_n estaba escribiendo en su blog de mami the vikings mama un post, y quería poner algunas fotos. Al ser ciegos, el tema de las imágenes en los posts es algo complejo. Nunca sabemos si la foto ha quedado bien, o se corta, o se descentra… así que después de ponerlas en el post, preguntamos a una compañera de trabajo, y me dijo que había fotos que estaban giradas… Ya os podéis imaginar, la típica foto tomada en horizontal, y que necesitas girar para que no tengas que darle la vuelta a la pantalla.

Así que me dije: Los smartphones tienen acelerómetros y giroscopios para detectar el cambio de orientación y adaptar las interfaces a esos cambios. ¿No guardarán esta información en los metadatos exif de las fotos? Y sorprendentemente, ¡la respuesta es sí! Imagino que también habrá cámaras que lo hacen, pero no he podido comprobarlo.

Buscando un poco por internet, me encontré con ExifExtractor, una pequeña librería en c# que me permitía extraer los datos exif de las imágenes. Por otro lado, en este artículo sobre las etiquetas exif encontré los valores y significados de esta etiqueta…

¡Problema resuelto!

He desarrollado una pequeñísima aplicación en c# que dada una imagen, extrae la orientación, la rota o voltea (rotate / flip) en consecuencia e informa al usuario de la operación que ha realizado sobre ella.

Podéis descargar la herramienta FixPictureOrientation desde este enlace.

Solo tenéis que descomprimirlo en alguna carpeta, ejecutar el exe y pulsar el botón de abrir y corregir foto, buscar la foto, abrirla… ¡y ya está!

Nota: Para ejecutar el programa deberéis tener instalado el .NET Framework 4 (si usáis Windows 7 o superior ya viene por defecto, y si no, casi seguro que lo tenéis de todos modos 😉 Si no fuera así, podéis descargar el instalador del .NET Framework 4 desde este enlace

El código c# es muy sencillo (obviando toda la parte de la extracción de datos Exif, cuyo código podéis encontrar en el sitio de codeproject que os puse arriba:

using Goheer.EXIF;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace FixPictureOrientation
{
	public partial class FrmPrincipal : Form
	{
		public FrmPrincipal()
		{
			InitializeComponent();
		}

		private void BtnCorregir_Click(object sender, EventArgs e)
		{
			OpenFileDialog dlg = new OpenFileDialog()
			{
				Filter = "Archivos de imagen JPG (*.jpg)|*.jpg",
				Title = "Abrir foto",
				ShowReadOnly = false,
				ShowHelp = false,
				InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer),
				CheckFileExists = true
			};
			DialogResult res = dlg.ShowDialog();
			if (res == DialogResult.Cancel) return;
			var rutaImagen = dlg.FileName;
			// Rotamos la imagen de acuerdo a los datos de exif
			Bitmap bmp;
			try
			{
				bmp = new Bitmap(rutaImagen);
			}
			catch (Exception)
			{
				MessageBox.Show("¡Pero qué me has dado! Esto no parece una imagen válida!", "¡Esto no se come!", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}
			try
			{
				var exif = new EXIFextractor(ref bmp, Environment.NewLine);
				if (exif["Orientation"] != null)
				{
					int iOrientacion;
					if (!Int32.TryParse(exif["Orientation"].ToString(), out iOrientacion))
					{
						MessageBox.Show("Los datos de orientación parecen no ser correctos. ¡Disculpa!", "Uups!", MessageBoxButtons.OK, MessageBoxIcon.Error);
						return;
					}
					string rotado;
					RotateFlipType tipoRotacion = GetDesiredRotation(iOrientacion, out rotado);
					if (tipoRotacion == RotateFlipType.RotateNoneFlipNone)
					{
						MessageBox.Show("¡Esta foto no necesita ninguna rotación! ¡Puedes usarla sin problemas!", "¡Esta ya está!", MessageBoxButtons.OK, MessageBoxIcon.Information);
						return;
					}

					bmp.RotateFlip(tipoRotacion);
					var equivalenciasExif = new Goheer.EXIF.translation();
					int idOrientacion = equivalenciasExif.Keys.OfType<int>().FirstOrDefault(k => equivalenciasExif[k].ToString() == "Orientation");
					// Reajustamos la propiedad de orientación para que coincida con la orientación actual.
					bmp.RemovePropertyItem(idOrientacion);
					exif.setTag(idOrientacion, "1");
					bmp.Save(rutaImagen, ImageFormat.Jpeg);
					MessageBox.Show($"¡Perfecto! La imagen se ha rotado {rotado}.", "¡Listo!", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
				else
				{
					MessageBox.Show("Lo siento, no hay datos de orientación en los metadatos de la foto... ¡No puedo detectar la posición de la fotografía!", "Me cachis!", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show($"Se produjo un error inesperado: {ex.Message}.", "Uuups.", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
			finally
			{
				if (bmp != null) bmp.Dispose();
			}
		}
		
		private void BtnSalir_Click(object sender, EventArgs e)
		{
			this.Close();
		}

		/// <summary>
		///  Devuelve la rotación que habría que realizar para que la foto rote a la posición adecuada en función del dato de orientación EXIF
		/// </summary>
		/// <param name="orientation"> Valor numérico de orientación que indica la orientación actual de la foto</param>
		/// <param name="rotationExplanation">Explicación de qué rotación se va a realizar</param>
		/// <returns>Uno de los valores de la enumeración RotateFlipType indicando la rotación que se debe aplicar</returns>
		private static RotateFlipType GetDesiredRotation(int orientation, out string rotationExplanation)
		{
			RotateFlipType rotationType;
			switch (orientation)
			{
				case 1: // fila 0 = arriba, columna 0 = lado izquierdo
					rotationExplanation = "0 grados";
					rotationType = RotateFlipType.RotateNoneFlipNone;
					break;
				case 2: // fila 0 = arriba, columna 0 = lado derecho
					rotationExplanation = "0 grados y volteo horizontal";
					rotationType = RotateFlipType.RotateNoneFlipX;
					break;
				case 3: // fila 0 = abajo, columna 0 = lado derecho
					rotationExplanation = "180 grados en sentido horario";
					rotationType = RotateFlipType.Rotate180FlipNone;
					break;
				case 4: // fila 0 = abajo, columna 0 = lado izquierdo
					rotationExplanation = "180 grados en sentido horario y volteo horizontal";
					rotationType = RotateFlipType.Rotate180FlipX;
					break;
				case 5: // fila 0 = lado izquierdo, columna 0 = arriba
					rotationExplanation = "90 grados en sentido horario y volteo horizontal";
					rotationType = RotateFlipType.Rotate90FlipX;
					break;
				case 6: // fila 0 = lado derecho, columna 0 = arriba
					rotationExplanation = "90 grados en sentido horario";
					rotationType = RotateFlipType.Rotate90FlipNone;
					break;
				case 7: // fila 0 = lado derecho, columna 0 = abajo
					rotationExplanation = "270 grados en sentido horario y volteo horizontal";
					rotationType = RotateFlipType.Rotate270FlipX;
					break;
				case 8: // fila 0 = lado izquierdo, columna 0 = abajo
					rotationExplanation = "270 grados en sentido horario";
					rotationType = RotateFlipType.Rotate270FlipNone;
					break;
				default:
					rotationExplanation = "0 grados";
					rotationType = RotateFlipType.RotateNoneFlipNone;
					break;
			}
			return rotationType;
		}

	}
}

¡Espero que os sea útil!

¡Un saludo!

Entrada visitada 581 veces

Reproductor accesible html5

¡Hola!

Hace algunos meses, Salvi Melguizo me pidió que le hiciera un reproductor accesible para su blog, así que como me gusta cacharrear y ganarme un sobresueldo cuando se puede :P, a ello me puse.
Se trata de un script jQuery que, por cada elemento audio de HTML5 de una página, genera un conjunto de controles encima del elemento, que permiten manejar de forma totalmente accesible dicho reproductor.

El reproductor está probado con Internet Explorer 9, 10 y 11, Firefox 42 y 43, Chrome 47 y Safari con iOS 8 y 9. Funciona correctamente con JAWS, NVDA y VoiceOver para MAC e iOS… He de confesar que con window Eyes no lo he probado 😉

Podéis ver
una demo del reproductor accesible aquí.
El reproductor está en español, aunque por defecto el ejemplo está con la traducción al inglés macarrónico (lo he traducido yo), así que si alguien se anima a corregir la traducción, yo encantado.
Podéis colaborar con el proyecto en su repositorio de github, o si solo queréis probarlo, podéis descargar la última versión del proyecto en formato zip.

Sugerencias, críticas constructivas, mejoras… ¡son bienvenidas!

¡Espero que os parezca interesante!

¡Un saludo!

Entrada visitada 477 veces

ValidationSummary accesible en MVC 5

¡Hola!

Hoy os vengo con un truquillo que, en caso de estar desarrollando webs accesibles en MVC, os puede venir bien.

Una de las cosas que considero muy importantes a la hora de mostrar una lista de errores de cumplimentación de formularios, es encabezar la misma con una cabecera, de modo que el usuario de lector de pantallas, al navegar por la lista de encabezados, dé con el resumen de los errores de formulario de forma fácil. Si obviamos esta cabecera, es posible que esa lista pase desapercibida, o al menos es lo que me dice la experiencia 🙂

En MVC 4, hasta donde recuerdo, no existía la sobrecarga de ValidationSummaryFor que os muestro a continuación, y que sí está disponible en MVC 5:

@Html.ValidationSummary("Revisa los siguientes errores", new { @class = "text-danger" }, "h3")

Esta extensión de HtmlHelper pinta una lista de errores, añadiéndole la clase text-danger (estoy usando bootstrap), y encabezando la lista con una etiqueta “h3” que encerrará el mensaje pasado en el primer parámetro.

¿La única pega de todo esto? Que el encabezado con el mensaje se ve siempre aunque no haya errores 🙁 .
Sin embargo, este método pinta una clase por defecto cuando el formulario es válido: “validation-summary-valid”; así que con un poco de magia CSS, podemos añadir lo siguiente a nuestra hoja de estilos:

/* para ocultar los encabezados de los validation summary si son válidos */
.validation-summary-valid {
	display: none;
}

¿Más fácil? ¡Imposible!

Y ahora ya, como truquillo de nota, podríamos hacer una pequeña mejora a la experiencia del usuario, añadiendo, mediante jquery, una función que ponga el foco en la lista de errores cuando esta esté visible. Así, un usuario de lector de pantallas, escuchará el encabezado con el mensaje que hayamos puesto, y será consciente al instante de que algo pasa con ese formulario.
Añadamos este script a nuestro sitio (recordad que necesitaréis jquery):

$(function () {
	var encabezado = $(".validation-summary-errors:last :header");
	if (encabezado.length) {
		encabezado.attr("tabindex", "-1");
		encabezado.focus();
	}
});

¡

Et Voilà!

¿Qué estamos haciendo aquí?

  1. Adjuntándonos al evento onDocumentReady, para que esto se ejecute cuando la página esté cargada.
  2. Buscando el último elemento con la clase validation-summary-error, y dentro de él, cualquier encabezado, sea del nivel que sea.
  3. Si lo encuentra, le pone tabIndex a -1 para poder focalizar en él de forma programática.
  4. Y por último, fuerza el foco a ese encabezado.

A mí personalmente me gusta crear un bundle de scripts de accesibilidad, y meter en él pequeños ficheros, cada uno de los cuales realiza una acción concreta. Así tengo la sensación de que mi código es más fácil de mantener.

¡Espero que os sirva!

¡A disfrutar picando! 😉

Entrada visitada 396 veces

Evitar interacciones no deseadas al ocultar elementos con animación en jQuery

¡Hola!

Esta mini entrada surge por un problema que estaba teniendo con unas animaciones en jQuery.

Resulta que estoy mostrando y ocultando una lista de enlaces usando fadeIn, fadeOut, slideDown y slideUp, dándole un tiempo de animación tanto en mostrar como en ocultar. ¡Estas cosas que a los que veis os molan tanto, que eso de que aparezcan y desaparezcan elementos de la pantalla así de golpe como que no os mola! 😉
Pues resulta que me di cuenta de algo obvio pero que no se me había ocurrido: Durante una animación de desaparición (por deslizamiento o por transparencia), el usuario de lector de pantallas puede seguir interactuando con los elementos, sin darse cuenta de que esos elementos están desapareciendo.
Y como a mí me la traen al fresco las animaciones y las desapariciones progresivas, se me ocurrió usar, durante las desapariciones, un atributo que oculta elementos para lectores de pantalla. Este atributo, dentro de las recomendaciones ARIA de la w3c, es “aria-hidden“, que ajustado a true, oculta la etiqueta en la que se aplique a lectores de pantalla.

Así que, podríamos usar el siguiente código para ocultar un ul durante 400 milisegundos, pero que desde el primer momento ya no se muestre a lectores de pantalla aunque tarde un rato en desaparecer:

lista.attr("aria-hidden", "true").slideUp(200, function () { $(this).removeAttr("aria-hidden"); });

¿Qué estamos haciendo?

  1. Ocultamos el elemento a lectores de pantalla.
  2. Lanzamos la animación de desaparición.
  3. Cuando se completa la animación, volvemos a mostrar el elemento a lectores de pantalla, pues como ya está oculto con display:none, no será visible para nadie. Vuelvo a quitar el aria-hidden, ya que si luego queremos volver a mostrar el elemento, los lectores no lo verán si esa propiedad se ha quedado a true :).

¡Mucho cuidado con aria-hidden y display:none! Si un elemento tiene display:none pero aria-hidden está a false explícitamente (no que no esté el atributo, sino puesto a false), el elemento será mostrado a lectores de pantalla pese al display:none. ¡Flipante!

¡Espero que este truquillo os pueda servir!

¡Buena semana!

Entrada visitada 345 veces

Tecla rápida para salir de escritorio remoto sin desconectarnos de él

¡Hola!

Hoy vengo con una entrada muy corta, pero que creo podrá servir a más de uno.

Cuando trabajamos con escritorio remoto, una vez estamos en la ventana y con pantalla completa, las combinaciones de teclas se ejecutan en el servidor)… Así que, ¿cómo podemos volver a nuestra máquina sin desconectarnos?
Con ratón sé que hay una forma que desconozco, pero para usuarios de lectores de pantalla que usamos el teclado… podemos usar la combinación de teclas control + alt + pause. Esto desactiva la pantalla completa, y podremos pulsar alt + tab para cambiar a la ventana anterior, y así volver a nuestra máquina.
Cuando queramos volver al escritorio remoto y a ejecutar comandos en él, deberemos situarnos otra vez en la ventana de dicho escritorio, y volver a pulsar control + alt + pause.

¡Espero que os sirva!

¡Saludos!

Entrada visitada 1700 veces

Los captchas y el concepto de discapacidad

  • ¿Qué es una persona con discapacidad? – le pregunté.
  • Pues… una persona que tenga una discapacidad, ¿no? es decir: un sordo, un ciego, un tetrapléjico.
  • ¿Y tú?
  • ¿Yo qué?
  • ¿Tú eres discapacitado?
  • No no, claro, yo no, yo no tengo ninguna discapacidad, gracias a dios.
  • ¿Y yo?
  • ¿Tú? ¿Esta pregunta tiene trampa?
  • No hombre, no.
  • Pues… pues claro, ostias, eres ciego.
  • Ajam…

    Imagina que aquí tienes un formulario. Pongamos que el formulario es largo, de cuarenta campos. Te has pasado más de cinco minutos rellenando el dichoso formulario, pero no te importa, porque en cuanto le des a enviar, te habrás registrado en una página maravillosa a la que tenías muchas ganas de acceder.

    Ahora imagína que, antes de enviar el formulario, tienes que resolver un captcha. Pero ¡vaya! no es un captcha en el que tienes que descifrar una imagen, sino que se trata de un captcha musical.

    Pulsa en el botón “Reproducir”, escucha las notas, y escríbelas en el cuadro de edición. El 1 es el do, el 2 el re, y así hasta el siete, que es el si. Así, si las notas son re, mi, fa, sol, la si, tendrás que pulsar en el teclado: 234567.


Pulsa las notas usando los botones, si lo prefieres:

Notas que has pulsado:


    Vista no, pero buen oído tengo, afortunadamente; y gracias a mis años de conservatorio, muy entrenado para detectar las notas musicales. Te puedo resolver este captcha con un 95% de aciertos.
    ¿Que no? En esta demo te lo demuestro:< ¿Y tú? ¿Eres capaz?... ¿no?... ¡vaya! ¡Y luego dicen que yo soy el discapacitado! 😉 Y ahora, pregúntate: ¿es justo que alguien ponga este captcha en una página web?
    ¿No? ¿Por qué?
    Pues evidentemente, porque no todas las personas tienen formación musical y buen oído. Así que tampoco es justo que me pongas un captcha con imagen, porque no puedo verla. Al menos tú puedes entrenar la oreja 😉

    Así que cuando tengas que desarrollar tu próximo sitio, piensa en cuántas personas con capacidades diferentes podrán entrar a tu web: personas que ven bien, personas que usan gafas, personas que no ven, personas que no escuchan, personas que no pueden usar el teclado o personas que no pueden usar el ratón, personas expertas en informática, personas menos expertas… ¡el abanico es amplísimo!

    ¿Que es imposible tener en cuenta a todo el mundo? ¡Claro! Pero hay que intentarlo, ¿no? las propias siglas de la www te lo están pidiendo: world wide web: la web a lo ancho del mundo… Y mira que hay personas distintas en el mundo!

    Entrada visitada 1874 veces