์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŒˆ ์ž๋ฐ”๋กœ ๋งŒ๋“œ๋Š” ๋ชจ๋‘๋ฅผ ์œ„ํ•œ ์„ธ์ƒ

๐ŸŒˆ ์ž๋ฐ”๋กœ ๋งŒ๋“œ๋Š” ๋ชจ๋‘๋ฅผ ์œ„ํ•œ ์„ธ์ƒ

์ ‘๊ทผ์„ฑ๊ณผ Accessibility API๋ฅผ ํ™œ์šฉํ•œ ํฌ์šฉ์  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ

์•ˆ๋…•, ์นœ๊ตฌ! ๐Ÿ‘‹ ์˜ค๋Š˜์€ ์ •๋ง ์ค‘์š”ํ•˜์ง€๋งŒ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฐ„๊ณผํ•˜๊ธฐ ์‰ฌ์šด ์ฃผ์ œ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊นŒ ํ•ด. ๋ฐ”๋กœ ์ ‘๊ทผ์„ฑ(Accessibility)์ด์•ผ. ํ˜น์‹œ "๋‚ด ์•ฑ์€ ๋ชจ๋“  ์‚ฌ๋žŒ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ?"๋ผ๋Š” ์งˆ๋ฌธ์„ ์Šค์Šค๋กœ์—๊ฒŒ ํ•ด๋ณธ ์  ์žˆ์–ด? ๐Ÿค”

์ „ ์„ธ๊ณ„ ์ธ๊ตฌ์˜ ์•ฝ 15%๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ๋กœ๋“  ์žฅ์• ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด. ์ด๊ฑด ๋‹จ์ˆœํžˆ ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ผ, ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“œ๋Š” ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์–ดํ•˜๋Š” ์ˆ˜์–ต ๋ช…์˜ ์ž ์žฌ์  ์‚ฌ์šฉ์ž๋ฅผ ์˜๋ฏธํ•˜์ง€. ์ž๋ฐ” ๊ฐœ๋ฐœ์ž๋กœ์„œ ์šฐ๋ฆฌ๋Š” ์ด๋“ค์„ ์œ„ํ•œ ํฌ์šฉ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ฑ…์ž„์ด ์žˆ์–ด. ๊ทธ๋ฆฌ๊ณ  ๋‹คํ–‰ํžˆ๋„ ์ž๋ฐ”๋Š” ์ด๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด! ๐ŸŽฏ

Inclusive Application ๋ชจ๋‘๊ฐ€ ํ•จ๊ป˜ํ•˜๋Š” ๋””์ง€ํ„ธ ์„ธ์ƒ

๐ŸŽฏ ์ ‘๊ทผ์„ฑ์ด ๋ญ๊ธธ๋ž˜ ์ด๋ ‡๊ฒŒ ์ค‘์š”ํ• ๊นŒ?

์ ‘๊ทผ์„ฑ(Accessibility, ์ค„์—ฌ์„œ a11y๋ผ๊ณ ๋„ ํ•ด)์€ ๋‹จ์ˆœํžˆ "์žฅ์• ์ธ์„ ์œ„ํ•œ ๊ธฐ๋Šฅ"์ด ์•„๋‹ˆ์•ผ. ์ด๊ฑด ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋™๋“ฑํ•˜๊ฒŒ ์ •๋ณด์— ์ ‘๊ทผํ•˜๊ณ  ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์„ค๊ณ„ ์ฒ ํ•™์ด์ง€. ๐ŸŒŸ

์ƒ๊ฐํ•ด๋ด. ์‹œ๊ฐ ์žฅ์• ๊ฐ€ ์žˆ๋Š” ์‚ฌ๋žŒ์ด ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์•ฑ์„ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•  ๋•Œ, ๋ฒ„ํŠผ์— ์ œ๋Œ€๋กœ ๋œ ๋ ˆ์ด๋ธ”์ด ์—†๋‹ค๋ฉด? ์ฒญ๊ฐ ์žฅ์• ๊ฐ€ ์žˆ๋Š” ์‚ฌ๋žŒ์ด ์˜ค๋””์˜ค ์•Œ๋ฆผ๋งŒ์œผ๋กœ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค๋ฉด? ์šด๋™ ์žฅ์• ๋กœ ๋งˆ์šฐ์Šค ์‚ฌ์šฉ์ด ์–ด๋ ค์šด ์‚ฌ๋žŒ์ด ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ๋Š” ๋ชจ๋“  ๊ธฐ๋Šฅ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค๋ฉด?

๐Ÿ’ก ์ ‘๊ทผ์„ฑ์˜ ํ•ต์‹ฌ ์›์น™ (POUR)

Perceivable (์ธ์ง€ ๊ฐ€๋Šฅ): ์ •๋ณด์™€ UI ์ปดํฌ๋„ŒํŠธ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด
Operable (์กฐ์ž‘ ๊ฐ€๋Šฅ): UI ์ปดํฌ๋„ŒํŠธ์™€ ๋‚ด๋น„๊ฒŒ์ด์…˜์€ ์กฐ์ž‘ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ด
Understandable (์ดํ•ด ๊ฐ€๋Šฅ): ์ •๋ณด์™€ UI ์กฐ์ž‘์€ ์ดํ•ด ๊ฐ€๋Šฅํ•ด์•ผ ํ•ด
Robust (๊ฒฌ๊ณ ํ•จ): ์ฝ˜ํ…์ธ ๋Š” ๋ณด์กฐ ๊ธฐ์ˆ ์„ ํฌํ•จํ•œ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ์—์„œ ํ•ด์„๋  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด

๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฑด, ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์„ค๊ณ„๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋‚˜์€ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•œ๋‹ค๋Š” ๊ฑฐ์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ช…ํ™•ํ•œ ๋ ˆ์ด๋ธ”๊ณผ ๊ตฌ์กฐํ™”๋œ ๋‚ด๋น„๊ฒŒ์ด์…˜์€ ์žฅ์• ๊ฐ€ ์—†๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ๋„ ๋” ์ง๊ด€์ ์ด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์ง€. ๐Ÿ“ฑ

โ˜• ์ž๋ฐ”์˜ ์ ‘๊ทผ์„ฑ ์ง€์›: Java Accessibility API

์ž๋ฐ”๋Š” JDK 1.2๋ถ€ํ„ฐ Java Accessibility API๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด. ์ด API๋Š” javax.accessibility ํŒจํ‚ค์ง€์— ํฌํ•จ๋˜์–ด ์žˆ๊ณ , ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ณด์กฐ ๊ธฐ์ˆ (Assistive Technology, AT)๊ณผ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋‹ค๋ฆฌ ์—ญํ• ์„ ํ•˜์ง€. ๐ŸŒ‰

๋ณด์กฐ ๊ธฐ์ˆ ์ด๋ผ๊ณ  ํ•˜๋ฉด ์Šคํฌ๋ฆฐ ๋ฆฌ๋”(JAWS, NVDA ๋“ฑ), ํ™”๋ฉด ํ™•๋Œ€๊ธฐ, ์Œ์„ฑ ์ธ์‹ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐ™์€ ๊ฒƒ๋“ค์„ ๋งํ•ด. ์ด๋Ÿฐ ๋„๊ตฌ๋“ค์€ Accessibility API๋ฅผ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ UI ์ปดํฌ๋„ŒํŠธ ์ •๋ณด๋ฅผ ์ฝ๊ณ , ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ์ „๋‹ฌํ•˜๊ฒŒ ๋ผ.

๐Ÿ”ง ํ•ต์‹ฌ ์ธํ„ฐํŽ˜์ด์Šค์™€ ํด๋ž˜์Šค๋“ค

Java Accessibility API์˜ ํ•ต์‹ฌ์€ ๋ช‡ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ํด๋ž˜์Šค๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์–ด. ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณผ๊นŒ?

๐ŸŽ“ Accessible ์ธํ„ฐํŽ˜์ด์Šค

๋ชจ๋“  ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๊ฐ€ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ์ตœ์ƒ์œ„ ์ธํ„ฐํŽ˜์ด์Šค์•ผ. ๋‹จ ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ๋งŒ ์ •์˜ํ•˜๊ณ  ์žˆ์–ด:

AccessibleContext getAccessibleContext()

์ด ๋ฉ”์„œ๋“œ๋Š” ๊ฐ์ฒด์˜ ์ ‘๊ทผ์„ฑ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” AccessibleContext๋ฅผ ๋ฐ˜ํ™˜ํ•ด. Swing์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฏธ ์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์–ด์„œ, ์šฐ๋ฆฌ๋Š” ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€! ๐Ÿ˜Š
// Accessible ์ธํ„ฐํŽ˜์ด์Šค ์‚ฌ์šฉ ์˜ˆ์‹œ
JButton button = new JButton("ํด๋ฆญํ•˜์„ธ์š”");
Accessible accessible = button;
AccessibleContext context = accessible.getAccessibleContext();

// ์ ‘๊ทผ์„ฑ ์ •๋ณด ์ถœ๋ ฅ
System.out.println("์—ญํ• : " + context.getAccessibleRole());
System.out.println("์ด๋ฆ„: " + context.getAccessibleName());
System.out.println("์„ค๋ช…: " + context.getAccessibleDescription());

AccessibleContext๋Š” ์ ‘๊ทผ์„ฑ ์ •๋ณด์˜ ์ค‘์‹ฌ์ด์•ผ. ์ด ํด๋ž˜์Šค๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์—ญํ• , ์ด๋ฆ„, ์„ค๋ช…, ์ƒํƒœ ๋“ฑ ๋ณด์กฐ ๊ธฐ์ˆ ์ด ํ•„์š”๋กœ ํ•˜๋Š” ๋ชจ๋“  ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ด. ๊ฐ Swing ์ปดํฌ๋„ŒํŠธ๋Š” ์ž์‹ ๋งŒ์˜ AccessibleContext ๊ตฌํ˜„์ฒด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด.

๋ฉ”์„œ๋“œ ์„ค๋ช… ํ™œ์šฉ ์˜ˆ์‹œ
getAccessibleName() ์ปดํฌ๋„ŒํŠธ์˜ ์ด๋ฆ„ ๋ฐ˜ํ™˜ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ "์ €์žฅ ๋ฒ„ํŠผ"์ด๋ผ๊ณ  ์ฝ์–ด์คŒ
getAccessibleDescription() ์ปดํฌ๋„ŒํŠธ์˜ ์ƒ์„ธ ์„ค๋ช… ๋ฐ˜ํ™˜ "ํ˜„์žฌ ๋ฌธ์„œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค"
getAccessibleRole() ์ปดํฌ๋„ŒํŠธ์˜ ์—ญํ•  ๋ฐ˜ํ™˜ BUTTON, TEXT_FIELD, LABEL ๋“ฑ
getAccessibleStateSet() ํ˜„์žฌ ์ƒํƒœ ์ง‘ํ•ฉ ๋ฐ˜ํ™˜ ENABLED, FOCUSED, VISIBLE ๋“ฑ
getAccessibleParent() ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ๋ฐ˜ํ™˜ ๊ณ„์ธต ๊ตฌ์กฐ ํƒ์ƒ‰์— ์‚ฌ์šฉ
getAccessibleChildrenCount() ์ž์‹ ์ปดํฌ๋„ŒํŠธ ๊ฐœ์ˆ˜ ๋ฐ˜ํ™˜ ์ปจํ…Œ์ด๋„ˆ์˜ ๋‚ด์šฉ๋ฌผ ํŒŒ์•…

๐ŸŽญ AccessibleRole: ์ปดํฌ๋„ŒํŠธ์˜ ์ •์ฒด์„ฑ

AccessibleRole์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฌด์—‡์ธ์ง€๋ฅผ ์ •์˜ํ•˜๋Š” ์—ด๊ฑฐํ˜• ํด๋ž˜์Šค์•ผ. ๋ฒ„ํŠผ์ธ์ง€, ํ…์ŠคํŠธ ํ•„๋“œ์ธ์ง€, ์ฒดํฌ๋ฐ•์Šค์ธ์ง€ ๋“ฑ์„ ๋‚˜ํƒ€๋‚ด์ง€. ๋ณด์กฐ ๊ธฐ์ˆ ์€ ์ด ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ๋ฐฉ์‹์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ค๋ช…ํ•ด์ค˜. ๐ŸŽช

// ๋‹ค์–‘ํ•œ AccessibleRole ์˜ˆ์‹œ
JButton button = new JButton("ํ™•์ธ");
System.out.println(button.getAccessibleContext().getAccessibleRole()); 
// ์ถœ๋ ฅ: push button

JTextField textField = new JTextField();
System.out.println(textField.getAccessibleContext().getAccessibleRole()); 
// ์ถœ๋ ฅ: text

JCheckBox checkBox = new JCheckBox("๋™์˜ํ•ฉ๋‹ˆ๋‹ค");
System.out.println(checkBox.getAccessibleContext().getAccessibleRole()); 
// ์ถœ๋ ฅ: check box

JLabel label = new JLabel("์ด๋ฆ„:");
System.out.println(label.getAccessibleContext().getAccessibleRole()); 
// ์ถœ๋ ฅ: label

๊ฐ ์—ญํ• ์€ ๋ณด์กฐ ๊ธฐ์ˆ ์—๊ฒŒ "์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๋‹ค๋ค„์•ผ ํ•˜๋Š”์ง€"์— ๋Œ€ํ•œ ํžŒํŠธ๋ฅผ ์ œ๊ณตํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด, PUSH_BUTTON ์—ญํ• ์„ ๊ฐ€์ง„ ์ปดํฌ๋„ŒํŠธ๋Š” ํ™œ์„ฑํ™”๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ์˜๋ฏธํ•˜๊ณ , TEXT ์—ญํ• ์€ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ๋‚˜ํƒ€๋‚ด์ง€.

๐Ÿšฆ AccessibleState: ์ปดํฌ๋„ŒํŠธ์˜ ํ˜„์žฌ ์ƒํƒœ

AccessibleState๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํด๋ž˜์Šค์•ผ. ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š”์ง€, ์„ ํƒ๋˜์–ด ์žˆ๋Š”์ง€, ํฌ์ปค์Šค๋ฅผ ๋ฐ›์•˜๋Š”์ง€ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ด. ์ด ์ •๋ณด๋Š” AccessibleStateSet์œผ๋กœ ๊ด€๋ฆฌ๋˜๋Š”๋ฐ, ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ์ƒํƒœ๋ฅผ ๋™์‹œ์— ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด์•ผ. ๐ŸŽจ

JButton button = new JButton("ํด๋ฆญ");
AccessibleContext context = button.getAccessibleContext();
AccessibleStateSet stateSet = context.getAccessibleStateSet();

// ์ƒํƒœ ํ™•์ธ
if (stateSet.contains(AccessibleState.ENABLED)) {
    System.out.println("๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค");
}

