d46ef54998ba15d388d23488863c02e6ef86bc88
[nikiroo-utils.git] / src / jexer / TEditColorThemeWindow.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer;
32
33 import java.util.List;
34
35 import jexer.bits.Color;
36 import jexer.bits.ColorTheme;
37 import jexer.bits.CellAttributes;
38 import jexer.bits.GraphicsChars;
39 import jexer.event.TKeypressEvent;
40 import jexer.event.TMouseEvent;
41 import static jexer.TKeypress.*;
42
43 /**
44 * TEditColorThemeWindow provides an easy UI for users to alter the running
45 * color theme.
46 *
47 */
48 public final class TEditColorThemeWindow extends TWindow {
49
50 /**
51 * The foreground color picker.
52 */
53 class ForegroundPicker extends TWidget {
54
55 /**
56 * The selected color.
57 */
58 Color color;
59
60 /**
61 * The bold flag.
62 */
63 boolean bold;
64
65 /**
66 * Public constructor.
67 *
68 * @param parent parent widget
69 * @param x column relative to parent
70 * @param y row relative to parent
71 * @param width width of text area
72 * @param height height of text area
73 */
74 public ForegroundPicker(final TWidget parent, final int x,
75 final int y, final int width, final int height) {
76
77 super(parent, x, y, width, height);
78 }
79
80 /**
81 * Get the X grid coordinate for this color.
82 *
83 * @param color the Color value
84 * @return the X coordinate
85 */
86 private int getXColorPosition(final Color color) {
87 if (color.equals(Color.BLACK)) {
88 return 2;
89 } else if (color.equals(Color.BLUE)) {
90 return 5;
91 } else if (color.equals(Color.GREEN)) {
92 return 8;
93 } else if (color.equals(Color.CYAN)) {
94 return 11;
95 } else if (color.equals(Color.RED)) {
96 return 2;
97 } else if (color.equals(Color.MAGENTA)) {
98 return 5;
99 } else if (color.equals(Color.YELLOW)) {
100 return 8;
101 } else if (color.equals(Color.WHITE)) {
102 return 11;
103 }
104 throw new IllegalArgumentException("Invalid color: " + color);
105 }
106
107 /**
108 * Get the Y grid coordinate for this color.
109 *
110 * @param color the Color value
111 * @param bold if true use bold color
112 * @return the Y coordinate
113 */
114 private int getYColorPosition(final Color color, final boolean bold) {
115 int dotY = 1;
116 if (color.equals(Color.RED)) {
117 dotY = 2;
118 } else if (color.equals(Color.MAGENTA)) {
119 dotY = 2;
120 } else if (color.equals(Color.YELLOW)) {
121 dotY = 2;
122 } else if (color.equals(Color.WHITE)) {
123 dotY = 2;
124 }
125 if (bold) {
126 dotY += 2;
127 }
128 return dotY;
129 }
130
131 /**
132 * Get the bold value based on Y grid coordinate.
133 *
134 * @param dotY the Y coordinate
135 * @return the bold value
136 */
137 private boolean getBoldFromPosition(final int dotY) {
138 if (dotY > 2) {
139 return true;
140 }
141 return false;
142 }
143
144 /**
145 * Get the color based on (X, Y) grid coordinate.
146 *
147 * @param dotX the X coordinate
148 * @param dotY the Y coordinate
149 * @return the Color value
150 */
151 private Color getColorFromPosition(final int dotX, final int dotY) {
152 int y = dotY;
153 if (y > 2) {
154 y -= 2;
155 }
156 if ((1 <= dotX) && (dotX <= 3) && (y == 1)) {
157 return Color.BLACK;
158 }
159 if ((4 <= dotX) && (dotX <= 6) && (y == 1)) {
160 return Color.BLUE;
161 }
162 if ((7 <= dotX) && (dotX <= 9) && (y == 1)) {
163 return Color.GREEN;
164 }
165 if ((10 <= dotX) && (dotX <= 12) && (y == 1)) {
166 return Color.CYAN;
167 }
168 if ((1 <= dotX) && (dotX <= 3) && (y == 2)) {
169 return Color.RED;
170 }
171 if ((4 <= dotX) && (dotX <= 6) && (y == 2)) {
172 return Color.MAGENTA;
173 }
174 if ((7 <= dotX) && (dotX <= 9) && (y == 2)) {
175 return Color.YELLOW;
176 }
177 if ((10 <= dotX) && (dotX <= 12) && (y == 2)) {
178 return Color.WHITE;
179 }
180
181 throw new IllegalArgumentException("Invalid coordinates: "
182 + dotX + ", " + dotY);
183 }
184
185 /**
186 * Draw the foreground colors grid.
187 */
188 @Override
189 public void draw() {
190 CellAttributes border = getWindow().getBorder();
191 CellAttributes background = getWindow().getBackground();
192 CellAttributes attr = new CellAttributes();
193
194 getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
195 background, 1, false);
196
197 attr.setTo(getTheme().getColor("twindow.background.modal"));
198 if (isActive()) {
199 attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
200 attr.setBold(getTheme().getColor("tlabel").isBold());
201 }
202 getScreen().putStringXY(1, 0, " Foreground ", attr);
203
204 // Have to draw the colors manually because the int value matches
205 // SGR, not CGA.
206 attr.reset();
207 attr.setReverse(true);
208 attr.setForeColor(Color.BLACK);
209 putStringXY(1, 1, " ", attr);
210 attr.setForeColor(Color.BLUE);
211 putStringXY(4, 1, " ", attr);
212 attr.setForeColor(Color.GREEN);
213 putStringXY(7, 1, " ", attr);
214 attr.setForeColor(Color.CYAN);
215 putStringXY(10, 1, " ", attr);
216 attr.setForeColor(Color.RED);
217 putStringXY(1, 2, " ", attr);
218 attr.setForeColor(Color.MAGENTA);
219 putStringXY(4, 2, " ", attr);
220 attr.setForeColor(Color.YELLOW);
221 putStringXY(7, 2, " ", attr);
222 attr.setForeColor(Color.WHITE);
223 putStringXY(10, 2, " ", attr);
224
225 attr.setBold(true);
226 attr.setForeColor(Color.BLACK);
227 putStringXY(1, 3, " ", attr);
228 attr.setForeColor(Color.BLUE);
229 putStringXY(4, 3, " ", attr);
230 attr.setForeColor(Color.GREEN);
231 putStringXY(7, 3, " ", attr);
232 attr.setForeColor(Color.CYAN);
233 putStringXY(10, 3, " ", attr);
234 attr.setForeColor(Color.RED);
235 putStringXY(1, 4, " ", attr);
236 attr.setForeColor(Color.MAGENTA);
237 putStringXY(4, 4, " ", attr);
238 attr.setForeColor(Color.YELLOW);
239 putStringXY(7, 4, " ", attr);
240 attr.setForeColor(Color.WHITE);
241 putStringXY(10, 4, " ", attr);
242
243 // Draw the dot
244 int dotX = getXColorPosition(color);
245 int dotY = getYColorPosition(color, bold);
246 if (color.equals(Color.BLACK) && !bold) {
247 // Use white-on-black for black. All other colors use
248 // black-on-whatever.
249 attr.reset();
250 getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07],
251 attr);
252 } else {
253 getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07]);
254 }
255 }
256
257 /**
258 * Handle keystrokes.
259 *
260 * @param keypress keystroke event
261 */
262 @Override
263 public void onKeypress(final TKeypressEvent keypress) {
264 if (keypress.equals(kbRight)) {
265 int dotX = getXColorPosition(color);
266 int dotY = getYColorPosition(color, bold);
267 if (dotX < 10) {
268 dotX += 3;
269 }
270 color = getColorFromPosition(dotX, dotY);
271 } else if (keypress.equals(kbLeft)) {
272 int dotX = getXColorPosition(color);
273 int dotY = getYColorPosition(color, bold);
274 if (dotX > 3) {
275 dotX -= 3;
276 }
277 color = getColorFromPosition(dotX, dotY);
278 } else if (keypress.equals(kbUp)) {
279 int dotX = getXColorPosition(color);
280 int dotY = getYColorPosition(color, bold);
281 if (dotY > 1) {
282 dotY--;
283 }
284 color = getColorFromPosition(dotX, dotY);
285 bold = getBoldFromPosition(dotY);
286 } else if (keypress.equals(kbDown)) {
287 int dotX = getXColorPosition(color);
288 int dotY = getYColorPosition(color, bold);
289 if (dotY < 4) {
290 dotY++;
291 }
292 color = getColorFromPosition(dotX, dotY);
293 bold = getBoldFromPosition(dotY);
294 } else {
295 // Pass to my parent
296 super.onKeypress(keypress);
297 return;
298 }
299
300 // Save this update to the local theme.
301 ((TEditColorThemeWindow) getWindow()).saveToEditTheme();
302 }
303
304 /**
305 * Handle mouse press events.
306 *
307 * @param mouse mouse button press event
308 */
309 @Override
310 public void onMouseDown(final TMouseEvent mouse) {
311 if (mouse.isMouseWheelUp()) {
312 // Do this like kbUp
313 int dotX = getXColorPosition(color);
314 int dotY = getYColorPosition(color, bold);
315 if (dotY > 1) {
316 dotY--;
317 }
318 color = getColorFromPosition(dotX, dotY);
319 bold = getBoldFromPosition(dotY);
320 } else if (mouse.isMouseWheelDown()) {
321 // Do this like kbDown
322 int dotX = getXColorPosition(color);
323 int dotY = getYColorPosition(color, bold);
324 if (dotY < 4) {
325 dotY++;
326 }
327 color = getColorFromPosition(dotX, dotY);
328 bold = getBoldFromPosition(dotY);
329 } else if ((mouse.getX() > 0)
330 && (mouse.getX() < getWidth() - 1)
331 && (mouse.getY() > 0)
332 && (mouse.getY() < getHeight() - 1)
333 ) {
334 color = getColorFromPosition(mouse.getX(), mouse.getY());
335 bold = getBoldFromPosition(mouse.getY());
336 } else {
337 // Let parent class handle it.
338 super.onMouseDown(mouse);
339 return;
340 }
341
342 // Save this update to the local theme.
343 ((TEditColorThemeWindow) getWindow()).saveToEditTheme();
344 }
345
346 }
347
348 /**
349 * The background color picker.
350 */
351 class BackgroundPicker extends TWidget {
352
353 /**
354 * The selected color.
355 */
356 Color color;
357
358 /**
359 * Public constructor.
360 *
361 * @param parent parent widget
362 * @param x column relative to parent
363 * @param y row relative to parent
364 * @param width width of text area
365 * @param height height of text area
366 */
367 public BackgroundPicker(final TWidget parent, final int x,
368 final int y, final int width, final int height) {
369
370 super(parent, x, y, width, height);
371 }
372
373 /**
374 * Get the X grid coordinate for this color.
375 *
376 * @param color the Color value
377 * @return the X coordinate
378 */
379 private int getXColorPosition(final Color color) {
380 if (color.equals(Color.BLACK)) {
381 return 2;
382 } else if (color.equals(Color.BLUE)) {
383 return 5;
384 } else if (color.equals(Color.GREEN)) {
385 return 8;
386 } else if (color.equals(Color.CYAN)) {
387 return 11;
388 } else if (color.equals(Color.RED)) {
389 return 2;
390 } else if (color.equals(Color.MAGENTA)) {
391 return 5;
392 } else if (color.equals(Color.YELLOW)) {
393 return 8;
394 } else if (color.equals(Color.WHITE)) {
395 return 11;
396 }
397 throw new IllegalArgumentException("Invalid color: " + color);
398 }
399
400 /**
401 * Get the Y grid coordinate for this color.
402 *
403 * @param color the Color value
404 * @return the Y coordinate
405 */
406 private int getYColorPosition(final Color color) {
407 int dotY = 1;
408 if (color.equals(Color.RED)) {
409 dotY = 2;
410 } else if (color.equals(Color.MAGENTA)) {
411 dotY = 2;
412 } else if (color.equals(Color.YELLOW)) {
413 dotY = 2;
414 } else if (color.equals(Color.WHITE)) {
415 dotY = 2;
416 }
417 return dotY;
418 }
419
420 /**
421 * Get the color based on (X, Y) grid coordinate.
422 *
423 * @param dotX the X coordinate
424 * @param dotY the Y coordinate
425 * @return the Color value
426 */
427 private Color getColorFromPosition(final int dotX, final int dotY) {
428 if ((1 <= dotX) && (dotX <= 3) && (dotY == 1)) {
429 return Color.BLACK;
430 }
431 if ((4 <= dotX) && (dotX <= 6) && (dotY == 1)) {
432 return Color.BLUE;
433 }
434 if ((7 <= dotX) && (dotX <= 9) && (dotY == 1)) {
435 return Color.GREEN;
436 }
437 if ((10 <= dotX) && (dotX <= 12) && (dotY == 1)) {
438 return Color.CYAN;
439 }
440 if ((1 <= dotX) && (dotX <= 3) && (dotY == 2)) {
441 return Color.RED;
442 }
443 if ((4 <= dotX) && (dotX <= 6) && (dotY == 2)) {
444 return Color.MAGENTA;
445 }
446 if ((7 <= dotX) && (dotX <= 9) && (dotY == 2)) {
447 return Color.YELLOW;
448 }
449 if ((10 <= dotX) && (dotX <= 12) && (dotY == 2)) {
450 return Color.WHITE;
451 }
452
453 throw new IllegalArgumentException("Invalid coordinates: "
454 + dotX + ", " + dotY);
455 }
456
457 /**
458 * Draw the background colors grid.
459 */
460 @Override
461 public void draw() {
462 CellAttributes border = getWindow().getBorder();
463 CellAttributes background = getWindow().getBackground();
464 CellAttributes attr = new CellAttributes();
465
466 getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
467 background, 1, false);
468
469 attr.setTo(getTheme().getColor("twindow.background.modal"));
470 if (isActive()) {
471 attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
472 attr.setBold(getTheme().getColor("tlabel").isBold());
473 }
474 getScreen().putStringXY(1, 0, " Background ", attr);
475
476 // Have to draw the colors manually because the int value matches
477 // SGR, not CGA.
478 attr.reset();
479 attr.setReverse(true);
480 attr.setForeColor(Color.BLACK);
481 putStringXY(1, 1, " ", attr);
482 attr.setForeColor(Color.BLUE);
483 putStringXY(4, 1, " ", attr);
484 attr.setForeColor(Color.GREEN);
485 putStringXY(7, 1, " ", attr);
486 attr.setForeColor(Color.CYAN);
487 putStringXY(10, 1, " ", attr);
488 attr.setForeColor(Color.RED);
489 putStringXY(1, 2, " ", attr);
490 attr.setForeColor(Color.MAGENTA);
491 putStringXY(4, 2, " ", attr);
492 attr.setForeColor(Color.YELLOW);
493 putStringXY(7, 2, " ", attr);
494 attr.setForeColor(Color.WHITE);
495 putStringXY(10, 2, " ", attr);
496
497 // Draw the dot
498 int dotX = getXColorPosition(color);
499 int dotY = getYColorPosition(color);
500 if (color.equals(Color.BLACK)) {
501 // Use white-on-black for black. All other colors use
502 // black-on-whatever.
503 attr.reset();
504 getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07],
505 attr);
506 } else {
507 getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07]);
508 }
509
510 }
511
512 /**
513 * Handle keystrokes.
514 *
515 * @param keypress keystroke event
516 */
517 @Override
518 public void onKeypress(final TKeypressEvent keypress) {
519 if (keypress.equals(kbRight)) {
520 int dotX = getXColorPosition(color);
521 int dotY = getYColorPosition(color);
522 if (dotX < 10) {
523 dotX += 3;
524 }
525 color = getColorFromPosition(dotX, dotY);
526 } else if (keypress.equals(kbLeft)) {
527 int dotX = getXColorPosition(color);
528 int dotY = getYColorPosition(color);
529 if (dotX > 3) {
530 dotX -= 3;
531 }
532 color = getColorFromPosition(dotX, dotY);
533 } else if (keypress.equals(kbUp)) {
534 int dotX = getXColorPosition(color);
535 int dotY = getYColorPosition(color);
536 if (dotY == 2) {
537 dotY--;
538 }
539 color = getColorFromPosition(dotX, dotY);
540 } else if (keypress.equals(kbDown)) {
541 int dotX = getXColorPosition(color);
542 int dotY = getYColorPosition(color);
543 if (dotY == 1) {
544 dotY++;
545 }
546 color = getColorFromPosition(dotX, dotY);
547 } else {
548 // Pass to my parent
549 super.onKeypress(keypress);
550 }
551
552 // Save this update to the local theme.
553 ((TEditColorThemeWindow) getWindow()).saveToEditTheme();
554 }
555
556 /**
557 * Handle mouse press events.
558 *
559 * @param mouse mouse button press event
560 */
561 @Override
562 public void onMouseDown(final TMouseEvent mouse) {
563 if (mouse.isMouseWheelUp()) {
564 // Do this like kbUp
565 int dotX = getXColorPosition(color);
566 int dotY = getYColorPosition(color);
567 if (dotY == 2) {
568 dotY--;
569 }
570 color = getColorFromPosition(dotX, dotY);
571 } else if (mouse.isMouseWheelDown()) {
572 // Do this like kbDown
573 int dotX = getXColorPosition(color);
574 int dotY = getYColorPosition(color);
575 if (dotY == 1) {
576 dotY++;
577 }
578 color = getColorFromPosition(dotX, dotY);
579 return;
580 } else if ((mouse.getX() > 0)
581 && (mouse.getX() < getWidth() - 1)
582 && (mouse.getY() > 0)
583 && (mouse.getY() < getHeight() - 1)
584 ) {
585 color = getColorFromPosition(mouse.getX(), mouse.getY());
586 } else {
587 // Let parent class handle it.
588 super.onMouseDown(mouse);
589 return;
590 }
591
592 // Save this update to the local theme.
593 ((TEditColorThemeWindow) getWindow()).saveToEditTheme();
594 }
595
596 }
597
598 /**
599 * The current editing theme.
600 */
601 private ColorTheme editTheme;
602
603 /**
604 * The left-side list of colors pane.
605 */
606 private TList colorNames;
607
608 /**
609 * The foreground color.
610 */
611 private ForegroundPicker foreground;
612
613 /**
614 * The background color.
615 */
616 private BackgroundPicker background;
617
618 /**
619 * Set various widgets/values to the editing theme color.
620 *
621 * @param colorName name of color from theme
622 */
623 private void refreshFromTheme(final String colorName) {
624 CellAttributes attr = editTheme.getColor(colorName);
625 foreground.color = attr.getForeColor();
626 foreground.bold = attr.isBold();
627 background.color = attr.getBackColor();
628 }
629
630 /**
631 * Examines foreground, background, and colorNames and sets the color in
632 * editTheme.
633 */
634 private void saveToEditTheme() {
635 String colorName = colorNames.getSelected();
636 if (colorName == null) {
637 return;
638 }
639 CellAttributes attr = editTheme.getColor(colorName);
640 attr.setForeColor(foreground.color);
641 attr.setBold(foreground.bold);
642 attr.setBackColor(background.color);
643 editTheme.setColor(colorName, attr);
644 }
645
646 /**
647 * Public constructor. The file open box will be centered on screen.
648 *
649 * @param application the TApplication that manages this window
650 */
651 public TEditColorThemeWindow(final TApplication application) {
652
653 // Register with the TApplication
654 super(application, "Colors", 0, 0, 60, 18, MODAL);
655
656 // Initialize with the first color
657 List<String> colors = getTheme().getColorNames();
658 assert (colors.size() > 0);
659 editTheme = new ColorTheme();
660 for (String key: colors) {
661 CellAttributes attr = new CellAttributes();
662 attr.setTo(getTheme().getColor(key));
663 editTheme.setColor(key, attr);
664 }
665
666 colorNames = addList(colors, 2, 2, 38, getHeight() - 7,
667 new TAction() {
668 // When the user presses Enter
669 public void DO() {
670 refreshFromTheme(colorNames.getSelected());
671 }
672 },
673 new TAction() {
674 // When the user navigates with keyboard
675 public void DO() {
676 refreshFromTheme(colorNames.getSelected());
677 }
678 }
679 );
680 foreground = new ForegroundPicker(this, 42, 1, 14, 6);
681 background = new BackgroundPicker(this, 42, 7, 14, 4);
682 refreshFromTheme(colors.get(0));
683 colorNames.setSelectedIndex(0);
684
685 addButton(" &OK ", getWidth() - 37, getHeight() - 4,
686 new TAction() {
687 public void DO() {
688 ColorTheme global = getTheme();
689 List<String> colors = editTheme.getColorNames();
690 for (String key: colors) {
691 CellAttributes attr = new CellAttributes();
692 attr.setTo(editTheme.getColor(key));
693 global.setColor(key, attr);
694 }
695 getApplication().closeWindow(TEditColorThemeWindow.this);
696 }
697 }
698 );
699
700 addButton("&Cancel", getWidth() - 25, getHeight() - 4,
701 new TAction() {
702 public void DO() {
703 getApplication().closeWindow(TEditColorThemeWindow.this);
704 }
705 }
706 );
707
708 // Default to the color list
709 activate(colorNames);
710
711 }
712
713 /**
714 * Draw me on screen.
715 */
716 @Override
717 public void draw() {
718 super.draw();
719 CellAttributes attr = new CellAttributes();
720
721 // Draw the label on colorNames
722 attr.setTo(getTheme().getColor("twindow.background.modal"));
723 if (colorNames.isActive()) {
724 attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
725 attr.setBold(getTheme().getColor("tlabel").isBold());
726 }
727 getScreen().putStringXY(3, 2, "Color Name", attr);
728
729 // Draw the sample text box
730 attr.reset();
731 attr.setForeColor(foreground.color);
732 attr.setBold(foreground.bold);
733 attr.setBackColor(background.color);
734 getScreen().putStringXY(getWidth() - 17, getHeight() - 6,
735 "Text Text Text", attr);
736 getScreen().putStringXY(getWidth() - 17, getHeight() - 5,
737 "Text Text Text", attr);
738 }
739
740 /**
741 * Handle keystrokes.
742 *
743 * @param keypress keystroke event
744 */
745 @Override
746 public void onKeypress(final TKeypressEvent keypress) {
747 // Escape - behave like cancel
748 if (keypress.equals(kbEsc)) {
749 getApplication().closeWindow(this);
750 return;
751 }
752
753 // Pass to my parent
754 super.onKeypress(keypress);
755 }
756
757 }