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