if (stateSet.contains(AccessibleState.FOCUSED)) {
    System.out.println("๋ฒ„ํŠผ์ด ํฌ์ปค์Šค๋ฅผ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค");
}

if (stateSet.contains(AccessibleState.VISIBLE)) {
    System.out.println("๋ฒ„ํŠผ์ด ํ™”๋ฉด์— ๋ณด์ž…๋‹ˆ๋‹ค");
}

// ๋ฒ„ํŠผ ๋น„ํ™œ์„ฑํ™”
button.setEnabled(false);
stateSet = context.getAccessibleStateSet();

if (!stateSet.contains(AccessibleState.ENABLED)) {
    System.out.println("์ด์ œ ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค");
}
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

AccessibleStateSet์€ ์ปดํฌ๋„ŒํŠธ์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๋ฐ˜์˜ํ•˜๋Š” ์Šค๋ƒ…์ƒท์ด์•ผ. ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋‹ค์‹œ getAccessibleStateSet()์„ ํ˜ธ์ถœํ•ด์„œ ์ตœ์‹  ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ด. ์ด์ „์— ๊ฐ€์ ธ์˜จ StateSet ๊ฐ์ฒด๋Š” ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์•„!

๐ŸŽจ ์‹ค์ „! ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ Swing ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งŒ๋“ค๊ธฐ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ ํผ์„ ์˜ˆ์ œ๋กœ ์‚ฌ์šฉํ•ด๋ณผ๊ฒŒ. ์ด ๊ณผ์ •์—์„œ ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ์™€ ๊ณ ๋ คํ•œ ์ฝ”๋“œ๋ฅผ ๋น„๊ตํ•ด๋ณด๋ฉด์„œ, ์–ด๋–ค ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž! ๐Ÿš€

โŒ ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ ๋‚˜์œ ์˜ˆ

import javax.swing.*;
import java.awt.*;

public class BadLoginForm extends JFrame {
    public BadLoginForm() {
        setTitle("๋กœ๊ทธ์ธ");
        setSize(400, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JPanel panel = new JPanel(new GridLayout(3, 2, 10, 10));
        
        // ๋ ˆ์ด๋ธ”๊ณผ ํ•„๋“œ๋ฅผ ๋‹จ์ˆœํžˆ ๋‚˜์—ด
        panel.add(new JLabel("์•„์ด๋””"));
        JTextField userField = new JTextField();
        panel.add(userField);
        
        panel.add(new JLabel("๋น„๋ฐ€๋ฒˆํ˜ธ"));
        JPasswordField passField = new JPasswordField();
        panel.add(passField);
        
        // ๋ฒ„ํŠผ์— ์•„์ด์ฝ˜๋งŒ ์‚ฌ์šฉ
        JButton loginBtn = new JButton("โ†’");
        JButton cancelBtn = new JButton("โœ•");
        panel.add(loginBtn);
        panel.add(cancelBtn);
        
        add(panel);
        setVisible(true);
    }
}

์œ„ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์ด ๋ญ˜๊นŒ? ๐Ÿค”

๐Ÿšซ ๋ฌธ์ œ์  ๋ถ„์„

1๏ธโƒฃ ๋ ˆ์ด๋ธ”๊ณผ ์ž…๋ ฅ ํ•„๋“œ์˜ ์—ฐ๊ฒฐ์ด ๋ช…์‹œ์ ์ด์ง€ ์•Š์•„ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ์–ด๋–ค ํ•„๋“œ์ธ์ง€ ์•Œ๋ ค์ฃผ์ง€ ๋ชปํ•จ
2๏ธโƒฃ ๋ฒ„ํŠผ์— ์˜๋ฏธ ์žˆ๋Š” ํ…์ŠคํŠธ๊ฐ€ ์—†์–ด์„œ ์•„์ด์ฝ˜์˜ ์˜๋ฏธ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž๋Š” ๋ฒ„ํŠผ์˜ ๊ธฐ๋Šฅ์„ ์•Œ ์ˆ˜ ์—†์Œ
3๏ธโƒฃ ์ ‘๊ทผ์„ฑ ์„ค๋ช…(Accessible Description)์ด ์—†์–ด์„œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•จ
4๏ธโƒฃ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค๋‚˜ ๋‹ˆ๋ชจ๋‹‰(Mnemonic)์ด ์„ค์ •๋˜์ง€ ์•Š์•„ ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ ํšจ์œจ์ ์ธ ํƒ์ƒ‰์ด ์–ด๋ ค์›€

โœ… ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์ข‹์€ ์˜ˆ

์ด์ œ ๊ฐ™์€ ๋กœ๊ทธ์ธ ํผ์„ ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•ด์„œ ๋‹ค์‹œ ๋งŒ๋“ค์–ด๋ณด์ž:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class GoodLoginForm extends JFrame {
    public GoodLoginForm() {
        setTitle("๋กœ๊ทธ์ธ");
        setSize(400, 250);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JPanel panel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5);
        gbc.fill = GridBagConstraints.HORIZONTAL;
        
        // ์•„์ด๋”” ๋ ˆ์ด๋ธ”๊ณผ ํ•„๋“œ
        JLabel userLabel = new JLabel("์•„์ด๋””(I):");
        userLabel.setDisplayedMnemonic('I'); // Alt+I๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ
        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(userLabel, gbc);
        
        JTextField userField = new JTextField(20);
        userLabel.setLabelFor(userField); // ๋ ˆ์ด๋ธ”๊ณผ ํ•„๋“œ ์—ฐ๊ฒฐ
        
        // ์ ‘๊ทผ์„ฑ ์ •๋ณด ์„ค์ •
        userField.getAccessibleContext().setAccessibleName("์•„์ด๋”” ์ž…๋ ฅ");
        userField.getAccessibleContext().setAccessibleDescription(
            "๋กœ๊ทธ์ธํ•  ์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. ์˜๋ฌธ๊ณผ ์ˆซ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.");
        
        gbc.gridx = 1;
        gbc.gridy = 0;
        panel.add(userField, gbc);
        
        // ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ ˆ์ด๋ธ”๊ณผ ํ•„๋“œ
        JLabel passLabel = new JLabel("๋น„๋ฐ€๋ฒˆํ˜ธ(P):");
        passLabel.setDisplayedMnemonic('P'); // Alt+P๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ
        gbc.gridx = 0;
        gbc.gridy = 1;
        panel.add(passLabel, gbc);
        
        JPasswordField passField = new JPasswordField(20);
        passLabel.setLabelFor(passField); // ๋ ˆ์ด๋ธ”๊ณผ ํ•„๋“œ ์—ฐ๊ฒฐ
        
        // ์ ‘๊ทผ์„ฑ ์ •๋ณด ์„ค์ •
        passField.getAccessibleContext().setAccessibleName("๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ");
        passField.getAccessibleContext().setAccessibleDescription(
            "๋กœ๊ทธ์ธํ•  ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.");
        
        gbc.gridx = 1;
        gbc.gridy = 1;
        panel.add(passField, gbc);
        
        // ๋ฒ„ํŠผ ํŒจ๋„
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        
        // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ
        JButton loginBtn = new JButton("๋กœ๊ทธ์ธ(L)");
        loginBtn.setMnemonic('L'); // Alt+L๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ
        loginBtn.getAccessibleContext().setAccessibleDescription(
            "์ž…๋ ฅํ•œ ์ •๋ณด๋กœ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค");
        
        // ์—”ํ„ฐ ํ‚ค๋กœ๋„ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๋„๋ก
        getRootPane().setDefaultButton(loginBtn);
        
        loginBtn.addActionListener(e -> {
            // ๋กœ๊ทธ์ธ ๋กœ์ง
            System.out.println("๋กœ๊ทธ์ธ ์‹œ๋„");
        });
        
        // ์ทจ์†Œ ๋ฒ„ํŠผ
        JButton cancelBtn = new JButton("์ทจ์†Œ(C)");
        cancelBtn.setMnemonic('C'); // Alt+C๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ
        cancelBtn.getAccessibleContext().setAccessibleDescription(
            "๋กœ๊ทธ์ธ์„ ์ทจ์†Œํ•˜๊ณ  ์ฐฝ์„ ๋‹ซ์Šต๋‹ˆ๋‹ค");
        
        // ESC ํ‚ค๋กœ๋„ ์ทจ์†Œ ๊ฐ€๋Šฅํ•˜๋„๋ก
        cancelBtn.addActionListener(e -> dispose());
        KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        cancelBtn.registerKeyboardAction(
            e -> dispose(),
            escapeKey,
            JComponent.WHEN_IN_FOCUSED_WINDOW
        );
        
        buttonPanel.add(loginBtn);
        buttonPanel.add(cancelBtn);
        
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        panel.add(buttonPanel, gbc);
        
        // ์ƒํƒœ ํ‘œ์‹œ ๋ ˆ์ด๋ธ” (์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก)
        JLabel statusLabel = new JLabel(" ");
        statusLabel.getAccessibleContext().setAccessibleName("์ƒํƒœ ๋ฉ”์‹œ์ง€");
        gbc.gridy = 3;
        panel.add(statusLabel, gbc);
        
        add(panel);
        
        // ์ดˆ๊ธฐ ํฌ์ปค์Šค ์„ค์ •
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent e) {
                userField.requestFocusInWindow();
            }
        });
        
        setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new GoodLoginForm());
    }
}

์™€! ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊ธธ์–ด์กŒ์ง€? ํ•˜์ง€๋งŒ ์ด์ œ ์ด ๋กœ๊ทธ์ธ ํผ์€ ์ง„์ •์œผ๋กœ ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํผ์ด ๋˜์—ˆ์–ด. ํ•˜๋‚˜์”ฉ ๊ฐœ์„ ๋œ ์ ์„ ์‚ดํŽด๋ณด์ž:

โœจ ๊ฐœ์„ ๋œ ์ ๋“ค

1. ๋ ˆ์ด๋ธ”๊ณผ ํ•„๋“œ์˜ ๋ช…์‹œ์  ์—ฐ๊ฒฐ
setLabelFor() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ ˆ์ด๋ธ”๊ณผ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์—ฐ๊ฒฐํ–ˆ์–ด. ์ด์ œ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋Š” "์•„์ด๋”” ์ž…๋ ฅ ํŽธ์ง‘ ์ƒ์ž"๋ผ๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์ฝ์–ด์ค„ ์ˆ˜ ์žˆ์ง€.

2. ๋‹ˆ๋ชจ๋‹‰(Mnemonic) ์„ค์ •
Alt+I, Alt+P, Alt+L, Alt+C ๊ฐ™์€ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค๋ฅผ ์„ค์ •ํ–ˆ์–ด. ๋งˆ์šฐ์Šค ์—†์ด๋„ ๋น ๋ฅด๊ฒŒ ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์ง€.

3. ์ ‘๊ทผ์„ฑ ์ด๋ฆ„๊ณผ ์„ค๋ช…
๊ฐ ์ปดํฌ๋„ŒํŠธ์— ๋ช…ํ™•ํ•œ ์ด๋ฆ„๊ณผ ์ƒ์„ธํ•œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ–ˆ์–ด. ๋ณด์กฐ ๊ธฐ์ˆ ์ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์ง€.

4. ํ‚ค๋ณด๋“œ ๋‚ด๋น„๊ฒŒ์ด์…˜
Enter ํ‚ค๋กœ ๋กœ๊ทธ์ธ, ESC ํ‚ค๋กœ ์ทจ์†Œ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ–ˆ์–ด. ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ๋„ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

5. ์ดˆ๊ธฐ ํฌ์ปค์Šค ์„ค์ •
์ฐฝ์ด ์—ด๋ฆฌ๋ฉด ์ž๋™์œผ๋กœ ์•„์ด๋”” ์ž…๋ ฅ ํ•„๋“œ์— ํฌ์ปค์Šค๊ฐ€ ๊ฐ€๋„๋ก ํ–ˆ์–ด. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ”๋กœ ์ž…๋ ฅ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์ง€.

๐Ÿ” ๊ณ ๊ธ‰ ์ ‘๊ทผ์„ฑ ๊ธฐ๋Šฅ ํ™œ์šฉํ•˜๊ธฐ

๊ธฐ๋ณธ์ ์ธ ์ ‘๊ทผ์„ฑ ์„ค์ •์„ ๋„˜์–ด์„œ, Java Accessibility API๋Š” ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๋ฅผ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ด. ์ด๋Ÿฐ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ์ ‘๊ทผ์„ฑ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ช

๐Ÿ“ AccessibleText: ํ…์ŠคํŠธ ์ ‘๊ทผ์„ฑ

AccessibleText ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํ…์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด์šฉ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜. ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ์ฝ๊ณ , ์ปค์„œ ์œ„์น˜๋ฅผ ํŒŒ์•…ํ•˜๊ณ , ์„ ํƒ๋œ ํ…์ŠคํŠธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์ง€.

import javax.accessibility.*;
import javax.swing.*;

public class AccessibleTextExample {
    public static void main(String[] args) {
        JTextArea textArea = new JTextArea("์•ˆ๋…•ํ•˜์„ธ์š”!\n์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.");
        
        AccessibleContext context = textArea.getAccessibleContext();
        AccessibleText accessibleText = context.getAccessibleText();
        
        if (accessibleText != null) {
            // ์ „์ฒด ํ…์ŠคํŠธ ๊ธธ์ด
            int charCount = accessibleText.getCharCount();
            System.out.println("์ „์ฒด ๋ฌธ์ž ์ˆ˜: " + charCount);
            
            // ํŠน์ • ์œ„์น˜์˜ ๋ฌธ์ž
            char charAt5 = accessibleText.getAtIndex(
                AccessibleText.CHARACTER, 5);
            System.out.println("5๋ฒˆ์งธ ๋ฌธ์ž: " + charAt5);
            
            // ํŠน์ • ์œ„์น˜์˜ ๋‹จ์–ด
            String wordAt5 = accessibleText.getAtIndex(
                AccessibleText.WORD, 5);
            System.out.println("5๋ฒˆ์งธ ์œ„์น˜์˜ ๋‹จ์–ด: " + wordAt5);
            
            // ์ปค์„œ ์œ„์น˜
            int caretPos = accessibleText.getCaretPosition();
            System.out.println("ํ˜„์žฌ ์ปค์„œ ์œ„์น˜: " + caretPos);
            
            // ์„ ํƒ๋œ ํ…์ŠคํŠธ
            String selectedText = accessibleText.getSelectedText();
            if (selectedText != null) {
                System.out.println("์„ ํƒ๋œ ํ…์ŠคํŠธ: " + selectedText);
            }
            
            // ํŠน์ • ๋ฒ”์œ„์˜ ํ…์ŠคํŠธ
            String textBetween = accessibleText.getTextRange(0, 5);
            System.out.println("0~5 ๋ฒ”์œ„์˜ ํ…์ŠคํŠธ: " + textBetween);
        }
    }
}

