diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index f12b38e3..9787f95e 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -437,7 +437,7 @@ const struct prefs vars[] = {"gui_tab_dialogs", P_OFFINT (hex_gui_tab_dialogs), TYPE_BOOL}, {"gui_tab_dots", P_OFFINT (hex_gui_tab_dots), TYPE_BOOL}, {"gui_tab_icons", P_OFFINT (hex_gui_tab_icons), TYPE_BOOL}, - {"gui_dark_mode", P_OFFINT (hex_gui_dark_mode), TYPE_BOOL}, + {"gui_dark_mode", P_OFFINT (hex_gui_dark_mode), TYPE_INT}, {"gui_tab_layout", P_OFFINT (hex_gui_tab_layout), TYPE_INT}, {"gui_tab_middleclose", P_OFFINT (hex_gui_tab_middleclose), TYPE_BOOL}, {"gui_tab_newtofront", P_OFFINT (hex_gui_tab_newtofront), TYPE_INT}, diff --git a/src/common/zoitechat.h b/src/common/zoitechat.h index 893beea5..f8f0b58f 100644 --- a/src/common/zoitechat.h +++ b/src/common/zoitechat.h @@ -83,6 +83,10 @@ gboolean zoitechat_import_theme (const char *path, GError **error); #define USERNAMELEN 10 #define HIDDEN_CHAR 8 /* invisible character for xtext */ +#define ZOITECHAT_DARK_MODE_AUTO 0 +#define ZOITECHAT_DARK_MODE_DARK 1 +#define ZOITECHAT_DARK_MODE_LIGHT 2 + struct nbexec { int myfd; diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c index 9dd8359e..bfceeab2 100644 --- a/src/fe-gtk/chanview-tree.c +++ b/src/fe-gtk/chanview-tree.c @@ -117,7 +117,11 @@ cv_tree_init (chanview *cv) view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store)); gtk_widget_set_name (view, "zoitechat-tree"); if (cv->style) - gtk_widget_set_style (view, cv->style); + { + gtk_widget_modify_base (view, GTK_STATE_NORMAL, &cv->style->base[GTK_STATE_NORMAL]); + gtk_widget_modify_text (view, GTK_STATE_NORMAL, &cv->style->text[GTK_STATE_NORMAL]); + gtk_widget_modify_font (view, cv->style->font_desc); + } /*gtk_widget_modify_base (view, GTK_STATE_NORMAL, &colors[COL_BG]);*/ gtk_widget_set_can_focus (view, FALSE); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c index ea5113b7..c4520df3 100644 --- a/src/fe-gtk/chanview.c +++ b/src/fe-gtk/chanview.c @@ -122,7 +122,7 @@ chanview_apply_theme (chanview *cv) return; w = GTK_WIDGET (tv->tree); - if (prefs.hex_gui_dark_mode) + if (fe_dark_mode_is_enabled () || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_LIGHT) { gtk_widget_modify_base (w, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_text (w, GTK_STATE_NORMAL, &colors[COL_FG]); diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index da415d25..04d0d265 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -58,7 +58,7 @@ #include #endif -GdkPixmap *channelwin_pix; +cairo_surface_t *channelwin_pix; #ifdef USE_LIBCANBERRA static ca_context *ca_con; @@ -270,6 +270,88 @@ static const char adwaita_workaround_rc[] = "}" "widget \"*.zoitechat-inputbox\" style \"zoitechat-input-workaround\""; +static gboolean +fe_system_prefers_dark (void) +{ + GtkSettings *settings = gtk_settings_get_default (); + gboolean prefer_dark = FALSE; + char *theme_name = NULL; + + if (!settings) + return FALSE; + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (settings), + "gtk-application-prefer-dark-theme")) + { + g_object_get (settings, "gtk-application-prefer-dark-theme", &prefer_dark, NULL); + } + + if (!prefer_dark) + { + g_object_get (settings, "gtk-theme-name", &theme_name, NULL); + if (theme_name) + { + char *lower = g_ascii_strdown (theme_name, -1); + if (g_str_has_suffix (lower, "-dark") || g_strrstr (lower, "dark")) + prefer_dark = TRUE; + g_free (lower); + g_free (theme_name); + } + } + + return prefer_dark; +} + +static gboolean auto_dark_mode_enabled = FALSE; + +static void +fe_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec, gpointer data) +{ + gboolean enabled; + + (void) settings; + (void) pspec; + (void) data; + + if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO) + return; + + enabled = fe_system_prefers_dark (); + if (enabled == auto_dark_mode_enabled) + return; + + auto_dark_mode_enabled = enabled; + palette_apply_dark_mode (enabled); + setup_apply_real (0, TRUE, FALSE, FALSE); +} + +void +fe_set_auto_dark_mode_state (gboolean enabled) +{ + auto_dark_mode_enabled = enabled; +} + +gboolean +fe_dark_mode_is_enabled_for (unsigned int mode) +{ + switch (mode) + { + case ZOITECHAT_DARK_MODE_DARK: + return TRUE; + case ZOITECHAT_DARK_MODE_LIGHT: + return FALSE; + case ZOITECHAT_DARK_MODE_AUTO: + default: + return fe_system_prefers_dark (); + } +} + +gboolean +fe_dark_mode_is_enabled (void) +{ + return fe_dark_mode_is_enabled_for (prefs.hex_gui_dark_mode); +} + GtkStyle * create_input_style (GtkStyle *style) { @@ -316,8 +398,10 @@ create_input_style (GtkStyle *style) void fe_init (void) { + GtkSettings *settings; + palette_load (); - palette_apply_dark_mode (prefs.hex_gui_dark_mode); + palette_apply_dark_mode (fe_dark_mode_is_enabled ()); key_init (); pixmaps_init (); @@ -326,6 +410,16 @@ fe_init (void) #endif channelwin_pix = pixmap_load_from_file (prefs.hex_text_background); input_style = create_input_style (gtk_style_new ()); + + settings = gtk_settings_get_default (); + if (settings) + { + auto_dark_mode_enabled = fe_system_prefers_dark (); + g_signal_connect (settings, "notify::gtk-application-prefer-dark-theme", + G_CALLBACK (fe_auto_dark_mode_changed), NULL); + g_signal_connect (settings, "notify::gtk-theme-name", + G_CALLBACK (fe_auto_dark_mode_changed), NULL); + } } #ifdef HAVE_GTK_MAC diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index cfb74035..12cc5d19 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -30,6 +30,7 @@ #include #include +#include #ifdef HAVE_GTK_MAC #include @@ -178,8 +179,12 @@ typedef struct session_gui } session_gui; -extern GdkPixmap *channelwin_pix; -extern GdkPixmap *dialogwin_pix; +extern cairo_surface_t *channelwin_pix; +extern cairo_surface_t *dialogwin_pix; + +gboolean fe_dark_mode_is_enabled (void); +gboolean fe_dark_mode_is_enabled_for (unsigned int mode); +void fe_set_auto_dark_mode_state (gboolean enabled); #define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e)))) #define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c index ac281764..bef7e9b5 100644 --- a/src/fe-gtk/fkeys.c +++ b/src/fe-gtk/fkeys.c @@ -803,6 +803,7 @@ key_dialog_show () GtkWidget *vbox, *box; GtkWidget *view, *xtext; GtkListStore *store; + XTextColor xtext_palette[XTEXT_COLS]; char buf[128]; if (key_dialog) @@ -816,7 +817,8 @@ key_dialog_show () NULL, 600, 360, &vbox, 0); view = key_dialog_treeview_new (vbox); - xtext = gtk_xtext_new (colors, 0); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + xtext = gtk_xtext_new (xtext_palette, 0); gtk_box_pack_start (GTK_BOX (vbox), xtext, FALSE, TRUE, 2); gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font); diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index 29a2c9a6..22463555 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -95,7 +95,7 @@ gtkutil_check_file (char *filename, struct file_req *freq) } else { - GFileInfo *fi = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, NULL); + GFileInfo *fi = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (fi != NULL) { diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index cac03b2e..d410a940 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -21,7 +21,10 @@ #include #include +#include #include +#include +#include #include "../common/zoitechat.h" #include "../common/fe.h" @@ -82,6 +85,136 @@ static void mg_emoji_button_cb (GtkWidget *widget, session_gui *gui); static GtkWidget *mg_create_emoji_menu (session_gui *gui); static void mg_emoji_insert_cb (GtkMenuItem *item, session_gui *gui); +static inline void +mg_set_source_color (cairo_t *cr, const XTextColor *color) +{ + cairo_set_source_rgba (cr, color->red, color->green, color->blue, color->alpha); +} + +static inline guint16 +mg_color_component_to_pango (double value) +{ + if (value < 0.0) + value = 0.0; + if (value > 1.0) + value = 1.0; + + return (guint16)(value * 65535.0 + 0.5); +} + +static void +mg_pixbuf_destroy (guchar *pixels, gpointer data) +{ + g_free (pixels); +} + +static GdkPixbuf * +mg_pixbuf_from_surface (cairo_surface_t *surface, int width, int height) +{ + const unsigned char *src; + int src_stride; + int rowstride; + guchar *pixels; + int x; + int y; + + if (!surface || width <= 0 || height <= 0) + return NULL; + + src = cairo_image_surface_get_data (surface); + src_stride = cairo_image_surface_get_stride (surface); + rowstride = width * 4; + pixels = g_malloc ((gsize)rowstride * height); + + for (y = 0; y < height; y++) + { + const unsigned char *src_row = src + (y * src_stride); + guchar *dest_row = pixels + (y * rowstride); + + for (x = 0; x < width; x++) + { + guint8 a; + guint8 r; + guint8 g; + guint8 b; + const unsigned char *src_px = src_row + (x * 4); + guchar *dest_px = dest_row + (x * 4); + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + b = src_px[0]; + g = src_px[1]; + r = src_px[2]; + a = src_px[3]; +#else + a = src_px[0]; + r = src_px[1]; + g = src_px[2]; + b = src_px[3]; +#endif + + if (a) + { + r = (guint8)((r * 255 + (a / 2)) / a); + g = (guint8)((g * 255 + (a / 2)) / a); + b = (guint8)((b * 255 + (a / 2)) / a); + } + else + { + r = g = b = 0; + } + + dest_px[0] = r; + dest_px[1] = g; + dest_px[2] = b; + dest_px[3] = a; + } + } + + return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8, + width, height, rowstride, mg_pixbuf_destroy, NULL); +} + +static GdkPixbuf * +mg_pixbuf_from_window (GdkWindow *window, int width, int height) +{ + int src_width; + int src_height; + cairo_surface_t *surface; + cairo_t *cr; + GdkPixbuf *pixbuf; + + if (!window) + return NULL; + + src_width = gdk_window_get_width (window); + src_height = gdk_window_get_height (window); + if (width <= 0 || height <= 0) + { + width = src_width; + height = src_height; + } + + if (width <= 0 || height <= 0) + return NULL; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + cr = cairo_create (surface); + gdk_cairo_set_source_window (cr, window, 0.0, 0.0); + cairo_paint (cr); + cairo_destroy (cr); + + pixbuf = mg_pixbuf_from_surface (surface, width, height); + cairo_surface_destroy (surface); + + return pixbuf; +} + static void mg_create_entry (session *sess, GtkWidget *box); static void mg_create_search (session *sess, GtkWidget *box); #ifdef G_OS_WIN32 @@ -106,7 +239,7 @@ static PangoAttrList *newmsg_list; static PangoAttrList *plain_list = NULL; static PangoAttrList * -mg_attr_list_create (GdkColor *col, int size) +mg_attr_list_create (const XTextColor *col, int size) { PangoAttribute *attr; PangoAttrList *list; @@ -115,7 +248,10 @@ mg_attr_list_create (GdkColor *col, int size) if (col) { - attr = pango_attr_foreground_new (col->red, col->green, col->blue); + attr = pango_attr_foreground_new ( + mg_color_component_to_pango (col->red), + mg_color_component_to_pango (col->green), + mg_color_component_to_pango (col->blue)); attr->start_index = 0; attr->end_index = 0xffff; pango_attr_list_insert (list, attr); @@ -135,6 +271,8 @@ mg_attr_list_create (GdkColor *col, int size) static void mg_create_tab_colors (void) { + XTextColor gui_palette[MAX_COL + 1]; + if (plain_list) { pango_attr_list_unref (plain_list); @@ -144,11 +282,12 @@ mg_create_tab_colors (void) pango_attr_list_unref (away_list); } + palette_get_xtext_colors (gui_palette, G_N_ELEMENTS (gui_palette)); plain_list = mg_attr_list_create (NULL, prefs.hex_gui_tab_small); - newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.hex_gui_tab_small); - nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.hex_gui_tab_small); - newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.hex_gui_tab_small); - away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE); + newdata_list = mg_attr_list_create (&gui_palette[COL_NEW_DATA], prefs.hex_gui_tab_small); + nickseen_list = mg_attr_list_create (&gui_palette[COL_HILIGHT], prefs.hex_gui_tab_small); + newmsg_list = mg_attr_list_create (&gui_palette[COL_NEW_MSG], prefs.hex_gui_tab_small); + away_list = mg_attr_list_create (&gui_palette[COL_AWAY], FALSE); } static void @@ -2314,8 +2453,10 @@ mg_update_xtext (GtkWidget *wid) { GtkXText *xtext = GTK_XTEXT (wid); const gchar *font_name; + XTextColor xtext_palette[XTEXT_COLS]; - gtk_xtext_set_palette (xtext, colors); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + gtk_xtext_set_palette (xtext, xtext_palette); gtk_xtext_set_max_lines (xtext, prefs.hex_text_max_lines); gtk_xtext_set_background (xtext, channelwin_pix); gtk_xtext_set_wordwrap (xtext, prefs.hex_text_wordwrap); @@ -2340,6 +2481,7 @@ mg_create_textarea (session *sess, GtkWidget *box) { GtkWidget *inbox, *vbox, *frame; GtkXText *xtext; + XTextColor xtext_palette[XTEXT_COLS]; session_gui *gui = sess->gui; static const GtkTargetEntry dnd_targets[] = { @@ -2360,7 +2502,8 @@ mg_create_textarea (session *sess, GtkWidget *box) gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (inbox), frame); - gui->xtext = gtk_xtext_new (colors, TRUE); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + gui->xtext = gtk_xtext_new (xtext_palette, TRUE); xtext = GTK_XTEXT (gui->xtext); gtk_xtext_set_max_indent (xtext, prefs.hex_text_max_indent); gtk_xtext_set_thin_separator (xtext, prefs.hex_text_thin_sep); @@ -2496,7 +2639,7 @@ mg_create_userlist (session_gui *gui, GtkWidget *box) if (prefs.hex_gui_ulist_style) { - gtk_widget_set_style (ulist, input_style); + gtk_widget_modify_font (ulist, input_style->font_desc); } /* @@ -2507,7 +2650,7 @@ mg_create_userlist (session_gui *gui, GtkWidget *box) * - When "Dark mode" is enabled, we also force the user list to use the * palette colors so it doesn't stay blindingly white on GTK2 themes. */ - if (prefs.hex_gui_ulist_style || prefs.hex_gui_dark_mode) + if (prefs.hex_gui_ulist_style || fe_dark_mode_is_enabled ()) { gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_text (ulist, GTK_STATE_NORMAL, &colors[COL_FG]); @@ -4053,18 +4196,20 @@ gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) { int width, height; - GdkColormap *cmap; GdkPixbuf *pix, *pix2; + GdkWindow *window; /* ignore file drops */ if (!mg_is_gui_target (context)) return FALSE; - cmap = gtk_widget_get_colormap (widget); - width = gdk_window_get_width (gtk_widget_get_window (widget)); - height = gdk_window_get_height (gtk_widget_get_window (widget)); + window = gtk_widget_get_window (widget); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); - pix = gdk_pixbuf_get_from_drawable (NULL, gtk_widget_get_window (widget), cmap, 0, 0, 0, 0, width, height); + pix = mg_pixbuf_from_window (window, width, height); + if (!pix) + return FALSE; pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER); g_object_unref (pix); @@ -4115,12 +4260,11 @@ mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar) { - GdkGC *gc; - GdkColor col; - GdkGCValues val; + XTextColor col; + cairo_t *cr; int half, width, height; int ox, oy; - GdkDrawable *draw; + GdkWindow *window; GtkAllocation allocation; /* ignore file drops */ @@ -4134,55 +4278,57 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui oy = allocation.y; width = allocation.width; height = allocation.height; - draw = gtk_widget_get_window (widget); + window = gtk_widget_get_window (widget); } else { ox = oy = 0; - width = gdk_window_get_width (gtk_widget_get_window (widget)); - height = gdk_window_get_height (gtk_widget_get_window (widget)); - draw = gtk_widget_get_window (widget); + window = gtk_widget_get_window (widget); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); } - val.subwindow_mode = GDK_INCLUDE_INFERIORS; - val.graphics_exposures = 0; - val.function = GDK_XOR; - - gc = gdk_gc_new_with_values (gtk_widget_get_window (widget), &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION); - col.red = rand() % 0xffff; - col.green = rand() % 0xffff; - col.blue = rand() % 0xffff; - gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE); - gdk_gc_set_foreground (gc, &col); + col.red = (double)rand () / (double)RAND_MAX; + col.green = (double)rand () / (double)RAND_MAX; + col.blue = (double)rand () / (double)RAND_MAX; + col.alpha = 1.0; + cr = gdk_cairo_create (window); + cairo_set_operator (cr, CAIRO_OPERATOR_XOR); + mg_set_source_color (cr, &col); + cairo_set_line_width (cr, 1.0); half = height / 2; #if 0 /* are both tree/userlist on the same side? */ + GtkPaned *paned; paned = (GtkPaned *)widget->parent->parent; if (paned->child1 != NULL && paned->child2 != NULL) { - gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4); - gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2); - g_object_unref (gc); + cairo_rectangle (cr, 1 + ox, 2 + oy, width - 3, height - 4); + cairo_rectangle (cr, 0 + ox, 1 + oy, width - 1, height - 2); + cairo_stroke (cr); + cairo_destroy (cr); return TRUE; } #endif if (y < half) { - gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4); - gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2); + cairo_rectangle (cr, 1 + ox, 2 + oy, width - 3, half - 4); + cairo_rectangle (cr, 0 + ox, 1 + oy, width - 1, half - 2); + cairo_stroke (cr); gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half); } else { - gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2); - gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4); + cairo_rectangle (cr, 0 + ox, half + 1 + oy, width - 1, half - 2); + cairo_rectangle (cr, 1 + ox, half + 2 + oy, width - 3, half - 4); + cairo_stroke (cr); gtk_widget_queue_draw_area (widget, ox, oy, width, half); } - g_object_unref (gc); + cairo_destroy (cr); return TRUE; } diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c index a973bcc8..f2af5bd1 100644 --- a/src/fe-gtk/palette.c +++ b/src/fe-gtk/palette.c @@ -38,6 +38,18 @@ #include "../common/cfgfiles.h" #include "../common/typedef.h" +static XTextColor +palette_color_from_gdk (const GdkColor *color) +{ + XTextColor result; + + result.red = color->red / 65535.0; + result.green = color->green / 65535.0; + result.blue = color->blue / 65535.0; + result.alpha = 1.0; + + return result; +} GdkColor colors[] = { /* colors for xtext */ @@ -143,6 +155,18 @@ static const GdkColor dark_colors[MAX_COL + 1] = { {0, 0xf4f4, 0x4747, 0x4747}, /* 41 COL_SPELL (spellcheck underline) */ }; +void +palette_get_xtext_colors (XTextColor *palette, size_t palette_len) +{ + size_t i; + size_t count = palette_len < G_N_ELEMENTS (colors) ? palette_len : G_N_ELEMENTS (colors); + + for (i = 0; i < count; i++) + { + palette[i] = palette_color_from_gdk (&colors[i]); + } +} + void palette_user_set_color (int idx, const GdkColor *col) { @@ -287,20 +311,21 @@ palette_save (void) char prefname[256]; const GdkColor *lightpal = colors; const GdkColor *darkpal = NULL; + gboolean dark_mode_active = fe_dark_mode_is_enabled (); /* If we're currently in dark mode, keep colors.conf's legacy keys as the user's light palette. */ - if (prefs.hex_gui_dark_mode && user_colors_valid) + if (dark_mode_active && user_colors_valid) lightpal = user_colors; /* If we're currently in light mode, ensure the snapshot stays in sync. */ - if (!prefs.hex_gui_dark_mode) + if (!dark_mode_active) { memcpy (user_colors, colors, sizeof (user_colors)); user_colors_valid = TRUE; } /* If dark mode is enabled but we haven't snapshotted a custom dark palette yet, capture it now. */ - if (prefs.hex_gui_dark_mode && !dark_user_colors_valid) + if (dark_mode_active && !dark_user_colors_valid) { memcpy (dark_user_colors, colors, sizeof (dark_user_colors)); dark_user_colors_valid = TRUE; @@ -308,7 +333,7 @@ palette_save (void) if (dark_user_colors_valid) darkpal = dark_user_colors; - else if (prefs.hex_gui_dark_mode) + else if (dark_mode_active) darkpal = colors; /* current dark palette (likely defaults) */ fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); @@ -399,4 +424,3 @@ palette_apply_dark_mode (gboolean enable) return changed; } - diff --git a/src/fe-gtk/palette.h b/src/fe-gtk/palette.h index 7598ed63..3c09e957 100644 --- a/src/fe-gtk/palette.h +++ b/src/fe-gtk/palette.h @@ -20,6 +20,10 @@ #ifndef HEXCHAT_PALETTE_H #define HEXCHAT_PALETTE_H +#include + +#include "xtext-color.h" + extern GdkColor colors[]; #define COL_MARK_FG 32 @@ -59,4 +63,6 @@ void palette_dark_set_color (int idx, const GdkColor *col); */ gboolean palette_apply_dark_mode (gboolean enable); +void palette_get_xtext_colors (XTextColor *palette, size_t palette_len); + #endif diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c index 3e613684..132fc325 100644 --- a/src/fe-gtk/pixmaps.c +++ b/src/fe-gtk/pixmaps.c @@ -28,6 +28,7 @@ #include #include +#include GdkPixbuf *pix_ulist_voice; GdkPixbuf *pix_ulist_halfop; @@ -49,26 +50,89 @@ GdkPixbuf *pix_tree_util; GdkPixbuf *pix_book; GdkPixbuf *pix_zoitechat; -static GdkPixmap * +static cairo_surface_t * +pixbuf_to_cairo_surface (GdkPixbuf *pixbuf) +{ + cairo_surface_t *surface; + gboolean has_alpha; + int width; + int height; + int src_stride; + int dest_stride; + int n_channels; + const guchar *src_pixels; + unsigned char *dest_pixels; + int x; + int y; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + src_stride = gdk_pixbuf_get_rowstride (pixbuf); + src_pixels = gdk_pixbuf_get_pixels (pixbuf); + dest_stride = cairo_image_surface_get_stride (surface); + dest_pixels = cairo_image_surface_get_data (surface); + + for (y = 0; y < height; y++) + { + const guchar *src_row = src_pixels + (y * src_stride); + guint32 *dest_row = (guint32 *)(dest_pixels + (y * dest_stride)); + + for (x = 0; x < width; x++) + { + const guchar *src = src_row + (x * n_channels); + guchar alpha = has_alpha ? src[3] : 0xff; + guchar red = src[0]; + guchar green = src[1]; + guchar blue = src[2]; + guchar premul_red = (guchar)((red * alpha + 127) / 255); + guchar premul_green = (guchar)((green * alpha + 127) / 255); + guchar premul_blue = (guchar)((blue * alpha + 127) / 255); + + dest_row[x] = ((guint32)alpha << 24) | + ((guint32)premul_red << 16) | + ((guint32)premul_green << 8) | + ((guint32)premul_blue); + } + } + + cairo_surface_mark_dirty (surface); + + return surface; +} + +static cairo_surface_t * pixmap_load_from_file_real (char *file) { GdkPixbuf *img; - GdkPixmap *pixmap; + cairo_surface_t *surface; img = gdk_pixbuf_new_from_file (file, 0); if (!img) return NULL; - gdk_pixbuf_render_pixmap_and_mask (img, &pixmap, NULL, 128); + + surface = pixbuf_to_cairo_surface (img); g_object_unref (img); - return pixmap; + return surface; } -GdkPixmap * +cairo_surface_t * pixmap_load_from_file (char *filename) { char buf[256]; - GdkPixmap *pix; + cairo_surface_t *pix; if (filename[0] == '\0') return NULL; diff --git a/src/fe-gtk/pixmaps.h b/src/fe-gtk/pixmaps.h index a8ef6009..b97fe946 100644 --- a/src/fe-gtk/pixmaps.h +++ b/src/fe-gtk/pixmaps.h @@ -20,6 +20,8 @@ #ifndef HEXCHAT_PIXMAPS_H #define HEXCHAT_PIXMAPS_H +#include + extern GdkPixbuf *pix_ulist_voice; extern GdkPixbuf *pix_ulist_halfop; extern GdkPixbuf *pix_ulist_op; @@ -40,7 +42,7 @@ extern GdkPixbuf *pix_tree_util; extern GdkPixbuf *pix_book; extern GdkPixbuf *pix_zoitechat; -extern GdkPixmap *pixmap_load_from_file (char *file); +extern cairo_surface_t *pixmap_load_from_file (char *file); extern void pixmaps_init (void); #endif diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c index 194e5108..284b5c6d 100644 --- a/src/fe-gtk/rawlog.c +++ b/src/fe-gtk/rawlog.c @@ -101,6 +101,7 @@ void open_rawlog (struct server *serv) { GtkWidget *bbox, *scrolledwindow, *vbox; + XTextColor xtext_palette[XTEXT_COLS]; char tbuf[256]; if (serv->gui->rawlog_window) @@ -120,7 +121,8 @@ open_rawlog (struct server *serv) gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (vbox), scrolledwindow); - serv->gui->rawlog_textlist = gtk_xtext_new (colors, 0); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + serv->gui->rawlog_textlist = gtk_xtext_new (xtext_palette, 0); gtk_container_add (GTK_CONTAINER (scrolledwindow), serv->gui->rawlog_textlist); gtk_xtext_set_font (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.hex_text_font); GTK_XTEXT (serv->gui->rawlog_textlist)->ignore_hidden = 1; diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 91f1a486..a4d028b9 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -58,7 +58,6 @@ static int last_selected_row = 0; /* sound row */ static gboolean color_change; static struct zoitechatprefs setup_prefs; static GSList *color_selector_widgets; -static GtkWidget *dark_mode_toggle_widget; static GtkWidget *cancel_button; static GtkWidget *font_dialog = NULL; void setup_apply_real (int new_pix, int do_ulist, int do_layout, int do_identd); @@ -348,22 +347,30 @@ static const setting tabs_settings[] = static const setting color_settings[] = { - {ST_TOGGLE, N_("Messages"), P_OFFINTNL(hex_text_stripcolor_msg), 0, 0, 0}, - {ST_TOGGLE, N_("Scrollback"), P_OFFINTNL(hex_text_stripcolor_replay), 0, 0, 0}, - {ST_TOGGLE, N_("Topic"), P_OFFINTNL(hex_text_stripcolor_topic), 0, 0, 0}, + {ST_TOGGLE, N_("Messages"), P_OFFINTNL(hex_text_stripcolor_msg), 0, 0, 0}, + {ST_TOGGLE, N_("Scrollback"), P_OFFINTNL(hex_text_stripcolor_replay), 0, 0, 0}, + {ST_TOGGLE, N_("Topic"), P_OFFINTNL(hex_text_stripcolor_topic), 0, 0, 0}, - {ST_END, 0, 0, 0, 0, 0} + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const dark_mode_modes[] = +{ + N_("Auto (system)"), + N_("Dark"), + N_("Light"), + NULL }; static const setting dark_mode_setting = { - ST_TOGGLE, - N_("Enable dark mode"), - P_OFFINTNL(hex_gui_dark_mode), - N_("Applies ZoiteChat's built-in dark palette to the chat buffer, channel list, and user list.\n" - "This includes message colors, selection colors, and interface highlights.\n"), - 0, - 0 + ST_MENU, + N_("Dark mode:"), + P_OFFINTNL(hex_gui_dark_mode), + N_("Choose how ZoiteChat selects its color palette for the chat buffer, channel list, and user list.\n" + "This includes message colors, selection colors, and interface highlights.\n"), + dark_mode_modes, + 0 }; static const char *const dccaccept[] = @@ -1440,12 +1447,65 @@ setup_color_selectors_set_sensitive (gboolean sensitive) } static void -setup_dark_mode_ui_toggle_cb (GtkToggleButton *but, gpointer userdata) +setup_dark_mode_menu_cb (GtkWidget *cbox, const setting *set) { - (void) but; - (void) userdata; - /* Keep color selectors usable even when dark mode is enabled. */ - setup_color_selectors_set_sensitive (TRUE); + setup_menu_cb (cbox, set); + /* Keep color selectors usable even when dark mode is enabled. */ + setup_color_selectors_set_sensitive (TRUE); +} + +static GtkWidget * +setup_create_dark_mode_menu (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid, *cbox, *box; + const char **text = (const char **)set->list; + int i; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + cbox = gtk_combo_box_text_new (); + + for (i = 0; text[i]; i++) + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _(text[i])); + + gtk_combo_box_set_active (GTK_COMBO_BOX (cbox), + setup_get_int (&setup_prefs, set) - set->extra); + g_signal_connect (G_OBJECT (cbox), "changed", + G_CALLBACK (setup_dark_mode_menu_cb), (gpointer)set); + + box = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0); + gtk_table_attach (GTK_TABLE (table), box, 3, 4, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + return cbox; +} + +static void +setup_color_button_apply (GtkWidget *button, const GdkColor *color) +{ + GtkWidget *target = g_object_get_data (G_OBJECT (button), "zoitechat-color-box"); + GtkStateType states[] = { + GTK_STATE_NORMAL, + GTK_STATE_PRELIGHT, + GTK_STATE_ACTIVE, + GTK_STATE_SELECTED, + GTK_STATE_INSENSITIVE + }; + guint i; + GtkWidget *apply_widget = GTK_IS_WIDGET (target) ? target : button; + + for (i = 0; i < G_N_ELEMENTS (states); i++) + gtk_widget_modify_bg (apply_widget, states[i], color); + + if (apply_widget != button) + for (i = 0; i < G_N_ELEMENTS (states); i++) + gtk_widget_modify_bg (button, states[i], color); + + gtk_widget_queue_draw (button); } static void @@ -1454,8 +1514,6 @@ setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) GtkColorSelectionDialog *cdialog = GTK_COLOR_SELECTION_DIALOG (dialog); GdkColor *col; GdkColor old_color; - GtkStyle *style; - col = g_object_get_data (G_OBJECT (button), "c"); old_color = *col; @@ -1473,19 +1531,16 @@ setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE); - style = gtk_style_new (); - style->bg[0] = *col; - gtk_widget_set_style (button, style); - g_object_unref (style); + setup_color_button_apply (button, col); /* is this line correct?? */ gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1); - /* Persist custom colors for the palette the user is editing. */ - if (setup_prefs.hex_gui_dark_mode) - palette_dark_set_color ((int)(col - colors), col); - else - palette_user_set_color ((int)(col - colors), col); + /* Persist custom colors for the palette the user is editing. */ + if (fe_dark_mode_is_enabled_for (setup_prefs.hex_gui_dark_mode)) + palette_dark_set_color ((int)(col - colors), col); + else + palette_user_set_color ((int)(col - colors), col); gtk_widget_destroy (dialog); } @@ -1528,26 +1583,41 @@ static void setup_create_color_button (GtkWidget *table, int num, int row, int col) { GtkWidget *but; - GtkStyle *style; + GtkWidget *label; + GtkWidget *box; + GtkWidget *alignment; char buf[64]; if (num > 31) - strcpy (buf, " "); + strcpy (buf, "  "); + else if (num < 10) + sprintf (buf, " %d", num); else /* 12345678901 23456789 01 23456789 */ sprintf (buf, "%d", num); - but = gtk_button_new_with_label (" "); - gtk_label_set_markup (GTK_LABEL (gtk_bin_get_child (GTK_BIN (but))), buf); + but = gtk_button_new (); + label = gtk_label_new (" "); + gtk_label_set_markup (GTK_LABEL (label), buf); + box = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (box), TRUE); + gtk_container_add (GTK_CONTAINER (box), label); + gtk_container_add (GTK_CONTAINER (but), box); + alignment = gtk_bin_get_child (GTK_BIN (but)); + if (GTK_IS_ALIGNMENT (alignment)) + { + gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.5, 0.5, 1.0, 1.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 0); + } + gtk_widget_show (label); + gtk_widget_show (box); /* win32 build uses this to turn off themeing */ g_object_set_data (G_OBJECT (but), "zoitechat-color", (gpointer)1); + g_object_set_data (G_OBJECT (but), "zoitechat-color-box", box); gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1, - GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + GTK_SHRINK, GTK_SHRINK, 0, 0); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num)); - style = gtk_style_new (); - style->bg[GTK_STATE_NORMAL] = colors[num]; - gtk_widget_set_style (but, style); - g_object_unref (style); + setup_color_button_apply (but, &colors[num]); /* Track all color selector widgets (used for dark mode UI behavior). */ color_selector_widgets = g_slist_prepend (color_selector_widgets, but); @@ -1580,11 +1650,10 @@ setup_create_other_color (char *text, int num, int row, GtkWidget *tab) static GtkWidget * setup_create_color_page (void) { - color_selector_widgets = NULL; - dark_mode_toggle_widget = NULL; + color_selector_widgets = NULL; - GtkWidget *tab, *box, *label; - int i; + GtkWidget *tab, *box, *label; + int i; box = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (box), 6); @@ -1625,15 +1694,13 @@ setup_create_color_page (void) setup_create_other_color (_("New data:"), COL_NEW_DATA, 9, tab); setup_create_other_colorR (_("Marker line:"), COL_MARKER, 9, tab); - setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab); - setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); - setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); - setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab); - dark_mode_toggle_widget = setup_create_toggleL (tab, 13, &dark_mode_setting); - g_signal_connect (G_OBJECT (dark_mode_toggle_widget), "toggled", - G_CALLBACK (setup_dark_mode_ui_toggle_cb), NULL); - setup_color_selectors_set_sensitive (TRUE); - setup_create_header (tab, 15, N_("Color Stripping")); + setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab); + setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); + setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); + setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab); + setup_create_dark_mode_menu (tab, 13, &dark_mode_setting); + setup_color_selectors_set_sensitive (TRUE); + setup_create_header (tab, 15, N_("Color Stripping")); /* label = gtk_label_new (_("Strip colors from:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); @@ -1809,9 +1876,9 @@ setup_theme_apply_cb (GtkWidget *button, gpointer user_data) g_unlink (events_dest); } - palette_load (); - palette_apply_dark_mode (prefs.hex_gui_dark_mode); - color_change = TRUE; + palette_load (); + palette_apply_dark_mode (fe_dark_mode_is_enabled ()); + color_change = TRUE; setup_apply_real (0, TRUE, FALSE, FALSE); setup_theme_show_message (GTK_MESSAGE_INFO, _("Theme applied. Some changes may require a restart to take full effect.")); @@ -2372,15 +2439,15 @@ setup_apply_to_sess (session_gui *gui) chanview_apply_theme ((chanview *) gui->chanview); if (prefs.hex_gui_ulist_style) - gtk_widget_set_style (gui->user_tree, input_style); + gtk_widget_modify_font (gui->user_tree, input_style->font_desc); - if (prefs.hex_gui_ulist_style || prefs.hex_gui_dark_mode) - { - gtk_widget_modify_base (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_BG]); - if (prefs.hex_gui_dark_mode) - gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_FG]); - else - gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, NULL); + if (prefs.hex_gui_ulist_style || fe_dark_mode_is_enabled ()) + { + gtk_widget_modify_base (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_BG]); + if (fe_dark_mode_is_enabled ()) + gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_FG]); + else + gtk_widget_modify_text (gui->user_tree, GTK_STATE_NORMAL, NULL); } else { @@ -2449,7 +2516,7 @@ setup_apply_real (int new_pix, int do_ulist, int do_layout, int do_identd) if (new_pix) { if (channelwin_pix) - g_object_unref (channelwin_pix); + cairo_surface_destroy (channelwin_pix); channelwin_pix = pixmap_load_from_file (prefs.hex_text_background); } @@ -2579,14 +2646,17 @@ setup_apply (struct zoitechatprefs *pr) * "Dark mode" applies ZoiteChat's built-in dark palette to the chat views. * * IMPORTANT: don't short-circuit this call. - * We MUST run palette_apply_dark_mode() when the toggle changes, otherwise - * the preference flips but the palette stays the same (aka: "nothing happens"). - */ - { - gboolean pal_changed = palette_apply_dark_mode (prefs.hex_gui_dark_mode); - if (prefs.hex_gui_dark_mode != old_dark_mode || pal_changed) - color_change = TRUE; - } + * We MUST run palette_apply_dark_mode() when the setting changes, otherwise + * the preference flips but the palette stays the same (aka: "nothing happens"). + */ + { + gboolean pal_changed = palette_apply_dark_mode (fe_dark_mode_is_enabled_for (prefs.hex_gui_dark_mode)); + if (prefs.hex_gui_dark_mode != old_dark_mode || pal_changed) + color_change = TRUE; + } + + if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO) + fe_set_auto_dark_mode_state (fe_dark_mode_is_enabled_for (ZOITECHAT_DARK_MODE_AUTO)); #ifdef WIN32 /* merge hex_font_main and hex_font_alternative into hex_font_normal */ @@ -2683,12 +2753,11 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin) { *swin = NULL; - if (color_selector_widgets) - { - g_slist_free (color_selector_widgets); - color_selector_widgets = NULL; - dark_mode_toggle_widget = NULL; - } + if (color_selector_widgets) + { + g_slist_free (color_selector_widgets); + color_selector_widgets = NULL; + } if (font_dialog) { diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c index 9b8c78d6..1e9e956f 100644 --- a/src/fe-gtk/textgui.c +++ b/src/fe-gtk/textgui.c @@ -438,6 +438,7 @@ void pevent_dialog_show () { GtkWidget *vbox, *hbox, *wid, *pane; + XTextColor xtext_palette[XTEXT_COLS]; if (pevent_dialog) { @@ -462,7 +463,8 @@ pevent_dialog_show () gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (wid), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); gtk_box_pack_start (GTK_BOX (vbox), wid, FALSE, TRUE, 0); - pevent_dialog_twid = gtk_xtext_new (colors, 0); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + pevent_dialog_twid = gtk_xtext_new (xtext_palette, 0); gtk_widget_set_sensitive (pevent_dialog_twid, FALSE); gtk_widget_set_size_request (pevent_dialog_twid, -1, 75); gtk_container_add (GTK_CONTAINER (wid), pevent_dialog_twid); diff --git a/src/fe-gtk/xtext-color.h b/src/fe-gtk/xtext-color.h new file mode 100644 index 00000000..4696c134 --- /dev/null +++ b/src/fe-gtk/xtext-color.h @@ -0,0 +1,20 @@ +/* ZoiteChat + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef HEXCHAT_XTEXT_COLOR_H +#define HEXCHAT_XTEXT_COLOR_H + +typedef struct +{ + double red; + double green; + double blue; + double alpha; +} XTextColor; + +#endif diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 687aaf48..8f3cc190 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -54,13 +54,16 @@ #include #include #include +#include #include #else #include +#include #ifdef GDK_WINDOWING_X11 #include #endif #endif +#include /* is delimiter */ #define is_del(c) \ @@ -147,7 +150,109 @@ static void gtk_xtext_search_fini (xtext_buffer *); static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk_xtext_search_flags flags, GError **perr); static char * gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, int *ret_off, int *ret_len, GSList **slp); -#define xtext_draw_bg(xt,x,y,w,h) gdk_draw_rectangle(xt->draw_buf, xt->bgc, 1, x, y, w, h); +static inline void +xtext_set_source_color (cairo_t *cr, const XTextColor *color, gdouble alpha) +{ + cairo_set_source_rgba (cr, color->red, color->green, + color->blue, color->alpha * alpha); +} + +static cairo_surface_t * +xtext_surface_from_window (GdkWindow *window) +{ + cairo_surface_t *surface = NULL; + int width; + int height; + cairo_t *cr; + + if (!window) + return NULL; + + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); + if (width <= 0 || height <= 0) + return NULL; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + cr = cairo_create (surface); + gdk_cairo_set_source_window (cr, window, 0.0, 0.0); + cairo_paint (cr); + cairo_destroy (cr); + + return surface; +} + +static cairo_t * +xtext_create_context (GtkXText *xtext) +{ + if (xtext->draw_surface) + return cairo_create (xtext->draw_surface); + + return gdk_cairo_create (xtext->draw_window); +} + +static inline void +xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, const XTextColor *color, int x, int y, int width, int height) +{ + cairo_save (cr); + + xtext_set_source_color (cr, color, 1.0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle (cr, (double)x, (double)y, (double)width, (double)height); + cairo_fill (cr); + + cairo_restore (cr); +} + +static inline void +xtext_draw_line (GtkXText *xtext, cairo_t *cr, const XTextColor *color, int x1, int y1, int x2, int y2) +{ + cairo_save (cr); + + /* Disable antialiasing for crispy 1-pixel lines */ + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + xtext_set_source_color (cr, color, 1.0); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_line_width (cr, 1.0); + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x2, y2); + cairo_stroke (cr); + + cairo_restore (cr); +} + +static inline void +xtext_draw_bg_offset (GtkXText *xtext, int x, int y, int width, int height, int tile_x, int tile_y) +{ + cairo_t *cr = xtext_create_context (xtext); + + if (xtext->background_surface) + { + cairo_set_source_surface (cr, xtext->background_surface, tile_x, tile_y); + cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT); + cairo_rectangle (cr, (double)x, (double)y, (double)width, (double)height); + cairo_fill (cr); + } + else + { + xtext_draw_rectangle (xtext, cr, &xtext->bgc, x, y, width, height); + } + + cairo_destroy (cr); +} + +static inline void +xtext_draw_bg (GtkXText *xtext, int x, int y, int width, int height) +{ + xtext_draw_bg_offset (xtext, x, y, width, height, xtext->ts_x, xtext->ts_y); +} /* ======================================= */ /* ============ PANGO BACKEND ============ */ @@ -354,77 +459,53 @@ backend_get_text_width_slp (GtkXText *xtext, guchar *str, GSList *slp) return width; } -/* simplified version of gdk_draw_layout_line_with_colors() */ - -static void -xtext_draw_layout_line (GdkDrawable *drawable, - GdkGC *gc, - gint x, - gint y, - PangoLayoutLine *line) -{ - GSList *tmp_list = line->runs; - PangoRectangle logical_rect; - gint x_off = 0; - - while (tmp_list) - { - PangoLayoutRun *run = tmp_list->data; - - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - NULL, &logical_rect); - - gdk_draw_glyphs (drawable, gc, run->item->analysis.font, - x + x_off / PANGO_SCALE, y, run->glyphs); - - x_off += logical_rect.width; - tmp_list = tmp_list->next; - } -} - static void -backend_draw_text_emph (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y, +backend_draw_text_emph (GtkXText *xtext, gboolean dofill, int x, int y, char *str, int len, int str_width, int emphasis) { - GdkGCValues val; - GdkColor col; + cairo_t *cr; PangoLayoutLine *line; + cr = xtext_create_context (xtext); + pango_layout_set_attributes (xtext->layout, attr_lists[emphasis]); pango_layout_set_text (xtext->layout, str, len); if (dofill) { - gdk_gc_get_values (gc, &val); - col.pixel = val.background.pixel; - gdk_gc_set_foreground (gc, &col); - gdk_draw_rectangle (xtext->draw_buf, gc, 1, x, y - + xtext_draw_rectangle (xtext, cr, &xtext->bgc, x, y - xtext->font->ascent, str_width, xtext->fontsize); - col.pixel = val.foreground.pixel; - gdk_gc_set_foreground (gc, &col); } - line = pango_layout_get_lines (xtext->layout)->data; + xtext_set_source_color (cr, &xtext->fgc, 1.0); + line = pango_layout_get_line_readonly (xtext->layout, 0); - xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line); + cairo_save (cr); + cairo_move_to (cr, x, y); + pango_cairo_show_layout_line (cr, line); + cairo_restore (cr); + + cairo_destroy (cr); } static void -xtext_set_fg (GtkXText *xtext, GdkGC *gc, int index) +xtext_set_fg (GtkXText *xtext, int index) { - gdk_gc_set_foreground (gc, &xtext->palette[index]); + xtext->fgc = xtext->palette[index]; } static void -xtext_set_bg (GtkXText *xtext, GdkGC *gc, int index) +xtext_set_bg (GtkXText *xtext, int index) { - gdk_gc_set_background (gc, &xtext->palette[index]); + xtext->bgc = xtext->palette[index]; } static void gtk_xtext_init (GtkXText * xtext) { - xtext->pixmap = NULL; + xtext->background_surface = NULL; + xtext->draw_window = NULL; + xtext->draw_surface = NULL; xtext->io_tag = 0; xtext->add_io_tag = 0; xtext->scroll_tag = 0; @@ -545,7 +626,7 @@ gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext) } GtkWidget * -gtk_xtext_new (GdkColor palette[], int separator) +gtk_xtext_new (const XTextColor *palette, int separator) { GtkXText *xtext; @@ -584,10 +665,10 @@ gtk_xtext_destroy (GtkObject * object) xtext->io_tag = 0; } - if (xtext->pixmap) + if (xtext->background_surface) { - g_object_unref (xtext->pixmap); - xtext->pixmap = NULL; + cairo_surface_destroy (xtext->background_surface); + xtext->background_surface = NULL; } if (xtext->font) @@ -605,41 +686,6 @@ gtk_xtext_destroy (GtkObject * object) xtext->adj = NULL; } - if (xtext->bgc) - { - g_object_unref (xtext->bgc); - xtext->bgc = NULL; - } - - if (xtext->fgc) - { - g_object_unref (xtext->fgc); - xtext->fgc = NULL; - } - - if (xtext->light_gc) - { - g_object_unref (xtext->light_gc); - xtext->light_gc = NULL; - } - - if (xtext->dark_gc) - { - g_object_unref (xtext->dark_gc); - xtext->dark_gc = NULL; - } - - if (xtext->thin_gc) - { - g_object_unref (xtext->thin_gc); - xtext->thin_gc = NULL; - } - - if (xtext->marker_gc) - { - g_object_unref (xtext->marker_gc); - xtext->marker_gc = NULL; - } if (xtext->hand_cursor) { @@ -680,9 +726,6 @@ gtk_xtext_realize (GtkWidget * widget) { GtkXText *xtext; GdkWindowAttr attributes; - GdkGCValues val; - GdkColor col; - GdkColormap *cmap; gtk_widget_set_realized (widget, TRUE); xtext = GTK_XTEXT (widget); @@ -697,8 +740,7 @@ gtk_xtext_realize (GtkWidget * widget) GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK; - cmap = gtk_widget_get_colormap (widget); - attributes.colormap = cmap; + attributes.colormap = gtk_widget_get_colormap (widget); attributes.visual = gtk_widget_get_visual (widget); widget->window = gdk_window_new (widget->parent->window, &attributes, @@ -709,60 +751,42 @@ gtk_xtext_realize (GtkWidget * widget) xtext->depth = gdk_window_get_visual (widget->window)->depth; - val.subwindow_mode = GDK_INCLUDE_INFERIORS; - val.graphics_exposures = 0; - - xtext->bgc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->fgc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->light_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->dark_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->thin_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->marker_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - /* for the separator bar (light) */ - col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff; - gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - gdk_gc_set_foreground (xtext->light_gc, &col); + xtext->light_gc.red = 1.0; + xtext->light_gc.green = 1.0; + xtext->light_gc.blue = 1.0; + xtext->light_gc.alpha = 1.0; /* for the separator bar (dark) */ - col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111; - gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - gdk_gc_set_foreground (xtext->dark_gc, &col); + xtext->dark_gc.red = 0x1111 / 65535.0; + xtext->dark_gc.green = 0x1111 / 65535.0; + xtext->dark_gc.blue = 0x1111 / 65535.0; + xtext->dark_gc.alpha = 1.0; /* for the separator bar (thinline) */ - col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38; - gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - gdk_gc_set_foreground (xtext->thin_gc, &col); + xtext->thin_gc.red = 0x8e38 / 65535.0; + xtext->thin_gc.green = 0x8e38 / 65535.0; + xtext->thin_gc.blue = 0x9f38 / 65535.0; + xtext->thin_gc.alpha = 1.0; /* for the marker bar (marker) */ - gdk_gc_set_foreground (xtext->marker_gc, &xtext->palette[XTEXT_MARKER]); + xtext->marker_gc = xtext->palette[XTEXT_MARKER]; - xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); - xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); - xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + xtext_set_fg (xtext, XTEXT_FG); + xtext_set_bg (xtext, XTEXT_BG); /* draw directly to window */ - xtext->draw_buf = widget->window; + xtext->draw_window = widget->window; - if (xtext->pixmap) + if (xtext->background_surface) { - gdk_gc_set_tile (xtext->bgc, xtext->pixmap); - gdk_gc_set_ts_origin (xtext->bgc, 0, 0); xtext->ts_x = xtext->ts_y = 0; - gdk_gc_set_fill (xtext->bgc, GDK_TILED); } xtext->hand_cursor = gdk_cursor_new_for_display (gdk_window_get_display (widget->window), GDK_HAND1); xtext->resize_cursor = gdk_cursor_new_for_display (gdk_window_get_display (widget->window), GDK_LEFT_SIDE); gdk_window_set_back_pixmap (widget->window, NULL, FALSE); - widget->style = gtk_style_attach (widget->style, widget->window); backend_init (xtext); } @@ -971,7 +995,7 @@ static void gtk_xtext_draw_sep (GtkXText * xtext, int y) { int x, height; - GdkGC *light, *dark; + cairo_t *cr; if (y == -1) { @@ -985,31 +1009,37 @@ gtk_xtext_draw_sep (GtkXText * xtext, int y) /* draw the separator line */ if (xtext->separator && xtext->buffer->indent) { - light = xtext->light_gc; - dark = xtext->dark_gc; + const XTextColor *light = &xtext->light_gc; + const XTextColor *dark = &xtext->dark_gc; + cr = xtext_create_context (xtext); x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); if (x < 1) + { + cairo_destroy (cr); return; + } if (xtext->thinline) { if (xtext->moving_separator) - gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + xtext_draw_line (xtext, cr, light, x, y, x, y + height); else - gdk_draw_line (xtext->draw_buf, xtext->thin_gc, x, y, x, y + height); + xtext_draw_line (xtext, cr, &xtext->thin_gc, x, y, x, y + height); } else { if (xtext->moving_separator) { - gdk_draw_line (xtext->draw_buf, light, x - 1, y, x - 1, y + height); - gdk_draw_line (xtext->draw_buf, dark, x, y, x, y + height); + xtext_draw_line (xtext, cr, light, x - 1, y, x - 1, y + height); + xtext_draw_line (xtext, cr, dark, x, y, x, y + height); } else { - gdk_draw_line (xtext->draw_buf, dark, x - 1, y, x - 1, y + height); - gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + xtext_draw_line (xtext, cr, dark, x - 1, y, x - 1, y + height); + xtext_draw_line (xtext, cr, light, x, y, x, y + height); } } + + cairo_destroy (cr); } } @@ -1017,6 +1047,7 @@ static void gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) { int x, width, render_y; + cairo_t *cr; if (!xtext->marker) return; @@ -1033,7 +1064,9 @@ gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) x = 0; width = GTK_WIDGET (xtext)->allocation.width; - gdk_draw_line (xtext->draw_buf, xtext->marker_gc, x, render_y, x + width, render_y); + cr = xtext_create_context (xtext); + xtext_draw_line (xtext, cr, &xtext->marker_gc, x, render_y, x + width, render_y); + cairo_destroy (cr); if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext))))) { @@ -2551,11 +2584,13 @@ gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len) static int gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, - int len, GdkGC *gc, int *emphasis) + int len, int *emphasis) { int str_width, dofill; - GdkDrawable *pix = NULL; + cairo_surface_t *surface = NULL; int dest_x = 0, dest_y = 0; + int tile_x = xtext->ts_x; + int tile_y = xtext->ts_y; if (xtext->dont_render || len < 1 || xtext->hidden) return 0; @@ -2579,39 +2614,40 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, goto dounder; } - pix = gdk_pixmap_new (xtext->draw_buf, str_width, xtext->fontsize, xtext->depth); - if (pix) + surface = gdk_window_create_similar_surface (GTK_WIDGET (xtext)->window, + CAIRO_CONTENT_COLOR_ALPHA, str_width, xtext->fontsize); + if (surface) { dest_x = x; dest_y = y - xtext->font->ascent; - - gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x - x, xtext->ts_y - dest_y); + tile_x = xtext->ts_x - x; + tile_y = xtext->ts_y - dest_y; x = 0; y = xtext->font->ascent; - xtext->draw_buf = pix; + xtext->draw_surface = surface; } dofill = TRUE; /* backcolor is always handled by XDrawImageString */ - if (!xtext->backcolor && xtext->pixmap) + if (!xtext->backcolor && xtext->background_surface) { - /* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */ - xtext_draw_bg (xtext, x, y - xtext->font->ascent, str_width, - xtext->fontsize); + /* draw the background surface behind the text - CAUSES FLICKER HERE!! */ + xtext_draw_bg_offset (xtext, x, y - xtext->font->ascent, str_width, + xtext->fontsize, tile_x, tile_y); dofill = FALSE; /* already drawn the background */ } - backend_draw_text_emph (xtext, dofill, gc, x, y, str, len, str_width, *emphasis); + backend_draw_text_emph (xtext, dofill, x, y, str, len, str_width, *emphasis); - if (pix) + if (surface) { GdkRectangle clip; GdkRectangle dest; + cairo_t *cr; - gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x, xtext->ts_y); - xtext->draw_buf = GTK_WIDGET (xtext)->window; + xtext->draw_surface = NULL; clip.x = xtext->clip_x; clip.y = xtext->clip_y; clip.width = xtext->clip_x2 - xtext->clip_x; @@ -2624,24 +2660,36 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, if (gdk_rectangle_intersect (&clip, &dest, &dest)) /* dump the DB to window, but only within the clip_x/x2/y/y2 */ - gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, - dest.x - dest_x, dest.y - dest_y, - dest.x, dest.y, dest.width, dest.height); - g_object_unref (pix); + { + cr = xtext_create_context (xtext); + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface (cr, surface, dest_x, dest_y); + cairo_rectangle (cr, dest.x, dest.y, dest.width, dest.height); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); + } + cairo_surface_destroy (surface); } if (xtext->strikethrough) { + cairo_t *cr; /* pango_attr_strikethrough_new does not render in the custom widget so we need to reinvent the wheel */ y = dest_y + (xtext->fontsize / 2); - gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + cr = xtext_create_context (xtext); + xtext_draw_line (xtext, cr, &xtext->fgc, dest_x, y, dest_x + str_width - 1, y); + cairo_destroy (cr); } if (xtext->underline) { dounder: + { + cairo_t *cr; - if (pix) + if (surface) y = dest_y + xtext->font->ascent + 1; else { @@ -2649,7 +2697,10 @@ dounder: dest_x = x; } /* draw directly to window, it's out of the range of our DB */ - gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + cr = xtext_create_context (xtext); + xtext_draw_line (xtext, cr, &xtext->fgc, dest_x, y, dest_x + str_width - 1, y); + cairo_destroy (cr); + } } return str_width; @@ -2668,9 +2719,9 @@ gtk_xtext_reset (GtkXText * xtext, int mark, int attribs) { xtext->backcolor = FALSE; if (xtext->col_fore != XTEXT_FG) - xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + xtext_set_fg (xtext, XTEXT_FG); if (xtext->col_back != XTEXT_BG) - xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + xtext_set_bg (xtext, XTEXT_BG); } xtext->col_fore = XTEXT_FG; xtext->col_back = XTEXT_BG; @@ -2738,14 +2789,13 @@ gtk_xtext_search_offset (xtext_buffer *buf, textentry *ent, unsigned int off) /* render a single line, which WONT wrap, and parse mIRC colors */ -#define RENDER_FLUSH x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, emphasis) +#define RENDER_FLUSH x += gtk_xtext_render_flush (xtext, x, y, pstr, j, emphasis) static int gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, unsigned char *str, int len, int win_width, int indent, int line, int left_only, int *x_size_ret, int *emphasis) { - GdkGC *gc; int i = 0, x = indent, j = 0; unsigned char *pstr = str; int col_num, tmp; @@ -2760,13 +2810,11 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, offset = str - ent->str; - gc = xtext->fgc; /* our foreground GC */ - if (ent->mark_start != -1 && ent->mark_start <= i + offset && ent->mark_end > i + offset) { - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; mark = TRUE; } @@ -2840,7 +2888,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, col_num = col_num % XTEXT_MIRC_COLS; xtext->col_fore = col_num; if (!mark) - xtext_set_fg (xtext, gc, col_num); + xtext_set_fg (xtext, col_num); } } else { @@ -2870,7 +2918,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, else xtext->backcolor = TRUE; if (!mark) - xtext_set_bg (xtext, gc, col_num); + xtext_set_bg (xtext, col_num); xtext->col_back = col_num; } else { @@ -2880,7 +2928,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, if (col_num > XTEXT_MAX_COLOR) col_num = col_num % XTEXT_MIRC_COLS; if (!mark) - xtext_set_fg (xtext, gc, col_num); + xtext_set_fg (xtext, col_num); xtext->col_fore = col_num; } xtext->parsing_backcolor = FALSE; @@ -2904,14 +2952,14 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, { if (k & GTK_MATCH_CUR) { - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; srch_mark = TRUE; } else { - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE; srch_mark = FALSE; } @@ -2921,15 +2969,15 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, xtext->underline = (k & GTK_MATCH_CUR)? TRUE: FALSE; if (k & (GTK_MATCH_START | GTK_MATCH_MID)) { - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; srch_mark = TRUE; } if (k & GTK_MATCH_END) { - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE; srch_mark = FALSE; xtext->underline = FALSE; @@ -2952,8 +3000,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, xtext->col_back = tmp; if (!mark) { - xtext_set_fg (xtext, gc, xtext->col_fore); - xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); } if (xtext->col_back != XTEXT_BG) xtext->backcolor = TRUE; @@ -3040,7 +3088,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, /* have we been told to stop rendering at this point? */ if (xtext->jump_out_offset > 0 && xtext->jump_out_offset <= (i + offset)) { - gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, emphasis); + gtk_xtext_render_flush (xtext, x, y, pstr, j, emphasis); ret = 0; /* skip the rest of the lines, we're done. */ j = 0; break; @@ -3074,8 +3122,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, RENDER_FLUSH; pstr += j; j = 0; - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; if (srch_underline) { @@ -3090,8 +3138,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, RENDER_FLUSH; pstr += j; j = 0; - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); if (xtext->col_back != XTEXT_BG) xtext->backcolor = TRUE; else @@ -3106,8 +3154,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, if (mark || srch_mark) { - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); if (xtext->col_back != XTEXT_BG) xtext->backcolor = TRUE; else @@ -3431,7 +3479,7 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line, } void -gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) +gtk_xtext_set_palette (GtkXText * xtext, const XTextColor *palette) { int i; @@ -3442,11 +3490,10 @@ gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) if (gtk_widget_get_realized (GTK_WIDGET(xtext))) { - xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); - xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); - xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + xtext_set_fg (xtext, XTEXT_FG); + xtext_set_bg (xtext, XTEXT_BG); - gdk_gc_set_foreground (xtext->marker_gc, &xtext->palette[XTEXT_MARKER]); + xtext->marker_gc = xtext->palette[XTEXT_MARKER]; } xtext->col_fore = XTEXT_FG; xtext->col_back = XTEXT_BG; @@ -3530,37 +3577,23 @@ gtk_xtext_set_font (GtkXText *xtext, char *name) } void -gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap) +gtk_xtext_set_background (GtkXText * xtext, cairo_surface_t *surface) { - GdkGCValues val; - - if (xtext->pixmap) + if (xtext->background_surface) { - g_object_unref (xtext->pixmap); - xtext->pixmap = NULL; + cairo_surface_destroy (xtext->background_surface); + xtext->background_surface = NULL; } dontscroll (xtext->buffer); - xtext->pixmap = pixmap; + if (surface) + { + xtext->background_surface = cairo_surface_reference (surface); + } - if (pixmap != 0) + if (surface && gtk_widget_get_realized (GTK_WIDGET(xtext))) { - g_object_ref (pixmap); - if (gtk_widget_get_realized (GTK_WIDGET(xtext))) - { - gdk_gc_set_tile (xtext->bgc, pixmap); - gdk_gc_set_ts_origin (xtext->bgc, 0, 0); - xtext->ts_x = xtext->ts_y = 0; - gdk_gc_set_fill (xtext->bgc, GDK_TILED); - } - } else if (gtk_widget_get_realized (GTK_WIDGET(xtext))) - { - g_object_unref (xtext->bgc); - val.subwindow_mode = GDK_INCLUDE_INFERIORS; - val.graphics_exposures = 0; - xtext->bgc = gdk_gc_new_with_values (GTK_WIDGET (xtext)->window, - &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + xtext->ts_x = xtext->ts_y = 0; } } @@ -3812,7 +3845,8 @@ gtk_xtext_render_page (GtkXText * xtext) if (xtext->buffer->indent < MARGIN) xtext->buffer->indent = MARGIN; /* 2 pixels is our left margin */ - gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + width = gdk_window_get_width (GTK_WIDGET (xtext)->window); + height = gdk_window_get_height (GTK_WIDGET (xtext)->window); if (width < 34 || height < xtext->fontsize || width < xtext->buffer->indent + 32) return; @@ -3837,30 +3871,70 @@ gtk_xtext_render_page (GtkXText * xtext) xtext->buffer->last_pixel_pos = pos; #ifndef __APPLE__ - if (!xtext->pixmap && abs (overlap) < height) + if (!xtext->background_surface && abs (overlap) < height) { GdkRectangle area; + cairo_t *cr; + int src_x = 0; + int src_y = 0; + int dest_x = 0; + int dest_y = 0; + int copy_height = 0; - /* so the obscured regions are exposed */ - gdk_gc_set_exposures (xtext->fgc, TRUE); if (overlap < 1) /* DOWN */ { int remainder; + cairo_surface_t *surface; - gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, - 0, -overlap, 0, 0, width, height + overlap); + src_y = -overlap; + dest_y = 0; + copy_height = height + overlap; + cr = xtext_create_context (xtext); + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + surface = xtext_surface_from_window (GTK_WIDGET (xtext)->window); + if (!surface) + { + cairo_restore (cr); + cairo_destroy (cr); + goto full_redraw; + } + cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); + cairo_surface_destroy (surface); + cairo_rectangle (cr, dest_x, dest_y, width, copy_height); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); remainder = ((height - xtext->font->descent) % xtext->fontsize) + xtext->font->descent; area.y = (height + overlap) - remainder; area.height = remainder - overlap; } else { - gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, - 0, 0, 0, overlap, width, height - overlap); + cairo_surface_t *surface; + + src_y = 0; + dest_y = overlap; + copy_height = height - overlap; + cr = xtext_create_context (xtext); + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + surface = xtext_surface_from_window (GTK_WIDGET (xtext)->window); + if (!surface) + { + cairo_restore (cr); + cairo_destroy (cr); + goto full_redraw; + } + cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); + cairo_surface_destroy (surface); + cairo_rectangle (cr, dest_x, dest_y, width, copy_height); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); area.y = 0; area.height = overlap; } - gdk_gc_set_exposures (xtext->fgc, FALSE); if (area.height > 0) { @@ -3873,6 +3947,7 @@ gtk_xtext_render_page (GtkXText * xtext) } #endif +full_redraw: width -= MARGIN; lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1; diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index beb77c17..1d30bbe9 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -21,6 +21,8 @@ #define HEXCHAT_XTEXT_H #include +#include +#include "xtext-color.h" #define GTK_TYPE_XTEXT (gtk_xtext_get_type ()) #define GTK_XTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText)) @@ -130,8 +132,9 @@ struct _GtkXText xtext_buffer *selection_buffer; GtkAdjustment *adj; - GdkPixmap *pixmap; /* 0 = use palette[19] */ - GdkDrawable *draw_buf; /* points to ->window */ + cairo_surface_t *background_surface; /* 0 = use palette[19] */ + GdkWindow *draw_window; /* points to ->window */ + cairo_surface_t *draw_surface; /* temporary surface for offscreen draws */ GdkCursor *hand_cursor; GdkCursor *resize_cursor; @@ -142,13 +145,13 @@ struct _GtkXText int last_win_h; int last_win_w; - GdkGC *bgc; /* backing pixmap */ - GdkGC *fgc; /* text foreground color */ - GdkGC *light_gc; /* sep bar */ - GdkGC *dark_gc; - GdkGC *thin_gc; - GdkGC *marker_gc; - GdkColor palette[XTEXT_COLS]; + XTextColor bgc; /* text background color */ + XTextColor fgc; /* text foreground color */ + XTextColor light_gc; /* sep bar */ + XTextColor dark_gc; + XTextColor thin_gc; + XTextColor marker_gc; + XTextColor palette[XTEXT_COLS]; gint io_tag; /* for delayed refresh events */ gint add_io_tag; /* "" when adding new text */ @@ -252,15 +255,15 @@ struct _GtkXTextClass void (*set_scroll_adjustments) (GtkXText *xtext, GtkAdjustment *hadj, GtkAdjustment *vadj); }; -GtkWidget *gtk_xtext_new (GdkColor palette[], int separator); +GtkWidget *gtk_xtext_new (const XTextColor *palette, int separator); void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp); void gtk_xtext_append_indent (xtext_buffer *buf, unsigned char *left_text, int left_len, unsigned char *right_text, int right_len, time_t stamp); int gtk_xtext_set_font (GtkXText *xtext, char *name); -void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap); -void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]); +void gtk_xtext_set_background (GtkXText * xtext, cairo_surface_t *surface); +void gtk_xtext_set_palette (GtkXText * xtext, const XTextColor *palette); void gtk_xtext_clear (xtext_buffer *buf, int lines); void gtk_xtext_save (GtkXText * xtext, int fh); void gtk_xtext_refresh (GtkXText * xtext);