1 /** \file crtcontrolwidget.c
2 * \brief GTK3 CRT settings widget
3 *
4 * \author Bas Wassink <b.wassink@ziggo.nl>
5 *
6 * Provides a list of GtkScale widgets to alter CRT settings.
7 *
8 * Supported settings per chip/video mode:
9 *
10 * |Setting |VICIIP|VICIIN|VDC|VICP|VICN|TEDP|TEDN|CRTC|
11 * |--------------------|------|------|---|----|----|----|----|----|
12 * |Brightness | yes | yes |yes|yes |yes |yes |yes |yes |
13 * |Contrast | yes | yes |yes|yes |yes |yes |yes |yes |
14 * |Saturation | yes | yes |yes|yes |yes |yes |yes |yes |
15 * |Tint | yes | yes |yes|yes |yes |yes |yes |yes |
16 * |Gamma | yes | yes |yes|yes |yes |yes |yes |yes |
17 * |Blur | yes | yes |yes|yes |yes |yes |yes |yes |
18 * |Scanline shade | yes | yes |yes|yes |yes |yes |yes |yes |
19 * |Odd lines phase | yes | no | no|yes | no |yes | no | no |
20 * |Odd lines offset | yes | no | no|yes | no |yes | no | no |
21 *
22 * TODO: Fix display of sliders when switching between PAL and NTSC
23 */
24
25 /*
26 * $VICERES CrtcColorBrightness xpet xcbm2
27 * $VICERES CrtcColorContrast xpet xcbm2
28 * $VICERES CrtcColorGamma xpet xcbm2
29 * $VICERES CrtcColorSaturation xpet xcbm2
30 * $VICERES CrtcColorTint xpet xcbm2
31 * $VICERES CrtcPALBlur xpet xcbm2
32 * $VICERES CrtcPALScanLineShade xpet xcbm2
33 *
34 * $VICERES TEDColorBrightness xplus4
35 * $VICERES TEDColorContrast xplus4
36 * $VICERES TEDColorGamma xplus4
37 * $VICERES TEDColorSaturation xplus4
38 * $VICERES TEDColorTint xplus4
39 * $VICERES TEDPALBlur xplus4
40 * $VICERES TEDPALOddLineOffset xplus4
41 * $VICERES TEDPALOddLinePhase xplus4
42 * $VICERES TEDPALScanLineShade xplus4
43 *
44 * $VICERES VDCColorBrightness x128
45 * $VICERES VDCColorContrast x128
46 * $VICERES VDCColorGamma x128
47 * $VICERES VDCColorSaturation x128
48 * $VICERES VDCColorTint x128
49 * $VICERES VDCPALBlur x128
50 * $VICERES VDCPALScanLineShade x128
51 *
52 * $VICERES VICColorBrightness xvic
53 * $VICERES VICColorContrast xvic
54 * $VICERES VICColorGamma xvic
55 * $VICERES VICColorSaturation xvic
56 * $VICERES VICColorTint xvic
57 * $VICERES VICPALBlur xvic
58 * $VICERES VICPALOddLineOffset xvic
59 * $VICERES VICPALOddLinePhase xvic
60 * $VICERES VICPALScanLineShade xvic
61 *
62 * $VICERES VICIIColorBrightness x64 x64sc x64dtv xscpu64 x128 xcbm5x0
63 * $VICERES VICIIColorContrast x64 x64sc x64dtv xscpu64 x128 xcbm5x0
64 * $VICERES VICIIColorGamma x64 x64sc x64dtv xscpu64 x128 xcbm5x0
65 * $VICERES VICIIColorSaturation x64 x64sc x64dtv xscpu64 x128 xcbm5x0
66 * $VICERES VICIIColorTint x64 x64sc x64dtv xscpu64 x128 xcbm5x0
67 * $VICERES VICIIPALBlur x64 x64sc x64dtv xscpu64 x128 xcbm5x0
68 * $VICERES VICIIPALOddLineOffset x64 x64sc x64dtv xscpu64 x128 xcbm5x0
69 * $VICERES VICIIPALOddLinePhase x64 x64sc x64dtv xscpu64 x128 xcbm5x0
70 * $VICERES VICIIPALScanLineShade x64 x64sc x64dtv xscpu64 x128 xcbm5x0
71 */
72
73 /*
74 * This file is part of VICE, the Versatile Commodore Emulator.
75 * See README for copyright notice.
76 *
77 * This program is free software; you can redistribute it and/or modify
78 * it under the terms of the GNU General Public License as published by
79 * the Free Software Foundation; either version 2 of the License, or
80 * (at your option) any later version.
81 *
82 * This program is distributed in the hope that it will be useful,
83 * but WITHOUT ANY WARRANTY; without even the implied warranty of
84 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
85 * GNU General Public License for more details.
86 *
87 * You should have received a copy of the GNU General Public License
88 * along with this program; if not, write to the Free Software
89 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
90 * 02111-1307 USA.
91 *
92 */
93
94 #include "vice.h"
95
96 #include <string.h>
97 #include <gtk/gtk.h>
98
99 #include "vice_gtk3.h"
100 #include "resources.h"
101 #include "lib.h"
102 #include "machine.h"
103
104
105 #include "crtcontrolwidget.h"
106
107 /** \brief CSS for the scales
108 *
109 * This makes the sliders take up less vertical space. The margin can be set
110 * to a negative value (in px) to allow the slider to be larger than the scale
111 * itself.
112 *
113 * Probably will require some testing/tweaking to get this to look acceptable
114 * with various themes (and OSes).
115 */
116 #define SLIDER_CSS "scale slider { min-width: 10px; min-height: 10px; margin: -3px; } scale { margin-top: -4px; margin-bottom: -4px; }"
117
118
119 /** \brief CSS for the labels
120 *
121 * Make font smaller and reduce the vertical size the labels use
122 *
123 * Here Be Dragons!
124 */
125 #define LABEL_CSS "label { font-size: 80%; margin-top: -2px; margin-bottom: -2px; }"
126
127
128 /** \brief Video chip identifiers
129 *
130 * Allows for easier/faster switching than using strcmp()
131 */
132 enum {
133 CHIP_CRTC,
134 CHIP_TED,
135 CHIP_VDC,
136 CHIP_VIC,
137 CHIP_VICII,
138
139 CHIP_ID_COUNT
140 };
141
142
143 /** \brief Struct mapping chip names to chip IDs
144 */
145 typedef struct chip_id_s {
146 const char *name; /**< chip name (ie VDC, VICII, etc) */
147 int id; /**< chip ID */
148 } chip_id_t;
149
150
151 static const chip_id_t chips[CHIP_ID_COUNT] = {
152 { "CRTC", CHIP_CRTC },
153 { "TED", CHIP_TED },
154 { "VDC", CHIP_VDC },
155 { "VIC", CHIP_VIC },
156 { "VICII", CHIP_VICII }
157 };
158
159
160 /** \brief Find chip ID by \a name
161 *
162 * \param[in] name chip name
163 *
164 * \return chip ID or -1 on error
165 */
get_chip_id(const char * name)166 static int get_chip_id(const char *name)
167 {
168 int i;
169
170 for (i = 0; i < CHIP_ID_COUNT; i++) {
171 if (strcmp(name, chips[i].name) == 0) {
172 return chips[i].id;
173 }
174 }
175 return -1;
176 }
177
178
179 /** \brief Object holding internal state of a CRT control widget
180 *
181 * Since we can have two video chips (C128's VICII+VDC), we cannot use static
182 * references to widgets and need to allocate memory for the references and
183 * clean that memory up once the widget is destroyed.
184 */
185 typedef struct crt_control_data_s {
186 char *chip;
187 GtkWidget *color_brightness;
188 GtkWidget *color_contrast;
189 GtkWidget *color_gamma;
190 GtkWidget *color_saturation;
191 GtkWidget *color_tint;
192 GtkWidget *pal_blur;
193 GtkWidget *pal_scanline_shade;
194 GtkWidget *pal_oddline_offset;
195 GtkWidget *pal_oddline_phase;
196 } crt_control_data_t;
197
198
199 /** \brief Reset all sliders to the resource value when creating the widget
200 *
201 * \param[in] widget reset button
202 * \param[in] user_data extra event data (unused)
203 */
on_reset_clicked(GtkWidget * widget,gpointer user_data)204 static void on_reset_clicked(GtkWidget *widget, gpointer user_data)
205 {
206 GtkWidget *parent;
207 crt_control_data_t *data;
208
209 /* parent widget (grid), contains the InternalState object */
210 parent = gtk_widget_get_parent(widget);
211 data = g_object_get_data(G_OBJECT(parent), "InternalState");
212
213 if (data->color_brightness != NULL) {
214 vice_gtk3_resource_scale_int_reset(data->color_brightness);
215 }
216 if (data->color_contrast != NULL) {
217 vice_gtk3_resource_scale_int_reset(data->color_contrast);
218 }
219 if (data->color_gamma != NULL) {
220 vice_gtk3_resource_scale_int_reset(data->color_gamma);
221 }
222 if (data->color_saturation != NULL) {
223 vice_gtk3_resource_scale_int_reset(data->color_saturation);
224 }
225 if (data->color_tint != NULL) {
226 vice_gtk3_resource_scale_int_reset(data->color_tint);
227 }
228 if (data->pal_blur != NULL) {
229 vice_gtk3_resource_scale_int_reset(data->pal_blur);
230 }
231 if (data->pal_scanline_shade != NULL) {
232 vice_gtk3_resource_scale_int_reset(data->pal_scanline_shade);
233 }
234 if (data->pal_oddline_offset != NULL) {
235 vice_gtk3_resource_scale_int_reset(data->pal_oddline_offset);
236 }
237 if (data->pal_oddline_phase != NULL) {
238 vice_gtk3_resource_scale_int_reset(data->pal_oddline_phase);
239 }
240 }
241
242
243 /** \brief Clean up memory used by the internal state of \a widget
244 *
245 * \param[in] widget widget
246 * \param[in] user_data extra event data (unused)
247 */
on_widget_destroy(GtkWidget * widget,gpointer user_data)248 static void on_widget_destroy(GtkWidget *widget, gpointer user_data)
249 {
250 crt_control_data_t *data;
251
252 data = (crt_control_data_t *)(g_object_get_data(
253 G_OBJECT(widget), "InternalState"));
254 lib_free(data->chip);
255 lib_free(data);
256 }
257
258
259
260
261 /** \brief Create right-aligned label with a smaller font
262 *
263 * \param[in] text label text
264 * \param[in] minimal reduce label to minimum size
265 *
266 * \return GtkLabel
267 */
create_label(const char * text,gboolean minimal)268 static GtkWidget *create_label(const char *text, gboolean minimal)
269 {
270 GtkWidget *label;
271 GtkCssProvider *provider;
272 GtkStyleContext *context;
273 GError *err = NULL;
274
275 label = gtk_label_new(text);
276 gtk_widget_set_halign(label, GTK_ALIGN_END);
277
278 if (minimal) {
279 provider = gtk_css_provider_new();
280 gtk_css_provider_load_from_data(provider, LABEL_CSS, -1, &err);
281 if (err != NULL) {
282 fprintf(stderr, "CSS error: %s\n", err->message);
283 g_error_free(err);
284 }
285
286 context = gtk_widget_get_style_context(label);
287 if (context != NULL) {
288 gtk_style_context_add_provider(context,
289 GTK_STYLE_PROVIDER(provider),
290 GTK_STYLE_PROVIDER_PRIORITY_USER);
291 }
292 }
293
294 return label;
295 }
296
297
298 /** \brief Create a customized GtkScale for \a resource
299 *
300 * \param[in] resource resource name without the video \a chip name prefix
301 * \param[in] chip video chip name
302 * \param[in] low lower bound
303 * \param[in] high upper bound
304 * \param[in] step step used to increase/decrease slider value
305 *
306 * \return GtkScale
307 */
create_slider(const char * resource,const char * chip,int low,int high,int step,gboolean minimal)308 static GtkWidget *create_slider(const char *resource, const char *chip,
309 int low, int high, int step, gboolean minimal)
310 {
311 GtkWidget *scale;
312 GtkCssProvider *css_provider;
313 GtkStyleContext *style_context;
314 GError *err = NULL;
315
316 scale = vice_gtk3_resource_scale_int_new_sprintf("%s%s",
317 GTK_ORIENTATION_HORIZONTAL, low, high, step,
318 chip, resource);
319 gtk_widget_set_hexpand(scale, TRUE);
320 gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_RIGHT);
321 vice_gtk3_resource_scale_int_set_marks(scale, step);
322
323 /* set up custom CSS to make the scale take up less space */
324 if (minimal) {
325 css_provider = gtk_css_provider_new();
326 gtk_css_provider_load_from_data(css_provider, SLIDER_CSS, -1, &err);
327 if (err != NULL) {
328 fprintf(stderr, "CSS error: %s\n", err->message);
329 g_error_free(err);
330 }
331
332 style_context = gtk_widget_get_style_context(scale);
333 if (style_context != NULL) {
334 gtk_style_context_add_provider(style_context,
335 GTK_STYLE_PROVIDER(css_provider),
336 GTK_STYLE_PROVIDER_PRIORITY_USER);
337 }
338 }
339
340 /* don't draw the value next to the scale */
341 /* gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); */
342
343 return scale;
344 }
345
346
347
348 /** \brief Add GtkScale sliders to \a grid
349 *
350 * \param[in,out] grid grid to add widgets to
351 * \param[in,out] data internal data of the main widget
352 * \param[in] minimal minimize size of the slider
353 *
354 * \return row number of last widget added
355 */
add_sliders(GtkGrid * grid,crt_control_data_t * data,gboolean minimal)356 static void add_sliders(GtkGrid *grid,
357 crt_control_data_t *data,
358 gboolean minimal)
359 {
360 GtkWidget *label;
361 const char *chip;
362 int video_standard;
363 int row = 1;
364 int chip_id;
365 gboolean enabled;
366 int oldrow;
367
368 chip = data->chip;
369 chip_id = get_chip_id(chip);
370 if (chip_id < 0) {
371 debug_gtk3("failed to get chip ID for '%s'.", chip);
372 return;
373 }
374
375 /* get PAL/NTSC mode */
376 if (resources_get_int("MachineVideoStandard", &video_standard) < 0) {
377 debug_gtk3("failed to get 'MachineVideoStandard' resource value.");
378 return;
379 }
380
381 oldrow = row;
382
383 label = create_label("Brightness", minimal);
384 data->color_brightness = create_slider("ColorBrightness", chip,
385 0, 2000, 100, minimal);
386 gtk_grid_attach(grid, label, 0, row, 1, 1);
387 gtk_grid_attach(grid, data->color_brightness, 1, row, 1, 1);
388 row++;
389
390 label = create_label("Contrast", minimal);
391 data->color_contrast = create_slider("ColorContrast", chip,
392 0, 2000, 100, minimal);
393 gtk_grid_attach(grid, label, 0, row, 1, 1);
394 gtk_grid_attach(grid, data->color_contrast, 1, row, 1, 1);
395 row++;
396
397 label = create_label("Saturation", minimal);
398 data->color_saturation = create_slider("ColorSaturation", chip,
399 0, 2000, 100, minimal);
400 gtk_grid_attach(grid, label, 0, row, 1, 1);
401 gtk_grid_attach(grid, data->color_saturation, 1, row, 1, 1);
402 row++;
403
404 label = create_label("Tint", minimal);
405 data->color_tint = create_slider("ColorTint", chip,
406 0, 2000, 100, minimal);
407 gtk_grid_attach(grid, label, 0, row, 1, 1);
408 gtk_grid_attach(grid, data->color_tint, 1, row, 1, 1);
409 row++;
410
411 label = create_label("Gamma", minimal);
412 data->color_gamma = create_slider("ColorGamma", chip,
413 0, 4000, 200, minimal);
414 gtk_grid_attach(grid, label, 0, row, 1, 1);
415 gtk_grid_attach(grid, data->color_gamma, 1, row, 1, 1);
416 row++;
417
418 if (!minimal) {
419
420 label = create_label("Blur", minimal);
421 data->pal_blur = create_slider("PALBlur", chip,
422 0, 1000, 50, minimal);
423 gtk_grid_attach(grid, label, 0, row, 1, 1);
424 gtk_grid_attach(grid, data->pal_blur, 1, row, 1, 1);
425 row++;
426
427 label = create_label("Scanline shade", minimal);
428 data->pal_scanline_shade = create_slider("PALScanLineShade", chip,
429 0, 1000, 50, minimal);
430 gtk_grid_attach(grid, label, 0, row, 1, 1);
431 gtk_grid_attach(grid, data->pal_scanline_shade, 1, row, 1, 1);
432 row++;
433
434 label = create_label("Odd lines phase", minimal);
435 data->pal_oddline_phase = create_slider("PALOddLinePhase", chip,
436 0, 2000, 100, minimal);
437 gtk_grid_attach(grid, label, 0, row, 1, 1);
438 gtk_grid_attach(grid, data->pal_oddline_phase, 1, row, 1, 1);
439 row++;
440
441 label = create_label("Odd lines offset", minimal);
442 data->pal_oddline_offset = create_slider("PALOddLineOffset", chip,
443 0, 2000, 100, minimal);
444 gtk_grid_attach(grid, label, 0, row, 1, 1);
445 gtk_grid_attach(grid, data->pal_oddline_offset, 1, row, 1, 1);
446 row++;
447 } else {
448 /* minimal display: two rows */
449
450 row = oldrow;
451 label = create_label("Blur", minimal);
452 data->pal_blur = create_slider("PALBlur", chip,
453 0, 1000, 50, minimal);
454 gtk_grid_attach(grid, label, 2, row, 1, 1);
455 gtk_grid_attach(grid, data->pal_blur, 3, row, 1, 1);
456 row++;
457
458 label = create_label("Scanline shade", minimal);
459 data->pal_scanline_shade = create_slider("PALScanLineShade", chip,
460 0, 1000, 50, minimal);
461 gtk_grid_attach(grid, label, 2, row, 1, 1);
462 gtk_grid_attach(grid, data->pal_scanline_shade, 3, row, 1, 1);
463 row++;
464
465 label = create_label("Odd lines phase", minimal);
466 data->pal_oddline_phase = create_slider("PALOddLinePhase", chip,
467 0, 2000, 100, minimal);
468 gtk_grid_attach(grid, label, 2, row, 1, 1);
469 gtk_grid_attach(grid, data->pal_oddline_phase, 3, row, 1, 1);
470 row++;
471
472 label = create_label("Odd lines offset", minimal);
473 data->pal_oddline_offset = create_slider("PALOddLineOffset", chip,
474 0, 2000, 100, minimal);
475 gtk_grid_attach(grid, label, 2, row, 1, 1);
476 gtk_grid_attach(grid, data->pal_oddline_offset, 3, row, 1, 1);
477 row++;
478
479 }
480
481 /* Standard controls: brightness, gamma etc */
482
483 enabled = ((video_standard == 0 /* PAL */
484 || video_standard == 1 /* Old PAL */
485 || video_standard == 4 /* PAL-N/Drean */
486 ) && chip_id != CHIP_CRTC && chip_id != CHIP_VDC);
487
488 gtk_widget_set_sensitive(data->pal_oddline_phase, enabled);
489 gtk_widget_set_sensitive(data->pal_oddline_offset, enabled);
490
491 }
492
493
494 /** \brief Create CRT control widget for \a chip
495 *
496 * \param[in] parent parent widget
497 * \param[in] chip video chip name
498 * \param[in] minimal reduce slider sizes to be used attached to the status
499 * bar
500 *
501 * \return GtkGrid
502 */
crt_control_widget_create(GtkWidget * parent,const char * chip,gboolean minimal)503 GtkWidget *crt_control_widget_create(GtkWidget *parent,
504 const char *chip,
505 gboolean minimal)
506 {
507 GtkWidget *grid;
508 GtkWidget *label;
509 GtkWidget *button;
510 gchar buffer[256];
511
512 crt_control_data_t *data;
513
514 data = lib_malloc(sizeof *data);
515 data->chip = lib_stralloc(chip);
516 data->color_brightness = NULL;
517 data->color_contrast = NULL;
518 data->color_gamma = NULL;
519 data->color_saturation = NULL;
520 data->color_tint = NULL;
521 data->pal_blur = NULL;
522 data->pal_oddline_offset = NULL;
523 data->pal_oddline_phase = NULL;
524 data->pal_scanline_shade = NULL;
525
526 grid = vice_gtk3_grid_new_spaced(16, 0);
527 /* g_object_set(grid, "font-size", 9, NULL); */
528 g_object_set(grid, "margin-left", 8, "margin-right", 8, NULL);
529
530 if (minimal) {
531 g_snprintf(buffer, 256, "<small><b>CRT settings (%s)</b></small>", chip);
532 } else {
533 g_snprintf(buffer, 256, "<b>CRT settings (%s)</b>", chip);
534 }
535 label = gtk_label_new(NULL);
536 gtk_label_set_markup(GTK_LABEL(label), buffer);
537 gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
538 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
539
540 add_sliders(GTK_GRID(grid), data, minimal);
541
542 button = gtk_button_new_with_label("Reset");
543 gtk_widget_set_halign(button, GTK_ALIGN_END);
544 gtk_grid_attach(GTK_GRID(grid), button, minimal ? 3 : 1, 0, 1, 1);
545 g_signal_connect(button, "clicked", G_CALLBACK(on_reset_clicked), NULL);
546
547 g_object_set_data(G_OBJECT(grid), "InternalState", (gpointer)data);
548 g_signal_connect(grid, "destroy", G_CALLBACK(on_widget_destroy), NULL);
549
550 gtk_widget_show_all(grid);
551 return grid;
552 }
553
554
555 /** \brief Custom callback for the resource widget manager
556 *
557 * This calls the reset methods on the various CRT sliders. It assumes the
558 * widget was created for the settings UI ('minimal' argument set to false).
559 *
560 * \param[in] widget CRT control widget
561 *
562 * \return bool
563 *
564 * FIXME: When using the CRT widget in the settings UI and not as a widget
565 * controlled from the statusbar, the "Reset" shouldn't be there.
566 * So this code is not quite correct yet. I could hide the button,
567 * leaving all code intact and working, but that's lame.
568 */
crt_control_widget_reset(GtkWidget * widget)569 gboolean crt_control_widget_reset(GtkWidget *widget)
570 {
571 GtkWidget *button;
572
573 /* this assumes the CRT widget was created with 'minimal' set to False */
574 button = gtk_grid_get_child_at(GTK_GRID(widget), 1, 0);
575 if (GTK_IS_BUTTON(button)) {
576 /* abuse event handler to reset widgets */
577 on_reset_clicked(button, NULL);
578 return TRUE;
579 } else {
580 return FALSE;
581 }
582 }
583