์ด ๊ธฐ๋Šฅ์€ ํŠนํžˆ ํ…์ŠคํŠธ ํŽธ์ง‘๊ธฐ๋‚˜ ๋ฌธ์„œ ๋ทฐ์–ด๋ฅผ ๋งŒ๋“ค ๋•Œ ์ค‘์š”ํ•ด. ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ๋ฌธ์„œ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๊ฑฐ๋“ . ๐Ÿ“–

๐ŸŽฌ AccessibleAction: ๋™์ž‘ ์ ‘๊ทผ์„ฑ

AccessibleAction ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋™์ž‘๋“ค์„ ์ •์˜ํ•ด. ๋ฒ„ํŠผ ํด๋ฆญ, ๋ฉ”๋‰ด ์„ ํƒ, ๋งํฌ ํ™œ์„ฑํ™” ๊ฐ™์€ ๋™์ž‘๋“ค์„ ๋ณด์กฐ ๊ธฐ์ˆ ์ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์ง€.

import javax.accessibility.*;
import javax.swing.*;

public class AccessibleActionExample extends JFrame {
    public AccessibleActionExample() {
        setTitle("๋™์ž‘ ์ ‘๊ทผ์„ฑ ์˜ˆ์ œ");
        setSize(400, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JButton button = new JButton("์ €์žฅ");
        button.addActionListener(e -> {
            System.out.println("ํŒŒ์ผ์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!");
        });
        
        // AccessibleAction ์ •๋ณด ํ™•์ธ
        AccessibleContext context = button.getAccessibleContext();
        AccessibleAction action = context.getAccessibleAction();
        
        if (action != null) {
            int actionCount = action.getAccessibleActionCount();
            System.out.println("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋™์ž‘ ์ˆ˜: " + actionCount);
            
            for (int i = 0; i < actionCount; i++) {
                String actionDesc = action.getAccessibleActionDescription(i);
                System.out.println("๋™์ž‘ " + i + ": " + actionDesc);
            }
            
            // ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ๋ฒ„ํŠผ ํด๋ฆญ
            // ๋ณด์กฐ ๊ธฐ์ˆ ์ด ์ด๋ ‡๊ฒŒ ๋ฒ„ํŠผ์„ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์–ด
            JButton testButton = new JButton("๋™์ž‘ ํ…Œ์ŠคํŠธ");
            testButton.addActionListener(e -> {
                boolean success = action.doAccessibleAction(0);
                System.out.println("๋™์ž‘ ์‹คํ–‰ " + 
                    (success ? "์„ฑ๊ณต" : "์‹คํŒจ"));
            });
            
            JPanel panel = new JPanel();
            panel.add(button);
            panel.add(testButton);
            add(panel);
        }
        
        setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new AccessibleActionExample());
    }
}

๐Ÿ”ข AccessibleValue: ๊ฐ’ ์ ‘๊ทผ์„ฑ

AccessibleValue ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ˆซ์ž ๊ฐ’์„ ๊ฐ€์ง„ ์ปดํฌ๋„ŒํŠธ(์Šฌ๋ผ์ด๋”, ์Šคํ”ผ๋„ˆ, ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ๋“ฑ)์˜ ๊ฐ’์— ์ ‘๊ทผํ•˜๊ณ  ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

import javax.accessibility.*;
import javax.swing.*;

public class AccessibleValueExample extends JFrame {
    public AccessibleValueExample() {
        setTitle("๊ฐ’ ์ ‘๊ทผ์„ฑ ์˜ˆ์ œ");
        setSize(400, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JPanel panel = new JPanel();
        
        // ์Šฌ๋ผ์ด๋” ์ƒ์„ฑ
        JSlider slider = new JSlider(0, 100, 50);
        slider.setMajorTickSpacing(25);
        slider.setMinorTickSpacing(5);
        slider.setPaintTicks(true);
        slider.setPaintLabels(true);
        
        // ์ ‘๊ทผ์„ฑ ์ •๋ณด ์„ค์ •
        AccessibleContext context = slider.getAccessibleContext();
        context.setAccessibleName("๋ณผ๋ฅจ ์กฐ์ ˆ");
        context.setAccessibleDescription(
            "0์—์„œ 100 ์‚ฌ์ด์˜ ๊ฐ’์œผ๋กœ ๋ณผ๋ฅจ์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค");
        
        // AccessibleValue ์‚ฌ์šฉ
        AccessibleValue accessibleValue = context.getAccessibleValue();
        
        if (accessibleValue != null) {
            // ํ˜„์žฌ ๊ฐ’
            Number currentValue = accessibleValue.getCurrentAccessibleValue();
            System.out.println("ํ˜„์žฌ ๊ฐ’: " + currentValue);
            
            // ์ตœ์†Œ๊ฐ’
            Number minValue = accessibleValue.getMinimumAccessibleValue();
            System.out.println("์ตœ์†Œ๊ฐ’: " + minValue);
            
            // ์ตœ๋Œ€๊ฐ’
            Number maxValue = accessibleValue.getMaximumAccessibleValue();
            System.out.println("์ตœ๋Œ€๊ฐ’: " + maxValue);
            
            // ๊ฐ’ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ
            JButton setButton = new JButton("75๋กœ ์„ค์ •");
            setButton.addActionListener(e -> {
                boolean success = accessibleValue.setCurrentAccessibleValue(75);
                System.out.println("๊ฐ’ ์„ค์ • " + 
                    (success ? "์„ฑ๊ณต" : "์‹คํŒจ"));
            });
            
            panel.add(slider);
            panel.add(setButton);
        }
        
        add(panel);
        setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new AccessibleValueExample());
    }
}

์ด๋Ÿฐ ์ธํ„ฐํŽ˜์ด์Šค๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ๋ณด์กฐ ๊ธฐ์ˆ ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ธก๋ฉด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ผ. ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์—๊ฒŒ ํŽธํ•œ ๋ฐฉ์‹์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์ง€! ๐ŸŽฎ

๐Ÿ“Š AccessibleTable: ํ…Œ์ด๋ธ” ์ ‘๊ทผ์„ฑ

ํ…Œ์ด๋ธ”์€ ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ ‘๊ทผ์„ฑ ์ธก๋ฉด์—์„œ ํŠน๋ณ„ํ•œ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ด. AccessibleTable ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํ…Œ์ด๋ธ”์˜ ๊ตฌ์กฐ์™€ ๋‚ด์šฉ์„ ๋ณด์กฐ ๊ธฐ์ˆ ์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

import javax.accessibility.*;
import javax.swing.*;
import javax.swing.table.*;

public class AccessibleTableExample extends JFrame {
    public AccessibleTableExample() {
        setTitle("ํ…Œ์ด๋ธ” ์ ‘๊ทผ์„ฑ ์˜ˆ์ œ");
        setSize(600, 400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ
        String[] columnNames = {"์ด๋ฆ„", "๋‚˜์ด", "์ง์—…"};
        Object[][] data = {
            {"๊น€์ฒ ์ˆ˜", 28, "๊ฐœ๋ฐœ์ž"},
            {"์ด์˜ํฌ", 32, "๋””์ž์ด๋„ˆ"},
            {"๋ฐ•๋ฏผ์ˆ˜", 25, "๊ธฐํš์ž"}
        };
        
        JTable table = new JTable(data, columnNames);
        
        // ์ ‘๊ทผ์„ฑ ์ •๋ณด ์„ค์ •
        AccessibleContext context = table.getAccessibleContext();
        context.setAccessibleName("์ง์› ๋ชฉ๋ก");
        context.setAccessibleDescription(
            "์ง์›๋“ค์˜ ์ด๋ฆ„, ๋‚˜์ด, ์ง์—… ์ •๋ณด๋ฅผ ๋‹ด์€ ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค");
        
        // ๊ฐ ์—ด์— ๋Œ€ํ•œ ์„ค๋ช… ์ถ”๊ฐ€
        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(0).setHeaderValue("์ด๋ฆ„");
        columnModel.getColumn(1).setHeaderValue("๋‚˜์ด");
        columnModel.getColumn(2).setHeaderValue("์ง์—…");
        
        // AccessibleTable ์ •๋ณด ํ™•์ธ
        AccessibleTable accessibleTable = context.getAccessibleTable();
        
        if (accessibleTable != null) {
            System.out.println("ํ–‰ ์ˆ˜: " + 
                accessibleTable.getAccessibleRowCount());
            System.out.println("์—ด ์ˆ˜: " + 
                accessibleTable.getAccessibleColumnCount());
            
            // ํŠน์ • ์…€์˜ ์„ค๋ช…
            Accessible cellAccessible = 
                accessibleTable.getAccessibleAt(0, 0);
            if (cellAccessible != null) {
                AccessibleContext cellContext = 
                    cellAccessible.getAccessibleContext();
                System.out.println("(0,0) ์…€ ๋‚ด์šฉ: " + 
                    cellContext.getAccessibleName());
            }
            
            // ์—ด ํ—ค๋” ์ •๋ณด
            AccessibleTable columnHeader = 
                accessibleTable.getAccessibleColumnHeader();
            if (columnHeader != null) {
                System.out.println("์—ด ํ—ค๋”๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค");
            }
        }
        
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
        
        setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new AccessibleTableExample());
    }
}
๐Ÿ’ก ํ…Œ์ด๋ธ” ์ ‘๊ทผ์„ฑ ํŒ

โ€ข ํ•ญ์ƒ ๋ช…ํ™•ํ•œ ์—ด ํ—ค๋”๋ฅผ ์ œ๊ณตํ•˜์„ธ์š”
โ€ข ๋ณต์žกํ•œ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ ์š”์•ฝ ์ •๋ณด๋ฅผ AccessibleDescription์— ์ถ”๊ฐ€ํ•˜์„ธ์š”
โ€ข ์…€ ๋ณ‘ํ•ฉ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํŠนํžˆ ์ฃผ์˜ํ•˜์„ธ์š” - ๋ณด์กฐ ๊ธฐ์ˆ ์ด ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด์š”
โ€ข ๊ฐ€๋Šฅํ•˜๋ฉด ํ…Œ์ด๋ธ” ์บก์…˜์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”

๐ŸŽฏ ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ์˜ ์ ‘๊ทผ์„ฑ ๊ตฌํ˜„

๋•Œ๋กœ๋Š” ๊ธฐ๋ณธ Swing ์ปดํฌ๋„ŒํŠธ๋กœ๋Š” ๋ถ€์กฑํ•ด์„œ ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ์ด๋Ÿด ๋•Œ๋„ ์ ‘๊ทผ์„ฑ์„ ์žŠ์œผ๋ฉด ์•ˆ ๋ผ! ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ์— ์ ‘๊ทผ์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž. ๐Ÿ› ๏ธ

์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ๋Š” AccessibleContext๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•ด. ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ๋‹จ๊ณ„๋ณ„๋กœ ๋”ฐ๋ผํ•˜๋ฉด ์–ด๋ ต์ง€ ์•Š์•„!

๐ŸŽจ ์ปค์Šคํ…€ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ ๋งŒ๋“ค๊ธฐ

๊ฐ„๋‹จํ•œ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ์ ‘๊ทผ์„ฑ์„ ๊ตฌํ˜„ํ•ด๋ณด์ž:

