¿Cómo debe comportarse "Deshacer la escritura"?

12

Estoy implementando una aplicación Java que incluye una pila Deshacer / Rehacer. Me di cuenta de que algunas aplicaciones (como TextEdit en Mac OS X) le permiten elegir "Deshacer escritura" en el menú Editar después de escribir texto. También me gustaría implementar ese tipo de cosas en mi aplicación, pero me cuesta mucho encontrar pautas sobre cómo debería comportarse.

Con un poco de prueba y error, mi mejor conjetura sobre cómo se comporta el Deshacer escritura de TextEdit es:

  • Cuando el usuario escriba un nuevo carácter (o escriba la clave de eliminación), combínelo en el elemento de Deshacer escritura anterior si hay uno en la parte superior de la pila Deshacer, a menos que ocurra una de las siguientes situaciones
  • Siempre cree un nuevo elemento Deshacer escritura después de que el usuario continúe escribiendo después de al menos 15 segundos de inactividad
  • Siempre cree un nuevo elemento Deshacer escritura después de que el usuario esté escribiendo durante un período prolongado de tiempo y se cumpla alguna condición (no se pudo determinar si esto se basó en el tiempo o en el recuento de caracteres).
  • Siempre cree un nuevo elemento Deshacer escritura cuando se seleccione cualquier texto y luego se elimine o se sobrescriba (seleccionar texto, no realizar ningún cambio, luego regresar al punto de inserción original y continuar escribiendo no activa esto)

En la práctica, la estrategia de Apple parece funcionar (al menos funciona para mí cuando escribo), pero como se señaló en el último punto, realmente no he podido descifrar las reglas. Además, parece que otros programas siguen reglas diferentes, como Microsoft Word. Google no ha presentado una lista definida de reglas para ninguna implementación de Deshacer escritura y no he encontrado ninguna de las mejores prácticas sobre cómo debería comportarse. Entonces, ¿cómo debería comportarse? ¿O solo depende de los caprichos del programador?

EDITAR: Solo para aclarar, no estoy interesado en los detalles de implementación en este momento. Tengo especial curiosidad por saber si existe o no una referencia autorizada (por ejemplo, mejores prácticas o documento de interfaz de usuario) que describa esto o una descripción de cómo se implementa en múltiples productos.

Forja del Trueno
fuente
Mi sugerencia: comprima las modificaciones hasta el punto en que todavía sea posible reconstruir la secuencia exacta de teclas presionadas a partir de la información de deshacer, y nada más. Por ejemplo, esto significa que si el usuario escribe algo e inmediatamente usa la tecla de retroceso para eliminarlo, debe haber un punto de deshacer en el medio.
Ambroz Bizjak
Tal vez también podría agregar un nuevo "elemento Deshacer escritura" cada vez que el usuario cree una nueva línea y tal vez cada vez que se use la barra espaciadora inmediatamente después de ingresar los caracteres. OMI 15 segundos de tiempo de espera antes de que un nuevo "elemento Deshacer mecanografía" pueda ser un poco largo, pero solo soy yo. (Iría por alrededor de 5 segundos)
user82529
O tal vez la "secuencia exacta de teclas presionadas" debería relajarse al "estado del texto después de cada modificación". La idea es evitar que cualquier texto se pierda debido a la compresión.
Ambroz Bizjak
Me parece que TextEdit combina espacios y elimina con el último elemento Deshacer escritura, siempre que no se cumplan las otras condiciones. Entonces 124<delete>3, deshacer y rehacer resulta en 123. Supongo que la ventaja de esto es que da como resultado el estado final del texto del usuario, algo así como la sugerencia anterior.
Thunderforge
¿Ya has intentado buscar patentes? (Las reglas generalmente están codificadas en la capa de la biblioteca, no están expuestas al código del usuario).
Donal Fellows

Respuestas:

5

Si está buscando una fuente autorizada, creo que el mejor material relacionado con Mac se encontrará en el documento Undo Architecture de Apple.

Sin embargo, no creo que vaya a encontrar una lista de reglas sobre cuándo debe o no fusionar los eventos de deshacer. Lo que se siente bien para una aplicación no necesariamente tendrá sentido para otra. Por ejemplo, la combinación de teclas tiene sentido en un editor de texto porque el usuario probablemente verá escribir un párrafo como una sola acción y no como 539 acciones separadas, y también porque no desea que el usuario tenga que deshacer 539 veces solo para obtener hasta el punto en que estaban antes de que escribieran ese párrafo. ¿Pero qué pasa con las operaciones de movimiento en una forma en un programa de dibujo? ¿O ajustes secuenciales a un color de relleno? Podría presentar un buen caso para que estos se fusionen o no, dependiendo de la naturaleza de su programa.

Siempre cree un nuevo elemento Deshacer escritura después de que el usuario esté escribiendo durante un período prolongado de tiempo y se cumpla alguna condición (no se pudo determinar si esto se basó en el tiempo o en el recuento de caracteres).

Se basa en el autoguardado. Por suerte para ti, el código fuente de TextEdit está disponible y bien comentado. Creo que si lo miras, tendrás una mejor idea de lo que está sucediendo y por qué. Por ejemplo:

- (void)saveToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation completionHandler:(void (^)(NSError *error))handler {
    // Note that we do the breakUndoCoalescing call even during autosave, which 
    // means the user's undo of long typing will take them back to the last spot an 
    // autosave occured. This might seem confusing, and a more elaborate solution may 
    // be possible (cause an autosave without having to breakUndoCoalescing), but since 
    // this change is coming late in Leopard, we decided to go with the lower risk fix.
    [[self windowControllers] makeObjectsPerformSelector:@selector(breakUndoCoalescing)];
 ...

Sé que dijiste que aún no estás interesado en los detalles de implementación, pero si observas la forma en que Apple implementó TextEdit, puedes informar las decisiones que tomas para tu propia aplicación.

Caleb
fuente
1
Creo que declararé que esta es la mejor respuesta. Le agradezco que le dé al enlace a la implementación de Apple, así como un código para el ejemplo específico que proporcioné. Sin embargo, creo que tiene razón, generalmente se basa en las necesidades de la aplicación y nadie realmente ha hecho un esfuerzo para estandarizar cuáles son esas necesidades y cómo abordarlas.
Thunderforge
1

en keydown -> temporizador que representa su inicio inactivo

en keydown / timer-running -> reset timer

al presionar tecla / sin temporizador -> reajustar bloques de celdas para prepararse para un nuevo estado preservado a medida que cambia la posición

idle-timer se agota -> Establecer un nuevo estado de deshacer

No rastrearía las identidades de pulsación de teclas. Me dividiría en bloques de texto celulares (por recuento de caracteres) que le permiten rastrear la posición por desplazamientos desde las posiciones iniciales de la celda más cercana para que no tenga que guardar el estado completo de una novela de tolstoy cada vez que se agota un temporizador inactivo . El reajuste de esas compensaciones cuando las celdas antes de que otras celdas sean editadas es la parte difícil.

Erik Reppen
fuente