openclonk/src/platform/C4WindowGTK.cpp

899 lines
29 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* A wrapper class to OS dependent event and window interfaces, GTK+ version */
#include <C4Include.h>
#include <C4Window.h>
#include <C4App.h>
#include "C4Version.h"
#include <C4Config.h>
#include <C4DrawGL.h>
#include <C4Draw.h>
#include <StdFile.h>
#include <StdBuf.h>
#include <C4Rect.h>
#include <C4Console.h>
#include <C4ViewportWindow.h>
#include <C4Viewport.h>
#include "C4MouseControl.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include <epoxy/glx.h>
// Some helper functions for choosing a proper visual
namespace {
static const std::map<int, int> base_attrib_map {
{GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT},
{GLX_X_RENDERABLE, True},
{GLX_RED_SIZE, 4},
{GLX_GREEN_SIZE, 4},
{GLX_BLUE_SIZE, 4},
{GLX_DEPTH_SIZE, 8}
};
// Turns an int->int map into an attribute list suitable for any GLX calls.
std::unique_ptr<int[]> MakeGLXAttribList(const std::map<int, int> &map)
{
// We need two ints for every attribute, plus one as a sentinel
auto list = std::make_unique<int[]>(map.size() * 2 + 1);
int *cursor = list.get();
for(const auto &attrib : map)
{
*cursor++ = attrib.first;
*cursor++ = attrib.second;
}
*cursor = None;
return list;
}
// This function picks an acceptable GLXFBConfig. To do this, we first
// request a list of framebuffer configs with no less than 4 bits per color;
// no less than 8 bits of depth buffer; if multisampling is not -1,
// with at least the requested number of samples; and with double buffering.
// If that returns no suitable configs, we retry with only a single buffer.
GLXFBConfig PickGLXFBConfig(Display* dpy, int multisampling)
{
std::map<int, int> attrib_map = base_attrib_map;
if (multisampling >= 0)
{
attrib_map[GLX_SAMPLE_BUFFERS] = multisampling > 0 ? 1 : 0;
attrib_map[GLX_SAMPLES] = multisampling;
}
GLXFBConfig *configs = NULL;
int config_count;
// Find a double-buffered FB config
attrib_map[GLX_DOUBLEBUFFER] = True;
std::unique_ptr<int[]> attribs = MakeGLXAttribList(attrib_map);
configs = glXChooseFBConfig(dpy, DefaultScreen(dpy), attribs.get(), &config_count);
if (config_count == 0)
{
// If none exists, try to find a single-buffered one
if (configs != NULL)
XFree(configs);
attrib_map[GLX_DOUBLEBUFFER] = False;
attribs = MakeGLXAttribList(attrib_map);
configs = glXChooseFBConfig(dpy, DefaultScreen(dpy), attribs.get(), &config_count);
}
GLXFBConfig config = NULL;
if (config_count > 0)
{
config = configs[0];
}
XFree(configs);
return config;
}
}
#elif defined(GDK_WINDOWING_WIN32)
#include <C4windowswrapper.h>
#include <gdk/gdkwin32.h>
#endif // GDK_WINDOWING_X11
static void OnDestroyStatic(GtkWidget* widget, gpointer data)
{
C4Window* wnd = static_cast<C4Window*>(data);
wnd->Clear();
}
static gboolean OnDelete(GtkWidget* widget, GdkEvent* event, gpointer data)
{
C4Window* wnd = static_cast<C4Window*>(data);
wnd->Close();
return true;
}
#ifdef GDK_WINDOWING_X11
static constexpr int x11scancodeoffset = 8;
#else
static constexpr int x11scancodeoffset = 0;
#endif
static gboolean OnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer data)
{
C4Window* wnd = static_cast<C4Window*>(data);
if (event->hardware_keycode <= x11scancodeoffset) return false;
Game.DoKeyboardInput(event->hardware_keycode - x11scancodeoffset, KEYEV_Down,
!!(event->state & GDK_MOD1_MASK),
!!(event->state & GDK_CONTROL_MASK),
!!(event->state & GDK_SHIFT_MASK), false, NULL);
wnd->CharIn(event->string); // FIXME: Use GtkIMContext somehow
return true;
}
static gboolean OnKeyRelease(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
{
if (event->hardware_keycode <= x11scancodeoffset) return false;
Game.DoKeyboardInput(event->hardware_keycode - x11scancodeoffset, KEYEV_Up,
!!(event->state & GDK_MOD1_MASK),
!!(event->state & GDK_CONTROL_MASK),
!!(event->state & GDK_SHIFT_MASK), false, NULL);
return true;
}
static void OnDragDataReceivedStatic(GtkWidget* widget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint info, guint time, gpointer user_data)
{
if (!Console.Editing) { Console.Message(LoadResStr("IDS_CNS_NONETEDIT")); return; }
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
gchar** uris = gtk_selection_data_get_uris(data);
if (!uris) return;
for (gchar** uri = uris; *uri != NULL; ++ uri)
{
gchar* file = g_filename_from_uri(*uri, NULL, NULL);
if (!file) continue;
window->cvp->DropFile(file, x, y);
g_free(file);
}
g_strfreev(uris);
}
static gboolean OnExposeStatic(GtkWidget* widget, void *, gpointer user_data)
{
C4Viewport* cvp = static_cast<C4ViewportWindow*>(user_data)->cvp;
cvp->Execute();
return true;
}
static void OnRealizeStatic(GtkWidget* widget, gpointer user_data)
{
// Initial PlayerLock
if (static_cast<C4ViewportWindow*>(user_data)->cvp->GetPlayerLock())
{
gtk_widget_hide(static_cast<C4ViewportWindow*>(user_data)->h_scrollbar);
gtk_widget_hide(static_cast<C4ViewportWindow*>(user_data)->v_scrollbar);
}
}
static gboolean OnKeyPressStatic(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
{
if (event->keyval == GDK_KEY_Scroll_Lock)
{
static_cast<C4ViewportWindow*>(user_data)->cvp->TogglePlayerLock();
return true;
}
if (event->hardware_keycode <= x11scancodeoffset) return false;
Console.EditCursor.KeyDown(event->hardware_keycode - x11scancodeoffset, event->state);
return false;
}
static gboolean OnKeyReleaseStatic(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
{
if (event->hardware_keycode <= x11scancodeoffset) return false;
Console.EditCursor.KeyUp(event->hardware_keycode - x11scancodeoffset, event->state);
return false;
}
static gboolean OnScrollVW(GtkWidget* widget, GdkEventScroll* event, gpointer user_data)
{
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
if (::MouseControl.IsViewport(window->cvp) && (Console.EditCursor.GetMode()==C4CNS_ModePlay))
{
switch (event->direction)
{
case GDK_SCROLL_UP:
C4GUI::MouseMove(C4MC_Button_Wheel, (int32_t)event->x, (int32_t)event->y, event->state + (short(1) << 16), window->cvp);
break;
case GDK_SCROLL_DOWN:
C4GUI::MouseMove(C4MC_Button_Wheel, (int32_t)event->x, (int32_t)event->y, event->state + (short(-1) << 16), window->cvp);
break;
default:
return false;
}
}
return true;
}
static gboolean OnButtonPressStatic(GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
if (::MouseControl.IsViewport(window->cvp) && (Console.EditCursor.GetMode()==C4CNS_ModePlay))
{
switch (event->button)
{
case 1:
if (event->type == GDK_BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_LeftDown, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
else if (event->type == GDK_2BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_LeftDouble, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
break;
case 2:
C4GUI::MouseMove(C4MC_Button_MiddleDown, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
break;
case 3:
if (event->type == GDK_BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_RightDown, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
else if (event->type == GDK_2BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_RightDouble, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
break;
}
}
else
{
switch (event->button)
{
case 1:
Console.EditCursor.LeftButtonDown(event->state);
break;
case 3:
Console.EditCursor.RightButtonDown(event->state);
break;
}
}
return true;
}
static gboolean OnButtonReleaseStatic(GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
if (::MouseControl.IsViewport(window->cvp) && (Console.EditCursor.GetMode()==C4CNS_ModePlay))
{
switch (event->button)
{
case 1:
C4GUI::MouseMove(C4MC_Button_LeftUp, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
break;
case 2:
C4GUI::MouseMove(C4MC_Button_MiddleUp, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
break;
case 3:
C4GUI::MouseMove(C4MC_Button_RightUp, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
break;
}
}
else
{
switch (event->button)
{
case 1:
Console.EditCursor.LeftButtonUp(event->state);
break;
case 3:
Console.EditCursor.RightButtonUp(event->state);
break;
}
}
return true;
}
static gboolean OnMotionNotifyStatic(GtkWidget* widget, GdkEventMotion* event, gpointer user_data)
{
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
if (::MouseControl.IsViewport(window->cvp) && (Console.EditCursor.GetMode()==C4CNS_ModePlay))
{
C4GUI::MouseMove(C4MC_Button_None, (int32_t)event->x, (int32_t)event->y, event->state, window->cvp);
}
else
{
window->EditCursorMove(event->x, event->y, event->state);
}
return true;
}
static gboolean OnConfigureStatic(GtkWidget* widget, GdkEventConfigure* event, gpointer user_data)
{
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
C4Viewport* cvp = window->cvp;
//cvp->UpdateOutputSize();
cvp->ScrollBarsByViewPosition();
return false;
}
static gboolean OnConfigureDareaStatic(GtkWidget* widget, GdkEventConfigure* event, gpointer user_data)
{
C4ViewportWindow* window = static_cast<C4ViewportWindow*>(user_data);
C4Viewport* cvp = window->cvp;
cvp->UpdateOutputSize();
return false;
}
static void OnVScrollStatic(GtkAdjustment* adjustment, gpointer user_data)
{
static_cast<C4ViewportWindow*>(user_data)->cvp->ViewPositionByScrollBars();
}
static void OnHScrollStatic(GtkAdjustment* adjustment, gpointer user_data)
{
static_cast<C4ViewportWindow*>(user_data)->cvp->ViewPositionByScrollBars();
}
static GtkTargetEntry drag_drop_entries[] =
{
{ const_cast<gchar*>("text/uri-list"), 0, 0 }
};
static gboolean OnConfigureNotify(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
Application.OnResolutionChanged(event->configure.width, event->configure.height);
return false;
}
static bool fullscreen_needs_restore = false;
static gboolean fullscreen_restore(gpointer data)
{
if (fullscreen_needs_restore)
Application.SetVideoMode(Application.GetConfigWidth(), Application.GetConfigHeight(), Config.Graphics.RefreshRate, Config.Graphics.Monitor, Application.FullScreenMode());
fullscreen_needs_restore = false;
return FALSE;
}
static gboolean OnFocusInFS(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
Application.Active = true;
if (Application.FullScreenMode())
{
fullscreen_needs_restore = true;
gdk_threads_add_idle(fullscreen_restore, NULL);
}
return false;
}
static gboolean OnFocusOutFS(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
Application.Active = false;
if (Application.FullScreenMode() && Application.GetConfigWidth() != -1)
{
Application.RestoreVideoMode();
gtk_window_iconify(GTK_WINDOW(widget));
fullscreen_needs_restore = false;
}
return false;
}
static gboolean OnButtonPressFS(GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
switch (event->button)
{
case 1:
if (event->type == GDK_BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_LeftDown, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
else if (event->type == GDK_2BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_LeftDouble, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
break;
case 2:
C4GUI::MouseMove(C4MC_Button_MiddleDown, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
break;
case 3:
if (event->type == GDK_BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_RightDown, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
else if (event->type == GDK_2BUTTON_PRESS)
C4GUI::MouseMove(C4MC_Button_RightDouble, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
break;
default:
return false;
}
return true;
}
gboolean OnButtonRelease(GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
int b;
switch (event->button)
{
case 1: b = C4MC_Button_LeftUp; break;
case 2: b = C4MC_Button_MiddleUp; break;
case 3: b = C4MC_Button_RightUp; break;
default: return false;
}
C4GUI::MouseMove(b, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
return true;
}
static gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event, gpointer user_data)
{
C4GUI::MouseMove(C4MC_Button_None, (int32_t)event->x, (int32_t)event->y, event->state, NULL);
return true;
}
static gboolean OnScroll(GtkWidget* widget, GdkEventScroll* event, gpointer user_data)
{
C4GUI::DialogWindow * window = static_cast<C4GUI::DialogWindow*>(user_data);
C4GUI::Dialog *pDlg = ::pGUI->GetDialog(window);
int idy;
switch (event->direction)
{
case GDK_SCROLL_UP: idy = 32; break;
case GDK_SCROLL_DOWN: idy = -32; break;
default: return false;
}
// FIXME: make the GUI api less insane here
if (pDlg)
::pGUI->MouseInput(C4MC_Button_Wheel, event->x, event->y, event->state + (idy << 16), pDlg, NULL);
else
C4GUI::MouseMove(C4MC_Button_Wheel, event->x, event->y, event->state + (idy << 16), NULL);
return true;
}
static gboolean OnButtonPressGD(GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
C4GUI::DialogWindow * window = static_cast<C4GUI::DialogWindow*>(user_data);
C4GUI::Dialog *pDlg = ::pGUI->GetDialog(window);
switch (event->button)
{
case 1:
if (event->type == GDK_2BUTTON_PRESS)
{
::pGUI->MouseInput(C4MC_Button_LeftDouble, event->x, event->y, event->state, pDlg, NULL);
}
else if (event->type == GDK_BUTTON_PRESS)
{
::pGUI->MouseInput(C4MC_Button_LeftDown,event->x, event->y, event->state, pDlg, NULL);
}
break;
case 2:
if (event->type == GDK_BUTTON_PRESS)
::pGUI->MouseInput(C4MC_Button_MiddleDown, event->x, event->y, event->state, pDlg, NULL);
break;
case 3:
if (event->type == GDK_2BUTTON_PRESS)
{
::pGUI->MouseInput(C4MC_Button_RightDouble, event->x, event->y, event->state, pDlg, NULL);
}
else if (event->type == GDK_BUTTON_PRESS)
{
::pGUI->MouseInput(C4MC_Button_RightDown, event->x, event->y, event->state, pDlg, NULL);
}
break;
}
return true;
}
static gboolean OnButtonReleaseGD(GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
C4GUI::DialogWindow * window = static_cast<C4GUI::DialogWindow*>(user_data);
C4GUI::Dialog *pDlg = ::pGUI->GetDialog(window);
switch (event->button)
{
case 1:
::pGUI->MouseInput(C4MC_Button_LeftUp, event->x, event->y, event->state, pDlg, NULL);
break;
case 2:
::pGUI->MouseInput(C4MC_Button_MiddleUp, event->x, event->y, event->state, pDlg, NULL);
break;
case 3:
::pGUI->MouseInput(C4MC_Button_RightUp, event->x, event->y, event->state, pDlg, NULL);
break;
}
return true;
}
static gboolean OnMotionNotifyGD(GtkWidget* widget, GdkEventMotion* event, gpointer user_data)
{
C4GUI::DialogWindow * window = static_cast<C4GUI::DialogWindow*>(user_data);
C4GUI::Dialog *pDlg = ::pGUI->GetDialog(window);
::pGUI->MouseInput(C4MC_Button_None, event->x, event->y, event->state, pDlg, NULL);
return true;
}
static gboolean OnConfigureGD(GtkWidget* widget, GdkEventConfigure* event, gpointer user_data)
{
C4GUI::DialogWindow * window = static_cast<C4GUI::DialogWindow*>(user_data);
window->pSurface->UpdateSize(event->width, event->height);
return false;
}
C4Window::C4Window ():
Active(false), pSurface(0),
renderwnd(0), Info(0), window(NULL)
{
}
C4Window::~C4Window ()
{
Clear();
}
#ifdef GDK_WINDOWING_X11
bool C4Window::FindFBConfig(int samples, GLXFBConfig *info)
{
Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default());
GLXFBConfig config = PickGLXFBConfig(dpy, samples);
if (info)
{
*info = config;
}
return config != NULL;
return false;
}
#endif
void C4Window::EnumerateMultiSamples(std::vector<int>& samples) const
{
#ifdef GDK_WINDOWING_X11
Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default());
std::map<int, int> attribs = base_attrib_map;
attribs[GLX_SAMPLE_BUFFERS_ARB] = 1;
int config_count = 0;
GLXFBConfig *configs = glXChooseFBConfig(dpy, DefaultScreen(dpy), MakeGLXAttribList(attribs).get(), &config_count);
std::set<int> multisamples;
for(int i = 0; i < config_count; ++i)
{
int v_samples;
glXGetFBConfigAttrib(dpy, configs[i], GLX_SAMPLES, &v_samples);
multisamples.insert(v_samples);
}
XFree(configs);
samples.assign(multisamples.cbegin(), multisamples.cend());
#else
if(pGL && pGL->pMainCtx)
samples = pGL->pMainCtx->EnumerateMultiSamples();
#endif
}
bool C4Window::StorePosition(const char *, const char *, bool) { return true; }
bool C4Window::RestorePosition(const char *, const char *, bool)
{
// The Windowmanager is responsible for window placement.
return true;
}
void C4Window::FlashWindow()
{
//FIXME - how is this reset? gtk_window_set_urgency_hint(window, true);
}
C4Window* C4Window::Init(WindowKind windowKind, C4AbstractApp * pApp, const char * Title, const C4Rect * size)
{
Active = true;
#ifdef GDK_WINDOWING_X11
if(!FindFBConfig(Config.Graphics.MultiSampling, &Info))
{
// Disable multisampling if we don't find a visual which
// supports the currently configured setting.
if(!FindFBConfig(0, &Info)) return NULL;
Config.Graphics.MultiSampling = 0;
}
#endif
assert(!window);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
if (windowKind == W_Viewport)
{
C4ViewportWindow * vw = static_cast<C4ViewportWindow *>(this);
// Cannot just use ScrolledWindow because this would just move
// the GdkWindow of the DrawingArea.
GtkWidget* table = gtk_grid_new();
render_widget = gtk_drawing_area_new();
gtk_widget_set_hexpand(GTK_WIDGET(render_widget), true);
gtk_widget_set_vexpand(GTK_WIDGET(render_widget), true);
gtk_grid_attach(GTK_GRID(table), GTK_WIDGET(render_widget), 0, 0, 1, 1);
vw->h_scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, NULL);
gtk_grid_attach(GTK_GRID(table), vw->h_scrollbar, 0, 1, 1, 1);
vw->v_scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, NULL);
gtk_grid_attach(GTK_GRID(table), vw->v_scrollbar, 1, 0, 1, 1);
GtkAdjustment* adjustment = gtk_range_get_adjustment(GTK_RANGE(vw->h_scrollbar));
g_signal_connect(
G_OBJECT(adjustment),
"value-changed",
G_CALLBACK(OnHScrollStatic),
this
);
adjustment = gtk_range_get_adjustment(GTK_RANGE(vw->v_scrollbar));
g_signal_connect(
G_OBJECT(adjustment),
"value-changed",
G_CALLBACK(OnVScrollStatic),
this
);
gtk_container_add(GTK_CONTAINER(window), table);
gtk_widget_add_events(GTK_WIDGET(window), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK | GDK_POINTER_MOTION_MASK);
gtk_drag_dest_set(GTK_WIDGET(render_widget), GTK_DEST_DEFAULT_ALL, drag_drop_entries, 1, GDK_ACTION_COPY);
g_signal_connect(G_OBJECT(render_widget), "drag-data-received", G_CALLBACK(OnDragDataReceivedStatic), this);
g_signal_connect(G_OBJECT(render_widget), "draw", G_CALLBACK(OnExposeStatic), this);
g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(OnKeyPressStatic), this);
g_signal_connect(G_OBJECT(window), "key-release-event", G_CALLBACK(OnKeyReleaseStatic), this);
g_signal_connect(G_OBJECT(window), "scroll-event", G_CALLBACK(OnScrollVW), this);
g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(OnButtonPressStatic), this);
g_signal_connect(G_OBJECT(window), "button-release-event", G_CALLBACK(OnButtonReleaseStatic), this);
g_signal_connect(G_OBJECT(window), "motion-notify-event", G_CALLBACK(OnMotionNotifyStatic), this);
g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(OnKeyPress), this);
g_signal_connect(G_OBJECT(window), "key-release-event", G_CALLBACK(OnKeyRelease), this);
g_signal_connect(G_OBJECT(window), "configure-event", G_CALLBACK(OnConfigureStatic), this);
g_signal_connect(G_OBJECT(window), "realize", G_CALLBACK(OnRealizeStatic), this);
g_signal_connect_after(G_OBJECT(render_widget), "configure-event", G_CALLBACK(OnConfigureDareaStatic), this);
#if !GTK_CHECK_VERSION(3,10,0)
// do not draw the default background
gtk_widget_set_double_buffered (GTK_WIDGET(render_widget), false);
#endif
gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(Console.window));
#if !GTK_CHECK_VERSION(3,14,0)
gtk_window_set_has_resize_grip(GTK_WINDOW(window), false);
#endif
}
else if (windowKind == W_Fullscreen)
{
render_widget = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(render_widget));
g_signal_connect(G_OBJECT(window), "configure-event", G_CALLBACK(OnConfigureNotify), this);
g_signal_connect(G_OBJECT(window), "focus-in-event", G_CALLBACK(OnFocusInFS), this);
g_signal_connect(G_OBJECT(window), "focus-out-event", G_CALLBACK(OnFocusOutFS), this);
g_signal_connect(G_OBJECT(window), "unmap-event", G_CALLBACK(OnFocusOutFS), this);
g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(OnButtonPressFS), this);
g_signal_connect(G_OBJECT(window), "button-release-event", G_CALLBACK(OnButtonRelease), this);
g_signal_connect(G_OBJECT(window), "motion-notify-event", G_CALLBACK(OnMotionNotify), this);
g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(OnKeyPress), this);
g_signal_connect(G_OBJECT(window), "key-release-event", G_CALLBACK(OnKeyRelease), this);
g_signal_connect(G_OBJECT(window), "scroll-event", G_CALLBACK(OnScroll), this);
gtk_widget_add_events(GTK_WIDGET(window), GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
#if !GTK_CHECK_VERSION(3,10,0)
gtk_widget_set_double_buffered (GTK_WIDGET(render_widget), false);
#endif
GValue val = {0,{{0}}};
g_value_init (&val, G_TYPE_BOOLEAN);
g_value_set_boolean (&val, true);
g_object_set_property (G_OBJECT (render_widget), "can-focus", &val);
g_object_set_property (G_OBJECT (window), "can-focus", &val);
g_value_unset (&val);
#if !GTK_CHECK_VERSION(3,14,0)
gtk_window_set_has_resize_grip(GTK_WINDOW(window), false);
#endif
}
else if (windowKind == W_GuiWindow)
{
render_widget = window;
g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(OnButtonPressGD), this);
g_signal_connect(G_OBJECT(window), "button-release-event", G_CALLBACK(OnButtonReleaseGD), this);
g_signal_connect(G_OBJECT(window), "motion-notify-event", G_CALLBACK(OnMotionNotifyGD), this);
g_signal_connect(G_OBJECT(window), "configure-event", G_CALLBACK(OnConfigureGD), this);
g_signal_connect(G_OBJECT(window), "scroll-event", G_CALLBACK(OnScroll), this);
gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(Console.window));
#if !GTK_CHECK_VERSION(3,14,0)
gtk_window_set_has_resize_grip(GTK_WINDOW(window), false);
#endif
}
else if (windowKind == W_Console)
{
render_widget = window;
}
assert(window);
assert(render_widget);
// Override gtk's default to match name/class of the XLib windows
gtk_window_set_wmclass(GTK_WINDOW(window), C4ENGINENAME, C4ENGINENAME);
gtk_window_set_default_size(GTK_WINDOW(window), size->Wdt, size->Hgt);
g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(OnDelete), this);
handlerDestroy = g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(OnDestroyStatic), this);
gtk_widget_add_events(GTK_WIDGET(window), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_SCROLL_MASK);
// TODO: It would be nice to support GDK_SCROLL_SMOOTH_MASK and
// smooth scrolling for scrolling in menus, however that should not
// change the scroll wheel behaviour ingame for zooming or
// inventory change. Note that when both GDK_SCROLL_MASK and
// GDK_SMOOTH_SCROLL_MASK are enabled, both type of scroll events
// are reported, so one needs to make sure to not double-process them.
// It would be nice to have smooth scrolling also e.g. for zooming
// ingame, but it probably requires the notion of smooth scrolling
// other parts of the engine as well.
#ifdef GDK_WINDOWING_X11
GdkScreen * scr = gtk_widget_get_screen(GTK_WIDGET(render_widget));
Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default());
XVisualInfo *vis_info = glXGetVisualFromFBConfig(dpy, Info);
assert(vis_info);
GdkVisual * vis = gdk_x11_screen_lookup_visual(scr, vis_info->visualid);
XFree(vis_info);
gtk_widget_set_visual(GTK_WIDGET(render_widget),vis);
#endif
gtk_widget_show_all(GTK_WIDGET(window));
// XVisualInfo vitmpl; int blub;
// vitmpl.visual = gdk_x11_visual_get_xvisual(gtk_widget_get_visual(window));
// vitmpl.visualid = XVisualIDFromVisual(vitmpl.visual);
// Info = XGetVisualInfo(dpy, VisualIDMask, &vitmpl, &blub);
// printf("%p\n", gtk_widget_get_visual(render_widget));
// Info = gdk_x11_visual_get_xvisual(gtk_widget_get_visual(render_widget));
// Default icon has been set right after gtk_init(),
// so we don't need to take care about setting the icon here.
SetTitle(Title);
GdkWindow* window_wnd = gtk_widget_get_window(GTK_WIDGET(window));
// Wait until window is mapped to get the window's XID
gtk_widget_show_now(GTK_WIDGET(window));
GdkWindow* render_gdk_wnd;
if (GTK_IS_LAYOUT(render_widget))
render_gdk_wnd = gtk_layout_get_bin_window(GTK_LAYOUT(render_widget));
else
render_gdk_wnd = gtk_widget_get_window(GTK_WIDGET(render_widget));
#ifdef GDK_WINDOWING_X11
renderwnd = GDK_WINDOW_XID(render_gdk_wnd);
#elif defined(GDK_WINDOWING_WIN32)
renderwnd = reinterpret_cast<HWND>(gdk_win32_window_get_handle(render_gdk_wnd));
#endif
// Make sure the window is shown and ready to be rendered into,
// this avoids an async X error.
gdk_flush();
if (windowKind == W_Fullscreen)
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(render_widget)),
gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR));
return this;
}
bool C4Window::ReInit(C4AbstractApp* pApp)
{
// Check whether multisampling settings was changed. If not then we
// don't need to ReInit anything.
#ifdef GDK_WINDOWING_X11
int value;
Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default());
glXGetFBConfigAttrib(dpy, Info, GLX_SAMPLES, &value);
if(value == Config.Graphics.MultiSampling) return true;
// Check whether we have a visual with the requested number of samples
GLXFBConfig new_info;
if(!FindFBConfig(Config.Graphics.MultiSampling, &new_info)) return false;
GdkScreen * scr = gtk_widget_get_screen(GTK_WIDGET(render_widget));
XVisualInfo *vis_info = glXGetVisualFromFBConfig(dpy, new_info);
assert(vis_info);
GdkVisual * vis = gdk_x11_screen_lookup_visual(scr, vis_info->visualid);
XFree(vis_info);
// Un- and re-realizing the render_widget does not work, the window
// remains hidden afterwards. So we re-create it from scratch.
gtk_widget_destroy(GTK_WIDGET(render_widget));
render_widget = gtk_drawing_area_new();
#if !GTK_CHECK_VERSION(3,10,0)
gtk_widget_set_double_buffered (GTK_WIDGET(render_widget), false);
#endif
g_object_set(G_OBJECT(render_widget), "can-focus", TRUE, NULL);
gtk_widget_set_visual(GTK_WIDGET(render_widget),vis);
Info = new_info;
// Wait until window is mapped to get the window's XID
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(render_widget));
gtk_widget_show_now(GTK_WIDGET(render_widget));
if (GTK_IS_LAYOUT(render_widget))
{
GdkWindow* bin_wnd = gtk_layout_get_bin_window(GTK_LAYOUT(render_widget));
renderwnd = GDK_WINDOW_XID(bin_wnd);
}
else
{
GdkWindow* render_wnd = gtk_widget_get_window(GTK_WIDGET(render_widget));
renderwnd = GDK_WINDOW_XID(render_wnd);
}
gdk_flush();
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(render_widget)),
gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR));
return true;
#endif
}
void C4Window::Clear()
{
if (window != NULL)
{
g_signal_handler_disconnect(window, handlerDestroy);
gtk_widget_destroy(GTK_WIDGET(window));
handlerDestroy = 0;
}
// Avoid that the base class tries to free these
renderwnd = 0;
window = NULL;
Active = false;
Info = 0;
}
void C4Window::SetSize(unsigned int width, unsigned int height)
{
gtk_window_resize(GTK_WINDOW(window), width, height);
}
bool C4Window::GetSize(C4Rect * r)
{
r->x = 0; r->y = 0;
gtk_window_get_size(GTK_WINDOW(window), &r->Wdt, &r->Hgt);
return true;
}
void C4Window::SetTitle(char const * Title)
{
gtk_window_set_title(GTK_WINDOW(window), Title);
}
void C4Window::RequestUpdate()
{
// just invoke directly
PerformUpdate();
}