import javax.accessibility.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class AccessibleColorPicker extends JComponent 
        implements Accessible {
    
    private Color selectedColor = Color.RED;
    private Color[] colors = {
        Color.RED, Color.ORANGE, Color.YELLOW,
        Color.GREEN, Color.BLUE, Color.MAGENTA
    };
    private int selectedIndex = 0;
    
    public AccessibleColorPicker() {
        setPreferredSize(new Dimension(300, 50));
        setFocusable(true);
        
        // ๋งˆ์šฐ์Šค ํด๋ฆญ ์ฒ˜๋ฆฌ
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int colorWidth = getWidth() / colors.length;
                int clickedIndex = e.getX() / colorWidth;
                if (clickedIndex >= 0 && clickedIndex < colors.length) {
                    setSelectedIndex(clickedIndex);
                }
            }
        });
        
        // ํ‚ค๋ณด๋“œ ๋‚ด๋น„๊ฒŒ์ด์…˜
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_LEFT:
                        if (selectedIndex > 0) {
                            setSelectedIndex(selectedIndex - 1);
                        }
                        break;
                    case KeyEvent.VK_RIGHT:
                        if (selectedIndex < colors.length - 1) {
                            setSelectedIndex(selectedIndex + 1);
                        }
                        break;
                    case KeyEvent.VK_HOME:
                        setSelectedIndex(0);
                        break;
                    case KeyEvent.VK_END:
                        setSelectedIndex(colors.length - 1);
                        break;
                }
            }
        });
        
        // ํฌ์ปค์Šค ์‹œ๊ฐํ™”
        addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                repaint();
            }
            
            @Override
            public void focusLost(FocusEvent e) {
                repaint();
            }
        });
    }
    
    private void setSelectedIndex(int index) {
        selectedIndex = index;
        selectedColor = colors[index];
        repaint();
        
        // ์ ‘๊ทผ์„ฑ ์ด๋ฒคํŠธ ๋ฐœ์ƒ
        firePropertyChange(
            AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
            null,
            selectedColor
        );
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        
        int colorWidth = getWidth() / colors.length;
        
        // ๊ฐ ์ƒ‰์ƒ ๊ทธ๋ฆฌ๊ธฐ
        for (int i = 0; i < colors.length; i++) {
            g.setColor(colors[i]);
            g.fillRect(i * colorWidth, 0, colorWidth, getHeight());
            
            // ์„ ํƒ๋œ ์ƒ‰์ƒ ํ‘œ์‹œ
            if (i == selectedIndex) {
                g.setColor(Color.WHITE);
                g.drawRect(i * colorWidth + 2, 2, 
                    colorWidth - 4, getHeight() - 4);
                g.drawRect(i * colorWidth + 3, 3, 
                    colorWidth - 6, getHeight() - 6);
            }
        }
        
        // ํฌ์ปค์Šค ํ‘œ์‹œ
        if (hasFocus()) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.BLACK);
            g2d.setStroke(new BasicStroke(2, 
                BasicStroke.CAP_BUTT, 
                BasicStroke.JOIN_MITER, 
                10, new float[]{5, 5}, 0));
            g2d.drawRect(1, 1, getWidth() - 2, getHeight() - 2);
        }
    }
    
    public Color getSelectedColor() {
        return selectedColor;
    }
    
    // AccessibleContext ๊ตฌํ˜„
    @Override
    public AccessibleContext getAccessibleContext() {
        if (accessibleContext == null) {
            accessibleContext = new AccessibleColorPicker();
        }
        return accessibleContext;
    }
    
    // ๋‚ด๋ถ€ AccessibleContext ํด๋ž˜์Šค
    protected class AccessibleColorPicker extends AccessibleJComponent
            implements AccessibleValue {
        
        @Override
        public AccessibleRole getAccessibleRole() {
            return AccessibleRole.SLIDER;
        }
        
        @Override
        public AccessibleStateSet getAccessibleStateSet() {
            AccessibleStateSet states = super.getAccessibleStateSet();
            if (hasFocus()) {
                states.add(AccessibleState.FOCUSED);
            }
            if (isEnabled()) {
                states.add(AccessibleState.ENABLED);
            }
            if (isVisible()) {
                states.add(AccessibleState.VISIBLE);
            }
            states.add(AccessibleState.FOCUSABLE);
            return states;
        }
        
        @Override
        public String getAccessibleName() {
            String name = super.getAccessibleName();
            if (name == null) {
                name = "์ƒ‰์ƒ ์„ ํƒ๊ธฐ";
            }
            return name;
        }
        
        @Override
        public String getAccessibleDescription() {
            String desc = super.getAccessibleDescription();
            if (desc == null) {
                desc = "ํ™”์‚ดํ‘œ ํ‚ค๋กœ ์ƒ‰์ƒ์„ ์„ ํƒํ•˜์„ธ์š”. " +
                       "ํ˜„์žฌ ์„ ํƒ: " + getColorName(selectedColor);
            }
            return desc;
        }
        
        // AccessibleValue ๊ตฌํ˜„
        @Override
        public Number getCurrentAccessibleValue() {
            return selectedIndex;
        }
        
        @Override
        public boolean setCurrentAccessibleValue(Number n) {
            int newIndex = n.intValue();
            if (newIndex >= 0 && newIndex < colors.length) {
                setSelectedIndex(newIndex);
                return true;
            }
            return false;
        }
        
        @Override
        public Number getMinimumAccessibleValue() {
            return 0;
        }
        
        @Override
        public Number getMaximumAccessibleValue() {
            return colors.length - 1;
        }
        
        @Override
        public AccessibleValue getAccessibleValue() {
            return this;
        }
    }
    
    private String getColorName(Color color) {
        if (color.equals(Color.RED)) return "๋นจ๊ฐ•";
        if (color.equals(Color.ORANGE)) return "์ฃผํ™ฉ";
        if (color.equals(Color.YELLOW)) return "๋…ธ๋ž‘";
        if (color.equals(Color.GREEN)) return "์ดˆ๋ก";
        if (color.equals(Color.BLUE)) return "ํŒŒ๋ž‘";
        if (color.equals(Color.MAGENTA)) return "์ž์ฃผ";
        return "์•Œ ์ˆ˜ ์—†์Œ";
    }
    
    // ํ…Œ์ŠคํŠธ์šฉ ๋ฉ”์ธ ๋ฉ”์„œ๋“œ
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            
            AccessibleColorPicker picker = new AccessibleColorPicker();
            picker.getAccessibleContext().setAccessibleName("๋ฐฐ๊ฒฝ์ƒ‰ ์„ ํƒ");
            picker.getAccessibleContext().setAccessibleDescription(
                "๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ์ƒ‰์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. " +
                "์ขŒ์šฐ ํ™”์‚ดํ‘œ ํ‚ค๋กœ ์ƒ‰์ƒ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.");
            
            JLabel label = new JLabel("์„ ํƒ๋œ ์ƒ‰์ƒ: ๋นจ๊ฐ•");
            label.setHorizontalAlignment(SwingConstants.CENTER);
            
            picker.addPropertyChangeListener(
                AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
                e -> {
                    Color color = picker.getSelectedColor();
                    label.setText("์„ ํƒ๋œ ์ƒ‰์ƒ: " + 
                        picker.getColorName(color));
                    label.setForeground(color);
                }
            );
            
            JPanel panel = new JPanel(new BorderLayout(10, 10));
            panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
            panel.add(picker, BorderLayout.CENTER);
            panel.add(label, BorderLayout.SOUTH);
            
            frame.add(panel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

์™€! ์ฝ”๋“œ๊ฐ€ ๊ฝค ๊ธธ์ง€? ํ•˜์ง€๋งŒ ์ด์ œ ์šฐ๋ฆฌ์˜ ์ปค์Šคํ…€ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ๋Š”:

โœ… ์™„๋ฒฝํ•œ ์ ‘๊ทผ์„ฑ์„ ๊ฐ–์ถ”์—ˆ์–ด!

๐ŸŽฏ ํ‚ค๋ณด๋“œ ๋‚ด๋น„๊ฒŒ์ด์…˜: ํ™”์‚ดํ‘œ ํ‚ค, Home, End ํ‚ค๋กœ ์™„์ „ํ•œ ์ œ์–ด
๐ŸŽฏ ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์ง€์›: ํ˜„์žฌ ์„ ํƒ๋œ ์ƒ‰์ƒ์„ ์Œ์„ฑ์œผ๋กœ ์•ˆ๋‚ด
๐ŸŽฏ ํฌ์ปค์Šค ํ‘œ์‹œ: ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ๋ช…ํ™•ํ•œ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ
๐ŸŽฏ AccessibleValue ๊ตฌํ˜„: ๋ณด์กฐ ๊ธฐ์ˆ ์ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ๊ฐ’ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
๐ŸŽฏ ์ƒํƒœ ๋ณ€๊ฒฝ ์•Œ๋ฆผ: PropertyChangeEvent๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ „๋‹ฌ

์ด๋Ÿฐ ์‹์œผ๋กœ ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ๋„ ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•˜๋ฉด, ์ •๋ง ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋˜๋Š” ๊ฑฐ์•ผ! ๐ŸŒŸ

๐Ÿงช ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธํ•˜๊ธฐ

์ ‘๊ทผ์„ฑ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค๋ฉด, ์ด์ œ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•ด. ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ๋Š” ์ผ๋ฐ˜์ ์ธ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ๋งŒํผ์ด๋‚˜ ์ค‘์š”ํ•ด! ๐Ÿ”ฌ

๐Ÿ› ๏ธ Java Access Bridge ํ™œ์šฉํ•˜๊ธฐ

Java Access Bridge๋Š” ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ Windows ๋ณด์กฐ ๊ธฐ์ˆ  ์‚ฌ์ด์˜ ๋‹ค๋ฆฌ ์—ญํ• ์„ ํ•ด. ์ด๊ฑธ ํ™œ์„ฑํ™”ํ•˜๋ฉด JAWS, NVDA ๊ฐ™์€ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ฝ์„ ์ˆ˜ ์žˆ๊ฒŒ ๋ผ.

๐Ÿ”ง Java Access Bridge ์„ค์ •ํ•˜๊ธฐ

Windows์—์„œ:
1. ๋ช…๋ น ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰
2. JDK bin ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™
3. jabswitch -enable ๋ช…๋ น ์‹คํ–‰

ํ™•์ธ ๋ฐฉ๋ฒ•:
โ€ข Windows ์ œ์–ดํŒ โ†’ ์ ‘๊ทผ์„ฑ ์„ผํ„ฐ์—์„œ Java Access Bridge๊ฐ€ ํ™œ์„ฑํ™”๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
โ€ข ์‹œ์Šคํ…œ ํŠธ๋ ˆ์ด์— Java Access Bridge ์•„์ด์ฝ˜์ด ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธ

๐ŸŽฎ Accessibility Checker ๋งŒ๋“ค๊ธฐ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ์„ฑ์„ ์ž๋™์œผ๋กœ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋„๊ตฌ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:

import javax.accessibility.*;
import javax.swing.*;
import java.awt.*;
import java.util.*;

public class AccessibilityChecker {
    
    private java.util.List<String> issues = new ArrayList<>();
    
    public void checkComponent(Component component) {
        issues.clear();
        checkComponentRecursive(component, "");
        printReport();
    }
    
    private void checkComponentRecursive(Component comp, String indent) {
        if (!(comp instanceof Accessible)) {
            issues.add(indent + "โš ๏ธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ Accessible์„ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์Œ: " + 
                comp.getClass().getSimpleName());
            return;
        }
        
        Accessible accessible = (Accessible) comp;
        AccessibleContext context = accessible.getAccessibleContext();
        
        if (context == null) {
            issues.add(indent + "โŒ AccessibleContext๊ฐ€ null: " + 
                comp.getClass().getSimpleName());
            return;
        }
        
        // ์ด๋ฆ„ ๊ฒ€์‚ฌ
        String name = context.getAccessibleName();
        if (name == null || name.trim().isEmpty()) {
            if (needsName(context.getAccessibleRole())) {
                issues.add(indent + "โš ๏ธ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„์ด ์—†์Œ: " + 
                    comp.getClass().getSimpleName() + 
                    " (์—ญํ• : " + context.getAccessibleRole() + ")");
            }
        }
        
        // ์„ค๋ช… ๊ฒ€์‚ฌ
        String description = context.getAccessibleDescription();
        if (description == null || description.trim().isEmpty()) {
            if (needsDescription(context.getAccessibleRole())) {
                issues.add(indent + "โ„น๏ธ ์ ‘๊ทผ์„ฑ ์„ค๋ช…์ด ์—†์Œ: " + 
                    comp.getClass().getSimpleName());
            }
        }
        
        // ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค ๊ฒ€์‚ฌ
        if (comp instanceof JComponent) {
            JComponent jcomp = (JComponent) comp;
            if (isInteractive(context.getAccessibleRole())) {
                if (!jcomp.isFocusable()) {
                    issues.add(indent + "โš ๏ธ ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ " +
                        "ํฌ์ปค์Šค๋ฅผ ๋ฐ›์„ ์ˆ˜ ์—†์Œ: " + 
                        comp.getClass().getSimpleName());
                }
            }
        }
        
        // ๋ ˆ์ด๋ธ” ์—ฐ๊ฒฐ ๊ฒ€์‚ฌ (ํ…์ŠคํŠธ ํ•„๋“œ์˜ ๊ฒฝ์šฐ)
        if (comp instanceof JTextField || 
            comp instanceof JTextArea ||
            comp instanceof JPasswordField) {
            
            AccessibleRelationSet relations = 
                context.getAccessibleRelationSet();
            if (relations == null || 
                relations.get(AccessibleRelation.LABELED_BY) == null) {
                issues.add(indent + "โš ๏ธ ํ…์ŠคํŠธ ์ž…๋ ฅ ํ•„๋“œ์— " +
                    "๋ ˆ์ด๋ธ”์ด ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Œ: " + 
                    comp.getClass().getSimpleName());
            }
        }
        
        // ์ž์‹ ์ปดํฌ๋„ŒํŠธ ๊ฒ€์‚ฌ
        if (comp instanceof Container) {
            Container container = (Container) comp;
            for (Component child : container.getComponents()) {
                checkComponentRecursive(child, indent + "  ");
            }
        }
    }
    
    private boolean needsName(AccessibleRole role) {
        return role == AccessibleRole.PUSH_BUTTON ||
               role == AccessibleRole.TEXT ||
               role == AccessibleRole.PASSWORD_TEXT ||
               role == AccessibleRole.CHECK_BOX ||
               role == AccessibleRole.RADIO_BUTTON ||
               role == AccessibleRole.COMBO_BOX ||
               role == AccessibleRole.LIST ||
               role == AccessibleRole.TABLE;
    }
    
    private boolean needsDescription(AccessibleRole role) {
        return role == AccessibleRole.PUSH_BUTTON ||
               role == AccessibleRole.TEXT ||
               role == AccessibleRole.PASSWORD_TEXT;
    }
    
    private boolean isInteractive(AccessibleRole role) {
        return role == AccessibleRole.PUSH_BUTTON ||
               role == AccessibleRole.TEXT ||
               role == AccessibleRole.PASSWORD_TEXT ||
               role == AccessibleRole.CHECK_BOX ||
               role == AccessibleRole.RADIO_BUTTON ||
               role == AccessibleRole.COMBO_BOX ||
               role == AccessibleRole.SLIDER;
    }
    
    private void printReport() {
        System.out.println("\n" + "=".repeat(60));
        System.out.println("์ ‘๊ทผ์„ฑ ๊ฒ€์‚ฌ ๋ณด๊ณ ์„œ");
        System.out.println("=".repeat(60));
        
        if (issues.isEmpty()) {
            System.out.println("โœ… ๋ฐœ๊ฒฌ๋œ ๋ฌธ์ œ ์—†์Œ! ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค!");
        } else {
            System.out.println("๋ฐœ๊ฒฌ๋œ ๋ฌธ์ œ: " + issues.size() + "๊ฐœ\n");
            for (String issue : issues) {
                System.out.println(issue);
            }
        }
        
        System.out.println("=".repeat(60) + "\n");
    }
    
    // ํ…Œ์ŠคํŠธ์šฉ ๋ฉ”์ธ ๋ฉ”์„œ๋“œ
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            // ๋‚˜์œ ์˜ˆ์ œ ํ”„๋ ˆ์ž„
            JFrame badFrame = new JFrame("์ ‘๊ทผ์„ฑ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ํผ");
            JPanel badPanel = new JPanel();
            badPanel.add(new JLabel("์ด๋ฆ„"));
            badPanel.add(new JTextField(20)); // ๋ ˆ์ด๋ธ” ์—ฐ๊ฒฐ ์—†์Œ
            JButton badButton = new JButton("โ†’"); // ์˜๋ฏธ ์—†๋Š” ํ…์ŠคํŠธ
            badButton.getAccessibleContext().setAccessibleName(null);
            badPanel.add(badButton);
            badFrame.add(badPanel);
            badFrame.pack();
            
            // ๊ฒ€์‚ฌ ์‹คํ–‰
            AccessibilityChecker checker = new AccessibilityChecker();
            System.out.println("๋‚˜์œ ์˜ˆ์ œ ๊ฒ€์‚ฌ:");
            checker.checkComponent(badFrame);
            
            // ์ข‹์€ ์˜ˆ์ œ ํ”„๋ ˆ์ž„
            JFrame goodFrame = new JFrame("์ ‘๊ทผ์„ฑ์ด ์ข‹์€ ํผ");
            JPanel goodPanel = new JPanel();
            JLabel nameLabel = new JLabel("์ด๋ฆ„:");
            JTextField nameField = new JTextField(20);
            nameLabel.setLabelFor(nameField); // ๋ ˆ์ด๋ธ” ์—ฐ๊ฒฐ
            nameField.getAccessibleContext().setAccessibleName("์ด๋ฆ„ ์ž…๋ ฅ");
            nameField.getAccessibleContext().setAccessibleDescription(
                "์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”");
            
            JButton goodButton = new JButton("์ œ์ถœ");
            goodButton.getAccessibleContext().setAccessibleDescription(
                "ํผ์„ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค");
            
            goodPanel.add(nameLabel);
            goodPanel.add(nameField);
            goodPanel.add(goodButton);
            goodFrame.add(goodPanel);
            goodFrame.pack();
            
            System.out.println("\n์ข‹์€ ์˜ˆ์ œ ๊ฒ€์‚ฌ:");
            checker.checkComponent(goodFrame);
        });
    }
}

