2011-02-24 04:14:30 +01:00
/* X-Chat
* Copyright (C) 1998-2005 Peter Zelezny.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
2012-12-23 11:36:54 -08:00
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
2011-02-24 04:14:30 +01:00
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
2026-03-09 14:21:20 -06:00
#include "fe-gtk.h"
2026-01-17 16:56:35 -07:00
#include <gdk/gdk.h>
2013-07-24 20:47:01 -04:00
#include <gdk/gdkkeysyms.h>
2026-01-17 16:19:56 -07:00
#include <gdk/gdkcairo.h>
2026-01-17 21:56:00 -07:00
#include <gdk-pixbuf/gdk-pixbuf.h>
2013-07-24 20:47:01 -04:00
2026-01-05 23:12:38 -07:00
#include "../common/zoitechat.h"
2011-02-24 04:14:30 +01:00
#include "../common/fe.h"
#include "../common/server.h"
2026-01-05 23:12:38 -07:00
#include "../common/zoitechatc.h"
2011-02-24 04:14:30 +01:00
#include "../common/outbound.h"
#include "../common/inbound.h"
#include "../common/plugin.h"
#include "../common/modes.h"
#include "../common/url.h"
2011-12-11 17:34:02 +01:00
#include "../common/util.h"
2012-07-21 14:26:19 +02:00
#include "../common/text.h"
2013-06-06 19:16:50 -04:00
#include "../common/chanopt.h"
2013-07-24 20:47:01 -04:00
#include "../common/cfgfiles.h"
2011-12-11 17:34:02 +01:00
2026-03-02 19:42:48 -07:00
#include "theme/theme-manager.h"
#include "theme/theme-css.h"
2011-02-24 04:14:30 +01:00
#include "banlist.h"
2026-09-10 07:13:41 -06:00
#include "emoji-picker.h"
#include "emoji-font.h"
2011-02-24 04:14:30 +01:00
#include "gtkutil.h"
2026-03-13 12:39:55 -06:00
#include "icon-resolver.h"
2011-02-24 04:14:30 +01:00
#include "joind.h"
2026-03-02 19:42:48 -07:00
#include "theme/theme-access.h"
#include "theme/theme-palette.h"
2011-02-24 04:14:30 +01:00
#include "maingui.h"
#include "menu.h"
2026-03-16 22:45:30 -06:00
#include "preferences-persistence.h"
2011-02-24 04:14:30 +01:00
#include "fkeys.h"
#include "userlistgui.h"
#include "chanview.h"
#include "pixmaps.h"
#include "plugin-tray.h"
#include "xtext.h"
2026-07-27 16:42:29 -06:00
#include "inline-image.h"
2011-02-24 04:14:30 +01:00
#include "sexy-spell-entry.h"
2017-08-31 09:52:45 -04:00
#include "gtkutil.h"
2011-02-24 04:14:30 +01:00
2019-12-16 03:42:31 -05:00
#ifdef G_OS_WIN32
2020-04-19 05:43:27 +02:00
#include <windows.h>
2026-02-25 14:48:18 -07:00
#include <shellapi.h>
2026-02-25 14:18:23 -07:00
#include <gdk/gdkwin32.h>
2026-07-07 23:44:22 -06:00
/* The shell only minimizes a window from its taskbar button when the
* window style carries WS_MINIMIZEBOX; without it the click merely
* deactivates the window (issue #65). GTK3 older than 3.24.50 can drop
* the bit from top-level windows (https://gitlab.gnome.org/GNOME/gtk/-/issues/7494),
* so guarantee it ourselves for session windows. */
static void
mg_win32_ensure_minimizable ( GdkWindow * gdk_window )
{
HWND hwnd ;
LONG_PTR window_style ;
if ( ! gdk_window )
return ;
hwnd = gdk_win32_window_get_handle ( gdk_window );
if ( ! hwnd )
return ;
window_style = GetWindowLongPtr ( hwnd , GWL_STYLE );
if ( ! window_style || ( window_style & WS_MINIMIZEBOX ))
return ;
SetWindowLongPtr ( hwnd , GWL_STYLE , window_style | WS_MINIMIZEBOX );
SetWindowPos ( hwnd , NULL , 0 , 0 , 0 , 0 ,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER );
}
2026-02-25 14:18:23 -07:00
static void
mg_win32_allow_autohide_taskbar ( GtkWindow * window , GdkEventWindowState * event )
{
GdkWindow * gdk_window ;
HWND hwnd ;
if ( ! window || ! event )
return ;
if (( event -> new_window_state & GDK_WINDOW_STATE_FULLSCREEN ) != 0 )
return ;
gdk_window = gtk_widget_get_window ( GTK_WIDGET ( window ));
if ( ! gdk_window )
return ;
hwnd = gdk_win32_window_get_handle ( gdk_window );
if ( ! hwnd )
return ;
2026-02-25 14:48:18 -07:00
if ( event -> new_window_state & GDK_WINDOW_STATE_MAXIMIZED )
{
APPBARDATA appbar_data ;
RECT work_area ;
ZeroMemory ( & appbar_data , sizeof ( APPBARDATA ));
appbar_data . cbSize = sizeof ( APPBARDATA );
if (( SHAppBarMessage ( ABM_GETSTATE , & appbar_data ) & ABS_AUTOHIDE ) != 0 &&
SHAppBarMessage ( ABM_GETTASKBARPOS , & appbar_data ) != 0 )
{
HMONITOR monitor ;
MONITORINFO monitor_info ;
monitor = MonitorFromWindow ( hwnd , MONITOR_DEFAULTTONEAREST );
ZeroMemory ( & monitor_info , sizeof ( MONITORINFO ));
monitor_info . cbSize = sizeof ( MONITORINFO );
if ( monitor && GetMonitorInfo ( monitor , & monitor_info ))
{
work_area = monitor_info . rcMonitor ;
switch ( appbar_data . uEdge )
{
case ABE_LEFT :
work_area . left += 1 ;
break ;
case ABE_TOP :
work_area . top += 1 ;
break ;
case ABE_RIGHT :
work_area . right -= 1 ;
break ;
case ABE_BOTTOM :
default :
work_area . bottom -= 1 ;
break ;
}
SetWindowPos ( hwnd ,
NULL ,
work_area . left ,
work_area . top ,
work_area . right - work_area . left ,
work_area . bottom - work_area . top ,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER );
}
}
}
2026-02-25 14:18:23 -07:00
SetWindowPos ( hwnd ,
HWND_NOTOPMOST ,
0 ,
0 ,
0 ,
0 ,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER );
}
2019-12-16 03:42:31 -05:00
#endif
2026-02-17 20:58:15 -07:00
#define ICON_TAB_DETACH "zc-menu-detach"
#define ICON_TAB_CLOSE "zc-menu-close"
2026-02-18 00:32:42 -07:00
#define ICON_TAB_PREVIOUS "zc-menu-previous"
#define ICON_TAB_NEXT "zc-menu-next"
2026-01-30 15:27:01 -07:00
#define ICON_ENTRY_ERROR "dialog-error"
2011-02-24 04:14:30 +01:00
#define GUI_SPACING (3)
#define GUI_BORDER (0)
enum
{
2026-01-15 18:35:27 -07:00
POS_INVALID = 0 ,
POS_TOPLEFT = 1 ,
POS_BOTTOMLEFT = 2 ,
POS_TOPRIGHT = 3 ,
POS_BOTTOMRIGHT = 4 ,
POS_TOP = 5 , /* for tabs only */
POS_BOTTOM = 6 ,
POS_HIDDEN = 7
2011-02-24 04:14:30 +01:00
};
/* two different types of tabs */
2026-01-15 18:35:27 -07:00
#define TAG_IRC 0 /* server, channel, dialog */
#define TAG_UTIL 1 /* dcc, notify, chanlist */
2026-06-25 14:51:04 -06:00
static void mg_reply_show_child ( GtkWidget * widget , gpointer data );
2011-02-24 04:14:30 +01:00
2026-03-16 21:57:21 -06:00
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
static guint mg_config_save_source_id = 0 ;
static gboolean mg_config_prefs_dirty = FALSE ;
2026-03-16 22:45:30 -06:00
static void
mg_show_save_failure ( const PreferencesPersistenceResult * save_result )
{
char buffer [ 192 ];
if ( ! save_result || save_result -> success )
return ;
if ( save_result -> partial_failure )
{
fe_message ( _ ( "Could not fully save preferences. zoitechat.conf was written, but colors.conf failed. Retry is possible." ), FE_MSG_ERROR );
return ;
}
g_snprintf ( buffer , sizeof ( buffer ), _ ( "Could not save preferences (%s). Retry is possible." ), save_result -> failed_file ? save_result -> failed_file : _ ( "unknown file" ));
fe_message ( buffer , FE_MSG_ERROR );
}
2026-03-16 21:57:21 -06:00
static gboolean
mg_config_save_timeout_cb ( gpointer userdata )
{
2026-03-16 22:45:30 -06:00
PreferencesPersistenceResult save_result ;
2026-03-16 21:57:21 -06:00
mg_config_save_source_id = 0 ;
if ( ! mg_config_prefs_dirty )
return G_SOURCE_REMOVE ;
2026-03-16 22:45:30 -06:00
save_result = preferences_persistence_save_all ();
if ( ! save_result . success )
mg_show_save_failure ( & save_result );
2026-03-16 21:57:21 -06:00
mg_config_prefs_dirty = FALSE ;
return G_SOURCE_REMOVE ;
}
static void
mg_schedule_config_save ( void )
{
if ( ! mg_config_prefs_dirty )
return ;
if ( mg_config_save_source_id != 0 )
g_source_remove ( mg_config_save_source_id );
mg_config_save_source_id = g_timeout_add ( MG_CONFIG_SAVE_DEBOUNCE_MS ,
mg_config_save_timeout_cb ,
NULL );
}
static void
mg_flush_config_save ( void )
{
2026-03-16 22:45:30 -06:00
PreferencesPersistenceResult save_result ;
2026-03-16 21:57:21 -06:00
if ( mg_config_save_source_id != 0 )
{
g_source_remove ( mg_config_save_source_id );
mg_config_save_source_id = 0 ;
}
if ( mg_config_prefs_dirty )
{
2026-03-16 22:45:30 -06:00
save_result = preferences_persistence_save_all ();
if ( ! save_result . success )
mg_show_save_failure ( & save_result );
2026-03-16 21:57:21 -06:00
mg_config_prefs_dirty = FALSE ;
}
}
2026-01-17 16:46:21 -07:00
static inline void
2026-01-17 21:56:00 -07:00
mg_set_source_color ( cairo_t * cr , const XTextColor * color )
2026-01-17 16:46:21 -07:00
{
2026-01-17 21:56:00 -07:00
cairo_set_source_rgba ( cr , color -> red , color -> green , color -> blue , color -> alpha );
2026-01-17 16:46:21 -07:00
}
2026-01-17 21:56:00 -07:00
static inline guint16
mg_color_component_to_pango ( double value )
2026-01-17 17:34:38 -07:00
{
2026-01-17 21:56:00 -07:00
if ( value < 0.0 )
value = 0.0 ;
if ( value > 1.0 )
value = 1.0 ;
2026-01-17 17:34:38 -07:00
2026-01-17 21:56:00 -07:00
return ( guint16 )( value * 65535.0 + 0.5 );
2026-01-17 17:34:38 -07:00
}
2026-01-19 19:37:03 -07:00
static void
mg_set_label_alignment_start ( GtkWidget * widget )
{
gtk_widget_set_halign ( widget , GTK_ALIGN_START );
gtk_widget_set_valign ( widget , GTK_ALIGN_CENTER );
}
2026-03-18 09:10:40 -06:00
static void
mg_apply_compact_mode_css ( GtkWidget * widget )
{
GtkStyleContext * context ;
GtkCssProvider * provider ;
if ( ! widget )
return ;
context = gtk_widget_get_style_context ( widget );
if ( ! context )
return ;
provider = g_object_get_data ( G_OBJECT ( widget ), "mg-mode-css-provider" );
if ( ! provider )
{
provider = gtk_css_provider_new ();
g_object_set_data_full ( G_OBJECT ( widget ), "mg-mode-css-provider" , provider , g_object_unref );
}
gtk_css_provider_load_from_data ( provider ,
".zoitechat-mode-control { min-height: 11px; padding-top: 0; padding-bottom: 0; }"
".zoitechat-mode-control label { padding-top: 0; padding-bottom: 0; }" ,
- 1 , NULL );
gtk_style_context_add_class ( context , "zoitechat-mode-control" );
theme_css_apply_widget_provider ( widget , GTK_STYLE_PROVIDER ( provider ));
}
2026-01-19 20:11:07 -07:00
static GtkWidget *
mg_box_new ( GtkOrientation orientation , gboolean homogeneous , gint spacing )
{
GtkWidget * box = gtk_box_new ( orientation , spacing );
gtk_box_set_homogeneous ( GTK_BOX ( box ), homogeneous );
return box ;
}
2026-01-17 22:25:12 -07:00
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 );
}
2026-01-17 17:34:38 -07:00
static GdkPixbuf *
mg_pixbuf_from_window ( GdkWindow * window , int width , int height )
{
2026-01-17 21:56:00 -07:00
int src_width ;
int src_height ;
2026-01-17 22:25:12 -07:00
cairo_surface_t * surface ;
cairo_t * cr ;
GdkPixbuf * pixbuf ;
2026-01-17 17:34:38 -07:00
2026-01-17 22:25:12 -07:00
if ( ! window )
2026-01-17 21:56:00 -07:00
return NULL ;
2026-01-17 17:34:38 -07:00
2026-01-17 22:25:12 -07:00
src_width = gdk_window_get_width ( window );
src_height = gdk_window_get_height ( window );
2026-01-17 21:56:00 -07:00
if ( width <= 0 || height <= 0 )
{
width = src_width ;
height = src_height ;
2026-01-17 17:34:38 -07:00
}
2026-01-17 22:25:12 -07:00
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 ;
2026-01-17 17:34:38 -07:00
}
2011-02-24 04:14:30 +01:00
static void mg_create_entry ( session * sess , GtkWidget * box );
2013-07-24 20:47:01 -04:00
static void mg_create_search ( session * sess , GtkWidget * box );
2026-01-16 22:07:01 -07:00
#ifdef G_OS_WIN32
static GdkFilterReturn mg_win32_filter ( GdkXEvent * xevent , GdkEvent * event , gpointer data );
#endif
2011-02-24 04:14:30 +01:00
static void mg_link_irctab ( session * sess , int focus );
static session_gui static_mg_gui ;
2026-01-15 18:35:27 -07:00
static session_gui * mg_gui = NULL ; /* the shared irc tab */
2011-02-24 04:14:30 +01:00
static int ignore_chanmode = FALSE ;
2015-04-21 16:07:12 -04:00
static const char chan_flags [] = { 'c' , 'n' , 't' , 'i' , 'm' , 'l' , 'k' };
2026-04-13 10:55:59 -06:00
typedef struct
{
int server_id ;
char channel [ CHANLEN ];
char key [ 64 ];
}
mg_closed_channel_tab ;
static GSList * mg_closed_channel_tabs ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
static chan * active_tab = NULL ; /* active tab */
GtkWidget * parent_window = NULL ; /* the master window */
2011-02-24 04:14:30 +01:00
2026-01-30 18:19:01 -07:00
InputStyle * input_style ;
2011-02-24 04:14:30 +01:00
static PangoAttrList * away_list ;
static PangoAttrList * newdata_list ;
static PangoAttrList * nickseen_list ;
static PangoAttrList * newmsg_list ;
static PangoAttrList * plain_list = NULL ;
static PangoAttrList *
2026-01-17 21:56:00 -07:00
mg_attr_list_create ( const XTextColor * col , int size )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
PangoAttribute * attr ;
PangoAttrList * list ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
list = pango_attr_list_new ();
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( col )
{
2026-01-17 21:56:00 -07:00
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 ));
2026-01-15 18:35:27 -07:00
attr -> start_index = 0 ;
attr -> end_index = 0xffff ;
pango_attr_list_insert ( list , attr );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( size > 0 )
{
attr = pango_attr_scale_new ( size == 1 ? PANGO_SCALE_SMALL : PANGO_SCALE_X_SMALL );
attr -> start_index = 0 ;
attr -> end_index = 0xffff ;
pango_attr_list_insert ( list , attr );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return list ;
2011-02-24 04:14:30 +01:00
}
static void
mg_create_tab_colors ( void )
{
2026-03-10 23:05:56 -06:00
XTextColor gui_palette [ THEME_TOKEN_COUNT ];
2026-01-17 21:56:00 -07:00
2026-01-15 18:35:27 -07:00
if ( plain_list )
{
pango_attr_list_unref ( plain_list );
pango_attr_list_unref ( newmsg_list );
pango_attr_list_unref ( newdata_list );
pango_attr_list_unref ( nickseen_list );
pango_attr_list_unref ( away_list );
}
2011-02-24 04:14:30 +01:00
2026-03-02 19:42:48 -07:00
theme_get_xtext_colors ( gui_palette , G_N_ELEMENTS ( gui_palette ));
2026-01-15 18:35:27 -07:00
plain_list = mg_attr_list_create ( NULL , prefs . hex_gui_tab_small );
2026-03-02 19:42:48 -07:00
newdata_list = mg_attr_list_create ( & gui_palette [ THEME_TOKEN_TAB_NEW_DATA ], prefs . hex_gui_tab_small );
nickseen_list = mg_attr_list_create ( & gui_palette [ THEME_TOKEN_TAB_HIGHLIGHT ], prefs . hex_gui_tab_small );
newmsg_list = mg_attr_list_create ( & gui_palette [ THEME_TOKEN_TAB_NEW_MESSAGE ], prefs . hex_gui_tab_small );
away_list = mg_attr_list_create ( & gui_palette [ THEME_TOKEN_TAB_AWAY ], FALSE );
2011-02-24 04:14:30 +01:00
}
2026-07-16 08:48:33 -06:00
static PangoAttrList *
mg_attr_list_for_tab_state ( int state )
{
if ( state & TAB_STATE_NEW_HILIGHT )
return nickseen_list ;
if ( state & TAB_STATE_NEW_MSG )
return newmsg_list ;
if ( state & TAB_STATE_NEW_DATA )
return newdata_list ;
return plain_list ;
}
2011-02-24 04:14:30 +01:00
static void
set_window_urgency ( GtkWidget * win , gboolean set )
{
2026-01-15 18:35:27 -07:00
gtk_window_set_urgency_hint ( GTK_WINDOW ( win ), set );
2011-02-24 04:14:30 +01:00
}
2026-03-07 19:21:37 -07:00
static gboolean
is_wayland_display ( void )
{
GdkDisplay * display = gdk_display_get_default ();
const char * name ;
if ( ! display )
return FALSE ;
name = gdk_display_get_name ( display );
if ( ! name )
return FALSE ;
return g_str_has_prefix ( name , "wayland" );
}
static gboolean
is_kde_desktop ( void )
{
const char * desktop = g_getenv ( "XDG_CURRENT_DESKTOP" );
if ( desktop && strstr ( desktop , "KDE" ))
return TRUE ;
return g_getenv ( "KDE_FULL_SESSION" ) != NULL ;
}
static gboolean
is_kde_wayland ( void )
{
return is_wayland_display () && is_kde_desktop ();
}
2011-02-24 04:14:30 +01:00
static void
flash_window ( GtkWidget * win )
{
2014-05-11 04:43:01 -04:00
#ifdef HAVE_GTK_MAC
2026-01-15 18:35:27 -07:00
gtkosx_application_attention_request ( osx_app , INFO_REQUEST );
2014-05-11 04:43:01 -04:00
#endif
2026-03-07 19:21:37 -07:00
if ( is_kde_wayland ())
gtk_window_present ( GTK_WINDOW ( win ));
2026-01-15 18:35:27 -07:00
set_window_urgency ( win , TRUE );
2011-02-24 04:14:30 +01:00
}
static void
unflash_window ( GtkWidget * win )
{
2026-01-15 18:35:27 -07:00
set_window_urgency ( win , FALSE );
2011-02-24 04:14:30 +01:00
}
/* flash the taskbar button */
void
fe_flash_window ( session * sess )
{
2026-01-15 18:35:27 -07:00
if ( fe_gui_info ( sess , 0 ) != 1 ) /* only do it if not focused */
flash_window ( sess -> gui -> window );
2011-02-24 04:14:30 +01:00
}
/* set a tab plain, red, light-red, or blue */
void
2021-10-14 07:44:11 -07:00
fe_set_tab_color ( struct session * sess , tabcolor col )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
struct session * server_sess = sess -> server -> server_session ;
int col_noflags = ( col & ~ FE_COLOR_ALLFLAGS );
int col_shouldoverride = ! ( col & FE_COLOR_FLAG_NOOVERRIDE );
if ( sess -> res -> tab && sess -> gui -> is_tab && ( col == 0 || sess != current_tab ))
{
switch ( col_noflags )
{
case 0 : /* no particular color (theme default) */
sess -> tab_state = TAB_STATE_NONE ;
chan_set_color ( sess -> res -> tab , plain_list );
break ;
case 1 : /* new data has been displayed (dark red) */
if ( col_shouldoverride || ! (( sess -> tab_state & TAB_STATE_NEW_MSG )
|| ( sess -> tab_state & TAB_STATE_NEW_HILIGHT ))) {
sess -> tab_state = TAB_STATE_NEW_DATA ;
chan_set_color ( sess -> res -> tab , newdata_list );
}
if ( chan_is_collapsed ( sess -> res -> tab )
&& ! (( server_sess -> tab_state & TAB_STATE_NEW_MSG )
|| ( server_sess -> tab_state & TAB_STATE_NEW_HILIGHT ))
&& ! ( server_sess == current_tab ))
{
server_sess -> tab_state = TAB_STATE_NEW_DATA ;
chan_set_color ( chan_get_parent ( sess -> res -> tab ), newdata_list );
}
break ;
case 2 : /* new message arrived in channel (light red) */
if ( col_shouldoverride || ! ( sess -> tab_state & TAB_STATE_NEW_HILIGHT )) {
sess -> tab_state = TAB_STATE_NEW_MSG ;
chan_set_color ( sess -> res -> tab , newmsg_list );
}
if ( chan_is_collapsed ( sess -> res -> tab )
&& ! ( server_sess -> tab_state & TAB_STATE_NEW_HILIGHT )
&& ! ( server_sess == current_tab ))
{
server_sess -> tab_state = TAB_STATE_NEW_MSG ;
chan_set_color ( chan_get_parent ( sess -> res -> tab ), newmsg_list );
}
break ;
case 3 : /* your nick has been seen (blue) */
sess -> tab_state = TAB_STATE_NEW_HILIGHT ;
chan_set_color ( sess -> res -> tab , nickseen_list );
if ( chan_is_collapsed ( sess -> res -> tab ) && ! ( server_sess == current_tab ))
{
server_sess -> tab_state = TAB_STATE_NEW_MSG ;
chan_set_color ( chan_get_parent ( sess -> res -> tab ), nickseen_list );
}
break ;
}
lastact_update ( sess );
sess -> last_tab_state = sess -> tab_state ; /* For plugins handling future prints */
}
2011-02-24 04:14:30 +01:00
}
static void
mg_set_myself_away ( session_gui * gui , gboolean away )
{
2026-01-15 18:35:27 -07:00
gtk_label_set_attributes ( GTK_LABEL ( gtk_bin_get_child ( GTK_BIN ( gui -> nick_label ))),
away ? away_list : NULL );
2011-02-24 04:14:30 +01:00
}
/* change the little icon to the left of your nickname */
void
mg_set_access_icon ( session_gui * gui , GdkPixbuf * pix , gboolean away )
{
2026-01-15 18:35:27 -07:00
if ( gui -> op_xpm )
{
2026-07-16 08:48:33 -06:00
/* no change? (the pref check makes sure a live settings
change can still remove the icon) */
if ( pix == gtk_image_get_pixbuf ( GTK_IMAGE ( gui -> op_xpm )) &&
prefs . hex_gui_input_icon )
2026-01-15 18:35:27 -07:00
{
mg_set_myself_away ( gui , away );
return ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( gui -> op_xpm );
gui -> op_xpm = NULL ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( pix && prefs . hex_gui_input_icon )
{
gui -> op_xpm = gtk_image_new_from_pixbuf ( pix );
gtk_box_pack_start ( GTK_BOX ( gui -> nick_box ), gui -> op_xpm , 0 , 0 , 0 );
gtk_widget_show ( gui -> op_xpm );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_set_myself_away ( gui , away );
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_inputbox_focus ( GtkWidget * widget , GdkEventFocus * event , session_gui * gui )
{
2026-01-15 18:35:27 -07:00
GSList * list ;
session * sess ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( gui -> is_tab )
return FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
list = sess_list ;
while ( list )
{
sess = list -> data ;
if ( sess -> gui == gui )
{
current_sess = sess ;
if ( ! sess -> server -> server_session )
sess -> server -> server_session = sess ;
break ;
}
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return FALSE ;
2011-02-24 04:14:30 +01:00
}
2026-06-25 14:51:04 -06:00
2026-09-26 06:10:20 -06:00
#define TYPING_THROTTLE_USEC (3 * G_USEC_PER_SEC)
2026-06-25 14:51:04 -06:00
static gboolean
mg_client_tag_allowed ( server * serv , const char * tag )
{
char ** deny ;
2026-09-26 06:10:20 -06:00
gboolean blocked = FALSE ;
2026-06-25 14:51:04 -06:00
int i ;
if ( ! serv -> have_message_tags )
return FALSE ;
if ( ! serv -> clienttagdeny || !* serv -> clienttagdeny )
return TRUE ;
deny = g_strsplit ( serv -> clienttagdeny , "," , 0 );
for ( i = 0 ; deny [ i ]; i ++ )
{
2026-09-26 06:10:20 -06:00
if ( ! strcmp ( deny [ i ], "*" ))
blocked = TRUE ;
else if ( deny [ i ][ 0 ] == '-' && ! strcmp ( deny [ i ] + 1 , tag ))
blocked = FALSE ;
else if ( ! strcmp ( deny [ i ], tag ) || ( deny [ i ][ 0 ] == '+' && ! strcmp ( deny [ i ] + 1 , tag )))
blocked = TRUE ;
2026-06-25 14:51:04 -06:00
}
g_strfreev ( deny );
2026-09-26 06:10:20 -06:00
return ! blocked ;
2026-06-25 14:51:04 -06:00
}
2026-09-26 06:10:20 -06:00
static gboolean
mg_typing_can_send ( session * sess )
{
gint64 now = g_get_monotonic_time ();
return sess -> typing_last_sent == 0 ||
now - sess -> typing_last_sent >= TYPING_THROTTLE_USEC ;
}
static gboolean
2026-06-25 14:51:04 -06:00
mg_send_typing ( session * sess , const char * state )
{
char tags [ 32 ];
if ( ! sess || ! sess -> server -> connected || ! mg_client_tag_allowed ( sess -> server , "typing" ) || ! sess -> channel [ 0 ])
2026-09-26 06:10:20 -06:00
return FALSE ;
2026-06-25 14:51:04 -06:00
if ( sess -> type != SESS_CHANNEL && sess -> type != SESS_DIALOG )
2026-09-26 06:10:20 -06:00
return FALSE ;
2026-06-25 14:51:04 -06:00
g_snprintf ( tags , sizeof ( tags ), "+typing=%s" , state );
sess -> server -> p_tagmsg ( sess -> server , tags , sess -> channel );
2026-09-26 06:10:20 -06:00
sess -> typing_last_sent = g_get_monotonic_time ();
return TRUE ;
2026-06-25 14:51:04 -06:00
}
static int
mg_typing_pause_cb ( session * sess )
{
sess -> typing_timeout_tag = 0 ;
if ( sess -> typing_status == 1 )
{
mg_send_typing ( sess , "paused" );
sess -> typing_status = 2 ;
}
return 0 ;
}
static void
mg_typing_update ( session * sess , const char * text )
{
if ( ! sess )
return ;
if ( sess -> typing_timeout_tag )
{
fe_timeout_remove ( sess -> typing_timeout_tag );
sess -> typing_timeout_tag = 0 ;
}
if ( ! text || !* text || text [ 0 ] == prefs . hex_input_command_char [ 0 ])
{
2026-09-26 06:10:20 -06:00
if ( sess -> typing_status && mg_typing_can_send ( sess ))
2026-06-25 14:51:04 -06:00
mg_send_typing ( sess , "done" );
sess -> typing_status = 0 ;
return ;
}
2026-09-26 06:10:20 -06:00
if ( mg_typing_can_send ( sess ))
2026-06-25 14:51:04 -06:00
mg_send_typing ( sess , "active" );
2026-09-26 06:10:20 -06:00
sess -> typing_status = 1 ;
2026-06-25 14:51:04 -06:00
sess -> typing_timeout_tag = fe_timeout_add_seconds ( 6 , mg_typing_pause_cb , sess );
}
static void
mg_reply_show_child ( GtkWidget * widget , gpointer data )
{
gtk_widget_show ( widget );
}
void
mg_reply_update ( session * sess )
{
char * nick ;
char * text ;
char * markup ;
if ( ! sess || ! sess -> gui || ! sess -> gui -> reply_box || ! sess -> gui -> reply_label )
return ;
if ( ! sess -> reply_msgid )
{
gtk_widget_hide ( sess -> gui -> reply_box );
return ;
}
nick = g_markup_escape_text ( sess -> reply_nick ? sess -> reply_nick : _ ( "message" ), - 1 );
text = g_markup_escape_text ( sess -> reply_text ? sess -> reply_text : _ ( "Original message unavailable" ), - 1 );
markup = g_strdup_printf ( "<span foreground='#7d8790'>↪ Replying to <b>%s</b> · %.160s</span>" , nick , text );
gtk_label_set_markup ( GTK_LABEL ( sess -> gui -> reply_label ), markup );
gtk_container_foreach ( GTK_CONTAINER ( sess -> gui -> reply_box ), mg_reply_show_child , NULL );
gtk_widget_show ( sess -> gui -> reply_box );
g_free ( markup );
g_free ( text );
g_free ( nick );
}
static void
mg_reply_cancel_cb ( GtkWidget * wid , session * sess )
{
reply_state_clear ( sess );
mg_reply_update ( sess );
}
static void
mg_send_reply_or_text ( session * sess , char * cmd )
{
char * reply_cmd ;
if ( ! sess -> reply_msgid || cmd [ 0 ] == prefs . hex_input_command_char [ 0 ])
{
handle_multiline ( sess , cmd , TRUE , FALSE );
return ;
}
if ( ! sess -> server -> connected || ! mg_client_tag_allowed ( sess -> server , "reply" ))
{
PrintText ( sess , _ ( "Replies are not supported on this server. Sending normally. \n " ));
reply_state_clear ( sess );
mg_reply_update ( sess );
handle_multiline ( sess , cmd , TRUE , FALSE );
return ;
}
reply_cmd = g_strdup_printf ( "%cREPLY %s %s" , prefs . hex_input_command_char [ 0 ], sess -> reply_msgid , cmd );
handle_multiline ( sess , reply_cmd , TRUE , FALSE );
g_free ( reply_cmd );
reply_state_clear ( sess );
mg_reply_update ( sess );
}
2026-09-09 00:10:26 -06:00
typedef struct
{
gboolean bold ;
gboolean italic ;
gboolean underline ;
gboolean strikethrough ;
gboolean reverse ;
gint foreground ;
gint background ;
gboolean pending_foreground ;
gboolean pending_background ;
} MgInputFormatState ;
static void
mg_input_format_state_at_cursor ( GtkEntry * entry , MgInputFormatState * state )
{
const char * text ;
const char * cursor ;
gsize limit ;
gsize i ;
gint cursor_pos ;
memset ( state , 0 , sizeof ( * state ));
state -> foreground = - 1 ;
state -> background = - 1 ;
text = gtk_entry_get_text ( entry );
cursor_pos = gtk_editable_get_position ( GTK_EDITABLE ( entry ));
if ( ! text || cursor_pos < 0 )
return ;
cursor = g_utf8_offset_to_pointer ( text , cursor_pos );
limit = ( gsize ) ( cursor - text );
for ( i = 0 ; i < limit ;)
{
guchar ch = ( guchar ) text [ i ++ ];
switch ( ch )
{
case ATTR_BOLD :
state -> bold = ! state -> bold ;
break ;
case ATTR_ITALICS :
state -> italic = ! state -> italic ;
break ;
case ATTR_UNDERLINE :
state -> underline = ! state -> underline ;
break ;
case ATTR_STRIKETHROUGH :
state -> strikethrough = ! state -> strikethrough ;
break ;
case ATTR_REVERSE :
state -> reverse = ! state -> reverse ;
break ;
case ATTR_RESET :
case '\n' :
state -> bold = FALSE ;
state -> italic = FALSE ;
state -> underline = FALSE ;
state -> strikethrough = FALSE ;
state -> reverse = FALSE ;
state -> foreground = - 1 ;
state -> background = - 1 ;
state -> pending_foreground = FALSE ;
state -> pending_background = FALSE ;
break ;
case ATTR_COLOR :
{
gint value = 0 ;
gint digits = 0 ;
state -> pending_foreground = FALSE ;
state -> pending_background = FALSE ;
if ( i >= limit )
{
state -> pending_foreground = TRUE ;
break ;
}
if ( ! g_ascii_isdigit ( text [ i ]))
{
state -> foreground = - 1 ;
state -> background = - 1 ;
break ;
}
while ( i < limit && digits < 2 && g_ascii_isdigit ( text [ i ]))
{
value = ( value * 10 ) + ( text [ i ] - '0' );
i ++ ;
digits ++ ;
}
state -> foreground = value ;
state -> background = - 1 ;
if ( i < limit && text [ i ] == ',' )
{
i ++ ;
if ( i >= limit )
{
state -> pending_background = TRUE ;
break ;
}
value = 0 ;
digits = 0 ;
while ( i < limit && digits < 2 && g_ascii_isdigit ( text [ i ]))
{
value = ( value * 10 ) + ( text [ i ] - '0' );
i ++ ;
digits ++ ;
}
if ( digits > 0 )
state -> background = value ;
}
}
break ;
default :
break ;
}
}
}
static void
mg_input_format_append ( GString * summary , const char * text )
{
if ( summary -> len > 0 )
g_string_append ( summary , " · " );
g_string_append ( summary , text );
}
static void
mg_input_format_color_value ( char * buffer , gsize buffer_len , const char * label , gint color_index )
{
guint16 red ;
guint16 green ;
guint16 blue ;
if ( theme_get_mirc_color_rgb16 (( unsigned int ) color_index , & red , & green , & blue ))
{
g_snprintf ( buffer , buffer_len ,
"%s <b>%02d</b> <span foreground= \" #%02x%02x%02x \" >■</span>" ,
label , color_index , ( guint ) ( red >> 8 ), ( guint ) ( green >> 8 ),
( guint ) ( blue >> 8 ));
return ;
}
g_snprintf ( buffer , buffer_len , "%s <b>%02d</b>" , label , color_index );
}
static void
mg_input_format_update ( GtkEntry * entry )
{
GtkWidget * format_box ;
GtkWidget * format_label ;
MgInputFormatState state ;
GString * summary ;
char color [ 96 ];
char * markup ;
format_box = g_object_get_data ( G_OBJECT ( entry ), "zoitechat-format-box" );
format_label = g_object_get_data ( G_OBJECT ( entry ), "zoitechat-format-label" );
if ( ! format_box || ! format_label )
return ;
if ( ! prefs . hex_gui_input_attr )
{
gtk_widget_hide ( format_box );
return ;
}
mg_input_format_state_at_cursor ( entry , & state );
summary = g_string_new ( NULL );
if ( state . bold )
mg_input_format_append ( summary , "<b>Bold</b>" );
if ( state . italic )
mg_input_format_append ( summary , "<i>Italic</i>" );
if ( state . underline )
mg_input_format_append ( summary , "<u>Underline</u>" );
if ( state . strikethrough )
mg_input_format_append ( summary , "<s>Strikethrough</s>" );
if ( state . reverse )
mg_input_format_append ( summary , "Reverse" );
if ( state . foreground >= 0 )
{
mg_input_format_color_value ( color , sizeof ( color ), "Text color" , state . foreground );
mg_input_format_append ( summary , color );
}
if ( state . background >= 0 )
{
mg_input_format_color_value ( color , sizeof ( color ), "Background" , state . background );
mg_input_format_append ( summary , color );
}
if ( state . pending_foreground )
mg_input_format_append ( summary , "Color: type foreground[,background]" );
else if ( state . pending_background )
mg_input_format_append ( summary , "Background: type color" );
if ( summary -> len == 0 )
{
gtk_widget_hide ( format_box );
g_string_free ( summary , TRUE );
return ;
}
markup = g_strdup_printf ( "<span foreground='#7d8790'>Formatting · %s</span>" , summary -> str );
gtk_label_set_markup ( GTK_LABEL ( format_label ), markup );
gtk_widget_show ( format_label );
gtk_widget_show ( format_box );
g_free ( markup );
g_string_free ( summary , TRUE );
}
static void
mg_inputbox_cursor_changed ( GObject * object , GParamSpec * pspec , gpointer userdata )
{
( void ) pspec ;
( void ) userdata ;
mg_input_format_update ( GTK_ENTRY ( object ));
}
2026-06-25 14:51:04 -06:00
static void
mg_inputbox_changed ( GtkEditable * editable , session_gui * gui )
{
key_check_replace_on_change ( editable , NULL );
2026-09-09 00:10:26 -06:00
mg_input_format_update ( GTK_ENTRY ( editable ));
2026-06-25 14:51:04 -06:00
if ( current_sess && current_sess -> gui == gui )
mg_typing_update ( current_sess , gtk_entry_get_text ( GTK_ENTRY ( editable )));
}
2011-02-24 04:14:30 +01:00
void
mg_inputbox_cb ( GtkWidget * igad , session_gui * gui )
{
2026-01-15 18:35:27 -07:00
char * cmd ;
static int ignore = FALSE ;
GSList * list ;
session * sess = NULL ;
if ( ignore )
return ;
cmd = SPELL_ENTRY_GET_TEXT ( igad );
if ( cmd [ 0 ] == 0 )
return ;
cmd = g_strdup ( cmd );
/* avoid recursive loop */
ignore = TRUE ;
SPELL_ENTRY_SET_TEXT ( igad , "" );
ignore = FALSE ;
/* where did this event come from? */
if ( gui -> is_tab )
{
sess = current_tab ;
} else
{
list = sess_list ;
while ( list )
{
sess = list -> data ;
if ( sess -> gui == gui )
break ;
list = list -> next ;
}
if ( ! list )
sess = NULL ;
}
if ( sess )
2026-06-25 14:51:04 -06:00
mg_send_reply_or_text ( sess , cmd );
2026-01-15 18:35:27 -07:00
g_free ( cmd );
2011-02-24 04:14:30 +01:00
}
2013-10-01 13:11:59 -04:00
static gboolean
mg_spellcheck_cb ( SexySpellEntry * entry , gchar * word , gpointer data )
{
2026-01-15 18:35:27 -07:00
/* This can cause freezes on long words, nicks arn't very long anyway. */
if ( strlen ( word ) > 20 )
2026-03-16 10:45:29 -06:00
return FALSE ;
2013-10-01 13:11:59 -04:00
2026-01-15 18:35:27 -07:00
/* Ignore anything we think is a valid url */
if ( url_check_word ( word ) != 0 )
return FALSE ;
2013-10-01 13:11:59 -04:00
2026-01-15 18:35:27 -07:00
return TRUE ;
2013-10-01 13:11:59 -04:00
}
2012-10-22 07:24:52 +02:00
#if 0
2011-02-24 04:14:30 +01:00
static gboolean
has_key (char *modes)
{
2026-01-15 18:35:27 -07:00
if (!modes)
return FALSE;
/* this is a crude check, but "-k" can't exist, so it works. */
while (*modes)
{
if (*modes == 'k')
return TRUE;
if (*modes == ' ')
return FALSE;
modes++;
}
return FALSE;
2011-02-24 04:14:30 +01:00
}
2012-10-22 07:24:52 +02:00
#endif
2011-02-24 04:14:30 +01:00
void
fe_set_title ( session * sess )
{
2026-01-15 18:35:27 -07:00
char tbuf [ 512 ];
int type ;
if ( sess -> gui -> is_tab && sess != current_tab )
return ;
type = sess -> type ;
if ( sess -> server -> connected == FALSE && sess -> type != SESS_DIALOG )
goto def ;
switch ( type )
{
case SESS_DIALOG :
g_snprintf ( tbuf , sizeof ( tbuf ), "%s %s @ %s - %s" ,
_ ( "Dialog with" ), sess -> channel , server_get_network ( sess -> server , TRUE ),
_ ( DISPLAY_NAME ));
break ;
case SESS_SERVER :
g_snprintf ( tbuf , sizeof ( tbuf ), "%s%s%s - %s" ,
prefs . hex_gui_win_nick ? sess -> server -> nick : "" ,
prefs . hex_gui_win_nick ? " @ " : "" , server_get_network ( sess -> server , TRUE ),
_ ( DISPLAY_NAME ));
break ;
case SESS_CHANNEL :
/* don't display keys in the titlebar */
g_snprintf ( tbuf , sizeof ( tbuf ),
"%s%s%s / %s%s%s%s - %s" ,
prefs . hex_gui_win_nick ? sess -> server -> nick : "" ,
prefs . hex_gui_win_nick ? " @ " : "" ,
server_get_network ( sess -> server , TRUE ), sess -> channel ,
prefs . hex_gui_win_modes && sess -> current_modes ? " (" : "" ,
prefs . hex_gui_win_modes && sess -> current_modes ? sess -> current_modes : "" ,
prefs . hex_gui_win_modes && sess -> current_modes ? ")" : "" ,
_ ( DISPLAY_NAME ));
if ( prefs . hex_gui_win_ucount )
{
g_snprintf ( tbuf + strlen ( tbuf ), 9 , " (%d)" , sess -> total );
}
break ;
case SESS_NOTICES :
case SESS_SNOTICES :
g_snprintf ( tbuf , sizeof ( tbuf ), "%s%s%s (notices) - %s" ,
prefs . hex_gui_win_nick ? sess -> server -> nick : "" ,
prefs . hex_gui_win_nick ? " @ " : "" , server_get_network ( sess -> server , TRUE ),
_ ( DISPLAY_NAME ));
break ;
default :
def :
g_snprintf ( tbuf , sizeof ( tbuf ), _ ( DISPLAY_NAME ));
gtk_window_set_title ( GTK_WINDOW ( sess -> gui -> window ), tbuf );
return ;
}
gtk_window_set_title ( GTK_WINDOW ( sess -> gui -> window ), tbuf );
2011-02-24 04:14:30 +01:00
}
2026-04-09 10:03:59 -06:00
static void
mg_topicbar_update_height ( GtkWidget * topic );
2026-06-04 15:24:37 -06:00
static void
mg_topicbar_queue_relayout ( GtkWidget * topic );
static void
mg_queue_window_relayout ( GtkWidget * window );
2026-04-09 10:03:59 -06:00
static session *
mg_session_from_window ( GtkWidget * wid )
{
GSList * list ;
session * sess ;
list = sess_list ;
while ( list )
{
sess = list -> data ;
if ( sess && sess -> gui && sess -> gui -> window == wid )
return sess ;
list = list -> next ;
}
return current_sess ;
}
2026-06-04 15:24:37 -06:00
static gboolean
mg_window_relayout_idle_cb ( gpointer userdata )
{
GtkWidget * window = GTK_WIDGET ( userdata );
session * sess ;
g_object_set_data ( G_OBJECT ( window ), "mg-window-relayout-source" , NULL );
sess = mg_session_from_window ( window );
if ( sess && sess -> gui )
{
if ( GTK_IS_WIDGET ( sess -> gui -> topic_entry ))
mg_topicbar_queue_relayout ( sess -> gui -> topic_entry );
if ( GTK_IS_XTEXT ( sess -> gui -> xtext ))
{
gtk_xtext_refresh ( GTK_XTEXT ( sess -> gui -> xtext ));
gtk_widget_queue_resize ( sess -> gui -> xtext );
gtk_widget_queue_draw ( sess -> gui -> xtext );
}
if ( GTK_IS_WIDGET ( sess -> gui -> window ))
{
gtk_widget_queue_resize ( sess -> gui -> window );
gtk_widget_queue_draw ( sess -> gui -> window );
}
}
g_object_unref ( window );
return G_SOURCE_REMOVE ;
}
static void
mg_queue_window_relayout ( GtkWidget * window )
{
guint source_id ;
if ( ! window || ! GTK_IS_WIDGET ( window ))
return ;
if ( g_object_get_data ( G_OBJECT ( window ), "mg-window-relayout-source" ) != NULL )
return ;
source_id = g_idle_add_full ( G_PRIORITY_DEFAULT_IDLE ,
mg_window_relayout_idle_cb ,
g_object_ref ( window ),
NULL );
g_object_set_data ( G_OBJECT ( window ), "mg-window-relayout-source" ,
GUINT_TO_POINTER ( source_id ));
}
2011-02-24 04:14:30 +01:00
static gboolean
mg_windowstate_cb ( GtkWindow * wid , GdkEventWindowState * event , gpointer userdata )
{
2026-03-16 21:57:21 -06:00
guint win_state ;
guint win_fullscreen ;
gboolean changed = FALSE ;
2026-04-09 10:03:59 -06:00
session * sess ;
2026-03-16 21:57:21 -06:00
2026-02-18 13:11:04 -07:00
if (( event -> changed_mask & GDK_WINDOW_STATE_ICONIFIED ) &&
( event -> new_window_state & GDK_WINDOW_STATE_ICONIFIED ) &&
prefs . hex_gui_tray_minimize && prefs . hex_gui_tray &&
2026-05-20 09:46:33 -06:00
gtkutil_tray_icon_supported ( wid )
#ifndef WIN32
)
#else
&& ! gtk_window_is_active ( wid ))
#endif
2026-02-18 13:11:04 -07:00
{
tray_toggle_visibility ( TRUE );
}
2011-02-24 04:14:30 +01:00
2026-03-16 21:57:21 -06:00
win_state = 0 ;
if ( event -> new_window_state & GDK_WINDOW_STATE_MAXIMIZED )
win_state = 1 ;
win_fullscreen = 0 ;
if ( event -> new_window_state & GDK_WINDOW_STATE_FULLSCREEN )
win_fullscreen = 1 ;
if ( prefs . hex_gui_win_state != win_state )
{
prefs . hex_gui_win_state = win_state ;
changed = TRUE ;
}
2012-12-16 12:54:15 -05:00
2026-03-16 21:57:21 -06:00
if ( prefs . hex_gui_win_fullscreen != win_fullscreen )
{
prefs . hex_gui_win_fullscreen = win_fullscreen ;
changed = TRUE ;
}
if ( changed )
{
mg_config_prefs_dirty = TRUE ;
mg_schedule_config_save ();
}
2013-09-19 17:52:17 -04:00
2026-04-09 10:03:59 -06:00
sess = mg_session_from_window ( GTK_WIDGET ( wid ));
if ( sess && sess -> gui && GTK_IS_WIDGET ( sess -> gui -> window ))
2026-06-04 15:24:37 -06:00
mg_queue_window_relayout ( sess -> gui -> window );
else
mg_queue_window_relayout ( GTK_WIDGET ( wid ));
2026-04-09 10:03:59 -06:00
2026-05-20 10:34:35 -06:00
if ( current_sess && current_sess -> gui )
menu_set_fullscreen ( current_sess -> gui , prefs . hex_gui_win_fullscreen );
2013-10-23 09:11:30 -04:00
2026-02-25 14:18:23 -07:00
#ifdef G_OS_WIN32
2026-06-09 14:02:41 -06:00
if ( event -> changed_mask &
( GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN ))
mg_win32_allow_autohide_taskbar ( wid , event );
2026-02-25 14:18:23 -07:00
#endif
2026-01-15 18:35:27 -07:00
return FALSE ;
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_configure_cb ( GtkWidget * wid , GdkEventConfigure * event , session * sess )
{
2026-03-16 21:57:21 -06:00
gboolean changed = FALSE ;
2026-04-09 10:03:59 -06:00
session * target_sess ;
2026-03-16 21:57:21 -06:00
2026-03-17 10:11:41 -06:00
if ( sess == NULL )
2026-01-15 18:35:27 -07:00
{
if ( mg_gui )
{
if ( prefs . hex_gui_win_save && ! prefs . hex_gui_win_state && ! prefs . hex_gui_win_fullscreen )
{
2026-03-16 21:57:21 -06:00
int win_left ;
int win_top ;
int win_width ;
int win_height ;
gtk_window_get_position ( GTK_WINDOW ( wid ), & win_left , & win_top );
gtk_window_get_size ( GTK_WINDOW ( wid ), & win_width , & win_height );
if ( prefs . hex_gui_win_left != win_left )
{
prefs . hex_gui_win_left = win_left ;
changed = TRUE ;
}
if ( prefs . hex_gui_win_top != win_top )
{
prefs . hex_gui_win_top = win_top ;
changed = TRUE ;
}
if ( prefs . hex_gui_win_width != win_width )
{
prefs . hex_gui_win_width = win_width ;
changed = TRUE ;
}
if ( prefs . hex_gui_win_height != win_height )
{
prefs . hex_gui_win_height = win_height ;
changed = TRUE ;
}
2026-01-15 18:35:27 -07:00
}
}
}
2026-03-17 10:11:41 -06:00
else if ( sess -> type == SESS_DIALOG && prefs . hex_gui_win_save )
2026-01-15 18:35:27 -07:00
{
2026-03-17 10:11:41 -06:00
int dialog_left ;
int dialog_top ;
int dialog_width ;
int dialog_height ;
2026-03-16 21:57:21 -06:00
2026-03-17 10:11:41 -06:00
gtk_window_get_position ( GTK_WINDOW ( wid ), & dialog_left , & dialog_top );
gtk_window_get_size ( GTK_WINDOW ( wid ), & dialog_width , & dialog_height );
2026-03-16 21:57:21 -06:00
2026-03-17 10:11:41 -06:00
if ( prefs . hex_gui_dialog_left != dialog_left )
{
prefs . hex_gui_dialog_left = dialog_left ;
changed = TRUE ;
}
2026-03-16 21:57:21 -06:00
2026-03-17 10:11:41 -06:00
if ( prefs . hex_gui_dialog_top != dialog_top )
{
prefs . hex_gui_dialog_top = dialog_top ;
changed = TRUE ;
}
2026-03-16 21:57:21 -06:00
2026-03-17 10:11:41 -06:00
if ( prefs . hex_gui_dialog_width != dialog_width )
{
prefs . hex_gui_dialog_width = dialog_width ;
changed = TRUE ;
}
2026-03-16 21:57:21 -06:00
2026-03-17 10:11:41 -06:00
if ( prefs . hex_gui_dialog_height != dialog_height )
{
prefs . hex_gui_dialog_height = dialog_height ;
changed = TRUE ;
2026-01-15 18:35:27 -07:00
}
}
2026-03-16 21:57:21 -06:00
if ( changed )
{
mg_config_prefs_dirty = TRUE ;
mg_schedule_config_save ();
}
2026-04-09 10:03:59 -06:00
target_sess = mg_session_from_window ( wid );
2026-06-04 15:24:37 -06:00
if ( target_sess && target_sess -> gui && GTK_IS_WIDGET ( target_sess -> gui -> window ))
mg_queue_window_relayout ( target_sess -> gui -> window );
else
mg_queue_window_relayout ( wid );
2026-04-09 10:03:59 -06:00
2026-01-15 18:35:27 -07:00
return FALSE ;
2011-02-24 04:14:30 +01:00
}
/* move to a non-irc tab */
static void
mg_show_generic_tab ( GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
int num ;
GtkWidget * f = NULL ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( current_sess && gtk_widget_has_focus ( current_sess -> gui -> input_box ))
f = current_sess -> gui -> input_box ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
num = gtk_notebook_page_num ( GTK_NOTEBOOK ( mg_gui -> note_book ), box );
gtk_notebook_set_current_page ( GTK_NOTEBOOK ( mg_gui -> note_book ), num );
gtk_tree_view_set_model ( GTK_TREE_VIEW ( mg_gui -> user_tree ), NULL );
gtk_window_set_title ( GTK_WINDOW ( mg_gui -> window ),
g_object_get_data ( G_OBJECT ( box ), "title" ));
gtk_widget_set_sensitive ( mg_gui -> menu , FALSE );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( f )
gtk_widget_grab_focus ( f );
2011-02-24 04:14:30 +01:00
}
/* a channel has been focused */
static void
mg_focus ( session * sess )
{
2026-01-15 18:35:27 -07:00
if ( sess -> gui -> is_tab )
current_tab = sess ;
current_sess = sess ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* dirty trick to avoid auto-selection */
SPELL_ENTRY_SET_EDITABLE ( sess -> gui -> input_box , FALSE );
gtk_widget_grab_focus ( sess -> gui -> input_box );
SPELL_ENTRY_SET_EDITABLE ( sess -> gui -> input_box , TRUE );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
sess -> server -> front_session = sess ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> server -> server_session != NULL )
{
if ( sess -> server -> server_session -> type != SESS_SERVER )
sess -> server -> server_session = sess ;
} else
{
sess -> server -> server_session = sess ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* when called via mg_changui_new, is_tab might be true, but
sess->res->tab is still NULL. */
if ( sess -> res -> tab )
fe_set_tab_color ( sess , FE_COLOR_NONE );
2011-02-24 04:14:30 +01:00
}
static int
mg_progressbar_update ( GtkWidget * bar )
{
2026-01-15 18:35:27 -07:00
static int type = 0 ;
static gdouble pos = 0 ;
pos += 0.05 ;
if ( pos >= 0.99 )
{
if ( type == 0 )
{
type = 1 ;
2026-01-30 17:40:44 -07:00
gtk_progress_bar_set_inverted ( GTK_PROGRESS_BAR ( bar ), TRUE );
2026-01-15 18:35:27 -07:00
} else
{
type = 0 ;
2026-01-30 17:40:44 -07:00
gtk_progress_bar_set_inverted ( GTK_PROGRESS_BAR ( bar ), FALSE );
2026-01-15 18:35:27 -07:00
}
pos = 0.05 ;
}
gtk_progress_bar_set_fraction (( GtkProgressBar * ) bar , pos );
return 1 ;
2011-02-24 04:14:30 +01:00
}
void
mg_progressbar_create ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
gui -> bar = gtk_progress_bar_new ();
gtk_box_pack_start ( GTK_BOX ( gui -> nick_box ), gui -> bar , 0 , 0 , 0 );
gtk_widget_show ( gui -> bar );
gui -> bartag = fe_timeout_add ( 50 , mg_progressbar_update , gui -> bar );
2011-02-24 04:14:30 +01:00
}
void
mg_progressbar_destroy ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
fe_timeout_remove ( gui -> bartag );
gtk_widget_destroy ( gui -> bar );
gui -> bar = 0 ;
gui -> bartag = 0 ;
2011-02-24 04:14:30 +01:00
}
/* switching tabs away from this one, so remember some info about it! */
static void
mg_unpopulate ( session * sess )
{
2026-01-15 18:35:27 -07:00
restore_gui * res ;
session_gui * gui ;
2026-03-14 19:57:42 -06:00
GtkTextBuffer * topic_buffer ;
GtkTextIter start ;
GtkTextIter end ;
2026-01-15 18:35:27 -07:00
int i ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui = sess -> gui ;
res = sess -> res ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
res -> input_text = g_strdup ( SPELL_ENTRY_GET_TEXT ( gui -> input_box ));
2026-03-14 19:57:42 -06:00
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( gui -> topic_entry ));
gtk_text_buffer_get_bounds ( topic_buffer , & start , & end );
res -> topic_text = gtk_text_buffer_get_text ( topic_buffer , & start , & end , FALSE );
2026-01-15 18:35:27 -07:00
res -> limit_text = g_strdup ( gtk_entry_get_text ( GTK_ENTRY ( gui -> limit_entry )));
res -> key_text = g_strdup ( gtk_entry_get_text ( GTK_ENTRY ( gui -> key_entry )));
if ( gui -> laginfo )
res -> lag_text = g_strdup ( gtk_label_get_text ( GTK_LABEL ( gui -> laginfo )));
if ( gui -> throttleinfo )
res -> queue_text = g_strdup ( gtk_label_get_text ( GTK_LABEL ( gui -> throttleinfo )));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
for ( i = 0 ; i < NUM_FLAG_WIDS - 1 ; i ++ )
res -> flag_wid_state [ i ] = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( gui -> flag_wid [ i ]));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
res -> old_ul_value = userlist_get_value ( gui -> user_tree );
if ( gui -> lagometer )
res -> lag_value = gtk_progress_bar_get_fraction (
GTK_PROGRESS_BAR ( gui -> lagometer ));
if ( gui -> throttlemeter )
res -> queue_value = gtk_progress_bar_get_fraction (
GTK_PROGRESS_BAR ( gui -> throttlemeter ));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( gui -> bar )
{
res -> c_graph = TRUE ; /* still have a graph, just not visible now */
mg_progressbar_destroy ( gui );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_restore_label ( GtkWidget * label , char ** text )
{
2026-01-15 18:35:27 -07:00
if ( ! label )
return ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( * text )
{
gtk_label_set_text ( GTK_LABEL ( label ), * text );
g_free ( * text );
* text = NULL ;
} else
{
gtk_label_set_text ( GTK_LABEL ( label ), "" );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_restore_entry ( GtkWidget * entry , char ** text )
{
2026-01-15 18:35:27 -07:00
if ( * text )
{
gtk_entry_set_text ( GTK_ENTRY ( entry ), * text );
g_free ( * text );
* text = NULL ;
} else
{
gtk_entry_set_text ( GTK_ENTRY ( entry ), "" );
}
gtk_editable_set_position ( GTK_EDITABLE ( entry ), - 1 );
2011-02-24 04:14:30 +01:00
}
static void
mg_restore_speller ( GtkWidget * entry , char ** text )
{
2026-01-15 18:35:27 -07:00
if ( * text )
{
SPELL_ENTRY_SET_TEXT ( entry , * text );
g_free ( * text );
* text = NULL ;
} else
{
SPELL_ENTRY_SET_TEXT ( entry , "" );
}
SPELL_ENTRY_SET_POS ( entry , - 1 );
2011-02-24 04:14:30 +01:00
}
void
mg_set_topic_tip ( session * sess )
{
2026-01-15 18:35:27 -07:00
char * text ;
2026-03-14 19:57:42 -06:00
GtkTextBuffer * topic_buffer ;
GtkTextIter start ;
GtkTextIter end ;
2026-01-15 18:35:27 -07:00
switch ( sess -> type )
{
case SESS_CHANNEL :
if ( sess -> topic )
{
text = g_strdup_printf ( _ ( "Topic for %s is: %s" ), sess -> channel ,
sess -> topic );
gtk_widget_set_tooltip_text ( sess -> gui -> topic_entry , text );
g_free ( text );
} else
gtk_widget_set_tooltip_text ( sess -> gui -> topic_entry , _ ( "No topic is set" ));
break ;
default :
2026-03-14 19:57:42 -06:00
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( sess -> gui -> topic_entry ));
gtk_text_buffer_get_bounds ( topic_buffer , & start , & end );
text = gtk_text_buffer_get_text ( topic_buffer , & start , & end , FALSE );
if ( text [ 0 ])
gtk_widget_set_tooltip_text ( sess -> gui -> topic_entry , text );
2026-01-15 18:35:27 -07:00
else
gtk_widget_set_tooltip_text ( sess -> gui -> topic_entry , NULL );
2026-03-14 19:57:42 -06:00
g_free ( text );
2026-01-15 18:35:27 -07:00
}
2011-02-24 04:14:30 +01:00
}
static void
mg_hide_empty_pane ( GtkPaned * pane )
{
2026-01-15 18:35:27 -07:00
if (( gtk_paned_get_child1 ( pane ) == NULL || ! gtk_widget_get_visible ( gtk_paned_get_child1 ( pane ))) &&
( gtk_paned_get_child2 ( pane ) == NULL || ! gtk_widget_get_visible ( gtk_paned_get_child2 ( pane ))))
{
gtk_widget_hide ( GTK_WIDGET ( pane ));
return ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_show ( GTK_WIDGET ( pane ));
2011-02-24 04:14:30 +01:00
}
static void
mg_hide_empty_boxes ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
/* hide empty vpanes - so the handle is not shown */
mg_hide_empty_pane (( GtkPaned * ) gui -> vpane_right );
mg_hide_empty_pane (( GtkPaned * ) gui -> vpane_left );
2011-02-24 04:14:30 +01:00
}
static void
mg_userlist_showhide ( session * sess , int show )
{
2026-01-15 18:35:27 -07:00
session_gui * gui = sess -> gui ;
int handle_size ;
int right_size ;
2026-03-15 18:21:01 -06:00
int min_right_size ;
2026-01-15 18:35:27 -07:00
GtkAllocation allocation ;
2012-05-28 23:17:09 +02:00
2026-03-15 18:21:01 -06:00
gtk_widget_get_size_request ( gui -> user_box , & min_right_size , NULL );
if ( min_right_size < 1 )
min_right_size = 1 ;
right_size = MAX ( prefs . hex_gui_pane_right_size , min_right_size );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( show )
{
gtk_widget_show ( gui -> user_box );
gui -> ul_hidden = 0 ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_get_allocation ( gui -> hpane_right , & allocation );
gtk_widget_style_get ( GTK_WIDGET ( gui -> hpane_right ), "handle-size" , & handle_size , NULL );
gtk_paned_set_position ( GTK_PANED ( gui -> hpane_right ), allocation . width - ( right_size + handle_size ));
}
else
{
gtk_widget_hide ( gui -> user_box );
gui -> ul_hidden = 1 ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_hide_empty_boxes ( gui );
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_is_userlist_and_tree_combined ( void )
{
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_tab_pos == POS_TOPLEFT && prefs . hex_gui_ulist_pos == POS_BOTTOMLEFT )
return TRUE ;
if ( prefs . hex_gui_tab_pos == POS_BOTTOMLEFT && prefs . hex_gui_ulist_pos == POS_TOPLEFT )
return TRUE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_tab_pos == POS_TOPRIGHT && prefs . hex_gui_ulist_pos == POS_BOTTOMRIGHT )
return TRUE ;
if ( prefs . hex_gui_tab_pos == POS_BOTTOMRIGHT && prefs . hex_gui_ulist_pos == POS_TOPRIGHT )
return TRUE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return FALSE ;
2011-02-24 04:14:30 +01:00
}
/* decide if the userlist should be shown or hidden for this tab */
void
mg_decide_userlist ( session * sess , gboolean switch_to_current )
{
2026-01-15 18:35:27 -07:00
/* when called from menu.c we need this */
if ( sess -> gui == mg_gui && switch_to_current )
sess = current_tab ;
if ( prefs . hex_gui_ulist_hide )
{
mg_userlist_showhide ( sess , FALSE );
return ;
}
switch ( sess -> type )
{
case SESS_SERVER :
case SESS_DIALOG :
case SESS_NOTICES :
case SESS_SNOTICES :
if ( mg_is_userlist_and_tree_combined ())
mg_userlist_showhide ( sess , TRUE ); /* show */
else
mg_userlist_showhide ( sess , FALSE ); /* hide */
break ;
default :
mg_userlist_showhide ( sess , TRUE ); /* show */
}
2011-02-24 04:14:30 +01:00
}
static int ul_tag = 0 ;
static gboolean
mg_populate_userlist ( session * sess )
{
2026-01-15 18:35:27 -07:00
if ( ! sess )
sess = current_tab ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( is_session ( sess ))
{
if ( sess -> type == SESS_DIALOG )
mg_set_access_icon ( sess -> gui , NULL , sess -> server -> is_away );
else
mg_set_access_icon ( sess -> gui , get_user_icon ( sess -> server , sess -> me ), sess -> server -> is_away );
userlist_show ( sess );
userlist_set_value ( sess -> gui -> user_tree , sess -> res -> old_ul_value );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
ul_tag = 0 ;
return 0 ;
2011-02-24 04:14:30 +01:00
}
/* fill the irc tab with a new channel */
static void
mg_populate ( session * sess )
{
2026-01-15 18:35:27 -07:00
session_gui * gui = sess -> gui ;
restore_gui * res = sess -> res ;
int i , render = TRUE ;
guint16 vis = gui -> ul_hidden ;
GtkAllocation allocation ;
switch ( sess -> type )
{
case SESS_DIALOG :
/* show the dialog buttons */
gtk_widget_show ( gui -> dialogbutton_box );
/* hide the chan-mode buttons */
gtk_widget_hide ( gui -> topicbutton_box );
/* hide the userlist */
mg_decide_userlist ( sess , FALSE );
/* shouldn't edit the topic */
2026-03-14 19:57:42 -06:00
gtk_text_view_set_editable ( GTK_TEXT_VIEW ( gui -> topic_entry ), FALSE );
2026-01-15 18:35:27 -07:00
/* might be hidden from server tab */
if ( prefs . hex_gui_topicbar )
gtk_widget_show ( gui -> topic_bar );
break ;
case SESS_SERVER :
if ( prefs . hex_gui_mode_buttons )
gtk_widget_show ( gui -> topicbutton_box );
/* hide the dialog buttons */
gtk_widget_hide ( gui -> dialogbutton_box );
/* hide the userlist */
mg_decide_userlist ( sess , FALSE );
/* servers don't have topics */
gtk_widget_hide ( gui -> topic_bar );
break ;
default :
/* hide the dialog buttons */
gtk_widget_hide ( gui -> dialogbutton_box );
if ( prefs . hex_gui_mode_buttons )
gtk_widget_show ( gui -> topicbutton_box );
/* show the userlist */
mg_decide_userlist ( sess , FALSE );
2026-03-14 19:57:42 -06:00
/* let the topic be edited */
gtk_text_view_set_editable ( GTK_TEXT_VIEW ( gui -> topic_entry ), TRUE );
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_topicbar )
gtk_widget_show ( gui -> topic_bar );
}
/* move to THE irc tab */
if ( gui -> is_tab )
gtk_notebook_set_current_page ( GTK_NOTEBOOK ( gui -> note_book ), 0 );
/* xtext size change? Then don't render, wait for the expose caused
2011-02-24 04:14:30 +01:00
by showing/hidding the userlist */
2026-01-15 18:35:27 -07:00
gtk_widget_get_allocation ( gui -> user_box , & allocation );
if ( vis != gui -> ul_hidden && allocation . width > 1 )
render = FALSE ;
gtk_xtext_buffer_show ( GTK_XTEXT ( gui -> xtext ), res -> buffer , render );
if ( gui -> is_tab )
gtk_widget_set_sensitive ( gui -> menu , TRUE );
2026-03-14 19:57:42 -06:00
if ( res -> topic_text )
{
GtkTextBuffer * topic_buffer ;
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( gui -> topic_entry ));
gtk_text_buffer_set_text ( topic_buffer , res -> topic_text , - 1 );
g_free ( res -> topic_text );
res -> topic_text = NULL ;
} else
{
GtkTextBuffer * topic_buffer ;
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( gui -> topic_entry ));
gtk_text_buffer_set_text ( topic_buffer , "" , - 1 );
}
2026-01-15 18:35:27 -07:00
mg_restore_speller ( gui -> input_box , & res -> input_text );
mg_restore_entry ( gui -> key_entry , & res -> key_text );
mg_restore_entry ( gui -> limit_entry , & res -> limit_text );
mg_restore_label ( gui -> laginfo , & res -> lag_text );
mg_restore_label ( gui -> throttleinfo , & res -> queue_text );
mg_focus ( sess );
fe_set_title ( sess );
/* this one flickers, so only change if necessary */
if ( strcmp ( sess -> server -> nick , gtk_button_get_label ( GTK_BUTTON ( gui -> nick_label ))) != 0 )
gtk_button_set_label ( GTK_BUTTON ( gui -> nick_label ), sess -> server -> nick );
/* this is slow, so make it a timeout event */
if ( ! gui -> is_tab )
{
mg_populate_userlist ( sess );
} else
{
if ( ul_tag == 0 )
ul_tag = g_idle_add (( GSourceFunc ) mg_populate_userlist , NULL );
}
fe_userlist_numbers ( sess );
/* restore all the channel mode buttons */
ignore_chanmode = TRUE ;
for ( i = 0 ; i < NUM_FLAG_WIDS - 1 ; i ++ )
{
/* Hide if mode not supported */
if ( sess -> server && strchr ( sess -> server -> chanmodes , chan_flags [ i ]) == NULL )
gtk_widget_hide ( sess -> gui -> flag_wid [ i ]);
else
gtk_widget_show ( sess -> gui -> flag_wid [ i ]);
/* Update state */
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( gui -> flag_wid [ i ]),
res -> flag_wid_state [ i ]);
}
ignore_chanmode = FALSE ;
if ( gui -> lagometer )
{
gtk_progress_bar_set_fraction ( GTK_PROGRESS_BAR ( gui -> lagometer ),
res -> lag_value );
if ( res -> lag_tip )
gtk_widget_set_tooltip_text ( gtk_widget_get_parent ( sess -> gui -> lagometer ), res -> lag_tip );
}
if ( gui -> throttlemeter )
{
gtk_progress_bar_set_fraction ( GTK_PROGRESS_BAR ( gui -> throttlemeter ),
res -> queue_value );
if ( res -> queue_tip )
gtk_widget_set_tooltip_text ( gtk_widget_get_parent ( sess -> gui -> throttlemeter ), res -> queue_tip );
}
/* did this tab have a connecting graph? restore it.. */
if ( res -> c_graph )
{
res -> c_graph = FALSE ;
mg_progressbar_create ( gui );
}
/* menu items */
menu_set_away ( gui , sess -> server -> is_away );
gtk_widget_set_sensitive ( gui -> menu_item [ MENU_ID_AWAY ], sess -> server -> connected );
gtk_widget_set_sensitive ( gui -> menu_item [ MENU_ID_JOIN ], sess -> server -> end_of_motd );
gtk_widget_set_sensitive ( gui -> menu_item [ MENU_ID_DISCONNECT ],
sess -> server -> connected || sess -> server -> recondelay_tag );
mg_set_topic_tip ( sess );
plugin_emit_dummy_print ( sess , "Focus Tab" );
2011-02-24 04:14:30 +01:00
}
void
2026-01-15 18:35:27 -07:00
mg_bring_tofront_sess ( session * sess ) /* IRC tab or window */
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
if ( sess -> gui -> is_tab )
chan_focus ( sess -> res -> tab );
else
gtk_window_present ( GTK_WINDOW ( sess -> gui -> window ));
2011-02-24 04:14:30 +01:00
}
void
2026-01-15 18:35:27 -07:00
mg_bring_tofront ( GtkWidget * vbox ) /* non-IRC tab or window */
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
chan * ch ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
ch = g_object_get_data ( G_OBJECT ( vbox ), "ch" );
if ( ch )
chan_focus ( ch );
else
gtk_window_present ( GTK_WINDOW ( gtk_widget_get_toplevel ( vbox )));
2011-02-24 04:14:30 +01:00
}
void
mg_switch_page ( int relative , int num )
{
2026-01-15 18:35:27 -07:00
if ( mg_gui )
chanview_move_focus ( mg_gui -> chanview , relative , num );
2011-02-24 04:14:30 +01:00
}
/* a toplevel IRC window was destroyed */
static void
mg_topdestroy_cb ( GtkWidget * win , session * sess )
{
2026-01-15 18:35:27 -07:00
session_free ( sess ); /* tell zoitechat.c about it */
2011-02-24 04:14:30 +01:00
}
/* cleanup an IRC tab */
static void
mg_ircdestroy ( session * sess )
{
2026-01-15 18:35:27 -07:00
GSList * list ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
session_free ( sess ); /* tell zoitechat.c about it */
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( mg_gui == NULL )
{
return ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
list = sess_list ;
while ( list )
{
sess = list -> data ;
if ( sess -> gui -> is_tab )
{
return ;
}
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( mg_gui -> window );
active_tab = NULL ;
mg_gui = NULL ;
parent_window = NULL ;
2011-02-24 04:14:30 +01:00
}
static void
mg_tab_close_cb ( GtkWidget * dialog , gint arg1 , session * sess )
{
2026-01-15 18:35:27 -07:00
GSList * list , * next ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( dialog );
if ( arg1 == GTK_RESPONSE_OK && is_session ( sess ))
{
/* force it NOT to send individual PARTs */
sess -> server -> sent_quit = TRUE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
for ( list = sess_list ; list ;)
{
next = list -> next ;
if ((( session * ) list -> data ) -> server == sess -> server &&
(( session * ) list -> data ) != sess )
fe_close_window (( session * ) list -> data );
list = next ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* just send one QUIT - better for BNCs */
sess -> server -> sent_quit = FALSE ;
fe_close_window ( sess );
}
2011-02-24 04:14:30 +01:00
}
2026-04-13 10:55:59 -06:00
static void
mg_closed_channel_tabs_add ( session * sess )
{
mg_closed_channel_tab * item ;
GSList * last ;
if ( ! sess || sess -> type != SESS_CHANNEL || ! sess -> channel [ 0 ])
return ;
item = g_new0 ( mg_closed_channel_tab , 1 );
item -> server_id = sess -> server -> id ;
g_strlcpy ( item -> channel , sess -> channel , sizeof ( item -> channel ));
g_strlcpy ( item -> key , sess -> channelkey , sizeof ( item -> key ));
mg_closed_channel_tabs = g_slist_prepend ( mg_closed_channel_tabs , item );
if ( g_slist_length ( mg_closed_channel_tabs ) > 20 )
{
last = g_slist_last ( mg_closed_channel_tabs );
g_free ( last -> data );
mg_closed_channel_tabs = g_slist_delete_link ( mg_closed_channel_tabs , last );
}
}
void
mg_reopen_closed_channel_tab ( void )
{
mg_closed_channel_tab * item ;
GSList * head ;
GSList * list ;
server * serv ;
session * sess ;
if ( ! mg_closed_channel_tabs )
return ;
head = mg_closed_channel_tabs ;
item = head -> data ;
mg_closed_channel_tabs = g_slist_delete_link ( mg_closed_channel_tabs , head );
if ( ! item )
return ;
serv = NULL ;
for ( list = serv_list ; list ; list = list -> next )
{
server * candidate = list -> data ;
if ( candidate -> id == item -> server_id )
{
serv = candidate ;
break ;
}
}
if ( serv && serv -> connected && item -> channel [ 0 ])
{
sess = find_channel ( serv , item -> channel );
if ( sess )
fe_ctrl_gui ( sess , 2 , 0 );
else
{
new_ircwindow ( serv , item -> channel , SESS_CHANNEL , 1 );
serv -> p_join ( serv , item -> channel , item -> key );
}
}
g_free ( item );
}
2011-02-24 04:14:30 +01:00
void
mg_tab_close ( session * sess )
{
2026-01-15 18:35:27 -07:00
GtkWidget * dialog ;
GSList * list ;
int i ;
2026-04-13 10:55:59 -06:00
if ( chan_remove ( sess -> res -> tab , FALSE ))
{
mg_closed_channel_tabs_add ( sess );
sess -> res -> tab = NULL ;
mg_ircdestroy ( sess );
}
2026-01-15 18:35:27 -07:00
else
{
for ( i = 0 , list = sess_list ; list ; list = list -> next )
{
session * s = ( session * ) list -> data ;
if ( s -> server == sess -> server && ( s -> type == SESS_CHANNEL || s -> type == SESS_DIALOG ))
i ++ ;
}
dialog = gtk_message_dialog_new ( GTK_WINDOW ( parent_window ), 0 ,
GTK_MESSAGE_WARNING , GTK_BUTTONS_OK_CANCEL ,
_ ( "This server still has %d channels or dialogs associated with it. "
"Close them all?" ), i );
2026-03-04 23:28:01 -07:00
theme_manager_attach_window ( dialog );
2026-01-15 18:35:27 -07:00
g_signal_connect ( G_OBJECT ( dialog ), "response" ,
G_CALLBACK ( mg_tab_close_cb ), sess );
if ( prefs . hex_gui_tab_layout )
{
gtk_window_set_position ( GTK_WINDOW ( dialog ), GTK_WIN_POS_MOUSE );
}
else
{
gtk_window_set_position ( GTK_WINDOW ( dialog ), GTK_WIN_POS_CENTER_ON_PARENT );
}
gtk_widget_show ( dialog );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_menu_destroy ( GtkWidget * menu , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( menu );
g_object_unref ( menu );
2011-02-24 04:14:30 +01:00
}
void
mg_create_icon_item ( char * label , char * stock , GtkWidget * menu ,
2026-01-15 18:35:27 -07:00
void * callback , void * userdata )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
GtkWidget * item ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
item = create_icon_menu ( label , stock , TRUE );
gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item );
g_signal_connect ( G_OBJECT ( item ), "activate" , G_CALLBACK ( callback ),
userdata );
gtk_widget_show ( item );
2011-02-24 04:14:30 +01:00
}
static int
mg_count_networks ( void )
{
2026-01-15 18:35:27 -07:00
int cons = 0 ;
GSList * list ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
for ( list = serv_list ; list ; list = list -> next )
{
if ((( server * ) list -> data ) -> connected )
cons ++ ;
}
return cons ;
2011-02-24 04:14:30 +01:00
}
static int
mg_count_dccs ( void )
{
2026-01-15 18:35:27 -07:00
GSList * list ;
struct DCC * dcc ;
int dccs = 0 ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
list = dcc_list ;
while ( list )
{
dcc = list -> data ;
if (( dcc -> type == TYPE_SEND || dcc -> type == TYPE_RECV ) &&
dcc -> dccstat == STAT_ACTIVE )
dccs ++ ;
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return dccs ;
2011-02-24 04:14:30 +01:00
}
void
mg_open_quit_dialog ( gboolean minimize_button )
{
2026-01-15 18:35:27 -07:00
static GtkWidget * dialog = NULL ;
2026-02-05 00:04:07 -07:00
GtkWidget * dialog_vbox1 ;
GtkWidget * table1 ;
GtkWidget * image ;
GtkWidget * checkbutton1 ;
GtkWidget * label ;
GtkWidget * button ;
char * text , * connecttext ;
int cons ;
int dccs ;
2026-01-15 18:35:27 -07:00
if ( dialog )
{
gtk_window_present ( GTK_WINDOW ( dialog ));
return ;
}
dccs = mg_count_dccs ();
cons = mg_count_networks ();
if ( dccs + cons == 0 || ! prefs . hex_gui_quit_dialog )
{
zoitechat_exit ();
return ;
}
dialog = gtk_dialog_new ();
2026-03-04 23:28:01 -07:00
theme_manager_attach_window ( dialog );
2026-01-15 18:35:27 -07:00
gtk_container_set_border_width ( GTK_CONTAINER ( dialog ), 6 );
gtk_window_set_title ( GTK_WINDOW ( dialog ), _ ( "Quit ZoiteChat?" ));
gtk_window_set_transient_for ( GTK_WINDOW ( dialog ), GTK_WINDOW ( parent_window ));
dialog_vbox1 = gtk_dialog_get_content_area ( GTK_DIALOG ( dialog ));
gtk_widget_show ( dialog_vbox1 );
2026-01-19 19:26:29 -07:00
table1 = gtk_grid_new ();
gtk_widget_show ( table1 );
gtk_box_pack_start ( GTK_BOX ( dialog_vbox1 ), table1 , TRUE , TRUE , 0 );
gtk_container_set_border_width ( GTK_CONTAINER ( table1 ), 6 );
gtk_grid_set_row_spacing ( GTK_GRID ( table1 ), 12 );
gtk_grid_set_column_spacing ( GTK_GRID ( table1 ), 12 );
2026-01-15 18:35:27 -07:00
2026-01-30 08:47:31 -07:00
image = gtk_image_new_from_icon_name ( "dialog-warning" , GTK_ICON_SIZE_DIALOG );
2026-01-15 18:35:27 -07:00
gtk_widget_show ( image );
2026-01-30 16:04:50 -07:00
gtk_widget_set_hexpand ( image , FALSE );
gtk_widget_set_vexpand ( image , FALSE );
gtk_widget_set_halign ( image , GTK_ALIGN_FILL );
gtk_widget_set_valign ( image , GTK_ALIGN_FILL );
2026-01-19 19:26:29 -07:00
gtk_grid_attach ( GTK_GRID ( table1 ), image , 0 , 0 , 1 , 1 );
2026-01-15 18:35:27 -07:00
checkbutton1 = gtk_check_button_new_with_mnemonic ( _ ( "Don't ask next time." ));
gtk_widget_show ( checkbutton1 );
2026-01-19 19:26:29 -07:00
gtk_widget_set_hexpand ( checkbutton1 , TRUE );
2026-01-30 16:04:50 -07:00
gtk_widget_set_vexpand ( checkbutton1 , FALSE );
2026-01-19 22:17:57 -07:00
gtk_widget_set_halign ( checkbutton1 , GTK_ALIGN_FILL );
gtk_widget_set_valign ( checkbutton1 , GTK_ALIGN_CENTER );
2026-01-30 16:14:35 -07:00
gtk_widget_set_margin_top ( checkbutton1 , 4 );
gtk_widget_set_margin_bottom ( checkbutton1 , 4 );
2026-01-30 16:04:50 -07:00
gtk_grid_attach ( GTK_GRID ( table1 ), checkbutton1 , 0 , 1 , 2 , 1 );
2026-01-15 18:35:27 -07:00
connecttext = g_strdup_printf ( _ ( "You are connected to %i IRC networks." ), cons );
text = g_strdup_printf ( "<span weight= \" bold \" size= \" larger \" >%s</span> \n\n %s \n %s" ,
_ ( "Are you sure you want to quit?" ),
cons ? connecttext : "" ,
dccs ? _ ( "Some file transfers are still active." ) : "" );
g_free ( connecttext );
label = gtk_label_new ( text );
g_free ( text );
gtk_widget_show ( label );
2026-01-19 19:37:03 -07:00
mg_set_label_alignment_start ( label );
2026-01-19 19:26:29 -07:00
gtk_widget_set_hexpand ( label , TRUE );
gtk_widget_set_vexpand ( label , TRUE );
2026-01-30 16:14:35 -07:00
gtk_widget_set_halign ( label , GTK_ALIGN_FILL );
gtk_widget_set_valign ( label , GTK_ALIGN_CENTER );
gtk_label_set_xalign ( GTK_LABEL ( label ), 0.0f );
2026-01-30 16:04:50 -07:00
gtk_grid_attach ( GTK_GRID ( table1 ), label , 1 , 0 , 1 , 1 );
2026-01-19 19:26:29 -07:00
gtk_label_set_use_markup ( GTK_LABEL ( label ), TRUE );
2026-01-15 18:35:27 -07:00
2026-02-05 00:04:07 -07:00
if ( minimize_button && gtkutil_tray_icon_supported ( GTK_WINDOW ( dialog )))
{
button = gtk_button_new_with_mnemonic ( _ ( "_Minimize to Tray" ));
gtk_widget_show ( button );
2026-01-15 18:35:27 -07:00
gtk_dialog_add_action_widget ( GTK_DIALOG ( dialog ), button , 1 );
}
2026-01-30 08:47:31 -07:00
button = gtk_button_new_with_mnemonic ( _ ( "_Cancel" ));
2026-01-15 18:35:27 -07:00
gtk_widget_show ( button );
gtk_dialog_add_action_widget ( GTK_DIALOG ( dialog ), button ,
GTK_RESPONSE_CANCEL );
gtk_widget_grab_focus ( button );
2026-01-30 08:47:31 -07:00
button = gtk_button_new_with_mnemonic ( _ ( "_Quit" ));
2026-01-15 18:35:27 -07:00
gtk_widget_show ( button );
gtk_dialog_add_action_widget ( GTK_DIALOG ( dialog ), button , 0 );
gtk_widget_show ( dialog );
switch ( gtk_dialog_run ( GTK_DIALOG ( dialog )))
{
case 0 :
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( checkbutton1 )))
prefs . hex_gui_quit_dialog = 0 ;
zoitechat_exit ();
break ;
case 1 : /* minimize to tray */
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( checkbutton1 )))
{
prefs . hex_gui_tray_close = 1 ;
/*prefs.hex_gui_quit_dialog = 0;*/
}
/* force tray icon ON, if not already */
if ( ! prefs . hex_gui_tray )
{
prefs . hex_gui_tray = 1 ;
tray_apply_setup ();
}
tray_toggle_visibility ( TRUE );
break ;
}
gtk_widget_destroy ( dialog );
dialog = NULL ;
2011-02-24 04:14:30 +01:00
}
void
mg_close_sess ( session * sess )
{
2026-01-15 18:35:27 -07:00
if ( sess_list -> next == NULL )
{
mg_open_quit_dialog ( FALSE );
return ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
fe_close_window ( sess );
2011-02-24 04:14:30 +01:00
}
static int
mg_chan_remove ( chan * ch )
{
2026-01-15 18:35:27 -07:00
/* remove the tab from chanview */
chan_remove ( ch , TRUE );
/* any tabs left? */
if ( chanview_get_size ( mg_gui -> chanview ) < 1 )
{
/* if not, destroy the main tab window */
gtk_widget_destroy ( mg_gui -> window );
current_tab = NULL ;
active_tab = NULL ;
mg_gui = NULL ;
parent_window = NULL ;
return TRUE ;
}
return FALSE ;
2011-02-24 04:14:30 +01:00
}
/* destroy non-irc tab/window */
static void
mg_close_gen ( chan * ch , GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
if ( ! ch )
ch = g_object_get_data ( G_OBJECT ( box ), "ch" );
if ( ch )
{
/* remove from notebook */
gtk_widget_destroy ( box );
/* remove the tab from chanview */
mg_chan_remove ( ch );
} else
{
gtk_widget_destroy ( gtk_widget_get_toplevel ( box ));
}
2011-02-24 04:14:30 +01:00
}
/* the "X" close button has been pressed (tab-view) */
static void
mg_xbutton_cb ( chanview * cv , chan * ch , int tag , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
if ( tag == TAG_IRC ) /* irc tab */
mg_close_sess ( userdata );
else /* non-irc utility tab */
mg_close_gen ( ch , userdata );
2011-02-24 04:14:30 +01:00
}
static void
mg_link_gentab ( chan * ch , GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
int num ;
GtkWidget * win ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
g_object_ref ( box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
num = gtk_notebook_page_num ( GTK_NOTEBOOK ( mg_gui -> note_book ), box );
gtk_notebook_remove_page ( GTK_NOTEBOOK ( mg_gui -> note_book ), num );
mg_chan_remove ( ch );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
win = gtkutil_window_new ( g_object_get_data ( G_OBJECT ( box ), "title" ), "" ,
GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( box ), "w" )),
GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( box ), "h" )),
2 );
/* so it doesn't try to chan_remove (there's no tab anymore) */
g_object_steal_data ( G_OBJECT ( box ), "ch" );
gtk_container_set_border_width ( GTK_CONTAINER ( box ), 0 );
gtk_container_add ( GTK_CONTAINER ( win ), box );
gtk_widget_show ( win );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
g_object_unref ( box );
2011-02-24 04:14:30 +01:00
}
static void
mg_detach_tab_cb ( GtkWidget * item , chan * ch )
{
2026-01-15 18:35:27 -07:00
if ( chan_get_tag ( ch ) == TAG_IRC ) /* IRC tab */
{
/* userdata is session * */
mg_link_irctab ( chan_get_userdata ( ch ), 1 );
return ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* userdata is GtkWidget * */
mg_link_gentab ( ch , chan_get_userdata ( ch )); /* non-IRC tab */
2011-02-24 04:14:30 +01:00
}
static void
mg_destroy_tab_cb ( GtkWidget * item , chan * ch )
{
2026-01-15 18:35:27 -07:00
/* treat it just like the X button press */
mg_xbutton_cb ( mg_gui -> chanview , ch , chan_get_tag ( ch ), chan_get_userdata ( ch ));
2011-02-24 04:14:30 +01:00
}
static void
mg_color_insert ( GtkWidget * item , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
char buf [ 32 ];
char * text ;
int num = GPOINTER_TO_INT ( userdata );
if ( num > 99 )
{
switch ( num )
{
case 100 :
text = " \002 " ; break ;
case 101 :
text = " \037 " ; break ;
case 102 :
text = " \035 " ; break ;
case 103 :
text = " \036 " ; break ;
default :
text = " \017 " ; break ;
}
key_action_insert ( current_sess -> gui -> input_box , 0 , text , 0 , 0 );
} else
{
sprintf ( buf , " \003 %02d" , num );
key_action_insert ( current_sess -> gui -> input_box , 0 , buf , 0 , 0 );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_markup_item ( GtkWidget * menu , char * text , int arg )
{
2026-01-15 18:35:27 -07:00
GtkWidget * item ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
item = gtk_menu_item_new_with_label ( "" );
gtk_label_set_markup ( GTK_LABEL ( gtk_bin_get_child ( GTK_BIN ( item ))), text );
g_signal_connect ( G_OBJECT ( item ), "activate" ,
G_CALLBACK ( mg_color_insert ), GINT_TO_POINTER ( arg ));
gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item );
gtk_widget_show ( item );
2011-02-24 04:14:30 +01:00
}
GtkWidget *
mg_submenu ( GtkWidget * menu , char * text )
{
2026-01-15 18:35:27 -07:00
GtkWidget * submenu , * item ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
item = gtk_menu_item_new_with_mnemonic ( text );
gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item );
gtk_widget_show ( item );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
submenu = gtk_menu_new ();
gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( item ), submenu );
gtk_widget_show ( submenu );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return submenu ;
2011-02-24 04:14:30 +01:00
}
static void
mg_create_color_menu ( GtkWidget * menu , session * sess )
{
2026-01-15 18:35:27 -07:00
GtkWidget * submenu ;
GtkWidget * subsubmenu ;
char buf [ 256 ];
int i ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
submenu = mg_submenu ( menu , _ ( "Insert Attribute or Color Code" ));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_markup_item ( submenu , _ ( "<b>Bold</b>" ), 100 );
mg_markup_item ( submenu , _ ( "<u>Underline</u>" ), 101 );
mg_markup_item ( submenu , _ ( "<i>Italic</i>" ), 102 );
mg_markup_item ( submenu , _ ( "<s>Strikethrough</s>" ), 103 );
mg_markup_item ( submenu , _ ( "Normal" ), 999 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
subsubmenu = mg_submenu ( submenu , _ ( "Colors 0-7" ));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
for ( i = 0 ; i < 8 ; i ++ )
{
2026-01-19 22:50:17 -07:00
guint16 red ;
guint16 green ;
guint16 blue ;
2026-03-02 19:42:48 -07:00
GdkRGBA color ;
if ( ! theme_get_mirc_color (( unsigned int ) i , & color ))
continue ;
red = ( guint16 ) CLAMP ( color . red * 65535.0 + 0.5 , 0.0 , 65535.0 );
green = ( guint16 ) CLAMP ( color . green * 65535.0 + 0.5 , 0.0 , 65535.0 );
blue = ( guint16 ) CLAMP ( color . blue * 65535.0 + 0.5 , 0.0 , 65535.0 );
2026-01-15 18:35:27 -07:00
sprintf ( buf , "<tt><sup>%02d</sup> <span background= \" #%02x%02x%02x \" >"
" </span></tt>" ,
2026-01-19 22:50:17 -07:00
i , red >> 8 , green >> 8 , blue >> 8 );
2026-01-15 18:35:27 -07:00
mg_markup_item ( subsubmenu , buf , i );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
subsubmenu = mg_submenu ( submenu , _ ( "Colors 8-15" ));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
for ( i = 8 ; i < 16 ; i ++ )
{
2026-01-19 22:50:17 -07:00
guint16 red ;
guint16 green ;
guint16 blue ;
2026-03-02 19:42:48 -07:00
GdkRGBA color ;
if ( ! theme_get_mirc_color (( unsigned int ) i , & color ))
continue ;
red = ( guint16 ) CLAMP ( color . red * 65535.0 + 0.5 , 0.0 , 65535.0 );
green = ( guint16 ) CLAMP ( color . green * 65535.0 + 0.5 , 0.0 , 65535.0 );
blue = ( guint16 ) CLAMP ( color . blue * 65535.0 + 0.5 , 0.0 , 65535.0 );
2026-01-15 18:35:27 -07:00
sprintf ( buf , "<tt><sup>%02d</sup> <span background= \" #%02x%02x%02x \" >"
" </span></tt>" ,
2026-01-19 22:50:17 -07:00
i , red >> 8 , green >> 8 , blue >> 8 );
2026-01-15 18:35:27 -07:00
mg_markup_item ( subsubmenu , buf , i );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_set_guint8 ( GtkCheckMenuItem * item , guint8 * setting )
{
2026-01-15 18:35:27 -07:00
session * sess = current_sess ;
guint8 logging = sess -> text_logging ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
* setting = SET_OFF ;
if ( gtk_check_menu_item_get_active ( item ))
* setting = SET_ON ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* has the logging setting changed? */
if ( logging != sess -> text_logging )
log_open_or_close ( sess );
2013-06-06 19:16:50 -04:00
2026-01-15 18:35:27 -07:00
chanopt_save ( sess );
chanopt_save_all ( FALSE );
2011-02-24 04:14:30 +01:00
}
static void
mg_perchan_menu_item ( char * label , GtkWidget * menu , guint8 * setting , guint global )
{
2026-01-15 18:35:27 -07:00
guint8 initial_value = * setting ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* if it's using global value, use that as initial state */
if ( initial_value == SET_DEFAULT )
initial_value = global ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
menu_toggle_item ( label , menu , mg_set_guint8 , setting , initial_value );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_perchannelmenu ( session * sess , GtkWidget * menu )
{
2026-01-15 18:35:27 -07:00
GtkWidget * submenu ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
submenu = menu_quick_sub ( _ ( "_Settings" ), menu , NULL , XCMENU_MNEMONIC , - 1 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_perchan_menu_item ( _ ( "_Log to Disk" ), submenu , & sess -> text_logging , prefs . hex_irc_logging );
mg_perchan_menu_item ( _ ( "_Reload Scrollback" ), submenu , & sess -> text_scrollback , prefs . hex_text_replay );
if ( sess -> type == SESS_CHANNEL )
{
mg_perchan_menu_item ( _ ( "Strip _Colors" ), submenu , & sess -> text_strip , prefs . hex_text_stripcolor_msg );
mg_perchan_menu_item ( _ ( "_Hide Join/Part Messages" ), submenu , & sess -> text_hidejoinpart , prefs . hex_irc_conf_mode );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_create_alertmenu ( session * sess , GtkWidget * menu )
{
2026-01-15 18:35:27 -07:00
GtkWidget * submenu ;
int hex_balloon , hex_beep , hex_tray , hex_flash ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
switch ( sess -> type ) {
case SESS_DIALOG :
hex_balloon = prefs . hex_input_balloon_priv ;
hex_beep = prefs . hex_input_beep_priv ;
hex_tray = prefs . hex_input_tray_priv ;
hex_flash = prefs . hex_input_flash_priv ;
break ;
default :
hex_balloon = prefs . hex_input_balloon_chans ;
hex_beep = prefs . hex_input_beep_chans ;
hex_tray = prefs . hex_input_tray_chans ;
hex_flash = prefs . hex_input_flash_chans ;
}
2020-07-29 09:50:12 -05:00
2026-01-15 18:35:27 -07:00
submenu = menu_quick_sub ( _ ( "_Extra Alerts" ), menu , NULL , XCMENU_MNEMONIC , - 1 );
2020-07-29 09:50:12 -05:00
2026-01-15 18:35:27 -07:00
mg_perchan_menu_item ( _ ( "Show Notifications" ), submenu , & sess -> alert_balloon , hex_balloon );
2020-07-29 09:50:12 -05:00
2026-01-15 18:35:27 -07:00
mg_perchan_menu_item ( _ ( "Beep on _Message" ), submenu , & sess -> alert_beep , hex_beep );
2011-08-09 21:04:11 +02:00
2026-01-15 18:35:27 -07:00
mg_perchan_menu_item ( _ ( "Blink Tray _Icon" ), submenu , & sess -> alert_tray , hex_tray );
2011-08-09 21:04:11 +02:00
2026-01-15 18:35:27 -07:00
mg_perchan_menu_item ( _ ( "Blink Task _Bar" ), submenu , & sess -> alert_taskbar , hex_flash );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_tabmenu ( session * sess , GdkEventButton * event , chan * ch )
{
2026-01-15 18:35:27 -07:00
GtkWidget * menu , * item ;
char buf [ 256 ];
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
menu = gtk_menu_new ();
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess )
{
char * name = g_markup_escape_text ( sess -> channel [ 0 ] ? sess -> channel : _ ( "<none>" ), - 1 );
g_snprintf ( buf , sizeof ( buf ), "<span foreground= \" #3344cc \" ><b>%s</b></span>" , name );
g_free ( name );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
item = gtk_menu_item_new_with_label ( "" );
gtk_label_set_markup ( GTK_LABEL ( gtk_bin_get_child ( GTK_BIN ( item ))), buf );
gtk_menu_shell_append ( GTK_MENU_SHELL ( menu ), item );
gtk_widget_show ( item );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* separator */
menu_quick_item ( 0 , 0 , menu , XCMENU_SHADED , 0 , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* per-channel alerts */
mg_create_alertmenu ( sess , menu );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* per-channel settings */
mg_create_perchannelmenu ( sess , menu );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* separator */
menu_quick_item ( 0 , 0 , menu , XCMENU_SHADED , 0 , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> type == SESS_CHANNEL )
menu_addfavoritemenu ( sess -> server , menu , sess -> channel , TRUE );
else if ( sess -> type == SESS_SERVER )
menu_addconnectmenu ( sess -> server , menu );
}
2011-02-24 04:14:30 +01:00
2026-01-30 15:27:01 -07:00
mg_create_icon_item ( _ ( "_Detach" ), ICON_TAB_DETACH , menu ,
2026-01-15 18:35:27 -07:00
mg_detach_tab_cb , ch );
2026-01-30 15:27:01 -07:00
mg_create_icon_item ( _ ( "_Close" ), ICON_TAB_CLOSE , menu ,
2026-01-15 18:35:27 -07:00
mg_destroy_tab_cb , ch );
if ( sess && tabmenu_list )
menu_create ( menu , tabmenu_list , sess -> channel , FALSE );
if ( sess )
menu_add_plugin_items ( menu , "\x4$TAB" , sess -> channel );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( event -> window )
gtk_menu_set_screen ( GTK_MENU ( menu ), gdk_window_get_screen ( event -> window ));
g_object_ref ( menu );
g_object_ref_sink ( menu );
g_object_unref ( menu );
g_signal_connect ( G_OBJECT ( menu ), "selection-done" ,
G_CALLBACK ( mg_menu_destroy ), NULL );
2026-01-31 12:57:05 -07:00
gtk_menu_popup_at_pointer ( GTK_MENU ( menu ), ( GdkEvent * ) event );
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_tab_contextmenu_cb ( chanview * cv , chan * ch , int tag , gpointer ud , GdkEventButton * event )
{
2026-01-15 18:35:27 -07:00
/* middle-click to close a tab */
if ((( prefs . hex_gui_tab_middleclose && event -> button == 2 ))
&& event -> type == GDK_BUTTON_PRESS )
{
mg_xbutton_cb ( cv , ch , tag , ud );
return TRUE ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( event -> button != 3 )
return FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( tag == TAG_IRC )
mg_create_tabmenu ( ud , event , ch );
else
mg_create_tabmenu ( NULL , event , ch );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return TRUE ;
2011-02-24 04:14:30 +01:00
}
void
mg_dnd_drop_file ( session * sess , char * target , char * uri )
{
2026-01-15 18:35:27 -07:00
char * p , * data , * next , * fname ;
p = data = g_strdup ( uri );
while ( * p )
{
next = strchr ( p , '\r' );
if ( g_ascii_strncasecmp ( "file:" , p , 5 ) == 0 )
{
if ( next )
* next = 0 ;
fname = g_filename_from_uri ( p , NULL , NULL );
if ( fname )
{
/* dcc_send() expects utf-8 */
p = g_filename_from_utf8 ( fname , - 1 , 0 , 0 , 0 );
if ( p )
{
dcc_send ( sess , target , p , prefs . hex_dcc_max_send_cps , 0 );
g_free ( p );
}
g_free ( fname );
}
}
if ( ! next )
break ;
p = next + 1 ;
if ( * p == '\n' )
p ++ ;
}
g_free ( data );
2011-02-24 04:14:30 +01:00
}
static void
mg_dialog_dnd_drop ( GtkWidget * widget , GdkDragContext * context , gint x ,
2026-01-15 18:35:27 -07:00
gint y , GtkSelectionData * selection_data , guint info ,
guint32 time , gpointer ud )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
if ( current_sess -> type == SESS_DIALOG )
/* sess->channel is really the nickname of dialogs */
mg_dnd_drop_file ( current_sess , current_sess -> channel , ( char * ) gtk_selection_data_get_data ( selection_data ));
2011-02-24 04:14:30 +01:00
}
/* add a tabbed channel */
static void
mg_add_chan ( session * sess )
{
2026-01-15 18:35:27 -07:00
GdkPixbuf * icon ;
char * name = _ ( "<none>" );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> channel [ 0 ])
name = sess -> channel ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
switch ( sess -> type )
{
case SESS_CHANNEL :
icon = pix_tree_channel ;
break ;
case SESS_SERVER :
icon = pix_tree_server ;
break ;
default :
icon = pix_tree_dialog ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
sess -> res -> tab = chanview_add ( sess -> gui -> chanview , name , sess -> server , sess ,
sess -> type == SESS_SERVER ? FALSE : TRUE ,
TAG_IRC , icon );
if ( plain_list == NULL )
mg_create_tab_colors ();
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
chan_set_color ( sess -> res -> tab , plain_list );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> res -> buffer == NULL )
{
sess -> res -> buffer = gtk_xtext_buffer_new ( GTK_XTEXT ( sess -> gui -> xtext ));
gtk_xtext_set_time_stamp ( sess -> res -> buffer , prefs . hex_stamp_text );
sess -> res -> user_model = userlist_create_model ( sess );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_userlist_button ( GtkWidget * box , char * label , char * cmd ,
2026-01-15 18:35:27 -07:00
int a , int b , int c , int d )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
GtkWidget * wid = gtk_button_new_with_label ( label );
g_signal_connect ( G_OBJECT ( wid ), "clicked" ,
G_CALLBACK ( userlist_button_cb ), cmd );
2026-01-19 19:26:29 -07:00
gtk_widget_set_hexpand ( wid , TRUE );
2026-05-11 01:35:57 -06:00
gtk_widget_set_vexpand ( wid , FALSE );
2026-01-19 19:26:29 -07:00
gtk_widget_set_halign ( wid , GTK_ALIGN_FILL );
2026-05-11 01:35:57 -06:00
gtk_widget_set_valign ( wid , GTK_ALIGN_CENTER );
2026-01-30 16:04:50 -07:00
gtk_grid_attach ( GTK_GRID ( box ), wid , a , c , b - a , d - c );
2026-01-15 18:35:27 -07:00
show_and_unfocus ( wid );
2011-02-24 04:14:30 +01:00
}
static GtkWidget *
mg_create_userlistbuttons ( GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
struct popup * pop ;
GSList * list = button_list ;
int a = 0 , b = 0 ;
GtkWidget * tab ;
2026-01-19 19:26:29 -07:00
tab = gtk_grid_new ();
2026-01-15 18:35:27 -07:00
gtk_box_pack_end ( GTK_BOX ( box ), tab , FALSE , FALSE , 0 );
while ( list )
{
pop = list -> data ;
if ( pop -> cmd [ 0 ])
{
mg_userlist_button ( tab , pop -> name , pop -> cmd , a , a + 1 , b , b + 1 );
a ++ ;
if ( a == 2 )
{
a = 0 ;
b ++ ;
}
}
list = list -> next ;
}
return tab ;
2011-02-24 04:14:30 +01:00
}
static void
2026-03-14 19:57:42 -06:00
mg_topic_cb ( GtkWidget * entry )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
session * sess = current_sess ;
2026-03-14 19:57:42 -06:00
GtkTextBuffer * topic_buffer ;
GtkTextIter start ;
GtkTextIter end ;
2026-01-15 18:35:27 -07:00
char * text ;
if ( sess -> channel [ 0 ] && sess -> server -> connected && sess -> type == SESS_CHANNEL )
{
2026-03-14 19:57:42 -06:00
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( entry ));
gtk_text_buffer_get_bounds ( topic_buffer , & start , & end );
text = gtk_text_buffer_get_text ( topic_buffer , & start , & end , FALSE );
2026-01-15 18:35:27 -07:00
if ( text [ 0 ] == 0 )
2026-03-14 19:57:42 -06:00
sess -> server -> p_topic ( sess -> server , sess -> channel , NULL );
else
sess -> server -> p_topic ( sess -> server , sess -> channel , text );
g_free ( text );
2026-01-15 18:35:27 -07:00
} else
2026-03-14 19:57:42 -06:00
{
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( entry ));
gtk_text_buffer_set_text ( topic_buffer , "" , - 1 );
}
2026-01-15 18:35:27 -07:00
/* restore focus to the input widget, where the next input will most
2011-02-24 04:14:30 +01:00
likely be */
2026-01-15 18:35:27 -07:00
gtk_widget_grab_focus ( sess -> gui -> input_box );
2011-02-24 04:14:30 +01:00
}
2026-03-14 19:57:42 -06:00
static gboolean
mg_topic_key_press_cb ( GtkWidget * entry , GdkEventKey * event , gpointer userdata )
{
if ( event -> keyval == GDK_KEY_Return || event -> keyval == GDK_KEY_KP_Enter )
{
mg_topic_cb ( entry );
return TRUE ;
}
return FALSE ;
}
static char *
2026-03-16 19:10:49 -06:00
mg_topic_get_word_at_pos ( GtkWidget * entry , gdouble event_x , gdouble event_y , int * word_pos )
2026-03-14 19:57:42 -06:00
{
GtkTextBuffer * buffer ;
GtkTextIter iter ;
GtkTextIter start ;
GtkTextIter end ;
2026-03-16 19:10:49 -06:00
GtkTextIter cursor ;
2026-03-14 19:57:42 -06:00
int x ;
int y ;
x = ( int ) event_x ;
y = ( int ) event_y ;
gtk_text_view_window_to_buffer_coords ( GTK_TEXT_VIEW ( entry ), GTK_TEXT_WINDOW_TEXT ,
x , y , & x , & y );
gtk_text_view_get_iter_at_location ( GTK_TEXT_VIEW ( entry ), & iter , x , y );
2026-03-16 19:10:49 -06:00
cursor = iter ;
2026-03-14 19:57:42 -06:00
start = iter ;
while ( ! gtk_text_iter_starts_line ( & start ))
{
GtkTextIter prev = start ;
gunichar ch ;
gtk_text_iter_backward_char ( & prev );
ch = gtk_text_iter_get_char ( & prev );
if ( g_unichar_isspace ( ch ))
break ;
start = prev ;
}
end = iter ;
while ( ! gtk_text_iter_ends_line ( & end ))
{
gunichar ch ;
ch = gtk_text_iter_get_char ( & end );
if ( ch == 0 || g_unichar_isspace ( ch ))
break ;
gtk_text_iter_forward_char ( & end );
}
if ( gtk_text_iter_equal ( & start , & end ))
return NULL ;
2026-03-16 19:10:49 -06:00
if ( word_pos )
{
char * prefix ;
buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( entry ));
prefix = gtk_text_buffer_get_text ( buffer , & start , & cursor , FALSE );
* word_pos = ( int ) strlen ( prefix );
g_free ( prefix );
}
2026-03-14 19:57:42 -06:00
buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( entry ));
return gtk_text_buffer_get_text ( buffer , & start , & end , FALSE );
}
static void
mg_topic_set_cursor ( GtkWidget * entry , GdkCursorType cursor_type )
{
GdkWindow * text_window ;
GdkDisplay * display ;
GdkCursor * cursor ;
text_window = gtk_text_view_get_window ( GTK_TEXT_VIEW ( entry ), GTK_TEXT_WINDOW_TEXT );
if ( ! text_window )
return ;
display = gdk_window_get_display ( text_window );
cursor = gdk_cursor_new_for_display ( display , cursor_type );
gdk_window_set_cursor ( text_window , cursor );
g_object_unref ( cursor );
}
static gboolean
2026-03-16 19:10:49 -06:00
mg_topic_word_is_clickable ( const char * word , int word_pos )
2026-03-14 19:57:42 -06:00
{
2026-03-16 19:10:49 -06:00
int start ;
int end ;
2026-04-13 00:08:00 -06:00
int word_type ;
2026-03-16 19:10:49 -06:00
2026-03-14 19:57:42 -06:00
if ( ! word || word [ 0 ] == 0 )
return FALSE ;
if ( strcmp ( word , "/" ) == 0 )
return FALSE ;
2026-04-13 00:08:00 -06:00
word_type = url_check_word ( word );
if ( word_type != WORD_URL && word_type != WORD_HOST && word_type != WORD_HOST6 )
2026-03-16 19:10:49 -06:00
return FALSE ;
url_last ( & start , & end );
return word_pos >= start && word_pos < end ;
2026-03-14 19:57:42 -06:00
}
static gboolean
mg_topic_motion_cb ( GtkWidget * entry , GdkEventMotion * event , gpointer userdata )
{
char * word ;
2026-03-16 19:10:49 -06:00
int word_pos ;
2026-03-14 19:57:42 -06:00
gboolean word_is_clickable ;
2026-03-16 19:10:49 -06:00
word_pos = 0 ;
word = mg_topic_get_word_at_pos ( entry , event -> x , event -> y , & word_pos );
word_is_clickable = mg_topic_word_is_clickable ( word , word_pos );
2026-03-14 19:57:42 -06:00
if ( word_is_clickable )
mg_topic_set_cursor ( entry , GDK_HAND2 );
else
mg_topic_set_cursor ( entry , GDK_XTERM );
g_free ( word );
return FALSE ;
}
static gboolean
mg_topic_leave_cb ( GtkWidget * entry , GdkEventCrossing * event , gpointer userdata )
{
mg_topic_set_cursor ( entry , GDK_XTERM );
return FALSE ;
}
static gboolean
mg_topic_button_release_cb ( GtkWidget * entry , GdkEventButton * event , gpointer userdata )
{
char * word ;
2026-03-16 19:10:49 -06:00
int word_pos ;
2026-03-14 19:57:42 -06:00
int start ;
int end ;
if ( event -> button != 1 )
return FALSE ;
2026-03-16 19:10:49 -06:00
word_pos = 0 ;
word = mg_topic_get_word_at_pos ( entry , event -> x , event -> y , & word_pos );
2026-03-14 19:57:42 -06:00
if ( ! word )
return FALSE ;
2026-04-13 00:08:00 -06:00
if (( event -> state & 13 ) == prefs . hex_gui_url_mod &&
mg_topic_word_is_clickable ( word , word_pos ))
2026-03-14 19:57:42 -06:00
{
url_last ( & start , & end );
word [ end ] = 0 ;
fe_open_url ( word + start );
g_free ( word );
return TRUE ;
}
g_free ( word );
return FALSE ;
}
2011-02-24 04:14:30 +01:00
static void
mg_tabwindow_kill_cb ( GtkWidget * win , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
GSList * list , * next ;
session * sess ;
2026-03-16 21:57:21 -06:00
mg_flush_config_save ();
2026-01-15 18:35:27 -07:00
zoitechat_is_quitting = TRUE ;
/* see if there's any non-tab windows left */
list = sess_list ;
while ( list )
{
sess = list -> data ;
next = list -> next ;
if ( ! sess -> gui -> is_tab )
{
zoitechat_is_quitting = FALSE ;
} else
{
mg_ircdestroy ( sess );
}
list = next ;
}
current_tab = NULL ;
active_tab = NULL ;
mg_gui = NULL ;
parent_window = NULL ;
2011-02-24 04:14:30 +01:00
}
static GtkWidget *
mg_changui_destroy ( session * sess )
{
2026-01-15 18:35:27 -07:00
GtkWidget * ret = NULL ;
if ( sess -> gui -> is_tab )
{
/* avoid calling the "destroy" callback */
g_signal_handlers_disconnect_by_func ( G_OBJECT ( sess -> gui -> window ),
mg_tabwindow_kill_cb , 0 );
/* remove the tab from the chanview */
if ( ! mg_chan_remove ( sess -> res -> tab ))
/* if the window still exists, restore the signal handler */
g_signal_connect ( G_OBJECT ( sess -> gui -> window ), "destroy" ,
G_CALLBACK ( mg_tabwindow_kill_cb ), 0 );
} else
{
/* avoid calling the "destroy" callback */
g_signal_handlers_disconnect_by_func ( G_OBJECT ( sess -> gui -> window ),
mg_topdestroy_cb , sess );
/* don't destroy until the new one is created. Not sure why, but */
/* it fixes: Gdk-CRITICAL **: gdk_colormap_get_screen: */
/* assertion `GDK_IS_COLORMAP (cmap)' failed */
ret = sess -> gui -> window ;
sess -> gui = NULL ;
}
return ret ;
2011-02-24 04:14:30 +01:00
}
static void
mg_link_irctab ( session * sess , int focus )
{
2026-01-15 18:35:27 -07:00
GtkWidget * win ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> gui -> is_tab )
{
win = mg_changui_destroy ( sess );
mg_changui_new ( sess , sess -> res , 0 , focus );
mg_populate ( sess );
zoitechat_is_quitting = FALSE ;
if ( win )
gtk_widget_destroy ( win );
return ;
}
2011-02-24 04:14:30 +01:00
2026-05-22 14:50:44 -06:00
session_gui * old_gui ;
2026-01-15 18:35:27 -07:00
mg_unpopulate ( sess );
2026-05-22 14:50:44 -06:00
old_gui = sess -> gui ;
2026-01-15 18:35:27 -07:00
win = mg_changui_destroy ( sess );
mg_changui_new ( sess , sess -> res , 1 , focus );
/* the buffer is now attached to a different widget */
(( xtext_buffer * ) sess -> res -> buffer ) -> xtext = ( GtkXText * ) sess -> gui -> xtext ;
if ( win )
gtk_widget_destroy ( win );
2026-05-22 14:50:44 -06:00
g_free ( old_gui );
2011-02-24 04:14:30 +01:00
}
void
mg_detach ( session * sess , int mode )
{
2026-01-15 18:35:27 -07:00
switch ( mode )
{
/* detach only */
case 1 :
if ( sess -> gui -> is_tab )
mg_link_irctab ( sess , 1 );
break ;
/* attach only */
case 2 :
if ( ! sess -> gui -> is_tab )
mg_link_irctab ( sess , 1 );
break ;
/* toggle */
default :
mg_link_irctab ( sess , 1 );
}
2011-02-24 04:14:30 +01:00
}
static int
check_is_number ( char * t )
{
2026-01-15 18:35:27 -07:00
while ( * t )
{
if ( * t < '0' || * t > '9' )
return FALSE ;
t ++ ;
}
return TRUE ;
2011-02-24 04:14:30 +01:00
}
static void
mg_change_flag ( GtkWidget * wid , session * sess , char flag )
{
2026-01-15 18:35:27 -07:00
server * serv = sess -> server ;
char mode [ 3 ];
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mode [ 1 ] = flag ;
mode [ 2 ] = '\0' ;
if ( serv -> connected && sess -> channel [ 0 ])
{
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( wid )))
mode [ 0 ] = '+' ;
else
mode [ 0 ] = '-' ;
serv -> p_mode ( serv , sess -> channel , mode );
serv -> p_join_info ( serv , sess -> channel );
sess -> ignore_mode = TRUE ;
sess -> ignore_date = TRUE ;
}
2011-02-24 04:14:30 +01:00
}
static void
flagl_hit ( GtkWidget * wid , struct session * sess )
{
2026-01-15 18:35:27 -07:00
char modes [ 512 ];
const char * limit_str ;
server * serv = sess -> server ;
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( wid )))
{
if ( serv -> connected && sess -> channel [ 0 ])
{
limit_str = gtk_entry_get_text ( GTK_ENTRY ( sess -> gui -> limit_entry ));
if ( check_is_number (( char * ) limit_str ) == FALSE )
{
fe_message ( _ ( "User limit must be a number! \n " ), FE_MSG_ERROR );
gtk_entry_set_text ( GTK_ENTRY ( sess -> gui -> limit_entry ), "" );
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( wid ), FALSE );
return ;
}
g_snprintf ( modes , sizeof ( modes ), "+l %d" , atoi ( limit_str ));
serv -> p_mode ( serv , sess -> channel , modes );
serv -> p_join_info ( serv , sess -> channel );
}
} else
mg_change_flag ( wid , sess , 'l' );
2011-02-24 04:14:30 +01:00
}
static void
flagk_hit ( GtkWidget * wid , struct session * sess )
{
2026-01-15 18:35:27 -07:00
char modes [ 512 ];
server * serv = sess -> server ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( serv -> connected && sess -> channel [ 0 ])
{
g_snprintf ( modes , sizeof ( modes ), "-k %s" ,
gtk_entry_get_text ( GTK_ENTRY ( sess -> gui -> key_entry )));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( wid )))
modes [ 0 ] = '+' ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
serv -> p_mode ( serv , sess -> channel , modes );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_flagbutton_cb ( GtkWidget * but , char * flag )
{
2026-01-15 18:35:27 -07:00
session * sess ;
char mode ;
if ( ignore_chanmode )
return ;
sess = current_sess ;
mode = tolower (( unsigned char ) flag [ 0 ]);
switch ( mode )
{
case 'l' :
flagl_hit ( but , sess );
break ;
case 'k' :
flagk_hit ( but , sess );
break ;
case 'b' :
ignore_chanmode = TRUE ;
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( sess -> gui -> flag_b ), FALSE );
ignore_chanmode = FALSE ;
banlist_opengui ( sess );
break ;
default :
mg_change_flag ( but , sess , mode );
}
2011-02-24 04:14:30 +01:00
}
static GtkWidget *
mg_create_flagbutton ( char * tip , GtkWidget * box , char * face )
{
2026-01-15 18:35:27 -07:00
GtkWidget * btn , * lbl ;
char label_markup [ 16 ];
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
g_snprintf ( label_markup , sizeof ( label_markup ), "<tt>%s</tt>" , face );
lbl = gtk_label_new ( NULL );
gtk_label_set_markup ( GTK_LABEL ( lbl ), label_markup );
2015-04-21 15:35:15 -04:00
2026-01-15 18:35:27 -07:00
btn = gtk_toggle_button_new ();
2026-03-18 09:10:40 -06:00
gtk_widget_set_size_request ( btn , - 1 , 11 );
2026-01-15 18:35:27 -07:00
gtk_widget_set_tooltip_text ( btn , tip );
2026-03-18 09:10:40 -06:00
gtk_button_set_relief ( GTK_BUTTON ( btn ), GTK_RELIEF_NONE );
mg_apply_compact_mode_css ( btn );
2026-01-15 18:35:27 -07:00
gtk_container_add ( GTK_CONTAINER ( btn ), lbl );
2015-04-21 15:35:15 -04:00
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), btn , 0 , 0 , 0 );
g_signal_connect ( G_OBJECT ( btn ), "toggled" ,
G_CALLBACK ( mg_flagbutton_cb ), face );
show_and_unfocus ( btn );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return btn ;
2011-02-24 04:14:30 +01:00
}
static void
mg_key_entry_cb ( GtkWidget * igad , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
char modes [ 512 ];
session * sess = current_sess ;
server * serv = sess -> server ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( serv -> connected && sess -> channel [ 0 ])
{
g_snprintf ( modes , sizeof ( modes ), "+k %s" ,
gtk_entry_get_text ( GTK_ENTRY ( igad )));
serv -> p_mode ( serv , sess -> channel , modes );
serv -> p_join_info ( serv , sess -> channel );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_limit_entry_cb ( GtkWidget * igad , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
char modes [ 512 ];
session * sess = current_sess ;
server * serv = sess -> server ;
if ( serv -> connected && sess -> channel [ 0 ])
{
if ( check_is_number (( char * ) gtk_entry_get_text ( GTK_ENTRY ( igad ))) == FALSE )
{
gtk_entry_set_text ( GTK_ENTRY ( igad ), "" );
fe_message ( _ ( "User limit must be a number! \n " ), FE_MSG_ERROR );
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( sess -> gui -> flag_l ), FALSE );
return ;
}
g_snprintf ( modes , sizeof ( modes ), "+l %d" ,
atoi ( gtk_entry_get_text ( GTK_ENTRY ( igad ))));
serv -> p_mode ( serv , sess -> channel , modes );
serv -> p_join_info ( serv , sess -> channel );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_apply_entry_style ( GtkWidget * entry )
{
2026-03-02 19:42:48 -07:00
theme_manager_apply_entry_palette ( entry , input_style -> font_desc );
2011-02-24 04:14:30 +01:00
}
2026-03-21 02:20:34 -06:00
static void
mg_apply_entry_scroll_artifact_fix ( GtkWidget * entry )
{
GtkStyleContext * context ;
GtkCssProvider * provider ;
if ( ! entry || ! GTK_IS_ENTRY ( entry ))
return ;
context = gtk_widget_get_style_context ( entry );
if ( ! context )
return ;
provider = g_object_get_data ( G_OBJECT ( entry ), "mg-entry-scroll-artifact-provider" );
if ( ! provider )
{
provider = gtk_css_provider_new ();
g_object_set_data_full ( G_OBJECT ( entry ), "mg-entry-scroll-artifact-provider" , provider , g_object_unref );
gtk_css_provider_load_from_data ( provider ,
"entry.zoitechat-no-undershoot undershoot, \n "
"entry.zoitechat-no-undershoot undershoot.left, \n "
"entry.zoitechat-no-undershoot undershoot.right, \n "
".zoitechat-no-undershoot undershoot, \n "
".zoitechat-no-undershoot undershoot.left, \n "
".zoitechat-no-undershoot undershoot.right { \n "
" background-image: none; \n "
" background-color: transparent; \n "
" border: none; \n "
" box-shadow: none; \n "
"} \n " ,
- 1 , NULL );
}
gtk_style_context_add_class ( context , "zoitechat-no-undershoot" );
theme_css_apply_widget_provider ( entry , GTK_STYLE_PROVIDER ( provider ));
}
2026-03-14 17:15:59 -06:00
static gboolean
mg_entry_select_all ( GtkWidget * entry , GdkEventKey * event , gpointer userdata )
{
if (( event -> state & GDK_CONTROL_MASK ) &&
! ( event -> state & ( GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_META_MASK )) &&
( event -> keyval == GDK_KEY_a || event -> keyval == GDK_KEY_A ))
{
gtk_editable_select_region ( GTK_EDITABLE ( entry ), 0 , - 1 );
return TRUE ;
}
return FALSE ;
}
2011-02-24 04:14:30 +01:00
static void
mg_create_chanmodebuttons ( session_gui * gui , GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
gui -> flag_c = mg_create_flagbutton ( _ ( "Filter Colors" ), box , "c" );
gui -> flag_n = mg_create_flagbutton ( _ ( "No outside messages" ), box , "n" );
gui -> flag_t = mg_create_flagbutton ( _ ( "Topic Protection" ), box , "t" );
gui -> flag_i = mg_create_flagbutton ( _ ( "Invite Only" ), box , "i" );
gui -> flag_m = mg_create_flagbutton ( _ ( "Moderated" ), box , "m" );
gui -> flag_b = mg_create_flagbutton ( _ ( "Ban List" ), box , "b" );
gui -> flag_k = mg_create_flagbutton ( _ ( "Keyword" ), box , "k" );
gui -> key_entry = gtk_entry_new ();
gtk_widget_set_name ( gui -> key_entry , "zoitechat-inputbox" );
gtk_entry_set_max_length ( GTK_ENTRY ( gui -> key_entry ), 23 );
2026-06-20 04:09:45 -06:00
gtk_widget_set_size_request ( gui -> key_entry , 58 , 11 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), gui -> key_entry , 0 , 0 , 0 );
2026-09-10 07:13:41 -06:00
emoji_font_apply ( gui -> key_entry );
2026-03-18 09:10:40 -06:00
mg_apply_compact_mode_css ( gui -> key_entry );
2026-01-15 18:35:27 -07:00
g_signal_connect ( G_OBJECT ( gui -> key_entry ), "activate" ,
G_CALLBACK ( mg_key_entry_cb ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> key_entry ), "key-press-event" ,
2026-03-14 17:15:59 -06:00
G_CALLBACK ( mg_entry_select_all ), NULL );
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_input_style )
mg_apply_entry_style ( gui -> key_entry );
2026-03-21 02:20:34 -06:00
mg_apply_entry_scroll_artifact_fix ( gui -> key_entry );
2026-01-15 18:35:27 -07:00
gui -> flag_l = mg_create_flagbutton ( _ ( "User Limit" ), box , "l" );
gui -> limit_entry = gtk_entry_new ();
gtk_widget_set_name ( gui -> limit_entry , "zoitechat-inputbox" );
gtk_entry_set_max_length ( GTK_ENTRY ( gui -> limit_entry ), 10 );
2026-06-20 04:09:45 -06:00
gtk_entry_set_width_chars ( GTK_ENTRY ( gui -> limit_entry ), 5 );
gtk_widget_set_size_request ( gui -> limit_entry , 45 , 11 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), gui -> limit_entry , 0 , 0 , 0 );
2026-09-10 07:13:41 -06:00
emoji_font_apply ( gui -> limit_entry );
2026-03-18 09:10:40 -06:00
mg_apply_compact_mode_css ( gui -> limit_entry );
2026-01-15 18:35:27 -07:00
g_signal_connect ( G_OBJECT ( gui -> limit_entry ), "activate" ,
G_CALLBACK ( mg_limit_entry_cb ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> limit_entry ), "key-press-event" ,
2026-03-14 17:15:59 -06:00
G_CALLBACK ( mg_entry_select_all ), NULL );
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_input_style )
mg_apply_entry_style ( gui -> limit_entry );
2026-03-21 02:20:34 -06:00
mg_apply_entry_scroll_artifact_fix ( gui -> limit_entry );
2011-02-24 04:14:30 +01:00
}
/*static void
mg_create_link_buttons (GtkWidget *box, gpointer userdata)
{
2026-01-30 15:27:01 -07:00
gtkutil_button (box, ICON_TAB_CLOSE, _("Close this tab/window"),
2026-01-15 18:35:27 -07:00
mg_x_click_cb, userdata, 0);
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if (!userdata)
2026-01-30 15:27:01 -07:00
gtkutil_button (box, ICON_TAB_DETACH, _("Attach/Detach this tab"),
2026-01-15 18:35:27 -07:00
mg_link_cb, userdata, 0);
2011-02-24 04:14:30 +01:00
}*/
static void
mg_dialog_button_cb ( GtkWidget * wid , char * cmd )
{
2026-01-15 18:35:27 -07:00
/* the longest cmd is 12, and the longest nickname is 64 */
char buf [ 128 ];
char * host = "" ;
char * topic ;
2026-03-14 19:57:42 -06:00
char * topic_text ;
GtkTextBuffer * topic_buffer ;
GtkTextIter start ;
GtkTextIter end ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! current_sess )
return ;
2011-02-24 04:14:30 +01:00
2026-03-14 19:57:42 -06:00
topic_buffer = gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( current_sess -> gui -> topic_entry ));
gtk_text_buffer_get_bounds ( topic_buffer , & start , & end );
topic_text = gtk_text_buffer_get_text ( topic_buffer , & start , & end , FALSE );
topic = strrchr ( topic_text , '@' );
2026-01-15 18:35:27 -07:00
if ( topic )
host = topic + 1 ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
auto_insert ( buf , sizeof ( buf ), cmd , 0 , 0 , "" , "" , "" ,
server_get_network ( current_sess -> server , TRUE ), host , "" ,
current_sess -> channel , "" );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
handle_command ( current_sess , buf , TRUE );
2026-03-14 19:57:42 -06:00
g_free ( topic_text );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* dirty trick to avoid auto-selection */
SPELL_ENTRY_SET_EDITABLE ( current_sess -> gui -> input_box , FALSE );
gtk_widget_grab_focus ( current_sess -> gui -> input_box );
SPELL_ENTRY_SET_EDITABLE ( current_sess -> gui -> input_box , TRUE );
2011-02-24 04:14:30 +01:00
}
static void
mg_dialog_button ( GtkWidget * box , char * name , char * cmd )
{
2026-01-15 18:35:27 -07:00
GtkWidget * wid ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
wid = gtk_button_new_with_label ( name );
gtk_box_pack_start ( GTK_BOX ( box ), wid , FALSE , FALSE , 0 );
g_signal_connect ( G_OBJECT ( wid ), "clicked" ,
G_CALLBACK ( mg_dialog_button_cb ), cmd );
gtk_widget_set_size_request ( wid , - 1 , 0 );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_dialogbuttons ( GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
struct popup * pop ;
GSList * list = dlgbutton_list ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
while ( list )
{
pop = list -> data ;
if ( pop -> cmd [ 0 ])
mg_dialog_button ( box , pop -> name , pop -> cmd );
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
}
2026-03-29 17:48:56 -06:00
static void
2026-04-09 10:03:59 -06:00
mg_topicbar_update_height ( GtkWidget * topic )
{
2026-06-04 15:24:37 -06:00
GtkWidget * parent ;
GtkWidget * grandparent ;
2026-04-09 10:03:59 -06:00
GtkTextBuffer * buffer ;
GtkTextIter start ;
GtkTextIter end ;
2026-06-04 15:24:37 -06:00
GtkTextView * view ;
2026-04-09 10:03:59 -06:00
PangoLayout * layout ;
char * text ;
int width ;
int line_height ;
int line_count ;
int target_height ;
2026-06-04 15:24:37 -06:00
int margin_left ;
int margin_right ;
int margin_top ;
int margin_bottom ;
int old_height ;
2026-03-29 17:48:56 -06:00
PangoContext * context ;
PangoFontMetrics * metrics ;
2026-04-09 10:03:59 -06:00
if ( ! topic || ! GTK_IS_TEXT_VIEW ( topic ))
return ;
2026-06-04 15:24:37 -06:00
view = GTK_TEXT_VIEW ( topic );
parent = gtk_widget_get_parent ( topic );
grandparent = parent ? gtk_widget_get_parent ( parent ) : NULL ;
2026-04-09 10:03:59 -06:00
2026-06-04 15:24:37 -06:00
margin_left = gtk_text_view_get_left_margin ( view );
margin_right = gtk_text_view_get_right_margin ( view );
margin_top = gtk_text_view_get_top_margin ( view );
margin_bottom = gtk_text_view_get_bottom_margin ( view );
buffer = gtk_text_view_get_buffer ( view );
2026-04-09 10:03:59 -06:00
gtk_text_buffer_get_bounds ( buffer , & start , & end );
text = gtk_text_buffer_get_text ( buffer , & start , & end , FALSE );
layout = gtk_widget_create_pango_layout ( topic , text && text [ 0 ] ? text : " " );
g_free ( text );
2026-06-04 15:24:37 -06:00
width = gtk_widget_get_allocated_width ( topic );
if ( width <= 1 && parent )
width = gtk_widget_get_allocated_width ( parent );
width -= margin_left + margin_right ;
if ( width < 1 )
width = 1 ;
2026-06-20 04:09:45 -06:00
if ( prefs . hex_gui_topicbar_multiline && ! prefs . hex_gui_mode_buttons_inline )
{
pango_layout_set_width ( layout , width * PANGO_SCALE );
pango_layout_set_wrap ( layout , PANGO_WRAP_WORD_CHAR );
}
2026-03-29 17:48:56 -06:00
context = gtk_widget_get_pango_context ( topic );
metrics = pango_context_get_metrics ( context ,
pango_context_get_font_description ( context ),
pango_context_get_language ( context ));
line_height = PANGO_PIXELS ( pango_font_metrics_get_ascent ( metrics ) +
pango_font_metrics_get_descent ( metrics ));
pango_font_metrics_unref ( metrics );
if ( line_height <= 0 )
line_height = 16 ;
2026-06-04 15:24:37 -06:00
2026-06-20 04:09:45 -06:00
line_count = prefs . hex_gui_topicbar_multiline && ! prefs . hex_gui_mode_buttons_inline ?
pango_layout_get_line_count ( layout ) : 1 ;
2026-04-09 10:03:59 -06:00
if ( line_count <= 0 )
line_count = 1 ;
2026-06-04 15:24:37 -06:00
target_height = ( line_height * line_count ) + margin_top + margin_bottom ;
if ( target_height < line_height + margin_top + margin_bottom )
target_height = line_height + margin_top + margin_bottom ;
old_height = GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( topic ),
"mg-topicbar-target-height" ));
if ( old_height != target_height )
2026-04-09 10:03:59 -06:00
{
2026-06-04 15:24:37 -06:00
g_object_set_data ( G_OBJECT ( topic ), "mg-topicbar-target-height" ,
GINT_TO_POINTER ( target_height ));
gtk_widget_set_size_request ( topic , - 1 , target_height );
if ( parent && GTK_IS_SCROLLED_WINDOW ( parent ))
{
2026-07-05 23:54:49 -06:00
GtkScrolledWindow * scrolled = GTK_SCROLLED_WINDOW ( parent );
int max_height = gtk_scrolled_window_get_max_content_height ( scrolled );
if ( max_height != - 1 && target_height > max_height )
{
gtk_scrolled_window_set_max_content_height ( scrolled , target_height );
gtk_scrolled_window_set_min_content_height ( scrolled , target_height );
}
else
{
gtk_scrolled_window_set_min_content_height ( scrolled , target_height );
gtk_scrolled_window_set_max_content_height ( scrolled , target_height );
}
2026-06-04 15:24:37 -06:00
gtk_widget_set_size_request ( parent , - 1 , target_height );
}
2026-04-09 10:03:59 -06:00
}
2026-06-04 15:24:37 -06:00
gtk_widget_queue_resize ( topic );
if ( parent )
gtk_widget_queue_resize ( parent );
if ( grandparent )
gtk_widget_queue_resize ( grandparent );
2026-04-09 10:03:59 -06:00
gtk_widget_queue_draw ( topic );
g_object_unref ( layout );
}
2026-03-29 17:48:56 -06:00
2026-06-04 15:24:37 -06:00
static gboolean
mg_topicbar_relayout_idle_cb ( gpointer userdata )
{
GtkWidget * topic = GTK_WIDGET ( userdata );
g_object_set_data ( G_OBJECT ( topic ), "mg-topicbar-relayout-source" , NULL );
mg_topicbar_update_height ( topic );
g_object_unref ( topic );
return G_SOURCE_REMOVE ;
}
static void
mg_topicbar_queue_relayout ( GtkWidget * topic )
{
guint source_id ;
if ( ! topic || ! GTK_IS_TEXT_VIEW ( topic ))
return ;
if ( g_object_get_data ( G_OBJECT ( topic ), "mg-topicbar-relayout-source" ) != NULL )
return ;
source_id = g_idle_add_full ( G_PRIORITY_DEFAULT_IDLE ,
mg_topicbar_relayout_idle_cb ,
g_object_ref ( topic ),
NULL );
g_object_set_data ( G_OBJECT ( topic ), "mg-topicbar-relayout-source" ,
GUINT_TO_POINTER ( source_id ));
}
2026-04-09 10:03:59 -06:00
static void
mg_topicbar_buffer_changed_cb ( GtkTextBuffer * buffer , gpointer userdata )
{
( void ) buffer ;
2026-06-04 15:24:37 -06:00
mg_topicbar_queue_relayout ( GTK_WIDGET ( userdata ));
2026-04-09 10:03:59 -06:00
}
2026-03-29 17:48:56 -06:00
2026-04-09 10:03:59 -06:00
static void
mg_topicbar_size_allocate_cb ( GtkWidget * widget , GtkAllocation * allocation , gpointer userdata )
{
2026-06-04 15:24:37 -06:00
int old_width ;
2026-04-09 10:03:59 -06:00
( void ) userdata ;
2026-06-04 15:24:37 -06:00
old_width = GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT ( widget ),
"mg-topicbar-allocated-width" ));
if ( allocation -> width == old_width )
return ;
g_object_set_data ( G_OBJECT ( widget ), "mg-topicbar-allocated-width" ,
GINT_TO_POINTER ( allocation -> width ));
mg_topicbar_queue_relayout ( widget );
2026-03-29 17:48:56 -06:00
}
2026-03-30 10:33:43 -06:00
void
mg_apply_session_font_prefs ( session_gui * gui )
{
const PangoFontDescription * font = NULL ;
if ( ! gui )
return ;
if ( input_style )
font = input_style -> font_desc ;
if ( gui -> topic_entry )
{
theme_manager_apply_entry_palette ( gui -> topic_entry , font );
2026-04-09 10:03:59 -06:00
mg_topicbar_update_height ( gui -> topic_entry );
2026-03-30 10:33:43 -06:00
}
2026-09-10 07:13:41 -06:00
if ( gui -> input_box )
{
if ( prefs . hex_gui_input_style )
theme_manager_apply_entry_palette ( gui -> input_box , font );
emoji_font_apply ( gui -> input_box );
}
2026-03-30 10:33:43 -06:00
if ( gui -> chanview )
chanview_apply_theme ( gui -> chanview );
if ( gui -> user_tree )
theme_manager_apply_userlist_style ( gui -> user_tree ,
theme_manager_get_userlist_palette_behavior ( font ));
}
2011-02-24 04:14:30 +01:00
static void
mg_create_topicbar ( session * sess , GtkWidget * box )
{
2026-06-20 04:09:45 -06:00
GtkWidget * vbox , * hbox , * mode_hbox , * topic , * topic_scroll , * bbox ;
2026-03-18 09:10:40 -06:00
session_gui * gui = sess -> gui ;
2011-02-24 04:14:30 +01:00
2026-03-18 09:10:40 -06:00
gui -> topic_bar = vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 0 );
gtk_box_pack_start ( GTK_BOX ( box ), vbox , 0 , 0 , 0 );
hbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
gtk_box_pack_start ( GTK_BOX ( vbox ), hbox , 0 , 0 , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! gui -> is_tab )
sess -> res -> tab = NULL ;
2011-02-24 04:14:30 +01:00
2026-03-14 19:57:42 -06:00
gui -> topic_entry = topic = gtk_text_view_new ();
2026-03-29 17:48:56 -06:00
gtk_widget_set_name ( topic , "zoitechat-topicbox" );
2026-06-20 04:09:45 -06:00
gtk_text_view_set_wrap_mode ( GTK_TEXT_VIEW ( topic ),
prefs . hex_gui_topicbar_multiline && ! prefs . hex_gui_mode_buttons_inline ?
GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE );
2026-03-14 19:57:42 -06:00
gtk_text_view_set_left_margin ( GTK_TEXT_VIEW ( topic ), 4 );
gtk_text_view_set_right_margin ( GTK_TEXT_VIEW ( topic ), 4 );
2026-04-09 10:56:39 -06:00
gtk_text_view_set_top_margin ( GTK_TEXT_VIEW ( topic ), 4 );
gtk_text_view_set_bottom_margin ( GTK_TEXT_VIEW ( topic ), 4 );
2026-04-09 10:03:59 -06:00
gtk_text_view_set_pixels_above_lines ( GTK_TEXT_VIEW ( topic ), 0 );
gtk_text_view_set_pixels_below_lines ( GTK_TEXT_VIEW ( topic ), 0 );
gtk_text_view_set_pixels_inside_wrap ( GTK_TEXT_VIEW ( topic ), 0 );
2026-03-30 10:33:43 -06:00
theme_manager_apply_entry_palette ( topic , input_style ? input_style -> font_desc : NULL );
2026-04-09 10:03:59 -06:00
g_signal_connect ( gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( topic )), "changed" ,
G_CALLBACK ( mg_topicbar_buffer_changed_cb ), topic );
g_signal_connect ( G_OBJECT ( topic ), "size-allocate" ,
G_CALLBACK ( mg_topicbar_size_allocate_cb ), NULL );
2026-06-20 04:09:45 -06:00
topic_scroll = gtk_scrolled_window_new ( NULL , NULL );
gtk_widget_set_hexpand ( topic_scroll , TRUE );
gtk_widget_set_size_request ( topic_scroll , 1 , - 1 );
gtk_widget_set_size_request ( topic , 1 , - 1 );
gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW ( topic_scroll ),
GTK_POLICY_EXTERNAL , GTK_POLICY_NEVER );
gtk_scrolled_window_set_propagate_natural_width ( GTK_SCROLLED_WINDOW ( topic_scroll ), FALSE );
gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW ( topic_scroll ), GTK_SHADOW_NONE );
gtk_container_add ( GTK_CONTAINER ( topic_scroll ), topic );
2026-04-09 10:03:59 -06:00
mg_topicbar_update_height ( topic );
2026-06-20 04:09:45 -06:00
gtk_box_pack_start ( GTK_BOX ( hbox ), topic_scroll , TRUE , TRUE , 0 );
2026-03-14 19:57:42 -06:00
gtk_widget_add_events ( topic , GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK );
g_signal_connect ( G_OBJECT ( topic ), "key-press-event" ,
G_CALLBACK ( mg_topic_key_press_cb ), NULL );
g_signal_connect ( G_OBJECT ( topic ), "button-release-event" ,
G_CALLBACK ( mg_topic_button_release_cb ), NULL );
g_signal_connect ( G_OBJECT ( topic ), "motion-notify-event" ,
G_CALLBACK ( mg_topic_motion_cb ), NULL );
g_signal_connect ( G_OBJECT ( topic ), "leave-notify-event" ,
G_CALLBACK ( mg_topic_leave_cb ), NULL );
2011-02-24 04:14:30 +01:00
2026-03-18 09:10:40 -06:00
gui -> dialogbutton_box = bbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
gtk_box_pack_start ( GTK_BOX ( hbox ), bbox , 0 , 0 , 0 );
mg_create_dialogbuttons ( bbox );
mode_hbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-06-20 04:09:45 -06:00
if ( prefs . hex_gui_mode_buttons_inline )
gtk_box_pack_start ( GTK_BOX ( hbox ), mode_hbox , 0 , 0 , 0 );
else
gtk_box_pack_start ( GTK_BOX ( vbox ), mode_hbox , 0 , 0 , 0 );
2011-02-24 04:14:30 +01:00
2026-03-18 09:10:40 -06:00
gui -> topicbutton_box = bbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-06-20 04:09:45 -06:00
gtk_widget_set_valign ( bbox , GTK_ALIGN_CENTER );
2026-03-18 09:10:40 -06:00
gtk_box_pack_end ( GTK_BOX ( mode_hbox ), bbox , 0 , 0 , 0 );
mg_create_chanmodebuttons ( gui , bbox );
2011-02-24 04:14:30 +01:00
}
2026-07-16 08:48:33 -06:00
/* re-apply the multi-line topic and inline mode button preferences to an
* already built topic bar (see mg_create_topicbar () for the layout) */
static void
mg_update_topicbar_layout ( session_gui * gui )
{
GtkWidget * topic = gui -> topic_entry ;
GtkWidget * vbox = gui -> topic_bar ;
GtkWidget * topic_scroll ;
GtkWidget * hbox ;
GtkWidget * mode_hbox ;
GtkWidget * desired_parent ;
GtkWidget * current_parent ;
gboolean wrap ;
if ( ! topic || ! GTK_IS_TEXT_VIEW ( topic ) || ! vbox || ! gui -> topicbutton_box )
return ;
wrap = prefs . hex_gui_topicbar_multiline && ! prefs . hex_gui_mode_buttons_inline ;
gtk_text_view_set_wrap_mode ( GTK_TEXT_VIEW ( topic ),
wrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE );
topic_scroll = gtk_widget_get_parent ( topic );
hbox = topic_scroll ? gtk_widget_get_parent ( topic_scroll ) : NULL ;
mode_hbox = gtk_widget_get_parent ( gui -> topicbutton_box );
if ( mode_hbox && hbox )
{
desired_parent = prefs . hex_gui_mode_buttons_inline ? hbox : vbox ;
current_parent = gtk_widget_get_parent ( mode_hbox );
if ( current_parent && current_parent != desired_parent )
{
g_object_ref ( mode_hbox );
gtk_container_remove ( GTK_CONTAINER ( current_parent ), mode_hbox );
gtk_box_pack_start ( GTK_BOX ( desired_parent ), mode_hbox , 0 , 0 , 0 );
g_object_unref ( mode_hbox );
}
}
mg_topicbar_update_height ( topic );
}
2011-02-24 04:14:30 +01:00
/* check if a word is clickable */
static int
2013-01-02 14:50:26 -08:00
mg_word_check ( GtkWidget * xtext , char * word )
2011-02-24 04:14:30 +01:00
{
2026-01-15 18:35:27 -07:00
session * sess = current_sess ;
int ret ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
ret = url_check_word ( word );
if ( ret == 0 && sess -> type == SESS_DIALOG )
return WORD_DIALOG ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return ret ;
2011-02-24 04:14:30 +01:00
}
/* mouse click inside text area */
static void
mg_word_clicked ( GtkWidget * xtext , char * word , GdkEventButton * even )
{
2026-01-15 18:35:27 -07:00
session * sess = current_sess ;
int word_type = 0 , start , end ;
char * tmp ;
if ( word )
{
word_type = mg_word_check ( xtext , word );
url_last ( & start , & end );
}
if ( even -> button == 1 ) /* left button */
{
if ( word == NULL )
{
2026-07-27 16:42:29 -06:00
/* a click on a shown inline image opens the viewer */
textentry * img_ent = gtk_xtext_get_clicked_image_entry ( GTK_XTEXT ( xtext ));
if ( img_ent )
inline_image_show_viewer ( GTK_XTEXT ( xtext ), img_ent );
else
mg_focus ( sess );
2026-01-15 18:35:27 -07:00
return ;
}
if (( even -> state & 13 ) == prefs . hex_gui_url_mod )
{
switch ( word_type )
{
case WORD_URL :
case WORD_HOST6 :
case WORD_HOST :
word [ end ] = 0 ;
2026-07-27 16:42:29 -06:00
/* image links toggle an inline preview instead
of opening the browser */
if ( word_type == WORD_URL &&
prefs . hex_net_remote_media &&
inline_image_is_image_url ( word + start ))
{
textentry * ent = gtk_xtext_get_clicked_entry ( GTK_XTEXT ( xtext ));
if ( ent )
{
inline_image_toggle ( sess , GTK_XTEXT ( xtext ), ent , word + start );
break ;
}
}
2026-01-15 18:35:27 -07:00
fe_open_url ( word + start );
}
}
return ;
}
if ( even -> button == 2 )
{
if ( sess -> type == SESS_DIALOG )
menu_middlemenu ( sess , even );
else if ( even -> type == GDK_2BUTTON_PRESS )
userlist_select ( sess , word );
return ;
}
if ( word == NULL )
return ;
switch ( word_type )
{
case 0 :
case WORD_PATH :
menu_middlemenu ( sess , even );
break ;
case WORD_URL :
case WORD_HOST6 :
case WORD_HOST :
word [ end ] = 0 ;
word += start ;
2026-07-27 16:42:29 -06:00
menu_urlmenu_entry ( even , word , xtext ,
gtk_xtext_get_clicked_entry ( GTK_XTEXT ( xtext )));
2026-01-15 18:35:27 -07:00
break ;
case WORD_NICK :
word [ end ] = 0 ;
word += start ;
menu_nickmenu ( sess , even , word , FALSE );
break ;
case WORD_CHANNEL :
word [ end ] = 0 ;
word += start ;
menu_chanmenu ( sess , even , word );
break ;
case WORD_EMAIL :
word [ end ] = 0 ;
word += start ;
tmp = g_strdup_printf ( "mailto:%s" , word + ( ispunct ( * word ) ? 1 : 0 ));
menu_urlmenu ( even , tmp );
g_free ( tmp );
break ;
case WORD_DIALOG :
menu_nickmenu ( sess , even , sess -> channel , FALSE );
break ;
}
2011-02-24 04:14:30 +01:00
}
void
mg_update_xtext ( GtkWidget * wid )
{
2026-01-15 18:35:27 -07:00
GtkXText * xtext = GTK_XTEXT ( wid );
2026-01-15 19:58:12 -07:00
const gchar * font_name ;
2026-01-17 21:39:45 -07:00
XTextColor xtext_palette [ XTEXT_COLS ];
2011-02-24 04:14:30 +01:00
2026-03-04 23:28:01 -07:00
theme_get_xtext_colors_for_widget ( wid , xtext_palette , XTEXT_COLS );
2026-01-17 21:39:45 -07:00
gtk_xtext_set_palette ( xtext , xtext_palette );
2026-01-15 18:35:27 -07:00
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 );
gtk_xtext_set_show_marker ( xtext , prefs . hex_text_show_marker );
gtk_xtext_set_show_separator ( xtext , prefs . hex_text_indent ? prefs . hex_text_show_sep : 0 );
gtk_xtext_set_indent ( xtext , prefs . hex_text_indent );
2011-02-24 04:14:30 +01:00
2026-03-11 01:49:14 -06:00
font_name = * prefs . hex_text_font
2026-01-15 19:58:12 -07:00
? prefs . hex_text_font
: "Sans 10" ;
if ( ! gtk_xtext_set_font ( xtext , ( char * ) font_name ))
2026-01-15 18:35:27 -07:00
{
fe_message ( "Failed to open any font. I'm out of here!" , FE_MSG_WAIT | FE_MSG_ERROR );
exit ( 1 );
}
gtk_xtext_refresh ( xtext );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_textarea ( session * sess , GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
GtkWidget * inbox , * vbox , * frame ;
GtkXText * xtext ;
2026-01-17 21:39:45 -07:00
XTextColor xtext_palette [ XTEXT_COLS ];
2026-01-15 18:35:27 -07:00
session_gui * gui = sess -> gui ;
static const GtkTargetEntry dnd_targets [] =
{
{ "text/uri-list" , 0 , 1 }
};
static const GtkTargetEntry dnd_dest_targets [] =
{
2026-01-25 16:13:47 -07:00
{ "ZOITECHAT_CHANVIEW" , GTK_TARGET_SAME_APP , 75 },
{ "ZOITECHAT_USERLIST" , GTK_TARGET_SAME_APP , 75 }
2026-01-15 18:35:27 -07:00
};
2026-01-19 20:11:07 -07:00
vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 0 );
2026-02-01 14:04:56 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), vbox , TRUE , TRUE , 0 );
2026-01-19 20:11:07 -07:00
inbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 2 );
2026-02-01 14:04:56 -07:00
gtk_box_pack_start ( GTK_BOX ( vbox ), inbox , TRUE , TRUE , 0 );
2026-01-15 18:35:27 -07:00
2026-05-07 13:29:22 -06:00
frame = gtk_scrolled_window_new ( NULL , NULL );
gtk_widget_set_hexpand ( frame , TRUE );
gtk_widget_set_vexpand ( frame , TRUE );
gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW ( frame ),
GTK_SHADOW_IN );
gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW ( frame ),
GTK_POLICY_NEVER , GTK_POLICY_AUTOMATIC );
2026-02-01 14:04:56 -07:00
gtk_box_pack_start ( GTK_BOX ( inbox ), frame , TRUE , TRUE , 0 );
2026-01-15 18:35:27 -07:00
2026-03-04 23:28:01 -07:00
theme_get_xtext_colors_for_widget ( frame , xtext_palette , XTEXT_COLS );
2026-01-17 21:39:45 -07:00
gui -> xtext = gtk_xtext_new ( xtext_palette , TRUE );
2026-01-15 18:35:27 -07:00
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 );
gtk_xtext_set_urlcheck_function ( xtext , mg_word_check );
gtk_xtext_set_max_lines ( xtext , prefs . hex_text_max_lines );
gtk_container_add ( GTK_CONTAINER ( frame ), GTK_WIDGET ( xtext ));
mg_update_xtext ( GTK_WIDGET ( xtext ));
g_signal_connect ( G_OBJECT ( xtext ), "word_click" ,
G_CALLBACK ( mg_word_clicked ), NULL );
2026-05-07 13:29:22 -06:00
gui -> vscrollbar = gtk_scrolled_window_get_vscrollbar ( GTK_SCROLLED_WINDOW ( frame ));
2026-01-15 18:35:27 -07:00
gtk_drag_dest_set ( gui -> vscrollbar , 5 , dnd_dest_targets , 2 ,
GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> vscrollbar ), "drag-begin" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_drag_begin_cb ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> vscrollbar ), "drag-drop" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_drag_drop_cb ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> vscrollbar ), "drag-motion" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_drag_motion_cb ), gui -> vscrollbar );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> vscrollbar ), "drag-end" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_drag_end_cb ), NULL );
gtk_drag_dest_set ( gui -> xtext , GTK_DEST_DEFAULT_ALL , dnd_targets , 1 ,
GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( gui -> xtext ), "drag-data-received" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_dialog_dnd_drop ), NULL );
2011-02-24 04:14:30 +01:00
}
static GtkWidget *
mg_create_infoframe ( GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
GtkWidget * frame , * label , * hbox ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
frame = gtk_frame_new ( 0 );
gtk_frame_set_shadow_type (( GtkFrame * ) frame , GTK_SHADOW_OUT );
2026-06-02 14:49:29 -06:00
gtk_box_pack_start ( GTK_BOX ( box ), frame , TRUE , TRUE , 0 );
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
hbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-01-15 18:35:27 -07:00
gtk_container_add ( GTK_CONTAINER ( frame ), hbox );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
label = gtk_label_new ( NULL );
2026-02-01 14:04:56 -07:00
gtk_box_pack_start ( GTK_BOX ( hbox ), label , TRUE , TRUE , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return label ;
2011-02-24 04:14:30 +01:00
}
static void
mg_create_meters ( session_gui * gui , GtkWidget * parent_box )
{
2026-01-15 18:35:27 -07:00
GtkWidget * infbox , * wid , * box ;
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
gui -> meter_box = infbox = box = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 1 );
2026-05-11 01:35:57 -06:00
gtk_box_pack_end ( GTK_BOX ( parent_box ), box , 0 , 0 , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if (( prefs . hex_gui_lagometer & 2 ) || ( prefs . hex_gui_throttlemeter & 2 ))
{
2026-06-02 14:49:29 -06:00
infbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , TRUE , 0 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), infbox , 0 , 0 , 0 );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_lagometer & 1 )
{
gui -> lagometer = wid = gtk_progress_bar_new ();
2011-02-24 04:14:30 +01:00
#ifdef WIN32
2026-01-15 18:35:27 -07:00
gtk_widget_set_size_request ( wid , 1 , 10 );
2011-02-24 04:14:30 +01:00
#else
2026-01-15 18:35:27 -07:00
gtk_widget_set_size_request ( wid , 1 , 8 );
2011-02-24 04:14:30 +01:00
#endif
2026-01-15 18:35:27 -07:00
wid = gtk_event_box_new ();
gtk_container_add ( GTK_CONTAINER ( wid ), gui -> lagometer );
gtk_box_pack_start ( GTK_BOX ( box ), wid , 0 , 0 , 0 );
}
if ( prefs . hex_gui_lagometer & 2 )
{
gui -> laginfo = wid = mg_create_infoframe ( infbox );
gtk_label_set_text (( GtkLabel * ) wid , "Lag" );
}
if ( prefs . hex_gui_throttlemeter & 1 )
{
gui -> throttlemeter = wid = gtk_progress_bar_new ();
2011-02-24 04:14:30 +01:00
#ifdef WIN32
2026-01-15 18:35:27 -07:00
gtk_widget_set_size_request ( wid , 1 , 10 );
2011-02-24 04:14:30 +01:00
#else
2026-01-15 18:35:27 -07:00
gtk_widget_set_size_request ( wid , 1 , 8 );
2011-02-24 04:14:30 +01:00
#endif
2026-01-15 18:35:27 -07:00
wid = gtk_event_box_new ();
gtk_container_add ( GTK_CONTAINER ( wid ), gui -> throttlemeter );
gtk_box_pack_start ( GTK_BOX ( box ), wid , 0 , 0 , 0 );
}
if ( prefs . hex_gui_throttlemeter & 2 )
{
gui -> throttleinfo = wid = mg_create_infoframe ( infbox );
gtk_label_set_text (( GtkLabel * ) wid , "Throttle" );
}
2011-02-24 04:14:30 +01:00
}
void
mg_update_meters ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( gui -> meter_box );
gui -> lagometer = NULL ;
gui -> laginfo = NULL ;
gui -> throttlemeter = NULL ;
gui -> throttleinfo = NULL ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_create_meters ( gui , gui -> button_box_parent );
gtk_widget_show_all ( gui -> meter_box );
2011-02-24 04:14:30 +01:00
}
2026-03-02 19:42:48 -07:00
static void
mg_theme_apply_userlist_style ( session_gui * gui )
{
const PangoFontDescription * font = NULL ;
if ( ! gui || ! gui -> user_tree )
return ;
if ( input_style )
font = input_style -> font_desc ;
theme_manager_apply_userlist_style ( gui -> user_tree ,
theme_manager_get_userlist_palette_behavior ( font ));
}
static void
mg_theme_userlist_changed ( const ThemeChangedEvent * event , gpointer userdata )
{
session_gui * gui = userdata ;
if ( ! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_USERLIST ) &&
! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_PALETTE ) &&
! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_WIDGET_STYLE ) &&
! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_MODE ) &&
! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_THEME_PACK ))
return ;
mg_theme_apply_userlist_style ( gui );
}
2026-04-27 15:36:22 -06:00
static void
mg_theme_refresh_menu_widget ( GtkWidget * widget )
{
GtkRequisition minimum ;
GtkRequisition natural ;
if ( ! widget )
return ;
gtk_widget_queue_resize ( widget );
gtk_widget_get_preferred_size ( widget , & minimum , & natural );
}
static void
mg_theme_refresh_menu_tree ( GtkWidget * menu )
{
GList * children ;
GList * node ;
if ( ! menu || ! GTK_IS_MENU_SHELL ( menu ))
return ;
children = gtk_container_get_children ( GTK_CONTAINER ( menu ));
for ( node = children ; node ; node = node -> next )
{
GtkWidget * item = GTK_WIDGET ( node -> data );
GtkWidget * submenu = NULL ;
if ( GTK_IS_MENU_ITEM ( item ))
submenu = gtk_menu_item_get_submenu ( GTK_MENU_ITEM ( item ));
if ( submenu )
mg_theme_refresh_menu_tree ( submenu );
mg_theme_refresh_menu_widget ( item );
}
g_list_free ( children );
mg_theme_refresh_menu_widget ( menu );
}
2026-03-02 19:42:48 -07:00
static void
mg_theme_window_changed ( const ThemeChangedEvent * event , gpointer userdata )
{
session_gui * gui = userdata ;
if ( ! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_MODE ) &&
! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_THEME_PACK ) &&
! theme_changed_event_has_reason ( event , THEME_CHANGED_REASON_WIDGET_STYLE ))
return ;
2026-04-27 15:36:22 -06:00
if ( ! gui )
return ;
theme_manager_apply_to_window ( gui -> window );
mg_theme_refresh_menu_tree ( gui -> menu );
2026-03-02 19:42:48 -07:00
}
static void
mg_theme_userlist_destroy_cb ( GtkWidget * widget , gpointer userdata )
{
session_gui * gui = userdata ;
( void ) widget ;
if ( ! gui )
return ;
if ( gui -> theme_userlist_listener_id )
{
theme_listener_unregister ( gui -> theme_userlist_listener_id );
gui -> theme_userlist_listener_id = 0 ;
}
}
static void
mg_theme_window_destroy_cb ( GtkWidget * widget , gpointer userdata )
{
session_gui * gui = userdata ;
( void ) widget ;
if ( ! gui )
return ;
theme_manager_detach_window ( gui -> window );
if ( gui -> theme_window_listener_id )
{
theme_listener_unregister ( gui -> theme_window_listener_id );
gui -> theme_window_listener_id = 0 ;
}
}
2011-02-24 04:14:30 +01:00
static void
mg_create_userlist ( session_gui * gui , GtkWidget * box )
{
2026-03-18 09:06:10 -06:00
GtkWidget * ulist , * vbox ;
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 1 );
2026-02-01 14:04:56 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), vbox , TRUE , TRUE , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> namelistinfo = gtk_label_new ( NULL );
2026-05-09 12:53:35 -06:00
gtk_label_set_xalign ( GTK_LABEL ( gui -> namelistinfo ), 0.5f );
gtk_label_set_justify ( GTK_LABEL ( gui -> namelistinfo ), GTK_JUSTIFY_CENTER );
gtk_label_set_ellipsize ( GTK_LABEL ( gui -> namelistinfo ), PANGO_ELLIPSIZE_END );
gtk_label_set_width_chars ( GTK_LABEL ( gui -> namelistinfo ), 1 );
gtk_widget_set_margin_start ( gui -> namelistinfo , 0 );
gtk_widget_set_margin_end ( gui -> namelistinfo , 0 );
gtk_widget_set_hexpand ( gui -> namelistinfo , TRUE );
gtk_widget_set_halign ( gui -> namelistinfo , GTK_ALIGN_FILL );
2026-07-16 08:48:33 -06:00
/* always pack it so the preference can be toggled without a
restart; visibility is controlled explicitly */
gtk_widget_set_no_show_all ( gui -> namelistinfo , TRUE );
gtk_box_pack_start ( GTK_BOX ( vbox ), gui -> namelistinfo , 0 , 0 , 0 );
2026-03-18 09:06:10 -06:00
if ( prefs . hex_gui_ulist_count )
2026-07-16 08:48:33 -06:00
gtk_widget_show ( gui -> namelistinfo );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> user_tree = ulist = userlist_create ( vbox );
2011-02-24 04:14:30 +01:00
2026-03-02 19:42:48 -07:00
if ( ! gui -> theme_userlist_listener_id )
gui -> theme_userlist_listener_id = theme_listener_register ( "maingui.userlist" , mg_theme_userlist_changed , gui );
g_signal_connect ( G_OBJECT ( ulist ), "destroy" , G_CALLBACK ( mg_theme_userlist_destroy_cb ), gui );
mg_theme_apply_userlist_style ( gui );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_create_meters ( gui , vbox );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> button_box_parent = vbox ;
gui -> button_box = mg_create_userlistbuttons ( vbox );
2011-02-24 04:14:30 +01:00
}
2012-11-30 17:22:12 -05:00
static void
mg_vpane_cb ( GtkPaned * pane , GParamSpec * param , session_gui * gui )
{
2026-01-15 18:35:27 -07:00
prefs . hex_gui_pane_divider_position = gtk_paned_get_position ( pane );
2012-11-30 17:22:12 -05:00
}
2011-02-24 04:14:30 +01:00
static void
mg_leftpane_cb ( GtkPaned * pane , GParamSpec * param , session_gui * gui )
{
2026-01-15 18:35:27 -07:00
prefs . hex_gui_pane_left_size = gtk_paned_get_position ( pane );
2011-02-24 04:14:30 +01:00
}
static void
mg_rightpane_cb ( GtkPaned * pane , GParamSpec * param , session_gui * gui )
{
2026-01-15 18:35:27 -07:00
int handle_size ;
GtkAllocation allocation ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_style_get ( GTK_WIDGET ( pane ), "handle-size" , & handle_size , NULL );
/* record the position from the RIGHT side */
gtk_widget_get_allocation ( GTK_WIDGET ( pane ), & allocation );
prefs . hex_gui_pane_right_size = allocation . width - gtk_paned_get_position ( pane ) - handle_size ;
2011-02-24 04:14:30 +01:00
}
2026-03-20 08:33:16 -06:00
static void
mg_restore_rightpane_cb ( GtkWidget * widget , GtkAllocation * allocation , gpointer data )
{
int handle_size ;
int saved_size ;
/* only restore once, then disconnect */
g_signal_handlers_disconnect_by_func ( widget , mg_restore_rightpane_cb , data );
/* use the value captured at connect time, since notify::position may
* have already overwritten prefs.hex_gui_pane_right_size during the
* initial layout pass */
saved_size = GPOINTER_TO_INT ( data );
if ( saved_size < 1 )
return ;
gtk_widget_style_get ( widget , "handle-size" , & handle_size , NULL );
gtk_paned_set_position ( GTK_PANED ( widget ), allocation -> width - saved_size - handle_size );
}
2011-02-24 04:14:30 +01:00
static gboolean
mg_add_pane_signals ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
g_signal_connect ( G_OBJECT ( gui -> hpane_right ), "notify::position" ,
G_CALLBACK ( mg_rightpane_cb ), gui );
g_signal_connect ( G_OBJECT ( gui -> hpane_left ), "notify::position" ,
G_CALLBACK ( mg_leftpane_cb ), gui );
g_signal_connect ( G_OBJECT ( gui -> vpane_left ), "notify::position" ,
G_CALLBACK ( mg_vpane_cb ), gui );
g_signal_connect ( G_OBJECT ( gui -> vpane_right ), "notify::position" ,
G_CALLBACK ( mg_vpane_cb ), gui );
return FALSE ;
2011-02-24 04:14:30 +01:00
}
static void
mg_create_center ( session * sess , session_gui * gui , GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
GtkWidget * vbox , * hbox , * book ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* sep between top and bottom of left side */
2026-01-30 17:44:30 -07:00
gui -> vpane_left = gtk_paned_new ( GTK_ORIENTATION_VERTICAL );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* sep between top and bottom of right side */
2026-01-30 17:44:30 -07:00
gui -> vpane_right = gtk_paned_new ( GTK_ORIENTATION_VERTICAL );
2011-02-24 04:14:30 +01:00
2026-01-30 19:02:51 -07:00
/* sep between left and xtext */
gui -> hpane_left = gtk_paned_new ( GTK_ORIENTATION_HORIZONTAL );
2026-02-15 22:31:45 -07:00
gtk_paned_set_wide_handle ( GTK_PANED ( gui -> hpane_left ), FALSE );
2026-01-30 19:02:51 -07:00
gtk_paned_set_position ( GTK_PANED ( gui -> hpane_left ), prefs . hex_gui_pane_left_size );
2011-02-24 04:14:30 +01:00
2026-01-30 19:02:51 -07:00
/* sep between xtext and right side */
gui -> hpane_right = gtk_paned_new ( GTK_ORIENTATION_HORIZONTAL );
2011-02-24 04:14:30 +01:00
2026-03-20 08:33:16 -06:00
/* restore right pane position after first allocation (needs widget width).
* capture the saved size now because notify::position will overwrite the
* pref during the initial layout before size-allocate fires. */
g_signal_connect ( gui -> hpane_right , "size-allocate" ,
G_CALLBACK ( mg_restore_rightpane_cb ),
GINT_TO_POINTER ( prefs . hex_gui_pane_right_size ));
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_win_swap )
{
2026-03-14 19:43:01 -06:00
gtk_paned_pack2 ( GTK_PANED ( gui -> hpane_left ), gui -> vpane_left , FALSE , FALSE );
2026-02-16 02:01:21 -07:00
gtk_paned_pack1 ( GTK_PANED ( gui -> hpane_left ), gui -> hpane_right , TRUE , TRUE );
2026-01-15 18:35:27 -07:00
}
else
{
2026-03-14 19:43:01 -06:00
gtk_paned_pack1 ( GTK_PANED ( gui -> hpane_left ), gui -> vpane_left , FALSE , FALSE );
2026-02-16 02:01:21 -07:00
gtk_paned_pack2 ( GTK_PANED ( gui -> hpane_left ), gui -> hpane_right , TRUE , TRUE );
2026-01-15 18:35:27 -07:00
}
2026-03-15 18:21:01 -06:00
gtk_paned_pack2 ( GTK_PANED ( gui -> hpane_right ), gui -> vpane_right , FALSE , TRUE );
2011-02-24 04:14:30 +01:00
2026-02-01 14:04:56 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), gui -> hpane_left , TRUE , TRUE , 0 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> note_book = book = gtk_notebook_new ();
gtk_notebook_set_show_tabs ( GTK_NOTEBOOK ( book ), FALSE );
gtk_notebook_set_show_border ( GTK_NOTEBOOK ( book ), FALSE );
gtk_paned_pack1 ( GTK_PANED ( gui -> hpane_right ), book , TRUE , TRUE );
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
hbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-01-15 18:35:27 -07:00
gtk_paned_pack1 ( GTK_PANED ( gui -> vpane_right ), hbox , FALSE , TRUE );
mg_create_userlist ( gui , hbox );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> user_box = hbox ;
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 3 );
2026-01-15 18:35:27 -07:00
gtk_notebook_append_page ( GTK_NOTEBOOK ( book ), vbox , NULL );
mg_create_topicbar ( sess , vbox );
2013-07-24 20:47:01 -04:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_search_pos )
{
mg_create_search ( sess , vbox );
mg_create_textarea ( sess , vbox );
}
else
{
mg_create_textarea ( sess , vbox );
mg_create_search ( sess , vbox );
}
2013-07-24 20:47:01 -04:00
2026-01-15 18:35:27 -07:00
mg_create_entry ( sess , vbox );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_add_pane_signals ( gui );
2011-02-24 04:14:30 +01:00
}
static void
mg_change_nick ( int cancel , char * text , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
char buf [ 256 ];
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! cancel )
{
g_snprintf ( buf , sizeof ( buf ), "nick %s" , text );
handle_command ( current_sess , buf , FALSE );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_nickclick_cb ( GtkWidget * button , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
fe_get_str ( _ ( "Enter new nickname:" ), current_sess -> server -> nick ,
mg_change_nick , ( void * ) 1 );
2011-02-24 04:14:30 +01:00
}
/* make sure chanview and userlist positions are sane */
static void
mg_sanitize_positions ( int * cv , int * ul )
{
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_tab_layout == 2 )
{
/* treeview can't be on TOP or BOTTOM */
if ( * cv == POS_TOP || * cv == POS_BOTTOM )
* cv = POS_TOPLEFT ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* userlist can't be on TOP or BOTTOM */
if ( * ul == POS_TOP || * ul == POS_BOTTOM )
* ul = POS_TOPRIGHT ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* can't have both in the same place */
if ( * cv == * ul )
{
* cv = POS_TOPRIGHT ;
if ( * ul == POS_TOPRIGHT )
* cv = POS_BOTTOMRIGHT ;
}
2011-02-24 04:14:30 +01:00
}
static void
mg_place_userlist_and_chanview_real ( session_gui * gui , GtkWidget * userlist , GtkWidget * chanview )
{
2026-01-15 18:35:27 -07:00
int unref_userlist = FALSE ;
int unref_chanview = FALSE ;
/* first, remove userlist/treeview from their containers */
if ( userlist && gtk_widget_get_parent ( userlist ))
{
g_object_ref ( userlist );
gtk_container_remove ( GTK_CONTAINER ( gtk_widget_get_parent ( userlist )), userlist );
unref_userlist = TRUE ;
}
if ( chanview && gtk_widget_get_parent ( chanview ))
{
g_object_ref ( chanview );
gtk_container_remove ( GTK_CONTAINER ( gtk_widget_get_parent ( chanview )), chanview );
unref_chanview = TRUE ;
}
if ( chanview )
{
/* incase the previous pos was POS_HIDDEN */
gtk_widget_show ( chanview );
2026-01-19 19:26:29 -07:00
gtk_widget_set_margin_top ( chanview , 0 );
gtk_widget_set_margin_bottom ( chanview , 0 );
2026-01-15 18:35:27 -07:00
/* then place them back in their new positions */
switch ( prefs . hex_gui_tab_pos )
{
case POS_TOPLEFT :
gtk_paned_pack1 ( GTK_PANED ( gui -> vpane_left ), chanview , FALSE , TRUE );
break ;
case POS_BOTTOMLEFT :
gtk_paned_pack2 ( GTK_PANED ( gui -> vpane_left ), chanview , FALSE , TRUE );
break ;
case POS_TOPRIGHT :
gtk_paned_pack1 ( GTK_PANED ( gui -> vpane_right ), chanview , FALSE , TRUE );
break ;
case POS_BOTTOMRIGHT :
gtk_paned_pack2 ( GTK_PANED ( gui -> vpane_right ), chanview , FALSE , TRUE );
break ;
case POS_TOP :
2026-01-19 19:26:29 -07:00
gtk_widget_set_margin_bottom ( chanview , GUI_SPACING - 1 );
2026-01-30 16:04:50 -07:00
gtk_widget_set_hexpand ( chanview , FALSE );
gtk_widget_set_vexpand ( chanview , FALSE );
gtk_widget_set_halign ( chanview , GTK_ALIGN_FILL );
gtk_widget_set_valign ( chanview , GTK_ALIGN_FILL );
2026-01-19 19:26:29 -07:00
gtk_grid_attach ( GTK_GRID ( gui -> main_table ), chanview ,
1 , 1 , 1 , 1 );
2026-01-15 18:35:27 -07:00
break ;
case POS_HIDDEN :
gtk_widget_hide ( chanview );
/* always attach it to something to avoid ref_count=0 */
if ( prefs . hex_gui_ulist_pos == POS_TOP )
2026-01-30 16:14:35 -07:00
{
2026-01-30 16:04:50 -07:00
gtk_widget_set_hexpand ( chanview , FALSE );
gtk_widget_set_vexpand ( chanview , FALSE );
gtk_widget_set_halign ( chanview , GTK_ALIGN_FILL );
gtk_widget_set_valign ( chanview , GTK_ALIGN_FILL );
2026-01-19 19:26:29 -07:00
gtk_grid_attach ( GTK_GRID ( gui -> main_table ), chanview ,
1 , 3 , 1 , 1 );
2026-01-30 16:14:35 -07:00
}
2026-01-15 18:35:27 -07:00
else
2026-01-30 16:14:35 -07:00
{
2026-01-30 16:04:50 -07:00
gtk_widget_set_hexpand ( chanview , FALSE );
gtk_widget_set_vexpand ( chanview , FALSE );
gtk_widget_set_halign ( chanview , GTK_ALIGN_FILL );
gtk_widget_set_valign ( chanview , GTK_ALIGN_FILL );
2026-01-19 19:26:29 -07:00
gtk_grid_attach ( GTK_GRID ( gui -> main_table ), chanview ,
1 , 1 , 1 , 1 );
2026-01-30 16:14:35 -07:00
}
2026-01-15 18:35:27 -07:00
break ;
default : /* POS_BOTTOM */
2026-01-19 19:26:29 -07:00
gtk_widget_set_margin_top ( chanview , 3 );
2026-01-30 16:04:50 -07:00
gtk_widget_set_hexpand ( chanview , FALSE );
gtk_widget_set_vexpand ( chanview , FALSE );
gtk_widget_set_halign ( chanview , GTK_ALIGN_FILL );
gtk_widget_set_valign ( chanview , GTK_ALIGN_FILL );
2026-01-19 19:26:29 -07:00
gtk_grid_attach ( GTK_GRID ( gui -> main_table ), chanview ,
1 , 3 , 1 , 1 );
2026-01-15 18:35:27 -07:00
}
}
if ( userlist )
{
switch ( prefs . hex_gui_ulist_pos )
{
case POS_TOPLEFT :
gtk_paned_pack1 ( GTK_PANED ( gui -> vpane_left ), userlist , FALSE , TRUE );
break ;
case POS_BOTTOMLEFT :
gtk_paned_pack2 ( GTK_PANED ( gui -> vpane_left ), userlist , FALSE , TRUE );
break ;
case POS_BOTTOMRIGHT :
gtk_paned_pack2 ( GTK_PANED ( gui -> vpane_right ), userlist , FALSE , TRUE );
break ;
/*case POS_HIDDEN:
break;*/ /* Hide using the VIEW menu instead */
default : /* POS_TOPRIGHT */
gtk_paned_pack1 ( GTK_PANED ( gui -> vpane_right ), userlist , FALSE , TRUE );
}
}
if ( mg_is_userlist_and_tree_combined () && prefs . hex_gui_pane_divider_position != 0 )
{
gtk_paned_set_position ( GTK_PANED ( gui -> vpane_left ), prefs . hex_gui_pane_divider_position );
gtk_paned_set_position ( GTK_PANED ( gui -> vpane_right ), prefs . hex_gui_pane_divider_position );
}
if ( unref_chanview )
g_object_unref ( chanview );
if ( unref_userlist )
g_object_unref ( userlist );
mg_hide_empty_boxes ( gui );
2011-02-24 04:14:30 +01:00
}
static void
mg_place_userlist_and_chanview ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
GtkOrientation orientation ;
GtkWidget * chanviewbox = NULL ;
int pos ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_sanitize_positions ( & prefs . hex_gui_tab_pos , & prefs . hex_gui_ulist_pos );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( gui -> chanview )
{
pos = prefs . hex_gui_tab_pos ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
orientation = chanview_get_orientation ( gui -> chanview );
if (( pos == POS_BOTTOM || pos == POS_TOP ) && orientation == GTK_ORIENTATION_VERTICAL )
chanview_set_orientation ( gui -> chanview , FALSE );
else if (( pos == POS_TOPLEFT || pos == POS_BOTTOMLEFT || pos == POS_TOPRIGHT || pos == POS_BOTTOMRIGHT ) && orientation == GTK_ORIENTATION_HORIZONTAL )
chanview_set_orientation ( gui -> chanview , TRUE );
chanviewbox = chanview_get_box ( gui -> chanview );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_place_userlist_and_chanview_real ( gui , gui -> user_box , chanviewbox );
2011-02-24 04:14:30 +01:00
}
void
mg_change_layout ( int type )
{
2026-01-15 18:35:27 -07:00
if ( mg_gui )
{
/* put tabs at the bottom */
if ( type == 0 && prefs . hex_gui_tab_pos != POS_BOTTOM && prefs . hex_gui_tab_pos != POS_TOP )
prefs . hex_gui_tab_pos = POS_BOTTOM ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_place_userlist_and_chanview ( mg_gui );
chanview_set_impl ( mg_gui -> chanview , type );
}
2011-02-24 04:14:30 +01:00
}
static void
mg_inputbox_rightclick ( GtkEntry * entry , GtkWidget * menu )
{
2026-01-15 18:35:27 -07:00
mg_create_color_menu ( menu , NULL );
}
2013-07-24 20:47:01 -04:00
/* Search bar adapted from Conspire's by William Pitcock */
2026-01-15 18:35:27 -07:00
#define SEARCH_CHANGE 1
#define SEARCH_NEXT 2
#define SEARCH_PREVIOUS 3
#define SEARCH_REFRESH 4
2013-07-24 20:47:01 -04:00
2026-03-27 15:27:31 -06:00
static session *
search_find_channel ( session * sess , const gchar * text )
{
GSList * list ;
session * item ;
if ( ! text || ! text [ 0 ])
return NULL ;
list = sess_list ;
while ( list )
{
item = list -> data ;
if ( item -> server == sess -> server &&
item -> type == SESS_CHANNEL &&
nocasestrstr ( item -> channel , text ))
return item ;
list = list -> next ;
}
return NULL ;
}
2013-07-24 20:47:01 -04:00
static void
search_handle_event ( int search_type , session * sess )
{
2026-01-15 18:35:27 -07:00
textentry * last ;
const gchar * text = NULL ;
gtk_xtext_search_flags flags ;
GError * err = NULL ;
gboolean backwards = FALSE ;
/* When just typing show most recent first */
if ( search_type == SEARCH_PREVIOUS || search_type == SEARCH_CHANGE )
backwards = TRUE ;
flags = (( prefs . hex_text_search_case_match == 1 ? case_match : 0 ) |
( backwards ? backward : 0 ) |
( prefs . hex_text_search_highlight_all == 1 ? highlight : 0 ) |
( prefs . hex_text_search_follow == 1 ? follow : 0 ) |
( prefs . hex_text_search_regexp == 1 ? regexp : 0 ));
if ( search_type != SEARCH_REFRESH )
text = gtk_entry_get_text ( GTK_ENTRY ( sess -> gui -> shentry ));
2026-03-27 15:27:31 -06:00
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( sess -> gui -> shchan )))
{
if ( search_type == SEARCH_CHANGE || search_type == SEARCH_REFRESH )
{
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , NULL );
return ;
}
session * match = search_find_channel ( sess , text );
if ( match )
{
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , NULL );
mg_bring_tofront_sess ( match );
}
else if ( text && text [ 0 ])
{
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , ICON_ENTRY_ERROR );
gtk_entry_set_icon_tooltip_text ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , _ ( "No channels found." ));
}
else
{
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , NULL );
}
return ;
}
2026-01-15 18:35:27 -07:00
last = gtk_xtext_search ( GTK_XTEXT ( sess -> gui -> xtext ), text , flags , & err );
if ( err )
{
2026-01-30 15:27:01 -07:00
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , ICON_ENTRY_ERROR );
2026-01-15 18:35:27 -07:00
gtk_entry_set_icon_tooltip_text ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , _ ( err -> message ));
g_error_free ( err );
}
else if ( ! last )
{
if ( text && text [ 0 ] == 0 ) /* empty string, no error */
{
2026-01-23 12:57:52 -07:00
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , NULL );
2026-01-15 18:35:27 -07:00
}
else
{
/* Either end of search or not found, try again to wrap if only end */
last = gtk_xtext_search ( GTK_XTEXT ( sess -> gui -> xtext ), text , flags , & err );
if ( ! last ) /* Not found error */
{
2026-01-30 15:27:01 -07:00
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , ICON_ENTRY_ERROR );
2026-01-15 18:35:27 -07:00
gtk_entry_set_icon_tooltip_text ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , _ ( "No results found." ));
}
}
}
else
{
2026-01-23 12:57:52 -07:00
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , NULL );
2026-01-15 18:35:27 -07:00
}
2013-07-24 20:47:01 -04:00
}
static void
search_handle_change ( GtkWidget * wid , session * sess )
{
2026-01-15 18:35:27 -07:00
search_handle_event ( SEARCH_CHANGE , sess );
2013-07-24 20:47:01 -04:00
}
static void
search_handle_refresh ( GtkWidget * wid , session * sess )
{
2026-01-15 18:35:27 -07:00
search_handle_event ( SEARCH_REFRESH , sess );
2013-07-24 20:47:01 -04:00
}
void
mg_search_handle_previous ( GtkWidget * wid , session * sess )
{
2026-01-15 18:35:27 -07:00
search_handle_event ( SEARCH_PREVIOUS , sess );
2013-07-24 20:47:01 -04:00
}
void
mg_search_handle_next ( GtkWidget * wid , session * sess )
{
2026-01-15 18:35:27 -07:00
search_handle_event ( SEARCH_NEXT , sess );
2013-07-24 20:47:01 -04:00
}
static void
search_set_option ( GtkToggleButton * but , guint * pref )
{
2026-01-15 18:35:27 -07:00
* pref = gtk_toggle_button_get_active ( but );
2026-03-16 21:55:36 -06:00
if ( ! save_config ())
fe_message ( _ ( "Could not save zoitechat.conf." ), FE_MSG_WARN );
2013-07-24 20:47:01 -04:00
}
void
mg_search_toggle ( session * sess )
{
2026-01-15 18:35:27 -07:00
if ( gtk_widget_get_visible ( sess -> gui -> shbox ))
{
gtk_widget_hide ( sess -> gui -> shbox );
gtk_widget_grab_focus ( sess -> gui -> input_box );
2026-08-31 00:26:18 -06:00
if ( prefs . hex_text_search_keep_position )
g_signal_handler_block ( sess -> gui -> shentry , sess -> gui -> search_changed_signal );
2026-01-15 18:35:27 -07:00
gtk_entry_set_text ( GTK_ENTRY ( sess -> gui -> shentry ), "" );
2026-08-31 00:26:18 -06:00
if ( prefs . hex_text_search_keep_position )
g_signal_handler_unblock ( sess -> gui -> shentry , sess -> gui -> search_changed_signal );
2026-01-15 18:35:27 -07:00
}
else
{
/* Reset search state */
2026-01-23 12:57:52 -07:00
gtk_entry_set_icon_from_icon_name ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , NULL );
2013-07-24 20:47:01 -04:00
2026-01-15 18:35:27 -07:00
/* Show and focus */
gtk_widget_show ( sess -> gui -> shbox );
gtk_widget_grab_focus ( sess -> gui -> shentry );
}
2013-07-24 20:47:01 -04:00
}
static gboolean
2026-08-31 00:26:18 -06:00
search_handle_keypress ( GtkWidget * win , GdkEventKey * key , session * sess )
2013-07-24 20:47:01 -04:00
{
2026-01-15 18:35:27 -07:00
if ( key -> keyval == GDK_KEY_Escape )
2026-08-31 00:26:18 -06:00
{
2026-01-15 18:35:27 -07:00
mg_search_toggle ( sess );
2026-08-31 00:26:18 -06:00
return TRUE ;
}
if ((( key -> keyval == GDK_KEY_Page_Up || key -> keyval == GDK_KEY_Page_Down ) &&
! ( key -> state & gtk_accelerator_get_default_mod_mask ())) ||
(( key -> keyval == GDK_KEY_Up || key -> keyval == GDK_KEY_Down ) &&
( key -> state & gtk_accelerator_get_default_mod_mask ()) == GDK_SHIFT_MASK ))
return key_handle_key_press ( sess -> gui -> input_box , key , sess );
2026-01-15 18:35:27 -07:00
return FALSE ;
2013-07-24 20:47:01 -04:00
}
static void
mg_create_search ( session * sess , GtkWidget * box )
{
2026-03-27 15:27:31 -06:00
GtkWidget * entry , * label , * next , * previous , * highlight , * matchcase , * regex , * close , * channels ;
2026-01-15 18:35:27 -07:00
session_gui * gui = sess -> gui ;
2026-01-19 20:11:07 -07:00
gui -> shbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 5 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), gui -> shbox , FALSE , FALSE , 0 );
close = gtk_button_new ();
2026-02-18 00:32:42 -07:00
gtk_button_set_image ( GTK_BUTTON ( close ), gtkutil_image_new_from_stock ( ICON_TAB_CLOSE , GTK_ICON_SIZE_MENU ));
2026-01-15 18:35:27 -07:00
gtk_button_set_relief ( GTK_BUTTON ( close ), GTK_RELIEF_NONE );
gtk_widget_set_can_focus ( close , FALSE );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), close , FALSE , FALSE , 0 );
g_signal_connect_swapped ( G_OBJECT ( close ), "clicked" , G_CALLBACK ( mg_search_toggle ), sess );
label = gtk_label_new ( _ ( "Find:" ));
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), label , FALSE , FALSE , 0 );
gui -> shentry = entry = gtk_entry_new ();
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), entry , FALSE , FALSE , 0 );
gtk_widget_set_size_request ( gui -> shentry , 180 , - 1 );
2026-09-10 07:13:41 -06:00
emoji_font_apply ( entry );
2026-03-21 02:20:34 -06:00
mg_apply_entry_scroll_artifact_fix ( entry );
2026-01-15 18:35:27 -07:00
gui -> search_changed_signal = g_signal_connect ( G_OBJECT ( entry ), "changed" , G_CALLBACK ( search_handle_change ), sess );
2026-08-31 00:26:18 -06:00
g_signal_connect ( G_OBJECT ( entry ), "key-press-event" , G_CALLBACK ( search_handle_keypress ), sess );
2026-01-15 18:35:27 -07:00
g_signal_connect ( G_OBJECT ( entry ), "activate" , G_CALLBACK ( mg_search_handle_next ), sess );
gtk_entry_set_icon_activatable ( GTK_ENTRY ( entry ), GTK_ENTRY_ICON_SECONDARY , FALSE );
gtk_entry_set_icon_tooltip_text ( GTK_ENTRY ( sess -> gui -> shentry ), GTK_ENTRY_ICON_SECONDARY , _ ( "Search hit end or not found." ));
previous = gtk_button_new ();
2026-02-18 00:32:42 -07:00
gtk_button_set_image ( GTK_BUTTON ( previous ), gtkutil_image_new_from_stock ( ICON_TAB_PREVIOUS , GTK_ICON_SIZE_MENU ));
2026-01-15 18:35:27 -07:00
gtk_button_set_relief ( GTK_BUTTON ( previous ), GTK_RELIEF_NONE );
gtk_widget_set_can_focus ( previous , FALSE );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), previous , FALSE , FALSE , 0 );
g_signal_connect ( G_OBJECT ( previous ), "clicked" , G_CALLBACK ( mg_search_handle_previous ), sess );
next = gtk_button_new ();
2026-02-18 00:32:42 -07:00
gtk_button_set_image ( GTK_BUTTON ( next ), gtkutil_image_new_from_stock ( ICON_TAB_NEXT , GTK_ICON_SIZE_MENU ));
2026-01-15 18:35:27 -07:00
gtk_button_set_relief ( GTK_BUTTON ( next ), GTK_RELIEF_NONE );
gtk_widget_set_can_focus ( next , FALSE );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), next , FALSE , FALSE , 0 );
g_signal_connect ( G_OBJECT ( next ), "clicked" , G_CALLBACK ( mg_search_handle_next ), sess );
highlight = gtk_check_button_new_with_mnemonic ( _ ( "_Highlight all" ));
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( highlight ), prefs . hex_text_search_highlight_all );
gtk_widget_set_can_focus ( highlight , FALSE );
g_signal_connect ( G_OBJECT ( highlight ), "toggled" , G_CALLBACK ( search_set_option ), & prefs . hex_text_search_highlight_all );
g_signal_connect ( G_OBJECT ( highlight ), "toggled" , G_CALLBACK ( search_handle_refresh ), sess );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), highlight , FALSE , FALSE , 0 );
gtk_widget_set_tooltip_text ( highlight , _ ( "Highlight all occurrences, and underline the current occurrence." ));
matchcase = gtk_check_button_new_with_mnemonic ( _ ( "Mat_ch case" ));
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( matchcase ), prefs . hex_text_search_case_match );
gtk_widget_set_can_focus ( matchcase , FALSE );
g_signal_connect ( G_OBJECT ( matchcase ), "toggled" , G_CALLBACK ( search_set_option ), & prefs . hex_text_search_case_match );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), matchcase , FALSE , FALSE , 0 );
gtk_widget_set_tooltip_text ( matchcase , _ ( "Perform a case-sensitive search." ));
regex = gtk_check_button_new_with_mnemonic ( _ ( "_Regex" ));
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( regex ), prefs . hex_text_search_regexp );
gtk_widget_set_can_focus ( regex , FALSE );
g_signal_connect ( G_OBJECT ( regex ), "toggled" , G_CALLBACK ( search_set_option ), & prefs . hex_text_search_regexp );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), regex , FALSE , FALSE , 0 );
gtk_widget_set_tooltip_text ( regex , _ ( "Regard search string as a regular expression." ));
2026-03-27 15:27:31 -06:00
gui -> shchan = channels = gtk_check_button_new_with_mnemonic ( _ ( "_Channels only" ));
gtk_widget_set_can_focus ( channels , FALSE );
gtk_box_pack_start ( GTK_BOX ( gui -> shbox ), channels , FALSE , FALSE , 0 );
gtk_widget_set_tooltip_text ( channels , _ ( "Search channel names in your current channel list." ));
g_signal_connect ( G_OBJECT ( channels ), "toggled" , G_CALLBACK ( search_handle_refresh ), sess );
2013-07-24 20:47:01 -04:00
}
2011-02-24 04:14:30 +01:00
static void
mg_create_entry ( session * sess , GtkWidget * box )
{
2026-09-09 00:10:26 -06:00
GtkWidget * hbox , * but , * entry , * format_box , * format_label ;
2026-01-15 18:35:27 -07:00
session_gui * gui = sess -> gui ;
2026-09-09 00:10:26 -06:00
format_box = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 6 );
gtk_widget_set_name ( format_box , "zoitechat-formatbar" );
gtk_widget_set_no_show_all ( format_box , TRUE );
gtk_box_pack_start ( GTK_BOX ( box ), format_box , 0 , 0 , 0 );
format_label = gtk_label_new ( "" );
gtk_label_set_ellipsize ( GTK_LABEL ( format_label ), PANGO_ELLIPSIZE_END );
gtk_box_pack_start ( GTK_BOX ( format_box ), format_label , TRUE , TRUE , 8 );
gtk_widget_show ( format_label );
gtk_widget_hide ( format_box );
2026-06-25 14:51:04 -06:00
gui -> reply_box = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 6 );
gtk_widget_set_name ( gui -> reply_box , "zoitechat-replybar" );
gtk_widget_set_no_show_all ( gui -> reply_box , TRUE );
gtk_box_pack_start ( GTK_BOX ( box ), gui -> reply_box , 0 , 0 , 0 );
gui -> reply_label = gtk_label_new ( "" );
gtk_label_set_ellipsize ( GTK_LABEL ( gui -> reply_label ), PANGO_ELLIPSIZE_END );
gtk_box_pack_start ( GTK_BOX ( gui -> reply_box ), gui -> reply_label , TRUE , TRUE , 8 );
but = gtk_button_new_with_label ( "× " );
gtk_button_set_relief ( GTK_BUTTON ( but ), GTK_RELIEF_NONE );
gtk_widget_set_can_focus ( but , FALSE );
gtk_box_pack_start ( GTK_BOX ( gui -> reply_box ), but , FALSE , FALSE , 0 );
g_signal_connect ( G_OBJECT ( but ), "clicked" , G_CALLBACK ( mg_reply_cancel_cb ), sess );
gtk_widget_hide ( gui -> reply_box );
2026-01-19 20:11:07 -07:00
hbox = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( box ), hbox , 0 , 0 , 0 );
2026-01-19 20:11:07 -07:00
gui -> nick_box = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( hbox ), gui -> nick_box , 0 , 0 , 0 );
gui -> nick_label = but = gtk_button_new_with_label ( sess -> server -> nick );
gtk_button_set_relief ( GTK_BUTTON ( but ), GTK_RELIEF_NONE );
gtk_widget_set_can_focus ( but , FALSE );
gtk_box_pack_end ( GTK_BOX ( gui -> nick_box ), but , 0 , 0 , 0 );
g_signal_connect ( G_OBJECT ( but ), "clicked" ,
G_CALLBACK ( mg_nickclick_cb ), NULL );
gui -> input_box = entry = sexy_spell_entry_new ();
sexy_spell_entry_set_checked (( SexySpellEntry * ) entry , prefs . hex_gui_input_spell );
sexy_spell_entry_set_parse_attributes (( SexySpellEntry * ) entry , prefs . hex_gui_input_attr );
2026-09-09 00:10:26 -06:00
g_object_set_data ( G_OBJECT ( entry ), "zoitechat-format-box" , format_box );
g_object_set_data ( G_OBJECT ( entry ), "zoitechat-format-label" , format_label );
2026-01-15 18:35:27 -07:00
gtk_entry_set_max_length ( GTK_ENTRY ( gui -> input_box ), 0 );
g_signal_connect ( G_OBJECT ( entry ), "activate" ,
G_CALLBACK ( mg_inputbox_cb ), gui );
g_signal_connect ( G_OBJECT ( entry ), "changed" ,
2026-06-25 14:51:04 -06:00
G_CALLBACK ( mg_inputbox_changed ), gui );
2026-09-09 00:10:26 -06:00
g_signal_connect ( G_OBJECT ( entry ), "notify::cursor-position" ,
G_CALLBACK ( mg_inputbox_cursor_changed ), gui );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( hbox ), entry , TRUE , TRUE , 0 );
gtk_widget_set_name ( entry , "zoitechat-inputbox" );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( entry ), "key-press-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( key_handle_key_press ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( entry ), "focus-in-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_inputbox_focus ), gui );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( entry ), "populate-popup" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_inputbox_rightclick ), NULL );
g_signal_connect ( G_OBJECT ( entry ), "word-check" ,
G_CALLBACK ( mg_spellcheck_cb ), NULL );
gtk_widget_grab_focus ( entry );
if ( prefs . hex_gui_input_style )
mg_apply_entry_style ( entry );
2026-09-10 07:13:41 -06:00
emoji_font_apply ( entry );
2026-03-21 02:20:34 -06:00
mg_apply_entry_scroll_artifact_fix ( entry );
2026-01-15 18:35:27 -07:00
2026-09-10 07:13:41 -06:00
/* ZoiteChat's own emoji picker; deliberately not GTK's
* "show-emoji-icon" machinery, so the catalog is the same on every
* GTK runtime and the button always has a working callback */
but = emoji_picker_button_new ( entry );
gtk_box_pack_start ( GTK_BOX ( hbox ), but , FALSE , FALSE , 0 );
2011-02-24 04:14:30 +01:00
}
static void
mg_switch_tab_cb ( chanview * cv , chan * ch , int tag , gpointer ud )
{
2026-01-15 18:35:27 -07:00
chan * old ;
session * sess = ud ;
old = active_tab ;
active_tab = ch ;
if ( tag == TAG_IRC )
{
if ( active_tab != old )
{
if ( old && current_tab )
mg_unpopulate ( current_tab );
mg_populate ( sess );
}
} else if ( old != active_tab )
{
/* userdata for non-irc tabs is actually the GtkBox */
mg_show_generic_tab ( ud );
if ( ! mg_is_userlist_and_tree_combined ())
mg_userlist_showhide ( current_sess , FALSE ); /* hide */
}
2011-02-24 04:14:30 +01:00
}
/* compare two tabs (for tab sorting function) */
static int
mg_tabs_compare ( session * a , session * b )
{
2026-01-15 18:35:27 -07:00
/* server tabs always go first */
if ( a -> type == SESS_SERVER )
return - 1 ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* then channels */
if ( a -> type == SESS_CHANNEL && b -> type != SESS_CHANNEL )
return - 1 ;
if ( a -> type != SESS_CHANNEL && b -> type == SESS_CHANNEL )
return 1 ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return g_ascii_strcasecmp ( a -> channel , b -> channel );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_tabs ( session_gui * gui )
{
2026-01-15 18:35:27 -07:00
gboolean use_icons = FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* if any one of these PNGs exist, the chanview will create
* the extra column for icons. */
if ( prefs . hex_gui_tab_icons && ( pix_tree_channel || pix_tree_dialog || pix_tree_server || pix_tree_util ))
{
use_icons = TRUE ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> chanview = chanview_new ( prefs . hex_gui_tab_layout , prefs . hex_gui_tab_trunc ,
prefs . hex_gui_tab_sort , use_icons ,
2026-02-16 13:58:10 -07:00
input_style ? input_style -> font_desc : NULL
2026-01-30 18:39:49 -07:00
);
2026-01-15 18:35:27 -07:00
chanview_set_callbacks ( gui -> chanview , mg_switch_tab_cb , mg_xbutton_cb ,
mg_tab_contextmenu_cb , ( void * ) mg_tabs_compare );
mg_place_userlist_and_chanview ( gui );
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_tabwin_focus_cb ( GtkWindow * win , GdkEventFocus * event , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
current_sess = current_tab ;
if ( current_sess )
{
gtk_xtext_check_marker_visibility ( GTK_XTEXT ( current_sess -> gui -> xtext ));
plugin_emit_dummy_print ( current_sess , "Focus Window" );
}
unflash_window ( GTK_WIDGET ( win ));
return FALSE ;
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_topwin_focus_cb ( GtkWindow * win , GdkEventFocus * event , session * sess )
{
2026-01-15 18:35:27 -07:00
current_sess = sess ;
if ( ! sess -> server -> server_session )
sess -> server -> server_session = sess ;
gtk_xtext_check_marker_visibility ( GTK_XTEXT ( current_sess -> gui -> xtext ));
unflash_window ( GTK_WIDGET ( win ));
plugin_emit_dummy_print ( sess , "Focus Window" );
return FALSE ;
2011-02-24 04:14:30 +01:00
}
static void
mg_create_menu ( session_gui * gui , GtkWidget * table , int away_state )
{
2026-01-15 18:35:27 -07:00
GtkAccelGroup * accel_group ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
accel_group = gtk_accel_group_new ();
gtk_window_add_accel_group ( GTK_WINDOW ( gtk_widget_get_toplevel ( table )),
accel_group );
g_object_unref ( accel_group );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gui -> menu = menu_create_main ( accel_group , TRUE , away_state , ! gui -> is_tab ,
gui -> menu_item );
2026-01-19 19:26:29 -07:00
gtk_widget_set_hexpand ( gui -> menu , TRUE );
2026-01-30 16:04:50 -07:00
gtk_widget_set_vexpand ( gui -> menu , FALSE );
2026-07-15 23:56:05 -06:00
/* Fill the row: with GTK_ALIGN_START the menu bar stops after its
* last item and the rest of the row shows bare window background,
* which reads as a two-colored menu bar whenever the theme styles
* menubar and window differently. */
gtk_widget_set_halign ( gui -> menu , GTK_ALIGN_FILL );
2026-01-30 16:04:50 -07:00
gtk_widget_set_valign ( gui -> menu , GTK_ALIGN_FILL );
gtk_grid_attach ( GTK_GRID ( table ), gui -> menu , 0 , 0 , 3 , 1 );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_irctab ( session * sess , GtkWidget * table )
{
2026-01-15 18:35:27 -07:00
GtkWidget * vbox ;
session_gui * gui = sess -> gui ;
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 0 );
2026-01-19 19:26:29 -07:00
gtk_widget_set_hexpand ( vbox , TRUE );
gtk_widget_set_vexpand ( vbox , TRUE );
2026-01-30 16:04:50 -07:00
gtk_widget_set_halign ( vbox , GTK_ALIGN_FILL );
gtk_widget_set_valign ( vbox , GTK_ALIGN_FILL );
gtk_grid_attach ( GTK_GRID ( table ), vbox , 1 , 2 , 1 , 1 );
2026-01-15 18:35:27 -07:00
mg_create_center ( sess , gui , vbox );
2011-02-24 04:14:30 +01:00
}
static void
mg_create_topwindow ( session * sess )
{
GtkWidget * win ;
GtkWidget * table ;
2026-01-16 22:07:01 -07:00
#ifdef G_OS_WIN32
GdkWindow * parent_win ;
#endif
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> type == SESS_DIALOG )
win = gtkutil_window_new ( "ZoiteChat" , NULL ,
prefs . hex_gui_dialog_width , prefs . hex_gui_dialog_height , 0 );
else
win = gtkutil_window_new ( "ZoiteChat" , NULL ,
prefs . hex_gui_win_width ,
prefs . hex_gui_win_height , 0 );
sess -> gui -> window = win ;
2026-09-10 07:13:41 -06:00
emoji_font_apply ( win );
2026-01-15 18:35:27 -07:00
gtk_container_set_border_width ( GTK_CONTAINER ( win ), GUI_BORDER );
2026-03-11 01:49:14 -06:00
gtk_widget_set_opacity ( win , ( prefs . hex_gui_transparency / 255. ));
2011-02-24 04:14:30 +01:00
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( win ), "focus-in-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_topwin_focus_cb ), sess );
g_signal_connect ( G_OBJECT ( win ), "destroy" ,
G_CALLBACK ( mg_topdestroy_cb ), sess );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( win ), "configure-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_configure_cb ), sess );
2011-02-24 04:14:30 +01:00
2026-01-19 19:26:29 -07:00
table = gtk_grid_new ();
/* spacing under the menubar */
gtk_grid_set_row_spacing ( GTK_GRID ( table ), GUI_SPACING );
/* left and right borders */
gtk_grid_set_column_spacing ( GTK_GRID ( table ), 1 );
2026-01-15 18:35:27 -07:00
gtk_container_add ( GTK_CONTAINER ( win ), table );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_create_irctab ( sess , table );
mg_create_menu ( sess -> gui , table , sess -> server -> is_away );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> res -> buffer == NULL )
{
sess -> res -> buffer = gtk_xtext_buffer_new ( GTK_XTEXT ( sess -> gui -> xtext ));
gtk_xtext_buffer_show ( GTK_XTEXT ( sess -> gui -> xtext ), sess -> res -> buffer , TRUE );
gtk_xtext_set_time_stamp ( sess -> res -> buffer , prefs . hex_stamp_text );
sess -> res -> user_model = userlist_create_model ( sess );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
userlist_show ( sess );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_show_all ( table );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_hide_menu )
gtk_widget_hide ( sess -> gui -> menu );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* Will be shown when needed */
gtk_widget_hide ( sess -> gui -> topic_bar );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! prefs . hex_gui_ulist_buttons )
gtk_widget_hide ( sess -> gui -> button_box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! prefs . hex_gui_input_nick )
gtk_widget_hide ( sess -> gui -> nick_box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_hide ( sess -> gui -> shbox );
2013-07-24 20:47:01 -04:00
2026-01-15 18:35:27 -07:00
mg_decide_userlist ( sess , FALSE );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> type == SESS_DIALOG )
{
/* hide the chan-mode buttons */
gtk_widget_hide ( sess -> gui -> topicbutton_box );
} else
{
gtk_widget_hide ( sess -> gui -> dialogbutton_box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! prefs . hex_gui_mode_buttons )
gtk_widget_hide ( sess -> gui -> topicbutton_box );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_place_userlist_and_chanview ( sess -> gui );
2011-02-24 04:14:30 +01:00
gtk_widget_show ( win );
2026-03-02 19:42:48 -07:00
if ( ! sess -> gui -> theme_window_listener_id )
sess -> gui -> theme_window_listener_id = theme_listener_register ( "maingui.window" , mg_theme_window_changed , sess -> gui );
g_signal_connect ( G_OBJECT ( win ), "destroy" , G_CALLBACK ( mg_theme_window_destroy_cb ), sess -> gui );
theme_manager_attach_window ( win );
2026-01-16 22:07:01 -07:00
#ifdef G_OS_WIN32
parent_win = gtk_widget_get_window ( win );
gdk_window_add_filter ( parent_win , mg_win32_filter , NULL );
2026-07-07 23:44:22 -06:00
mg_win32_ensure_minimizable ( parent_win );
2026-01-16 22:07:01 -07:00
#endif
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_tabwindow_de_cb ( GtkWidget * widget , GdkEvent * event , gpointer user_data )
{
2026-01-15 18:35:27 -07:00
GSList * list ;
session * sess ;
GtkWindow * win = GTK_WINDOW ( gtk_widget_get_toplevel ( widget ));
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_tray_close && gtkutil_tray_icon_supported ( win ) && tray_toggle_visibility ( FALSE ))
return TRUE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* check for remaining toplevel windows */
list = sess_list ;
while ( list )
{
sess = list -> data ;
if ( ! sess -> gui -> is_tab )
return FALSE ;
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_open_quit_dialog ( TRUE );
return TRUE ;
2011-02-24 04:14:30 +01:00
}
2017-11-14 21:47:17 +01:00
#ifdef G_OS_WIN32
static GdkFilterReturn
2026-01-16 22:07:01 -07:00
mg_win32_filter ( GdkXEvent * xevent , GdkEvent * event , gpointer data )
2017-11-14 21:47:17 +01:00
{
2026-01-15 18:35:27 -07:00
MSG * msg = ( MSG * ) xevent ;
2017-11-14 21:47:17 +01:00
2026-01-16 22:07:01 -07:00
if ( ! msg )
return GDK_FILTER_CONTINUE ;
2026-07-07 23:44:22 -06:00
if ( msg -> message == WM_STYLECHANGING && msg -> wParam == ( WPARAM ) GWL_STYLE )
{
STYLESTRUCT * style = ( STYLESTRUCT * ) msg -> lParam ;
/* Keep the window minimizable from its taskbar button; see
* mg_win32_ensure_minimizable (). */
if ( style )
style -> styleNew |= WS_MINIMIZEBOX ;
return GDK_FILTER_CONTINUE ;
}
2017-11-14 21:47:17 +01:00
if ( msg -> message == WM_TIMECHANGE )
{
_tzset ();
2026-01-16 22:07:01 -07:00
return GDK_FILTER_CONTINUE ;
}
2026-01-18 10:46:41 -07:00
if ( msg -> message == WM_SETTINGCHANGE || msg -> message == WM_THEMECHANGED )
{
2026-03-02 19:42:48 -07:00
theme_manager_refresh_auto_mode ();
2026-01-18 10:46:41 -07:00
return GDK_FILTER_CONTINUE ;
}
2026-01-16 22:07:01 -07:00
if ( msg -> message == WM_COPYDATA )
{
COPYDATASTRUCT * copy_data = ( COPYDATASTRUCT * ) msg -> lParam ;
if ( copy_data && copy_data -> lpData && copy_data -> cbData > 0 && current_sess )
{
char * command = g_strndup (( const char * ) copy_data -> lpData , copy_data -> cbData );
if ( command )
{
2026-02-18 13:28:17 -07:00
if ( strcmp ( command , "__WIN32_TASKBAR_TOGGLE__" ) == 0 )
{
2026-05-20 10:19:51 -06:00
GdkWindowState state = 0 ;
GdkWindow * gdk_window = gtk_widget_get_window ( current_sess -> gui -> window );
if ( gdk_window )
state = gdk_window_get_state ( gdk_window );
if ( gtk_widget_get_visible ( current_sess -> gui -> window )
&& ( state & GDK_WINDOW_STATE_ICONIFIED ) == 0 )
2026-02-18 13:28:17 -07:00
fe_ctrl_gui ( current_sess , FE_GUI_ICONIFY , 0 );
else
fe_ctrl_gui ( current_sess , FE_GUI_SHOW , 0 );
}
else
{
handle_command ( current_sess , command , FALSE );
}
2026-01-16 22:07:01 -07:00
g_free ( command );
return GDK_FILTER_REMOVE ;
}
}
2017-11-14 21:47:17 +01:00
}
2026-02-16 03:05:43 -07:00
if ( msg -> message == WM_MOUSEWHEEL || msg -> message == WM_MOUSEHWHEEL )
{
POINT cursor_pos ;
HWND hover_hwnd ;
DWORD hover_pid = 0 ;
if ( ! GetCursorPos ( & cursor_pos ))
return GDK_FILTER_CONTINUE ;
hover_hwnd = WindowFromPoint ( cursor_pos );
if ( ! hover_hwnd || hover_hwnd == msg -> hwnd )
return GDK_FILTER_CONTINUE ;
GetWindowThreadProcessId ( hover_hwnd , & hover_pid );
if ( hover_pid != GetCurrentProcessId ())
return GDK_FILTER_CONTINUE ;
PostMessage ( hover_hwnd , msg -> message , msg -> wParam , msg -> lParam );
return GDK_FILTER_REMOVE ;
}
2026-01-15 18:35:27 -07:00
return GDK_FILTER_CONTINUE ;
2017-11-14 21:47:17 +01:00
}
#endif
2011-02-24 04:14:30 +01:00
static void
mg_create_tabwindow ( session * sess )
{
2026-01-15 18:35:27 -07:00
GtkWidget * win ;
GtkWidget * table ;
2017-11-14 21:47:17 +01:00
#ifdef G_OS_WIN32
2026-01-15 18:35:27 -07:00
GdkWindow * parent_win ;
2017-11-14 21:47:17 +01:00
#endif
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
win = gtkutil_window_new ( "ZoiteChat" , NULL , prefs . hex_gui_win_width ,
prefs . hex_gui_win_height , 0 );
sess -> gui -> window = win ;
2026-09-10 07:13:41 -06:00
emoji_font_apply ( win );
2026-01-15 18:35:27 -07:00
gtk_window_move ( GTK_WINDOW ( win ), prefs . hex_gui_win_left ,
prefs . hex_gui_win_top );
if ( prefs . hex_gui_win_state )
gtk_window_maximize ( GTK_WINDOW ( win ));
if ( prefs . hex_gui_win_fullscreen )
gtk_window_fullscreen ( GTK_WINDOW ( win ));
2026-03-11 01:49:14 -06:00
gtk_widget_set_opacity ( win , ( prefs . hex_gui_transparency / 255. ));
2026-01-15 18:35:27 -07:00
gtk_container_set_border_width ( GTK_CONTAINER ( win ), GUI_BORDER );
2011-02-24 04:14:30 +01:00
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( win ), "delete-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_tabwindow_de_cb ), 0 );
g_signal_connect ( G_OBJECT ( win ), "destroy" ,
G_CALLBACK ( mg_tabwindow_kill_cb ), 0 );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( win ), "focus-in-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_tabwin_focus_cb ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( win ), "configure-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_configure_cb ), NULL );
2026-03-17 10:30:54 -06:00
g_signal_connect ( G_OBJECT ( win ), "window-state-event" ,
2026-01-15 18:35:27 -07:00
G_CALLBACK ( mg_windowstate_cb ), NULL );
2011-02-24 04:14:30 +01:00
2026-01-19 19:26:29 -07:00
sess -> gui -> main_table = table = gtk_grid_new ();
/* spacing under the menubar */
gtk_grid_set_row_spacing ( GTK_GRID ( table ), GUI_SPACING );
/* left and right borders */
gtk_grid_set_column_spacing ( GTK_GRID ( table ), 1 );
2026-01-15 18:35:27 -07:00
gtk_container_add ( GTK_CONTAINER ( win ), table );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_create_irctab ( sess , table );
mg_create_tabs ( sess -> gui );
mg_create_menu ( sess -> gui , table , sess -> server -> is_away );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_focus ( sess );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_show_all ( table );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_hide_menu )
gtk_widget_hide ( sess -> gui -> menu );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_decide_userlist ( sess , FALSE );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* Will be shown when needed */
gtk_widget_hide ( sess -> gui -> topic_bar );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! prefs . hex_gui_mode_buttons )
gtk_widget_hide ( sess -> gui -> topicbutton_box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! prefs . hex_gui_ulist_buttons )
gtk_widget_hide ( sess -> gui -> button_box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! prefs . hex_gui_input_nick )
gtk_widget_hide ( sess -> gui -> nick_box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_hide ( sess -> gui -> shbox );
2013-07-24 20:47:01 -04:00
2026-01-15 18:35:27 -07:00
mg_place_userlist_and_chanview ( sess -> gui );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_show ( win );
2026-03-02 19:42:48 -07:00
if ( ! sess -> gui -> theme_window_listener_id )
sess -> gui -> theme_window_listener_id = theme_listener_register ( "maingui.window" , mg_theme_window_changed , sess -> gui );
g_signal_connect ( G_OBJECT ( win ), "destroy" , G_CALLBACK ( mg_theme_window_destroy_cb ), sess -> gui );
theme_manager_attach_window ( win );
2017-11-14 21:47:17 +01:00
#ifdef G_OS_WIN32
parent_win = gtk_widget_get_window ( win );
2026-01-16 22:07:01 -07:00
gdk_window_add_filter ( parent_win , mg_win32_filter , NULL );
2026-07-07 23:44:22 -06:00
mg_win32_ensure_minimizable ( parent_win );
2017-11-14 21:47:17 +01:00
#endif
2011-02-24 04:14:30 +01:00
}
void
mg_apply_setup ( void )
{
2026-01-15 18:35:27 -07:00
GSList * list = sess_list ;
session * sess ;
int done_main = FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
mg_create_tab_colors ();
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
while ( list )
{
sess = list -> data ;
gtk_xtext_set_time_stamp ( sess -> res -> buffer , prefs . hex_stamp_text );
(( xtext_buffer * ) sess -> res -> buffer ) -> needs_recalc = TRUE ;
2026-07-16 08:48:33 -06:00
/* the attribute lists were just recreated, so re-apply the
color matching each tab's activity state */
if ( sess -> res -> tab )
chan_set_color ( sess -> res -> tab , mg_attr_list_for_tab_state ( sess -> tab_state ));
2026-01-15 18:35:27 -07:00
if ( ! sess -> gui -> is_tab || ! done_main )
mg_place_userlist_and_chanview ( sess -> gui );
if ( sess -> gui -> is_tab )
done_main = TRUE ;
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
}
static chan *
mg_add_generic_tab ( char * name , char * title , void * family , GtkWidget * box )
{
2026-01-15 18:35:27 -07:00
chan * ch ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_notebook_append_page ( GTK_NOTEBOOK ( mg_gui -> note_book ), box , NULL );
gtk_widget_show ( box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
ch = chanview_add ( mg_gui -> chanview , name , NULL , box , TRUE , TAG_UTIL , pix_tree_util );
chan_set_color ( ch , plain_list );
2016-07-24 21:10:34 -04:00
2026-01-15 18:35:27 -07:00
g_object_set_data_full ( G_OBJECT ( box ), "title" , g_strdup ( title ), g_free );
g_object_set_data ( G_OBJECT ( box ), "ch" , ch );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_tab_newtofront )
chan_focus ( ch );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return ch ;
2011-02-24 04:14:30 +01:00
}
void
fe_buttons_update ( session * sess )
{
2026-01-15 18:35:27 -07:00
session_gui * gui = sess -> gui ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( gui -> button_box );
gui -> button_box = mg_create_userlistbuttons ( gui -> button_box_parent );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( prefs . hex_gui_ulist_buttons )
gtk_widget_show ( sess -> gui -> button_box );
else
gtk_widget_hide ( sess -> gui -> button_box );
2011-02-24 04:14:30 +01:00
}
2026-07-16 08:48:33 -06:00
/* tab label for a session that has no joined channel: the channel we are
* waiting to join in parentheses, or "<none>". tbuf must hold CHANLEN+6. */
static void
mg_tab_pending_name ( session * sess , char * tbuf )
{
if ( sess -> waitchannel [ 0 ])
{
if ( prefs . hex_gui_tab_trunc > 2 && g_utf8_strlen ( sess -> waitchannel , - 1 ) > prefs . hex_gui_tab_trunc )
{
/* truncate long channel names */
tbuf [ 0 ] = '(' ;
strcpy ( tbuf + 1 , sess -> waitchannel );
g_utf8_offset_to_pointer ( tbuf , prefs . hex_gui_tab_trunc )[ 0 ] = 0 ;
strcat ( tbuf , "..)" );
} else
{
sprintf ( tbuf , "(%s)" , sess -> waitchannel );
}
}
else
strcpy ( tbuf , _ ( "<none>" ));
}
/* re-apply the tab label truncation preference to a session's tab */
static void
mg_retrunc_tab ( session * sess )
{
char tbuf [ CHANLEN + 6 ];
if ( ! sess -> res -> tab )
return ;
if ( sess -> channel [ 0 ])
chan_rename ( sess -> res -> tab , sess -> channel , prefs . hex_gui_tab_trunc );
else
{
mg_tab_pending_name ( sess , tbuf );
chan_rename ( sess -> res -> tab , tbuf , prefs . hex_gui_tab_trunc );
}
}
2011-02-24 04:14:30 +01:00
void
fe_clear_channel ( session * sess )
{
2026-01-15 18:35:27 -07:00
char tbuf [ CHANLEN + 6 ];
session_gui * gui = sess -> gui ;
if ( sess -> gui -> is_tab )
{
2026-07-16 08:48:33 -06:00
mg_tab_pending_name ( sess , tbuf );
2026-01-15 18:35:27 -07:00
chan_rename ( sess -> res -> tab , tbuf , prefs . hex_gui_tab_trunc );
}
if ( ! sess -> gui -> is_tab || sess == current_tab )
{
2026-03-14 19:57:42 -06:00
gtk_text_buffer_set_text (
gtk_text_view_get_buffer ( GTK_TEXT_VIEW ( gui -> topic_entry )), "" , - 1 );
2026-01-15 18:35:27 -07:00
if ( gui -> op_xpm )
{
gtk_widget_destroy ( gui -> op_xpm );
gui -> op_xpm = 0 ;
}
} else
{
if ( sess -> res -> topic_text )
{
g_free ( sess -> res -> topic_text );
sess -> res -> topic_text = NULL ;
}
}
2011-02-24 04:14:30 +01:00
}
void
fe_set_nonchannel ( session * sess , int state )
{
}
void
fe_dlgbuttons_update ( session * sess )
{
2026-01-15 18:35:27 -07:00
GtkWidget * box ;
session_gui * gui = sess -> gui ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_destroy ( gui -> dialogbutton_box );
2011-02-24 04:14:30 +01:00
2026-01-19 20:11:07 -07:00
gui -> dialogbutton_box = box = mg_box_new ( GTK_ORIENTATION_HORIZONTAL , FALSE , 0 );
2026-01-15 18:35:27 -07:00
gtk_box_pack_start ( GTK_BOX ( gui -> topic_bar ), box , 0 , 0 , 0 );
gtk_box_reorder_child ( GTK_BOX ( gui -> topic_bar ), box , 3 );
mg_create_dialogbuttons ( box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_widget_show_all ( box );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( current_tab && current_tab -> type != SESS_DIALOG )
gtk_widget_hide ( current_tab -> gui -> dialogbutton_box );
2011-02-24 04:14:30 +01:00
}
void
fe_update_mode_buttons ( session * sess , char mode , char sign )
{
2026-01-15 18:35:27 -07:00
int state , i ;
if ( sign == '+' )
state = TRUE ;
else
state = FALSE ;
for ( i = 0 ; i < NUM_FLAG_WIDS - 1 ; i ++ )
{
if ( chan_flags [ i ] == mode )
{
if ( ! sess -> gui -> is_tab || sess == current_tab )
{
ignore_chanmode = TRUE ;
if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( sess -> gui -> flag_wid [ i ])) != state )
gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( sess -> gui -> flag_wid [ i ]), state );
ignore_chanmode = FALSE ;
} else
{
sess -> res -> flag_wid_state [ i ] = state ;
}
return ;
}
}
2011-02-24 04:14:30 +01:00
}
void
fe_set_nick ( server * serv , char * newnick )
{
2026-01-15 18:35:27 -07:00
GSList * list = sess_list ;
session * sess ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
while ( list )
{
sess = list -> data ;
if ( sess -> server == serv )
{
if ( current_tab == sess || ! sess -> gui -> is_tab )
gtk_button_set_label ( GTK_BUTTON ( sess -> gui -> nick_label ), newnick );
}
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
}
void
fe_set_away ( server * serv )
{
2026-01-15 18:35:27 -07:00
GSList * list = sess_list ;
session * sess ;
while ( list )
{
sess = list -> data ;
if ( sess -> server == serv )
{
if ( ! sess -> gui -> is_tab || sess == current_tab )
{
menu_set_away ( sess -> gui , serv -> is_away );
/* gray out my nickname */
mg_set_myself_away ( sess -> gui , serv -> is_away );
}
}
list = list -> next ;
}
2011-02-24 04:14:30 +01:00
}
void
fe_set_channel ( session * sess )
{
2026-01-15 18:35:27 -07:00
if ( sess -> res -> tab != NULL )
chan_rename ( sess -> res -> tab , sess -> channel , prefs . hex_gui_tab_trunc );
2011-02-24 04:14:30 +01:00
}
2026-07-16 08:48:33 -06:00
static void
mg_apply_live_prefs_to_gui ( session_gui * gui , session * sess , const mg_live_prefs * changes )
{
if ( changes -> meters )
mg_update_meters ( gui );
if ( changes -> ulist_columns && gui -> user_tree )
userlist_rebuild_columns ( gui -> user_tree );
if ( changes -> ulist_count && gui -> namelistinfo )
{
if ( prefs . hex_gui_ulist_count )
gtk_widget_show ( gui -> namelistinfo );
else
gtk_widget_hide ( gui -> namelistinfo );
}
if ( changes -> input_box && gui -> nick_box )
{
if ( prefs . hex_gui_input_nick )
gtk_widget_show ( gui -> nick_box );
else
gtk_widget_hide ( gui -> nick_box );
}
/* refresh the access icon; re-filling the user list rows can
clear it as a side effect, so restore it here too */
if (( changes -> input_box || changes -> ulist_rows ) && sess && is_session ( sess ))
{
if ( sess -> type == SESS_DIALOG )
mg_set_access_icon ( gui , NULL , sess -> server -> is_away );
else
mg_set_access_icon ( gui , get_user_icon ( sess -> server , sess -> me ), sess -> server -> is_away );
}
if ( changes -> topic_bar )
mg_update_topicbar_layout ( gui );
if ( changes -> transparency && gui -> window )
gtk_widget_set_opacity ( gui -> window , ( prefs . hex_gui_transparency / 255. ));
}
/* apply preference changes that used to require a restart. called by
* setup.c after the new prefs have been committed. */
void
mg_apply_live_prefs ( const mg_live_prefs * changes )
{
GSList * list ;
session * sess ;
int done_main = FALSE ;
if ( ! changes )
return ;
/* the tab bar / channel tree lives in the shared tab window */
if ( mg_gui && mg_gui -> chanview )
{
if ( changes -> tab_resort )
{
chanview_set_sorted ( mg_gui -> chanview , prefs . hex_gui_tab_sort );
chanview_resort ( mg_gui -> chanview );
}
if ( changes -> tab_trunc )
chanview_set_trunc_len ( mg_gui -> chanview , prefs . hex_gui_tab_trunc );
if ( changes -> chanview || changes -> tab_resort )
{
chanview_set_use_icons ( mg_gui -> chanview ,
prefs . hex_gui_tab_icons && ( pix_tree_channel || pix_tree_dialog ||
pix_tree_server || pix_tree_util ));
/* rebuild the tab bar / tree widgets from the model */
chanview_set_impl ( mg_gui -> chanview , prefs . hex_gui_tab_layout );
}
}
list = sess_list ;
while ( list )
{
sess = list -> data ;
if ( changes -> tab_trunc )
mg_retrunc_tab ( sess );
if ( changes -> ulist_sort )
userlist_apply_sort ( sess );
if ( changes -> ulist_rows )
userlist_refill ( sess );
if ( sess -> gui -> is_tab )
{
/* only apply to the shared tab window once, using the
front-most session for session-dependent bits */
if ( ! done_main )
{
done_main = TRUE ;
mg_apply_live_prefs_to_gui ( sess -> gui , current_tab , changes );
}
}
else
mg_apply_live_prefs_to_gui ( sess -> gui , sess , changes );
list = list -> next ;
}
}
2026-06-25 14:51:04 -06:00
void
fe_set_typing ( session * sess , const char * nick , const char * state )
{
fe_userlist_set_typing ( sess , nick , state );
}
2011-02-24 04:14:30 +01:00
void
mg_changui_new ( session * sess , restore_gui * res , int tab , int focus )
{
2026-01-15 18:35:27 -07:00
int first_run = FALSE ;
session_gui * gui ;
if ( res == NULL )
{
res = g_new0 ( restore_gui , 1 );
}
sess -> res = res ;
if ( sess -> server -> front_session == NULL )
{
sess -> server -> front_session = sess ;
}
if ( ! tab )
{
gui = g_new0 ( session_gui , 1 );
gui -> is_tab = FALSE ;
sess -> gui = gui ;
mg_create_topwindow ( sess );
fe_set_title ( sess );
return ;
}
if ( mg_gui == NULL )
{
first_run = TRUE ;
gui = & static_mg_gui ;
memset ( gui , 0 , sizeof ( session_gui ));
gui -> is_tab = TRUE ;
sess -> gui = gui ;
mg_create_tabwindow ( sess );
mg_gui = gui ;
parent_window = gui -> window ;
} else
{
sess -> gui = gui = mg_gui ;
gui -> is_tab = TRUE ;
}
mg_add_chan ( sess );
if ( first_run || ( prefs . hex_gui_tab_newtofront == FOCUS_NEW_ONLY_ASKED && focus )
|| prefs . hex_gui_tab_newtofront == FOCUS_NEW_ALL )
chan_focus ( res -> tab );
2011-02-24 04:14:30 +01:00
}
GtkWidget *
mg_create_generic_tab ( char * name , char * title , int force_toplevel ,
2026-01-15 18:35:27 -07:00
int link_buttons ,
void * close_callback , void * userdata ,
int width , int height , GtkWidget ** vbox_ret ,
void * family )
{
GtkWidget * vbox , * win ;
if ( prefs . hex_gui_tab_pos == POS_HIDDEN && prefs . hex_gui_tab_utils )
prefs . hex_gui_tab_utils = 0 ;
if ( force_toplevel || ! prefs . hex_gui_tab_utils )
{
win = gtkutil_window_new ( title , name , width , height , 2 );
2026-01-19 20:11:07 -07:00
vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 0 );
2026-01-15 18:35:27 -07:00
* vbox_ret = vbox ;
gtk_container_add ( GTK_CONTAINER ( win ), vbox );
gtk_widget_show ( vbox );
if ( close_callback )
g_signal_connect ( G_OBJECT ( win ), "destroy" ,
G_CALLBACK ( close_callback ), userdata );
return win ;
}
2026-01-19 20:11:07 -07:00
vbox = mg_box_new ( GTK_ORIENTATION_VERTICAL , FALSE , 2 );
2026-01-15 18:35:27 -07:00
g_object_set_data ( G_OBJECT ( vbox ), "w" , GINT_TO_POINTER ( width ));
g_object_set_data ( G_OBJECT ( vbox ), "h" , GINT_TO_POINTER ( height ));
gtk_container_set_border_width ( GTK_CONTAINER ( vbox ), 3 );
* vbox_ret = vbox ;
if ( close_callback )
g_signal_connect ( G_OBJECT ( vbox ), "destroy" ,
G_CALLBACK ( close_callback ), userdata );
mg_add_generic_tab ( name , title , family , vbox );
/* if (link_buttons)
{
2026-01-19 20:11:07 -07:00
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
2026-01-15 18:35:27 -07:00
gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0);
mg_create_link_buttons (hbox, ch);
gtk_widget_show (hbox);
}*/
return vbox ;
2011-02-24 04:14:30 +01:00
}
void
mg_move_tab ( session * sess , int delta )
{
2026-01-15 18:35:27 -07:00
if ( sess -> gui -> is_tab )
chan_move ( sess -> res -> tab , delta );
2011-02-24 04:14:30 +01:00
}
void
mg_move_tab_family ( session * sess , int delta )
{
2026-01-15 18:35:27 -07:00
if ( sess -> gui -> is_tab )
chan_move_family ( sess -> res -> tab , delta );
2011-02-24 04:14:30 +01:00
}
void
mg_set_title ( GtkWidget * vbox , char * title ) /* for non-irc tab/window only */
{
2026-01-15 18:35:27 -07:00
char * old ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
old = g_object_get_data ( G_OBJECT ( vbox ), "title" );
if ( old )
{
g_object_set_data_full ( G_OBJECT ( vbox ), "title" , g_strdup ( title ), g_free );
} else
{
gtk_window_set_title ( GTK_WINDOW ( vbox ), title );
}
2011-02-24 04:14:30 +01:00
}
void
fe_server_callback ( server * serv )
{
2026-01-15 18:35:27 -07:00
joind_close ( serv );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( serv -> gui -> chanlist_window )
mg_close_gen ( NULL , serv -> gui -> chanlist_window );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( serv -> gui -> rawlog_window )
mg_close_gen ( NULL , serv -> gui -> rawlog_window );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
g_free ( serv -> gui );
2011-02-24 04:14:30 +01:00
}
/* called when a session is being killed */
void
fe_session_callback ( session * sess )
{
2026-01-15 18:35:27 -07:00
gtk_xtext_buffer_free ( sess -> res -> buffer );
g_object_unref ( G_OBJECT ( sess -> res -> user_model ));
2026-03-02 19:42:48 -07:00
if ( sess -> res -> user_row_refs )
g_hash_table_destroy ( sess -> res -> user_row_refs );
2016-07-24 21:14:11 -04:00
2026-01-15 18:35:27 -07:00
if ( sess -> res -> banlist && sess -> res -> banlist -> window )
mg_close_gen ( NULL , sess -> res -> banlist -> window );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
g_free ( sess -> res -> input_text );
g_free ( sess -> res -> topic_text );
g_free ( sess -> res -> limit_text );
g_free ( sess -> res -> key_text );
g_free ( sess -> res -> queue_text );
g_free ( sess -> res -> queue_tip );
g_free ( sess -> res -> lag_text );
g_free ( sess -> res -> lag_tip );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> gui -> bartag )
fe_timeout_remove ( sess -> gui -> bartag );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( sess -> gui != & static_mg_gui )
g_free ( sess -> gui );
g_free ( sess -> res );
2011-02-24 04:14:30 +01:00
}
/* ===== DRAG AND DROP STUFF ===== */
static gboolean
is_child_of ( GtkWidget * widget , GtkWidget * parent )
{
2026-01-15 18:35:27 -07:00
while ( widget )
{
if ( gtk_widget_get_parent ( widget ) == parent )
return TRUE ;
widget = gtk_widget_get_parent ( widget );
}
return FALSE ;
2011-02-24 04:14:30 +01:00
}
static void
mg_handle_drop ( GtkWidget * widget , int y , int * pos , int * other_pos )
{
2026-01-15 18:35:27 -07:00
int height ;
2026-05-19 15:46:06 -06:00
GdkWindow * window ;
2026-01-15 18:35:27 -07:00
session_gui * gui = current_sess -> gui ;
2026-05-19 15:46:06 -06:00
window = gtk_widget_get_window ( widget );
if ( ! window )
return ;
height = gdk_window_get_height ( window );
2026-01-15 18:35:27 -07:00
if ( y < height / 2 )
{
if ( is_child_of ( widget , gui -> vpane_left ))
* pos = 1 ; /* top left */
else
* pos = 3 ; /* top right */
}
else
{
if ( is_child_of ( widget , gui -> vpane_left ))
* pos = 2 ; /* bottom left */
else
* pos = 4 ; /* bottom right */
}
/* both in the same pos? must move one */
if ( * pos == * other_pos )
{
switch ( * other_pos )
{
case 1 :
* other_pos = 2 ;
break ;
case 2 :
* other_pos = 1 ;
break ;
case 3 :
* other_pos = 4 ;
break ;
case 4 :
* other_pos = 3 ;
break ;
}
}
mg_place_userlist_and_chanview ( gui );
2011-02-24 04:14:30 +01:00
}
static gboolean
mg_is_gui_target ( GdkDragContext * context )
{
2026-01-15 18:35:27 -07:00
char * target_name ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
if ( ! context || ! gdk_drag_context_list_targets ( context ) || ! gdk_drag_context_list_targets ( context ) -> data )
return FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
target_name = gdk_atom_name ( gdk_drag_context_list_targets ( context ) -> data );
if ( target_name )
{
2026-01-25 16:13:47 -07:00
/* if it's not ZOITECHAT_CHANVIEW or ZOITECHAT_USERLIST */
2026-01-15 18:35:27 -07:00
/* we should ignore it. */
if ( target_name [ 0 ] != 'H' )
{
g_free ( target_name );
return FALSE ;
}
g_free ( target_name );
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return TRUE ;
2011-02-24 04:14:30 +01:00
}
/* this begin callback just creates an nice of the source */
gboolean
mg_drag_begin_cb ( GtkWidget * widget , GdkDragContext * context , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
int width , height ;
GdkPixbuf * pix , * pix2 ;
2026-01-17 17:34:38 -07:00
GdkWindow * window ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
/* ignore file drops */
if ( ! mg_is_gui_target ( context ))
return FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-17 17:34:38 -07:00
window = gtk_widget_get_window ( widget );
2026-05-19 15:46:06 -06:00
if ( ! window )
return FALSE ;
2026-01-17 17:34:38 -07:00
width = gdk_window_get_width ( window );
height = gdk_window_get_height ( window );
2011-02-24 04:14:30 +01:00
2026-01-17 17:34:38 -07:00
pix = mg_pixbuf_from_window ( window , width , height );
2026-01-17 21:56:00 -07:00
if ( ! pix )
return FALSE ;
2026-01-15 18:35:27 -07:00
pix2 = gdk_pixbuf_scale_simple ( pix , width * 4 / 5 , height / 2 , GDK_INTERP_HYPER );
g_object_unref ( pix );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
gtk_drag_set_icon_pixbuf ( context , pix2 , 0 , 0 );
g_object_set_data ( G_OBJECT ( widget ), "ico" , pix2 );
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return TRUE ;
2011-02-24 04:14:30 +01:00
}
void
mg_drag_end_cb ( GtkWidget * widget , GdkDragContext * context , gpointer userdata )
{
2026-01-15 18:35:27 -07:00
/* ignore file drops */
if ( ! mg_is_gui_target ( context ))
return ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
g_object_unref ( g_object_get_data ( G_OBJECT ( widget ), "ico" ));
2011-02-24 04:14:30 +01:00
}
/* drop complete */
gboolean
mg_drag_drop_cb ( GtkWidget * widget , GdkDragContext * context , int x , int y , guint time , gpointer user_data )
{
2026-01-15 18:35:27 -07:00
/* ignore file drops */
if ( ! mg_is_gui_target ( context ))
return FALSE ;
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
switch ( gdk_drag_context_get_selected_action ( context ))
{
case GDK_ACTION_MOVE :
/* from userlist */
mg_handle_drop ( widget , y , & prefs . hex_gui_ulist_pos , & prefs . hex_gui_tab_pos );
break ;
case GDK_ACTION_COPY :
/* from tree - we use GDK_ACTION_COPY for the tree */
mg_handle_drop ( widget , y , & prefs . hex_gui_tab_pos , & prefs . hex_gui_ulist_pos );
break ;
default :
return FALSE ;
}
2011-02-24 04:14:30 +01:00
2026-01-15 18:35:27 -07:00
return TRUE ;
2011-02-24 04:14:30 +01:00
}
/* draw highlight rectangle in the destination */
gboolean
mg_drag_motion_cb ( GtkWidget * widget , GdkDragContext * context , int x , int y , guint time , gpointer scbar )
{
2026-01-17 21:56:00 -07:00
XTextColor col ;
2026-01-17 16:19:56 -07:00
cairo_t * cr ;
2026-03-11 01:49:14 -06:00
GdkDrawingContext * draw_context ;
cairo_region_t * region ;
2026-01-15 18:35:27 -07:00
int half , width , height ;
int ox , oy ;
2026-01-17 17:34:38 -07:00
GdkWindow * window ;
2026-01-15 18:35:27 -07:00
GtkAllocation allocation ;
/* ignore file drops */
if ( ! mg_is_gui_target ( context ))
return FALSE ;
if ( scbar ) /* scrollbar */
{
gtk_widget_get_allocation ( widget , & allocation );
ox = allocation . x ;
oy = allocation . y ;
width = allocation . width ;
height = allocation . height ;
2026-01-17 17:34:38 -07:00
window = gtk_widget_get_window ( widget );
2026-05-19 15:46:06 -06:00
if ( ! window )
return FALSE ;
2026-01-15 18:35:27 -07:00
}
else
{
ox = oy = 0 ;
2026-01-17 17:34:38 -07:00
window = gtk_widget_get_window ( widget );
2026-05-19 15:46:06 -06:00
if ( ! window )
return FALSE ;
2026-01-17 17:34:38 -07:00
width = gdk_window_get_width ( window );
height = gdk_window_get_height ( window );
2026-01-15 18:35:27 -07:00
}
2026-01-17 21:56:00 -07:00
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 ;
2026-03-11 01:49:14 -06:00
region = cairo_region_create ();
cairo_region_union_rectangle ( region , & ( cairo_rectangle_int_t ){ 0 , 0 , width , height });
draw_context = gdk_window_begin_draw_frame ( window , region );
cairo_region_destroy ( region );
cr = gdk_drawing_context_get_cairo_context ( draw_context );
2026-01-17 16:19:56 -07:00
cairo_set_operator ( cr , CAIRO_OPERATOR_XOR );
2026-01-17 16:46:21 -07:00
mg_set_source_color ( cr , & col );
2026-01-17 16:19:56 -07:00
cairo_set_line_width ( cr , 1.0 );
2026-01-15 18:35:27 -07:00
half = height / 2 ;
2011-02-24 04:14:30 +01:00
#if 0
2026-01-15 18:35:27 -07:00
/* are both tree/userlist on the same side? */
2026-01-17 16:34:38 -07:00
GtkPaned *paned;
2026-01-31 11:36:47 -07:00
GtkWidget *parent;
GtkWidget *grandparent;
parent = gtk_widget_get_parent (widget);
grandparent = parent ? gtk_widget_get_parent (parent) : NULL;
paned = grandparent ? GTK_PANED (grandparent) : NULL;
if (paned != NULL &&
gtk_paned_get_child1 (paned) != NULL &&
gtk_paned_get_child2 (paned) != NULL)
2026-01-15 18:35:27 -07:00
{
2026-01-17 16:34:38 -07:00
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);
2026-01-15 18:35:27 -07:00
return TRUE;
}
2011-02-24 04:14:30 +01:00
#endif
2026-01-15 18:35:27 -07:00
if ( y < half )
{
2026-01-17 16:19:56 -07:00
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 );
2026-01-15 18:35:27 -07:00
gtk_widget_queue_draw_area ( widget , ox , half + oy , width , height - half );
}
else
{
2026-01-17 16:19:56 -07:00
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 );
2026-01-15 18:35:27 -07:00
gtk_widget_queue_draw_area ( widget , ox , oy , width , half );
}
2026-03-11 01:49:14 -06:00
gdk_window_end_draw_frame ( window , draw_context );
2026-01-15 18:35:27 -07:00
return TRUE ;
2011-02-24 04:14:30 +01:00
}