์ด ์ฒด์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ์„ฑ ๋ฌธ์ œ๋ฅผ ์ž๋™์œผ๋กœ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์–ด. ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ์ฃผ๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋ฉด ์ ‘๊ทผ์„ฑ ๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์ง€! ๐ŸŽฏ

๐Ÿ“‹ ์ˆ˜๋™ ํ…Œ์ŠคํŠธ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•ด๋ณด๋Š” ์ˆ˜๋™ ํ…Œ์ŠคํŠธ๋„ ํ•„์ˆ˜์•ผ. ๋‹ค์Œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ํ™œ์šฉํ•ด๋ด:

โœ… ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ

โ–ก Tab ํ‚ค๋กœ ๋ชจ๋“  ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅํ•œ ์š”์†Œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?
โ–ก Shift+Tab์œผ๋กœ ์—ญ๋ฐฉํ–ฅ ํƒ์ƒ‰์ด ๊ฐ€๋Šฅํ•œ๊ฐ€?
โ–ก ํฌ์ปค์Šค ์ˆœ์„œ๊ฐ€ ๋…ผ๋ฆฌ์ ์ธ๊ฐ€?
โ–ก ํฌ์ปค์Šค๋ฅผ ๋ฐ›์€ ์š”์†Œ๊ฐ€ ์‹œ๊ฐ์ ์œผ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œ๋˜๋Š”๊ฐ€?
โ–ก Enter/Space ํ‚ค๋กœ ๋ฒ„ํŠผ๊ณผ ๋งํฌ๋ฅผ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?
โ–ก ํ™”์‚ดํ‘œ ํ‚ค๋กœ ๋ฆฌ์ŠคํŠธ, ํŠธ๋ฆฌ, ํ…Œ์ด๋ธ”์„ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?
โ–ก Esc ํ‚ค๋กœ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ๋‹ซ์„ ์ˆ˜ ์žˆ๋Š”๊ฐ€?
โ–ก ๋‹จ์ถ•ํ‚ค(๋‹ˆ๋ชจ๋‹‰)๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”๊ฐ€?

โœ… ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ํ…Œ์ŠคํŠธ

โ–ก ๋ชจ๋“  ํ…์ŠคํŠธ๊ฐ€ ์ฝํžˆ๋Š”๊ฐ€?
โ–ก ๋ฒ„ํŠผ๊ณผ ๋งํฌ์˜ ๋ชฉ์ ์ด ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌ๋˜๋Š”๊ฐ€?
โ–ก ํผ ํ•„๋“œ์— ์ ์ ˆํ•œ ๋ ˆ์ด๋ธ”์ด ์žˆ๋Š”๊ฐ€?
โ–ก ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ์ฝํžˆ๋Š”๊ฐ€?
โ–ก ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ์ฝ˜ํ…์ธ ๊ฐ€ ์•Œ๋ฆผ๋˜๋Š”๊ฐ€?
โ–ก ์ด๋ฏธ์ง€์— ๋Œ€์ฒด ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋Š”๊ฐ€?

โœ… ์‹œ๊ฐ์  ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ

โ–ก ์ƒ‰์ƒ ๋Œ€๋น„๊ฐ€ ์ถฉ๋ถ„ํ•œ๊ฐ€? (์ตœ์†Œ 4.5:1)
โ–ก ์ •๋ณด๊ฐ€ ์ƒ‰์ƒ์—๋งŒ ์˜์กดํ•˜์ง€ ์•Š๋Š”๊ฐ€?
โ–ก ํ…์ŠคํŠธ ํฌ๊ธฐ๋ฅผ 200%๋กœ ํ™•๋Œ€ํ•ด๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ๊ฐ€?
โ–ก ๊ณ ๋Œ€๋น„ ๋ชจ๋“œ์—์„œ๋„ ์ œ๋Œ€๋กœ ๋ณด์ด๋Š”๊ฐ€?

๐ŸŒ ์›น๊ณผ์˜ ์—ฐ๋™: JavaFX WebView ์ ‘๊ทผ์„ฑ

์š”์ฆ˜์€ ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์›น ์ฝ˜ํ…์ธ ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. JavaFX์˜ WebView๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋„ ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ด! ๐ŸŒ

JavaFX WebView๋Š” WebKit ์—”์ง„์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, HTML์˜ ์ ‘๊ทผ์„ฑ ๊ธฐ๋Šฅ(ARIA ์†์„ฑ ๋“ฑ)์„ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์ž๋ฐ” ์ธก์—์„œ๋„ ๋ช‡ ๊ฐ€์ง€ ์„ค์ •์ด ํ•„์š”ํ•ด.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class AccessibleWebViewExample extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        
        // ์ ‘๊ทผ์„ฑ ์ •๋ณด ์„ค์ •
        webView.setAccessibleText("์›น ์ฝ˜ํ…์ธ  ๋ทฐ์–ด");
        webView.setAccessibleHelp(
            "์ด ์˜์—ญ์—๋Š” ์›น ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. " +
            "Tab ํ‚ค๋กœ ๋งํฌ์™€ ํผ ์š”์†Œ๋ฅผ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.");
        
        // ์ ‘๊ทผ์„ฑ์ด ๊ณ ๋ ค๋œ HTML ๋กœ๋“œ
        String accessibleHTML = """
            
            
            
            
                <a href="#main" class="skip-link">๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ</a>
                
                <header role="banner">
                    <h1>์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜</h1>
                </header>
                
                <nav role="navigation" aria-label="์ฃผ ๋ฉ”๋‰ด">
                    <ul>
                        <li><a href="#home">ํ™ˆ</a></li>
                        <li><a href="#about">์†Œ๊ฐœ</a></li>
                        <li><a href="#contact">์—ฐ๋ฝ์ฒ˜</a></li>
                    </ul>
                </nav>
                
                <main id="main" role="main">
                    <h2>๋กœ๊ทธ์ธ</h2>
                    
                    <form onsubmit="return validateForm()">
                        <div>
                            <label for="username">์‚ฌ์šฉ์ž ์ด๋ฆ„:</label>
                            <input type="text" id="username" name="username" aria-required="true" aria-describedby="username-help">
                            <span id="username-help" class="help-text">
                                ์˜๋ฌธ๊ณผ ์ˆซ์ž๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค
                            </span>
                        </div>
                        
                        <div>
                            <label for="password">๋น„๋ฐ€๋ฒˆํ˜ธ:</label>
                            <input type="password" id="password" name="password" aria-required="true">
                        </div>
                        
                        <div role="alert" id="error-message" class="error" aria-live="assertive" style="display: none;">
                        </div>
                        
                        <button type="submit" aria-label="๋กœ๊ทธ์ธ ๋ฒ„ํŠผ">
                            ๋กœ๊ทธ์ธ
                        </button>
                    </form>
                    
                    <section aria-labelledby="status-heading">
                        <h3 id="status-heading">์‹œ์Šคํ…œ ์ƒํƒœ</h3>
                        <div role="status" aria-live="polite" id="status-message">
                            ๋ชจ๋“  ์‹œ์Šคํ…œ์ด ์ •์ƒ ์ž‘๋™ ์ค‘์ž…๋‹ˆ๋‹ค.
                        </div>
                    </section>
                </main>
                
                <footer role="contentinfo">
                    <p>ยฉ 2024 ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜</p>
                </footer>
                
                <script>
                    function validateForm() {
                        const username = 
                            document.getElementById('username').value;
                        const password = 
                            document.getElementById('password').value;
                        const errorDiv = 
                            document.getElementById('error-message');
                        
                        if (!username || !password) {
                            errorDiv.textContent = 
                                '๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.';
                            errorDiv.style.display = 'block';
                            
                            // ์ฒซ ๋ฒˆ์งธ ๋นˆ ํ•„๋“œ์— ํฌ์ปค์Šค
                            if (!username) {
                                document.getElementById('username').focus();
                            } else {
                                document.getElementById('password').focus();
                            }
                            
                            return false;
                        }
                        
                        errorDiv.style.display = 'none';
                        return true;
                    }
                    
                    // ๋™์  ์ฝ˜ํ…์ธ  ์—…๋ฐ์ดํŠธ ์˜ˆ์‹œ
                    setInterval(() => {
                        const now = new Date();
                        const statusDiv = 
                            document.getElementById('status-message');
                        statusDiv.textContent = 
                            '๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: ' + now.toLocaleTimeString();
                    }, 30000);
                </script>
            
            
            """;
        
        webView.getEngine().loadContent(accessibleHTML);
        
        Scene scene = new Scene(webView, 900, 700);
        primaryStage.setTitle("์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ WebView");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์œ„ ์˜ˆ์ œ์—์„œ ์‚ฌ์šฉ๋œ ์›น ์ ‘๊ทผ์„ฑ ๊ธฐ๋ฒ•๋“ค์„ ์‚ดํŽด๋ณด์ž:

๐ŸŒŸ ์›น ์ ‘๊ทผ์„ฑ ํ•ต์‹ฌ ๊ธฐ๋ฒ•

1. ARIA (Accessible Rich Internet Applications)
โ€ข role: ์š”์†Œ์˜ ์—ญํ•  ์ •์˜ (banner, navigation, main, contentinfo ๋“ฑ)
โ€ข aria-label: ์š”์†Œ์— ๋ ˆ์ด๋ธ” ์ œ๊ณต
โ€ข aria-describedby: ์ถ”๊ฐ€ ์„ค๋ช… ์—ฐ๊ฒฐ
โ€ข aria-live: ๋™์  ์ฝ˜ํ…์ธ  ๋ณ€๊ฒฝ ์•Œ๋ฆผ (polite, assertive)
โ€ข aria-required: ํ•„์ˆ˜ ์ž…๋ ฅ ํ•„๋“œ ํ‘œ์‹œ

2. ์‹œ๋งจํ‹ฑ HTML
โ€ข <header>, <nav>, <main>, <footer> ๊ฐ™์€ ์˜๋ฏธ ์žˆ๋Š” ํƒœ๊ทธ ์‚ฌ์šฉ
โ€ข ์ œ๋ชฉ ํƒœ๊ทธ (<h1>~<h6>)์˜ ๊ณ„์ธต์  ์‚ฌ์šฉ

3. ํผ ์ ‘๊ทผ์„ฑ
โ€ข <label>๊ณผ <input>์˜ ๋ช…์‹œ์  ์—ฐ๊ฒฐ (for/id)
โ€ข ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ role="alert"๋กœ ์ฆ‰์‹œ ์•Œ๋ฆผ
โ€ข ํ•„๋“œ ์„ค๋ช…์„ aria-describedby๋กœ ์—ฐ๊ฒฐ

4. ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ
โ€ข ๋ชจ๋“  ์ƒํ˜ธ์ž‘์šฉ ์š”์†Œ๊ฐ€ ํ‚ค๋ณด๋“œ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ
โ€ข ๊ฑด๋„ˆ๋›ฐ๊ธฐ ๋งํฌ (Skip Link) ์ œ๊ณต
โ€ข ๋…ผ๋ฆฌ์ ์ธ ํƒญ ์ˆœ์„œ

๐Ÿ’ผ ์‹ค๋ฌด์—์„œ์˜ ์ ‘๊ทผ์„ฑ: ๋ชจ๋ฒ” ์‚ฌ๋ก€์™€ ํŒ

์ด๋ก ๊ณผ ์˜ˆ์ œ๋ฅผ ๋งŽ์ด ๋ดค์œผ๋‹ˆ, ์ด์ œ ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์šฉ์ ์ธ ํŒ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ! ์ด๋Ÿฐ ๊ฒƒ๋“ค์€ ๊ฒฝํ—˜์—์„œ ๋‚˜์˜จ ๋…ธํ•˜์šฐ๋“ค์ด์•ผ. ๐Ÿ’ก

๐ŸŽฏ ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์šฐ์— ์ ‘๊ทผ์„ฑ ํ†ตํ•ฉํ•˜๊ธฐ

์ ‘๊ทผ์„ฑ์€ ํ”„๋กœ์ ํŠธ ๋งˆ์ง€๋ง‰์— "์ถ”๊ฐ€"ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค์— ํฌํ•จ๋˜์–ด์•ผ ํ•ด. ๋‹ค์Œ์€ ํšจ๊ณผ์ ์ธ ์›Œํฌํ”Œ๋กœ์šฐ์•ผ:

๐Ÿ“‹ ์ ‘๊ทผ์„ฑ ์ค‘์‹ฌ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค

1. ๊ธฐํš ๋‹จ๊ณ„
โ€ข ์‚ฌ์šฉ์ž ํŽ˜๋ฅด์†Œ๋‚˜์— ์žฅ์• ๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ์ž ํฌํ•จ
โ€ข ์ ‘๊ทผ์„ฑ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋ฌธ์„œํ™”
โ€ข WCAG 2.1 ๋ ˆ๋ฒจ AA๋ฅผ ์ตœ์†Œ ๋ชฉํ‘œ๋กœ ์„ค์ •

2. ์„ค๊ณ„ ๋‹จ๊ณ„
โ€ข ์ƒ‰์ƒ ๋Œ€๋น„ ๊ฒ€์‚ฌ (์ตœ์†Œ 4.5:1)
โ€ข ํ‚ค๋ณด๋“œ ๋‚ด๋น„๊ฒŒ์ด์…˜ ํ”Œ๋กœ์šฐ ์„ค๊ณ„
โ€ข ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ์ •๋ณด ๊ตฌ์กฐ ์„ค๊ณ„

3. ๊ฐœ๋ฐœ ๋‹จ๊ณ„
โ€ข ์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ ์‹œ ์ ‘๊ทผ์„ฑ API ๊ตฌํ˜„
โ€ข ์ฝ”๋“œ ๋ฆฌ๋ทฐ์— ์ ‘๊ทผ์„ฑ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ํฌํ•จ
โ€ข ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์— ์ ‘๊ทผ์„ฑ ๊ฒ€์ฆ ์ถ”๊ฐ€

4. ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„
โ€ข ์ž๋™ํ™”๋œ ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ ์‹คํ–‰
โ€ข ์‹ค์ œ ๋ณด์กฐ ๊ธฐ์ˆ ๋กœ ์ˆ˜๋™ ํ…Œ์ŠคํŠธ
โ€ข ๊ฐ€๋Šฅํ•˜๋ฉด ์žฅ์• ๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ ํ”ผ๋“œ๋ฐฑ ์ˆ˜์ง‘

5. ์œ ์ง€๋ณด์ˆ˜ ๋‹จ๊ณ„
โ€ข ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ์ ‘๊ทผ์„ฑ ์˜ํ–ฅ ํ‰๊ฐ€
โ€ข ์ •๊ธฐ์ ์ธ ์ ‘๊ทผ์„ฑ ๊ฐ์‚ฌ
โ€ข ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ ์ง€์†์  ๋ฐ˜์˜

๐Ÿ› ๏ธ ์œ ์šฉํ•œ ๋„๊ตฌ์™€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

์ ‘๊ทผ์„ฑ ๊ฐœ๋ฐœ์„ ๋„์™€์ฃผ๋Š” ๋„๊ตฌ๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ํ›จ์”ฌ ํšจ์œจ์ ์œผ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์–ด:

// ์ ‘๊ทผ์„ฑ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค ์˜ˆ์‹œ
public class AccessibilityUtils {
    
    /**
     * ์ปดํฌ๋„ŒํŠธ์— ๊ธฐ๋ณธ ์ ‘๊ทผ์„ฑ ์ •๋ณด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
     */
    public static void setupBasicAccessibility(
            JComponent component,
            String name,
            String description) {
        
        AccessibleContext context = component.getAccessibleContext();
        if (context != null) {
            context.setAccessibleName(name);
            context.setAccessibleDescription(description);
        }
    }
    
    /**
     * ๋ ˆ์ด๋ธ”๊ณผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—ฐ๊ฒฐํ•˜๊ณ  ๋‹ˆ๋ชจ๋‹‰์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
     */
    public static void linkLabelToComponent(
            JLabel label,
            JComponent component,
            char mnemonic) {
        
        label.setLabelFor(component);
        label.setDisplayedMnemonic(mnemonic);
    }
    
    /**
     * ๋ฒ„ํŠผ์— ์™„์ „ํ•œ ์ ‘๊ทผ์„ฑ ์ •๋ณด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
     */
    public static void setupAccessibleButton(
            JButton button,
            String text,
            String description,
            char mnemonic) {
        
        button.setText(text);
        button.setMnemonic(mnemonic);
        
        AccessibleContext context = button.getAccessibleContext();
        context.setAccessibleDescription(description);
    }
    
    /**
     * ๋‹ค์ด์–ผ๋กœ๊ทธ์˜ ์ดˆ๊ธฐ ํฌ์ปค์Šค๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
     */
    public static void setInitialFocus(
            final JDialog dialog,
            final JComponent component) {
        
        dialog.addWindowListener(new java.awt.event.WindowAdapter() {
            @Override
            public void windowOpened(java.awt.event.WindowEvent e) {
                component.requestFocusInWindow();
            }
        });
    }
    
    /**
     * ์ƒ‰์ƒ ๋Œ€๋น„๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
     * WCAG 2.1 ๊ธฐ์ค€: ์ผ๋ฐ˜ ํ…์ŠคํŠธ 4.5:1, ํฐ ํ…์ŠคํŠธ 3:1
     */
    public static double calculateContrastRatio(
            Color foreground,
            Color background) {
        
        double l1 = getRelativeLuminance(foreground);
        double l2 = getRelativeLuminance(background);
        
        double lighter = Math.max(l1, l2);
        double darker = Math.min(l1, l2);
        
        return (lighter + 0.05) / (darker + 0.05);
    }
    
    private static double getRelativeLuminance(Color color) {
        double r = color.getRed() / 255.0;
        double g = color.getGreen() / 255.0;
        double b = color.getBlue() / 255.0;
        
        r = (r <= 0.03928) ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
        g = (g <= 0.03928) ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
        b = (b <= 0.03928) ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
        
        return 0.2126 * r + 0.7152 * g + 0.0722 * b;
    }
    
    /**
     * ์ƒ‰์ƒ ๋Œ€๋น„๊ฐ€ ์ถฉ๋ถ„ํ•œ์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
     */
    public static boolean hasGoodContrast(
            Color foreground,
            Color background,
            boolean isLargeText) {
        
        double ratio = calculateContrastRatio(foreground, background);
        double requiredRatio = isLargeText ? 3.0 : 4.5;
        
        return ratio >= requiredRatio;
    }
    
    /**
     * ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์ ‘๊ทผ์„ฑ ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
     */
    public static void printAccessibilityTree(Component component) {
        printAccessibilityTreeRecursive(component, 0);
    }
    
    private static void printAccessibilityTreeRecursive(
            Component comp,
            int level) {
        
        String indent = "  ".repeat(level);
        
        if (comp instanceof Accessible) {
            Accessible accessible = (Accessible) comp;
            AccessibleContext context = accessible.getAccessibleContext();
            
            if (context != null) {
                System.out.println(indent + "โ”œโ”€ " + 
                    comp.getClass().getSimpleName());
                System.out.println(indent + "โ”‚  ์ด๋ฆ„: " + 
                    context.getAccessibleName());
                System.out.println(indent + "โ”‚  ์—ญํ• : " + 
                    context.getAccessibleRole());
                System.out.println(indent + "โ”‚  ์„ค๋ช…: " + 
                    context.getAccessibleDescription());
            }
        }
        
        if (comp instanceof Container) {
            Container container = (Container) comp;
            for (Component child : container.getComponents()) {
                printAccessibilityTreeRecursive(child, level + 1);
            }
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
public class AccessibilityUtilsDemo {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("์ ‘๊ทผ์„ฑ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฐ๋ชจ");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            
            JPanel panel = new JPanel(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.insets = new Insets(5, 5, 5, 5);
            gbc.fill = GridBagConstraints.HORIZONTAL;
            
            // ๋ ˆ์ด๋ธ”๊ณผ ํ…์ŠคํŠธ ํ•„๋“œ
            JLabel nameLabel = new JLabel("์ด๋ฆ„:");
            JTextField nameField = new JTextField(20);
            
            AccessibilityUtils.linkLabelToComponent(
                nameLabel, nameField, 'N');
            AccessibilityUtils.setupBasicAccessibility(
                nameField,
                "์ด๋ฆ„ ์ž…๋ ฅ",
                "์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”");
            
            gbc.gridx = 0;
            gbc.gridy = 0;
            panel.add(nameLabel, gbc);
            
            gbc.gridx = 1;
            panel.add(nameField, gbc);
            
            // ๋ฒ„ํŠผ
            JButton submitButton = new JButton();
            AccessibilityUtils.setupAccessibleButton(
                submitButton,
                "์ œ์ถœ",
                "์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค",
                'S');
            
            gbc.gridx = 0;
            gbc.gridy = 1;
            gbc.gridwidth = 2;
            panel.add(submitButton, gbc);
            
            // ์ƒ‰์ƒ ๋Œ€๋น„ ํ…Œ์ŠคํŠธ
            Color fg = Color.BLACK;
            Color bg = Color.WHITE;
            double ratio = AccessibilityUtils.calculateContrastRatio(fg, bg);
            boolean goodContrast = 
                AccessibilityUtils.hasGoodContrast(fg, bg, false);
            
            System.out.println("์ƒ‰์ƒ ๋Œ€๋น„ ๋น„์œจ: " + ratio);
            System.out.println("์ถฉ๋ถ„ํ•œ ๋Œ€๋น„: " + goodContrast);
            
            // ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ ์ถœ๋ ฅ
            System.out.println("\n์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ:");
            AccessibilityUtils.printAccessibilityTree(panel);
            
            frame.add(panel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

์ด๋Ÿฐ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ํ”„๋กœ์ ํŠธ์— ํฌํ•จ์‹œํ‚ค๋ฉด, ์ ‘๊ทผ์„ฑ ๊ตฌํ˜„์ด ํ›จ์”ฌ ๊ฐ„ํŽธํ•ด์ ธ. ๋งค๋ฒˆ ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง€๊ฑฐ๋“ ! ๐Ÿš€

๐ŸŽจ ๋””์ž์ธ ์‹œ์Šคํ…œ๊ณผ ์ ‘๊ทผ์„ฑ

์ผ๊ด€๋œ ์ ‘๊ทผ์„ฑ์„ ์œ ์ง€ํ•˜๋ ค๋ฉด ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ๋งŒ๋“œ๋Š” ๊ฒŒ ์ข‹์•„. ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฑฐ์ง€:

// ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ
public class AccessibleButton extends JButton {
    
    public AccessibleButton(String text) {
        super(text);
        setupDefaults();
    }
    
    public AccessibleButton(String text, String description) {
        super(text);
        setupDefaults();
        getAccessibleContext().setAccessibleDescription(description);
    }
    
    public AccessibleButton(String text, String description, char mnemonic) {
        super(text);
        setupDefaults();
        getAccessibleContext().setAccessibleDescription(description);
        setMnemonic(mnemonic);
    }
    
    private void setupDefaults() {
        // ์ตœ์†Œ ํฌ๊ธฐ ๋ณด์žฅ (ํ„ฐ์น˜ ํƒ€๊ฒŸ ํฌ๊ธฐ)
        setMinimumSize(new Dimension(44, 44));
        setPreferredSize(new Dimension(100, 44));
        
        // ํฌ์ปค์Šค ํ‘œ์‹œ ๊ฐ•ํ™”
        setFocusPainted(true);
        
        // ํˆดํŒ์„ ์ ‘๊ทผ์„ฑ ์„ค๋ช…๊ณผ ๋™๊ธฐํ™”
        addPropertyChangeListener(
            AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY,
            evt -> {
                String desc = getAccessibleContext()
                    .getAccessibleDescription();
                if (desc != null && !desc.isEmpty()) {
                    setToolTipText(desc);
                }
            }
        );
    }
    
    /**
     * ๋นŒ๋” ํŒจํ„ด์œผ๋กœ ์ ‘๊ทผ์„ฑ ์„ค์ •
     */
    public static class Builder {
        private String text;
        private String description;
        private Character mnemonic;
        private Icon icon;
        private ActionListener action;
        
        public Builder text(String text) {
            this.text = text;
            return this;
        }
        
        public Builder description(String description) {
            this.description = description;
            return this;
        }
        
        public Builder mnemonic(char mnemonic) {
            this.mnemonic = mnemonic;
            return this;
        }
        
        public Builder icon(Icon icon) {
            this.icon = icon;
            return this;
        }
        
        public Builder action(ActionListener action) {
            this.action = action;
            return this;
        }
        
        public AccessibleButton build() {
            AccessibleButton button = new AccessibleButton(
                text != null ? text : "",
                description != null ? description : "",
                mnemonic != null ? mnemonic : '\0'
            );
            
            if (icon != null) {
                button.setIcon(icon);
            }
            
            if (action != null) {
                button.addActionListener(action);
            }
            
            return button;
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
AccessibleButton saveButton = new AccessibleButton.Builder()
    .text("์ €์žฅ")
    .description("ํ˜„์žฌ ๋ฌธ์„œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค")
    .mnemonic('S')
    .action(e -> saveDocument())
    .build();

์ด๋ ‡๊ฒŒ ์ ‘๊ทผ์„ฑ์ด ๋‚ด์žฅ๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋‘๋ฉด, ํŒ€ ์ „์ฒด๊ฐ€ ์ผ๊ด€๋˜๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๊ฐœ๋ฐœ์ž๊ฐ€ ์ผ์ผ์ด ์ ‘๊ทผ์„ฑ์„ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ์ ์šฉ๋˜๋Š” ๊ฑฐ์ง€! ๐Ÿ˜Š

๐ŸŒŸ ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์‚ฌ๋ก€: ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์—๋””ํ„ฐ

์ด๋ก ๊ณผ ์ž‘์€ ์˜ˆ์ œ๋“ค์„ ๋งŽ์ด ๋ดค์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์™„์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์ž. ์ ‘๊ทผ์„ฑ์ด ์™„๋ฒฝํ•˜๊ฒŒ ๊ตฌํ˜„๋œ ๊ฐ„๋‹จํ•œ ํ…์ŠคํŠธ ์—๋””ํ„ฐ์•ผ! ๐Ÿ“

โš ๏ธ ์ฃผ์˜
์•„๋ž˜ ์ฝ”๋“œ๋Š” ๊ต์œก ๋ชฉ์ ์˜ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋” ๋งŽ์€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ์™€ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
import javax.accessibility.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.undo.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

public class AccessibleTextEditor extends JFrame {
    
    private JTextArea textArea;
    private JLabel statusLabel;
    private UndoManager undoManager;
    private File currentFile;
    private boolean isModified = false;
    
    public AccessibleTextEditor() {
        super("์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์—๋””ํ„ฐ");
        initializeComponents();
        setupAccessibility();
        setupKeyboardShortcuts();
        
        setSize(800, 600);
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                exitApplication();
            }
        });
        
        setLocationRelativeTo(null);
        setVisible(true);
    }
    
    private void initializeComponents() {
        // ๋ฉ”๋‰ด ๋ฐ”
        JMenuBar menuBar = createAccessibleMenuBar();
        setJMenuBar(menuBar);
        
        // ํ…์ŠคํŠธ ์˜์—ญ
        textArea = new JTextArea();
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
        
        // Undo ๊ธฐ๋Šฅ
        undoManager = new UndoManager();
        textArea.getDocument().addUndoableEditListener(undoManager);
        
        // ๋ฌธ์„œ ๋ณ€๊ฒฝ ๊ฐ์ง€
        textArea.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent e) { documentChanged(); }
            public void removeUpdate(DocumentEvent e) { documentChanged(); }
            public void changedUpdate(DocumentEvent e) { documentChanged(); }
        });
        
        JScrollPane scrollPane = new JScrollPane(textArea);
        add(scrollPane, BorderLayout.CENTER);
        
        // ์ƒํƒœ ํ‘œ์‹œ์ค„
        statusLabel = new JLabel(" ");
        statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
        add(statusLabel, BorderLayout.SOUTH);
        
        updateStatus();
    }
    
    private void setupAccessibility() {
        // ํ”„๋ ˆ์ž„ ์ ‘๊ทผ์„ฑ
        getAccessibleContext().setAccessibleName("ํ…์ŠคํŠธ ์—๋””ํ„ฐ ๋ฉ”์ธ ์ฐฝ");
        getAccessibleContext().setAccessibleDescription(
            "ํ…์ŠคํŠธ ํŒŒ์ผ์„ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ์—๋””ํ„ฐ์ž…๋‹ˆ๋‹ค");
        
        // ํ…์ŠคํŠธ ์˜์—ญ ์ ‘๊ทผ์„ฑ
        textArea.getAccessibleContext().setAccessibleName("๋ฌธ์„œ ํŽธ์ง‘ ์˜์—ญ");
        textArea.getAccessibleContext().setAccessibleDescription(
            "์—ฌ๊ธฐ์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. " +
            "Ctrl+Z๋กœ ์‹คํ–‰ ์ทจ์†Œ, Ctrl+Y๋กœ ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.");
        
        // ์ƒํƒœ ํ‘œ์‹œ์ค„ ์ ‘๊ทผ์„ฑ
        statusLabel.getAccessibleContext().setAccessibleName("์ƒํƒœ ํ‘œ์‹œ์ค„");
        statusLabel.getAccessibleContext().setAccessibleDescription(
            "ํ˜„์žฌ ๋ฌธ์„œ์˜ ์ƒํƒœ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค");
    }
    
    private JMenuBar createAccessibleMenuBar() {
        JMenuBar menuBar = new JMenuBar();
        
        // ํŒŒ์ผ ๋ฉ”๋‰ด
        JMenu fileMenu = new JMenu("ํŒŒ์ผ(F)");
        fileMenu.setMnemonic('F');
        fileMenu.getAccessibleContext().setAccessibleDescription(
            "ํŒŒ์ผ ๊ด€๋ จ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค");
        
        JMenuItem newItem = createMenuItem(
            "์ƒˆ ํŒŒ์ผ(N)", 'N',
            KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK),
            "์ƒˆ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค",
            e -> newFile()
        );
        fileMenu.add(newItem);
        
        JMenuItem openItem = createMenuItem(
            "์—ด๊ธฐ(O)", 'O',
            KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK),
            "ํŒŒ์ผ์„ ์—ฝ๋‹ˆ๋‹ค",
            e -> openFile()
        );
        fileMenu.add(openItem);
        
        JMenuItem saveItem = createMenuItem(
            "์ €์žฅ(S)", 'S',
            KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK),
            "ํ˜„์žฌ ๋ฌธ์„œ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค",
            e -> saveFile()
        );
        fileMenu.add(saveItem);
        
        JMenuItem saveAsItem = createMenuItem(
            "๋‹ค๋ฅธ ์ด๋ฆ„์œผ๋กœ ์ €์žฅ(A)", 'A',
            KeyStroke.getKeyStroke(KeyEvent.VK_S, 
                InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK),
            "ํ˜„์žฌ ๋ฌธ์„œ๋ฅผ ์ƒˆ ์ด๋ฆ„์œผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค",
            e -> saveFileAs()
        );
        fileMenu.add(saveAsItem);
        
        fileMenu.addSeparator();
        
        JMenuItem exitItem = createMenuItem(
            "์ข…๋ฃŒ(X)", 'X',
            KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK),
            "ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค",
            e -> exitApplication()
        );
        fileMenu.add(exitItem);
        
        menuBar.add(fileMenu);
        
        // ํŽธ์ง‘ ๋ฉ”๋‰ด
        JMenu editMenu = new JMenu("ํŽธ์ง‘(E)");
        editMenu.setMnemonic('E');
        editMenu.getAccessibleContext().setAccessibleDescription(
            "ํŽธ์ง‘ ๊ด€๋ จ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค");
        
        JMenuItem undoItem = createMenuItem(
            "์‹คํ–‰ ์ทจ์†Œ(U)", 'U',
            KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK),
            "๋งˆ์ง€๋ง‰ ์ž‘์—…์„ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค",
            e -> undo()
        );
        editMenu.add(undoItem);
        
        JMenuItem redoItem = createMenuItem(
            "๋‹ค์‹œ ์‹คํ–‰(R)", 'R',
            KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK),
            "์ทจ์†Œํ•œ ์ž‘์—…์„ ๋‹ค์‹œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค",
            e -> redo()
        );
        editMenu.add(redoItem);
        
        editMenu.addSeparator();
        
        JMenuItem selectAllItem = createMenuItem(
            "๋ชจ๋‘ ์„ ํƒ(A)", 'A',
            KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK),
            "๋ชจ๋“  ํ…์ŠคํŠธ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค",
            e -> textArea.selectAll()
        );
        editMenu.add(selectAllItem);
        
        menuBar.add(editMenu);
        
        // ๋„์›€๋ง ๋ฉ”๋‰ด
        JMenu helpMenu = new JMenu("๋„์›€๋ง(H)");
        helpMenu.setMnemonic('H');
        helpMenu.getAccessibleContext().setAccessibleDescription(
            "๋„์›€๋ง๊ณผ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค");
        
        JMenuItem aboutItem = createMenuItem(
            "์ •๋ณด(A)", 'A',
            null,
            "ํ”„๋กœ๊ทธ๋žจ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค",
            e -> showAbout()
        );
        helpMenu.add(aboutItem);
        
        menuBar.add(helpMenu);
        
        return menuBar;
    }
    
    private JMenuItem createMenuItem(
            String text,
            char mnemonic,
            KeyStroke accelerator,
            String description,
            ActionListener action) {
        
        JMenuItem item = new JMenuItem(text);
        item.setMnemonic(mnemonic);
        
        if (accelerator != null) {
            item.setAccelerator(accelerator);
        }
        
        item.getAccessibleContext().setAccessibleDescription(description);
        item.addActionListener(action);
        
        return item;
    }
    
    private void setupKeyboardShortcuts() {
        // ์ถ”๊ฐ€ ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ์„ค์ •
        InputMap inputMap = textArea.getInputMap(
            JComponent.WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = textArea.getActionMap();
        
        // Ctrl+Z: ์‹คํ–‰ ์ทจ์†Œ
        inputMap.put(
            KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK),
            "undo");
        actionMap.put("undo", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                undo();
            }
        });
        
        // Ctrl+Y: ๋‹ค์‹œ ์‹คํ–‰
        inputMap.put(
            KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK),
            "redo");
        actionMap.put("redo", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                redo();
            }
        });
    }
    
    private void newFile() {
        if (checkSaveBeforeAction()) {
            textArea.setText("");
            currentFile = null;
            isModified = false;
            updateTitle();
            updateStatus();
            announceToScreenReader("์ƒˆ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค");
        }
    }
    
    private void openFile() {
        if (!checkSaveBeforeAction()) {
            return;
        }
        
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("ํŒŒ์ผ ์—ด๊ธฐ");
        
        // ํŒŒ์ผ ์„ ํƒ๊ธฐ ์ ‘๊ทผ์„ฑ
        fileChooser.getAccessibleContext().setAccessibleDescription(
            "์—ด ํŒŒ์ผ์„ ์„ ํƒํ•˜์„ธ์š”");
        
        int result = fileChooser.showOpenDialog(this);
        
        if (result == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            try {
                StringBuilder content = new StringBuilder();
                BufferedReader reader = new BufferedReader(
                    new FileReader(file));
                String line;
                while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                reader.close();
                
                textArea.setText(content.toString());
                currentFile = file;
                isModified = false;
                updateTitle();
                updateStatus();
                announceToScreenReader("ํŒŒ์ผ์„ ์—ด์—ˆ์Šต๋‹ˆ๋‹ค: " + 
                    file.getName());
                
            } catch (IOException ex) {
                showError("ํŒŒ์ผ์„ ์—ด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + ex.getMessage());
            }
        }
    }
    
    private void saveFile() {
        if (currentFile == null) {
            saveFileAs();
        } else {
            try {
                BufferedWriter writer = new BufferedWriter(
                    new FileWriter(currentFile));
                writer.write(textArea.getText());
                writer.close();
                
                isModified = false;
                updateTitle();
                updateStatus();
                announceToScreenReader("ํŒŒ์ผ์„ ์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค");
                
            } catch (IOException ex) {
                showError("ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + ex.getMessage());
            }
        }
    }
    
    private void saveFileAs() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("๋‹ค๋ฅธ ์ด๋ฆ„์œผ๋กœ ์ €์žฅ");
        
        // ํŒŒ์ผ ์„ ํƒ๊ธฐ ์ ‘๊ทผ์„ฑ
        fileChooser.getAccessibleContext().setAccessibleDescription(
            "์ €์žฅํ•  ํŒŒ์ผ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”");
        
        int result = fileChooser.showSaveDialog(this);
        
        if (result == JFileChooser.APPROVE_OPTION) {
            currentFile = fileChooser.getSelectedFile();
            saveFile();
        }
    }
    
    private void undo() {
        if (undoManager.canUndo()) {
            undoManager.undo();
            announceToScreenReader("์‹คํ–‰ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค");
        } else {
            announceToScreenReader("๋” ์ด์ƒ ์ทจ์†Œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค");
        }
    }
    
    private void redo() {
        if (undoManager.canRedo()) {
            undoManager.redo();
            announceToScreenReader("๋‹ค์‹œ ์‹คํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค");
        } else {
            announceToScreenReader("๋” ์ด์ƒ ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค");
        }
    }
    
    private boolean checkSaveBeforeAction() {
        if (isModified) {
            int result = JOptionPane.showConfirmDialog(
                this,
                "๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ €์žฅํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
                "ํ™•์ธ",
                JOptionPane.YES_NO_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE
            );
            
            if (result == JOptionPane.YES_OPTION) {
                saveFile();
                return !isModified; // ์ €์žฅ ์„ฑ๊ณต ์‹œ true
            } else if (result == JOptionPane.CANCEL_OPTION) {
                return false;
            }
        }
        return true;
    }
    
    private void exitApplication() {
        if (checkSaveBeforeAction()) {
            System.exit(0);
        }
    }
    
    private void showAbout() {
        String message = 
            "์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์—๋””ํ„ฐ v1.0\n\n" +
            "๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ ํ…์ŠคํŠธ ํŽธ์ง‘๊ธฐ์ž…๋‹ˆ๋‹ค.\n" +
            "์™„์ „ํ•œ ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ๊ณผ ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์ง€์›์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.\n\n" +
            "๋‹จ์ถ•ํ‚ค:\n" +
            "Ctrl+N: ์ƒˆ ํŒŒ์ผ\n" +
            "Ctrl+O: ์—ด๊ธฐ\n" +
            "Ctrl+S: ์ €์žฅ\n" +
            "Ctrl+Z: ์‹คํ–‰ ์ทจ์†Œ\n" +
            "Ctrl+Y: ๋‹ค์‹œ ์‹คํ–‰";
        
        JOptionPane.showMessageDialog(
            this,
            message,
            "์ •๋ณด",
            JOptionPane.INFORMATION_MESSAGE
        );
    }
    
    private void showError(String message) {
        JOptionPane.showMessageDialog(
            this,
            message,
            "์˜ค๋ฅ˜",
            JOptionPane.ERROR_MESSAGE
        );
        announceToScreenReader("์˜ค๋ฅ˜: " + message);
    }
    
    private void documentChanged() {
        isModified = true;
        updateTitle();
        updateStatus();
    }
    
    private void updateTitle() {
        String title = "์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ํ…์ŠคํŠธ ์—๋””ํ„ฐ";
        if (currentFile != null) {
            title += " - " + currentFile.getName();
        }
        if (isModified) {
            title += " *";
        }
        setTitle(title);
    }
    
    private void updateStatus() {
        int lines = textArea.getLineCount();
        int chars = textArea.getText().length();
        String status = String.format(
            "์ค„: %d | ๋ฌธ์ž: %d%s",
            lines,
            chars,
            isModified ? " | ์ˆ˜์ •๋จ" : ""
        );
        statusLabel.setText(status);
        
        // ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์— ์•Œ๋ฆผ
        statusLabel.getAccessibleContext().firePropertyChange(
            AccessibleContext.ACCESSIBLE_TEXT_PROPERTY,
            null,
            status
        );
    }
    
    /**
     * ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์— ๋ฉ”์‹œ์ง€๋ฅผ ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
     */
    private void announceToScreenReader(String message) {
        // ์ž„์‹œ ๋ ˆ์ด๋ธ”์„ ์‚ฌ์šฉํ•œ ์•Œ๋ฆผ
        JLabel announcement = new JLabel(message);
        announcement.getAccessibleContext().setAccessibleName(message);
        announcement.getAccessibleContext().firePropertyChange(
            AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
            null,
            message
        );
    }
    
    public static void main(String[] args) {
        // Look and Feel ์„ค์ •
        try {
            UIManager.setLookAndFeel(
                UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        SwingUtilities.invokeLater(() -> new AccessibleTextEditor());
    }
}

์ด ํ…์ŠคํŠธ ์—๋””ํ„ฐ๋Š” ์™„์ „ํ•œ ์ ‘๊ทผ์„ฑ์„ ๊ฐ–์ถ”๊ณ  ์žˆ์–ด:

โœจ ๊ตฌํ˜„๋œ ์ ‘๊ทผ์„ฑ ๊ธฐ๋Šฅ๋“ค
  • โœ… ๋ชจ๋“  ๊ธฐ๋Šฅ์— ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค ์ œ๊ณต
  • โœ… ๋ฉ”๋‰ด์™€ ๋ฉ”๋‰ด ํ•ญ๋ชฉ์— ๋‹ˆ๋ชจ๋‹‰ ์„ค์ •
  • โœ… ๋ชจ๋“  UI ์š”์†Œ์— ๋ช…ํ™•ํ•œ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„๊ณผ ์„ค๋ช…
  • โœ… ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์— ์•Œ๋ฆผ
  • โœ… ํŒŒ์ผ ๋‹ค์ด์–ผ๋กœ๊ทธ์˜ ์ ‘๊ทผ์„ฑ ์„ค์ •
  • โœ… ์‹คํ–‰ ์ทจ์†Œ/๋‹ค์‹œ ์‹คํ–‰ ๊ธฐ๋Šฅ
  • โœ… ์ €์žฅ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ
  • โœ… ์ƒํƒœ ํ‘œ์‹œ์ค„์„ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ์ •๋ณด ์ œ๊ณต

์ด๋Ÿฐ ์™„์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ๋•Œ๋„ ์ ‘๊ทผ์„ฑ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ณ ๋ คํ•˜๋ฉด, ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๋™๋“ฑํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐ŸŽ‰

๐Ÿ”ฎ ๋ฏธ๋ž˜์˜ ์ ‘๊ทผ์„ฑ: ํŠธ๋ Œ๋“œ์™€ ์ „๋ง

์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์žˆ์„์ง€ ์‚ดํŽด๋ณด์ž! ๐Ÿš€

๐Ÿค– AI์™€ ๋จธ์‹ ๋Ÿฌ๋‹์„ ํ™œ์šฉํ•œ ์ ‘๊ทผ์„ฑ

์ธ๊ณต์ง€๋Šฅ ๊ธฐ์ˆ ์ด ์ ‘๊ทผ์„ฑ ๋ถ„์•ผ์—๋„ ํฐ ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์–ด:

๐ŸŒŸ AI ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ 

1. ์ž๋™ ๋Œ€์ฒด ํ…์ŠคํŠธ ์ƒ์„ฑ
์ด๋ฏธ์ง€ ์ธ์‹ AI๊ฐ€ ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€์˜ ๋‚ด์šฉ์„ ์„ค๋ช…ํ•˜๋Š” ๋Œ€์ฒด ํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ค˜. ๊ฐœ๋ฐœ์ž๊ฐ€ ์ผ์ผ์ด ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ๊ฑฐ์ง€.

2. ์‹ค์‹œ๊ฐ„ ์Œ์„ฑ ์ธ์‹๊ณผ ์ž๋ง‰
์˜ค๋””์˜ค ์ฝ˜ํ…์ธ ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ฒญ๊ฐ ์žฅ์• ์ธ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

3. ์ž๋™ ์ƒ‰์ƒ ๋Œ€๋น„ ์กฐ์ •
AI๊ฐ€ ์ƒ‰์ƒ ์กฐํ•ฉ์„ ๋ถ„์„ํ•ด์„œ ์ž๋™์œผ๋กœ ์ถฉ๋ถ„ํ•œ ๋Œ€๋น„๋ฅผ ์ œ๊ณตํ•˜๋„๋ก ์กฐ์ •ํ•ด์ค˜.

4. ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ๋„์›€๋ง
์‚ฌ์šฉ์ž์˜ ํ–‰๋™ ํŒจํ„ด์„ ํ•™์Šตํ•ด์„œ ํ•„์š”ํ•œ ์ˆœ๊ฐ„์— ์ ์ ˆํ•œ ๋„์›€๋ง์„ ์ œ๊ณตํ•ด์ค˜.

๐ŸŒ ์›น๊ณผ ๋„ค์ดํ‹ฐ๋ธŒ์˜ ๊ฒฝ๊ณ„ ํ—ˆ๋ฌผ๊ธฐ

Progressive Web Apps (PWA)์™€ ๊ฐ™์€ ๊ธฐ์ˆ ์ด ๋ฐœ์ „ํ•˜๋ฉด์„œ, ์›น๊ณผ ๋„ค์ดํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ๊ณ„๊ฐ€ ํ๋ ค์ง€๊ณ  ์žˆ์–ด. ์ž๋ฐ” ๊ฐœ๋ฐœ์ž๋„ ์ด๋Ÿฐ ํŠธ๋ Œ๋“œ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•ด:

// JavaFX์™€ ์›น ๊ธฐ์ˆ ์˜ ํ†ตํ•ฉ ์˜ˆ์‹œ
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class HybridAccessibleApp extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        WebEngine engine = webView.getEngine();
        
        // ์ž๋ฐ”์™€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ„ ๋ธŒ๋ฆฌ์ง€
        engine.getLoadWorker().stateProperty().addListener(
            (obs, oldState, newState) -> {
                if (newState == javafx.concurrent.Worker.State.SUCCEEDED) {
                    JSObject window = (JSObject) engine.executeScript("window");
                    window.setMember("javaApp", new JavaBridge());
                }
            }
        );
        
        // ์ ‘๊ทผ์„ฑ์ด ๊ณ ๋ ค๋œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•ฑ ๋กœ๋“œ
        String html = """
            
            
            
                <meta charset="UTF-8">
                <title>ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์•ฑ</title>
            
            
                <h1>ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜</h1>
                <button onclick="javaApp.performAction('save')" aria-label="์ €์žฅ">
                    ์ €์žฅ
                </button>
                <div role="status" aria-live="polite" id="status">
                    ์ค€๋น„๋จ
                </div>
            
            
            """;
        
        engine.loadContent(html);
        
        Scene scene = new Scene(webView, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.setTitle("ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์•ฑ");
        primaryStage.show();
    }
    
    // ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์ž๋ฐ” ํด๋ž˜์Šค
    public class JavaBridge {
        public void performAction(String action) {
            System.out.println("์ž๋ฐ”์—์„œ ์‹คํ–‰: " + action);
            // ์‹ค์ œ ์ž๋ฐ” ๋กœ์ง ์‹คํ–‰
        }
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ๊ณผ ํฌ๋กœ์Šค ํ”Œ๋žซํผ

์ž๋ฐ”๋Š” Android ๊ฐœ๋ฐœ์—์„œ๋„ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๊ณ  ์žˆ์–ด. ๋ชจ๋ฐ”์ผ ์ ‘๊ทผ์„ฑ๋„ ๊ณ ๋ คํ•ด์•ผ ํ•ด:

๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ์ ‘๊ทผ์„ฑ ๊ณ ๋ ค์‚ฌํ•ญ

โ€ข ํ„ฐ์น˜ ํƒ€๊ฒŸ ํฌ๊ธฐ: ์ตœ์†Œ 44x44 ํ”ฝ์…€ (iOS), 48x48dp (Android)
โ€ข ์ œ์Šค์ฒ˜ ๋Œ€์•ˆ: ๋ณต์žกํ•œ ์ œ์Šค์ฒ˜์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ๋Œ€์•ˆ ์ œ๊ณต
โ€ข ํ™”๋ฉด ํšŒ์ „: ๊ฐ€๋กœ/์„ธ๋กœ ๋ชจ๋“œ ๋ชจ๋‘ ์ง€์›
โ€ข ๋™์  ํƒ€์ž…: ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ๊ธ€๊ผด ํฌ๊ธฐ ์กด์ค‘
โ€ข ๋‹คํฌ ๋ชจ๋“œ: ์‹œ์Šคํ…œ ์„ค์ •์— ๋”ฐ๋ฅธ ํ…Œ๋งˆ ๋ณ€๊ฒฝ

๐Ÿ’ผ ์žฌ๋Šฅ๋„ท์—์„œ ์ ‘๊ทผ์„ฑ ์ „๋ฌธ๊ฐ€ ๋˜๊ธฐ

์ ‘๊ทผ์„ฑ์€ ์ด์ œ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜์•ผ. ๊ทธ๋ฆฌ๊ณ  ์ด ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€๋Š” ์•„์ง ๋งŽ์ง€ ์•Š์•„์„œ, ํฐ ๊ธฐํšŒ๊ฐ€ ์žˆ์–ด! ๐ŸŒŸ

์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ๋Š” ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์žฌ๋Šฅ์„ ๊ฑฐ๋ž˜ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ ‘๊ทผ์„ฑ ์ปจ์„คํŒ…์ด๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฉด ์ •๋ง ์ข‹์€ ๋ฐ˜์‘์„ ์–ป์„ ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ. ๋งŽ์€ ๊ธฐ์—…๋“ค์ด ์ ‘๊ทผ์„ฑ ์ค€์ˆ˜๋ฅผ ๋ฒ•์ ์œผ๋กœ ์š”๊ตฌ๋ฐ›๊ณ  ์žˆ์ง€๋งŒ, ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผ ํ• ์ง€ ๋ชจ๋ฅด๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ฑฐ๋“ .

๐Ÿ’ก ์žฌ๋Šฅ๋„ท์—์„œ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ์ ‘๊ทผ์„ฑ ๊ด€๋ จ ์„œ๋น„์Šค

๐ŸŽฏ ์ ‘๊ทผ์„ฑ ๊ฐ์‚ฌ ๋ฐ ์ปจ์„คํŒ…
๊ธฐ์กด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ์„ฑ ๋ฌธ์ œ๋ฅผ ์ง„๋‹จํ•˜๊ณ  ๊ฐœ์„  ๋ฐฉ์•ˆ์„ ์ œ์‹œ

๐ŸŽฏ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ UI ์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ
์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ ‘๊ทผ์„ฑ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ์ž‘

๐ŸŽฏ ์ ‘๊ทผ์„ฑ ๊ต์œก ๋ฐ ๋ฉ˜ํ† ๋ง
๊ฐœ๋ฐœํŒ€์—๊ฒŒ ์ ‘๊ทผ์„ฑ ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก  ๊ต์œก

๐ŸŽฏ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ ๋„๊ตฌ ๊ฐœ๋ฐœ
์ ‘๊ทผ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” ๋„๊ตฌ ์ œ์ž‘

๐ŸŽฏ ์ ‘๊ทผ์„ฑ ๋ฌธ์„œํ™”
VPAT(Voluntary Product Accessibility Template) ์ž‘์„ฑ ์ง€์›

์ ‘๊ทผ์„ฑ ์ „๋ฌธ ์ง€์‹์„ ๊ฐ–์ถ˜ ๊ฐœ๋ฐœ์ž๋Š” ์‹œ์žฅ์—์„œ ๋†’์€ ๊ฐ€์น˜๋ฅผ ์ธ์ •๋ฐ›์•„. ํŠนํžˆ ๊ณต๊ณต๊ธฐ๊ด€์ด๋‚˜ ๋Œ€๊ธฐ์—…์—์„œ๋Š” ์ ‘๊ทผ์„ฑ ์ค€์ˆ˜๊ฐ€ ํ•„์ˆ˜์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€์— ๋Œ€ํ•œ ์ˆ˜์š”๊ฐ€ ๊ณ„์† ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ์–ด! ๐Ÿ“ˆ

๐ŸŽ“ ํ•™์Šต ๋ฆฌ์†Œ์Šค์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ

์ ‘๊ทผ์„ฑ์— ๋Œ€ํ•ด ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๋‹ค์Œ ๋ฆฌ์†Œ์Šค๋“ค์„ ํ™œ์šฉํ•ด๋ด:

๐Ÿ“š ์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ

๊ณต์‹ ๋ฌธ์„œ
โ€ข Oracle Java Accessibility Guide
โ€ข Java Accessibility API Specification
โ€ข WCAG 2.1 Guidelines

๋„๊ตฌ
โ€ข Java Access Bridge
โ€ข NVDA (๋ฌด๋ฃŒ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”)
โ€ข aXe DevTools (์›น ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ)
โ€ข Color Contrast Analyzer

์ปค๋ฎค๋‹ˆํ‹ฐ
โ€ข WebAIM (Web Accessibility In Mind)
โ€ข A11y Project
โ€ข Inclusive Design 24 (์—ฐ๋ก€ ์ปจํผ๋Ÿฐ์Šค)

๋ฒ•๊ทœ ๋ฐ ํ‘œ์ค€
โ€ข ํ•œ๊ตญ ์›น ์ ‘๊ทผ์„ฑ ์ธ์ฆ์ œ๋„
โ€ข ์žฅ์• ์ธ์ฐจ๋ณ„๊ธˆ์ง€๋ฒ•
โ€ข Section 508 (๋ฏธ๊ตญ)
โ€ข EN 301 549 (์œ ๋Ÿฝ)

์ ‘๊ทผ์„ฑ์€ ๋‹จ์ˆœํžˆ ๊ธฐ์ˆ ์ ์ธ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ, ์‚ฌํšŒ์  ์ฑ…์ž„์ด์ž ์œค๋ฆฌ์  ์˜๋ฌด์•ผ. ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“œ๋Š” ์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ์ •๋ง ๋ชจ๋“  ์‚ฌ๋žŒ์„ ์œ„ํ•œ ๊ฒƒ์ด ๋˜๋ ค๋ฉด, ์ ‘๊ทผ์„ฑ์„ ํ•ญ์ƒ ์—ผ๋‘์— ๋‘๊ณ  ๊ฐœ๋ฐœํ•ด์•ผ ํ•ด. ๐ŸŒˆ

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ง€? ์šฐ๋ฆฌ๋Š” ์ž๋ฐ”์˜ ์ ‘๊ทผ์„ฑ API๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ๊ทธ๋ฆฌ๊ณ  ๋ฏธ๋ž˜ ํŠธ๋ Œ๋“œ๊นŒ์ง€ ๋ชจ๋‘ ์‚ดํŽด๋ดค์–ด. ๐ŸŽ‰

์ ‘๊ทผ์„ฑ์€ ์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•˜๊ณ  ์–ด๋ ค์›Œ ๋ณด์ผ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ํ•œ ๋ฒˆ ์ต์ˆ™ํ•ด์ง€๋ฉด, ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋ผ. ๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์ค‘์š”ํ•œ ๊ฑด, ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ๋” ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋„์›€์ด ๋œ๋‹ค๋Š” ๋ณด๋žŒ์ด์•ผ. ๐Ÿ˜Š

๐ŸŒŸ ํ•ต์‹ฌ ์š”์•ฝ

1๏ธโƒฃ ์ ‘๊ทผ์„ฑ์€ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜
์ „ ์„ธ๊ณ„ ์ธ๊ตฌ์˜ 15%๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ๋กœ๋“  ์žฅ์• ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด. ์ด๋“ค๋„ ์šฐ๋ฆฌ์˜ ์‚ฌ์šฉ์ž์•ผ.

2๏ธโƒฃ Java Accessibility API ํ™œ์šฉ
Accessible, AccessibleContext, AccessibleRole ๋“ฑ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•˜์ž.

3๏ธโƒฃ ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์„ฑ
๋ชจ๋“  ๊ธฐ๋Šฅ์ด ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ด. ๋งˆ์šฐ์Šค๋Š” ์„ ํƒ์‚ฌํ•ญ์ด์•ผ.

4๏ธโƒฃ ๋ช…ํ™•ํ•œ ๋ ˆ์ด๋ธ”๊ณผ ์„ค๋ช…
๋ชจ๋“  UI ์š”์†Œ์— ์˜๋ฏธ ์žˆ๋Š” ์ด๋ฆ„๊ณผ ์„ค๋ช…์„ ์ œ๊ณตํ•˜์ž.

5๏ธโƒฃ ํ…Œ์ŠคํŠธ, ํ…Œ์ŠคํŠธ, ํ…Œ์ŠคํŠธ
์ž๋™ํ™” ํ…Œ์ŠคํŠธ์™€ ์‹ค์ œ ๋ณด์กฐ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•œ ์ˆ˜๋™ ํ…Œ์ŠคํŠธ๋ฅผ ๋ณ‘ํ–‰ํ•˜์ž.

6๏ธโƒฃ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ณ ๋ คํ•˜๊ธฐ
์ ‘๊ทผ์„ฑ์€ ๋‚˜์ค‘์— ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ฒ˜์Œ๋ถ€ํ„ฐ ์„ค๊ณ„์— ํฌํ•จ๋˜์–ด์•ผ ํ•ด.

7๏ธโƒฃ ์ง€์†์ ์ธ ํ•™์Šต
์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ๊ณผ ํ‘œ์ค€์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์ตœ์‹  ํŠธ๋ Œ๋“œ๋ฅผ ๋”ฐ๋ผ๊ฐ€์ž.

์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์ง€์‹๊ณผ ๋„๊ตฌ๋ฅผ ๊ฐ–์ถ”์—ˆ์–ด! ๐ŸŽ“

๋‹ค์Œ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ, ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ๋“ค์„ ์ ์šฉํ•ด๋ด. ์ฒ˜์Œ์—๋Š” ์‹œ๊ฐ„์ด ์ข€ ๋” ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ณง ์ž์—ฐ์Šค๋Ÿฌ์šด ์Šต๊ด€์ด ๋  ๊ฑฐ์•ผ. ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๋ถ„์ด ๋งŒ๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ชจ๋“  ์‚ฌ๋žŒ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์— ์ž๋ถ€์‹ฌ์„ ๋А๋‚„ ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿ’ช

ํ˜น์‹œ ์ ‘๊ทผ์„ฑ ๊ด€๋ จํ•ด์„œ ๋„์›€์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜, ๋” ๊นŠ์ด ์žˆ๋Š” ์ง€์‹์„ ์–ป๊ณ  ์‹ถ๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€๋“ค์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์–ด. ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ํž˜์„ ํ™œ์šฉํ•˜์ž! ๐Ÿค

์ž, ์ด์ œ ๋‚˜๊ฐ€์„œ ๋ชจ๋‘๋ฅผ ์œ„ํ•œ ๋ฉ‹์ง„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์ž! ํ™”์ดํŒ…! ๐Ÿš€โœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.