window.cpp 33.9 KB
Newer Older
Graeme Gott's avatar
Graeme Gott committed
1
/*
Graeme Gott's avatar
Graeme Gott committed
2
 * Copyright (C) 2013-2020 Graeme Gott <graeme@gottcode.org>
Graeme Gott's avatar
Graeme Gott committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 * This library 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 library 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 library.  If not, see <http://www.gnu.org/licenses/>.
 */
Graeme Gott's avatar
Graeme Gott committed
17

18
#include "window.h"
Graeme Gott's avatar
Graeme Gott committed
19

20
#include "applications-page.h"
Graeme Gott's avatar
Graeme Gott committed
21
#include "category-button.h"
22
#include "command.h"
23 24
#include "favorites-page.h"
#include "launcher-view.h"
25
#include "plugin.h"
26
#include "profile-picture.h"
27
#include "recent-page.h"
Graeme Gott's avatar
Graeme Gott committed
28
#include "resize-grip.h"
29
#include "search-page.h"
Graeme Gott's avatar
Graeme Gott committed
30
#include "settings.h"
31
#include "slot.h"
32

Graeme Gott's avatar
Graeme Gott committed
33 34 35 36
#include <exo/exo.h>
#include <gdk/gdkkeysyms.h>
#include <libxfce4ui/libxfce4ui.h>

37 38
#include <ctime>

Graeme Gott's avatar
Graeme Gott committed
39 40 41 42
using namespace WhiskerMenu;

//-----------------------------------------------------------------------------

Graeme Gott's avatar
Graeme Gott committed
43
static void grab_pointer(GtkWidget* widget)
Graeme Gott's avatar
Graeme Gott committed
44
{
45 46 47 48 49
	if (wm_settings->stay_on_focus_out)
	{
		return;
	}

Graeme Gott's avatar
Graeme Gott committed
50 51
	GdkDisplay* display = gdk_display_get_default();
	GdkSeat* seat = gdk_display_get_default_seat(display);
Graeme Gott's avatar
Graeme Gott committed
52
	GdkWindow* window = gtk_widget_get_window(widget);
Graeme Gott's avatar
Graeme Gott committed
53
	gdk_seat_grab(seat, window, GDK_SEAT_CAPABILITY_ALL_POINTING, true, nullptr, nullptr, nullptr, nullptr);
Graeme Gott's avatar
Graeme Gott committed
54 55 56 57
}

static void ungrab_pointer()
{
58 59 60 61 62
	if (wm_settings->stay_on_focus_out)
	{
		return;
	}

Graeme Gott's avatar
Graeme Gott committed
63 64 65 66 67 68 69
	GdkDisplay* display = gdk_display_get_default();
	GdkSeat* seat = gdk_display_get_default_seat(display);
	gdk_seat_ungrab(seat);
}

//-----------------------------------------------------------------------------

70 71
WhiskerMenu::Window::Window(Plugin* plugin) :
	m_plugin(plugin),
Graeme Gott's avatar
Graeme Gott committed
72
	m_window(nullptr),
Graeme Gott's avatar
Graeme Gott committed
73 74
	m_search_cover(GTK_STACK_TRANSITION_TYPE_OVER_DOWN),
	m_search_uncover(GTK_STACK_TRANSITION_TYPE_UNDER_UP),
Graeme Gott's avatar
Graeme Gott committed
75
	m_sidebar_size_group(nullptr),
Graeme Gott's avatar
Graeme Gott committed
76
	m_geometry{0,0,wm_settings->menu_width,wm_settings->menu_height},
Graeme Gott's avatar
Graeme Gott committed
77
	m_layout_left(true),
78
	m_layout_bottom(true),
79
	m_layout_categories_alternate(false),
80
	m_layout_search_alternate(false),
81
	m_layout_commands_alternate(false),
82
	m_supports_alpha(false)
Graeme Gott's avatar
Graeme Gott committed
83 84 85
{
	// Create the window
	m_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
86
	gtk_widget_set_name(GTK_WIDGET(m_window), "whiskermenu-window");
87 88
	// Untranslated window title to allow window managers to identify it; not visible to users.
	gtk_window_set_title(m_window, "Whisker Menu");
Graeme Gott's avatar
Graeme Gott committed
89 90 91 92
	gtk_window_set_modal(m_window, true);
	gtk_window_set_decorated(m_window, false);
	gtk_window_set_skip_taskbar_hint(m_window, true);
	gtk_window_set_skip_pager_hint(m_window, true);
Graeme Gott's avatar
Graeme Gott committed
93
	gtk_window_set_type_hint(m_window, GDK_WINDOW_TYPE_HINT_POPUP_MENU);
Graeme Gott's avatar
Graeme Gott committed
94
	gtk_window_stick(m_window);
95
	gtk_widget_add_events(GTK_WIDGET(m_window), GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_STRUCTURE_MASK);
96 97 98
	g_signal_connect_slot(m_window, "enter-notify-event", &Window::on_enter_notify_event, this);
	g_signal_connect_slot(m_window, "leave-notify-event", &Window::on_leave_notify_event, this);
	g_signal_connect_slot(m_window, "button-press-event", &Window::on_button_press_event, this);
99
	g_signal_connect_slot(m_window, "button-release-event", &Window::on_button_release_event, this);
100 101 102
	g_signal_connect_slot(m_window, "key-press-event", &Window::on_key_press_event, this);
	g_signal_connect_slot(m_window, "key-press-event", &Window::on_key_press_event_after, this, true);
	g_signal_connect_slot(m_window, "map-event", &Window::on_map_event, this);
103
	g_signal_connect_slot(m_window, "state-flags-changed", &Window::on_state_flags_changed_event, this);
104
	g_signal_connect_slot(m_window, "configure-event", &Window::on_configure_event, this);
105
	g_signal_connect(m_window, "delete_event", G_CALLBACK(&gtk_widget_hide_on_delete), nullptr);
Graeme Gott's avatar
Graeme Gott committed
106 107

	// Create the border of the window
Graeme Gott's avatar
Graeme Gott committed
108
	GtkWidget* frame = gtk_frame_new(nullptr);
109 110
	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
	gtk_container_add(GTK_CONTAINER(m_window), frame);
Graeme Gott's avatar
Graeme Gott committed
111

112 113 114 115 116 117 118 119 120 121
	// Create window contents stack
	m_window_stack = GTK_STACK(gtk_stack_new());
	gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(m_window_stack));

	// Create loading message
	m_window_load_spinner = GTK_SPINNER(gtk_spinner_new());
	gtk_widget_set_halign(GTK_WIDGET(m_window_load_spinner), GTK_ALIGN_CENTER);
	gtk_widget_set_valign(GTK_WIDGET(m_window_load_spinner), GTK_ALIGN_CENTER);
	gtk_stack_add_named(m_window_stack, GTK_WIDGET(m_window_load_spinner), "load");

Graeme Gott's avatar
Graeme Gott committed
122
	// Create the profile picture
Graeme Gott's avatar
Graeme Gott committed
123
	m_profilepic = new ProfilePicture(this);
Graeme Gott's avatar
Graeme Gott committed
124

Graeme Gott's avatar
Graeme Gott committed
125
	// Create the username label
126 127 128 129 130 131
	const gchar* name = g_get_real_name();
	if (g_strcmp0(name, "Unknown") == 0)
	{
		name = g_get_user_name();
	}
	gchar* username = g_markup_printf_escaped("<b><big>%s</big></b>", name);
Graeme Gott's avatar
Graeme Gott committed
132
	m_username = GTK_LABEL(gtk_label_new(nullptr));
Graeme Gott's avatar
Graeme Gott committed
133
	gtk_label_set_markup(m_username, username);
Graeme Gott's avatar
Graeme Gott committed
134
	gtk_widget_set_halign(GTK_WIDGET(m_username), GTK_ALIGN_START);
Graeme Gott's avatar
Graeme Gott committed
135 136 137
	g_free(username);

	// Create action buttons
138
	for (int i = 0; i < 9; ++i)
Graeme Gott's avatar
Graeme Gott committed
139
	{
140
		m_commands_button[i] = wm_settings->command[i]->get_button();
141
		m_command_slots[i] = g_signal_connect_slot<GtkButton*>(m_commands_button[i], "clicked", &Window::hide, this);
Graeme Gott's avatar
Graeme Gott committed
142
	}
Graeme Gott's avatar
Graeme Gott committed
143

Graeme Gott's avatar
Graeme Gott committed
144
	m_resizer = new ResizeGrip(m_window);
Graeme Gott's avatar
Graeme Gott committed
145 146

	// Create search entry
Graeme Gott's avatar
Graeme Gott committed
147
	m_search_entry = GTK_ENTRY(gtk_search_entry_new());
Graeme Gott's avatar
Graeme Gott committed
148
	g_signal_connect_slot<GtkSearchEntry*>(m_search_entry, "changed", &Window::search, this);
Graeme Gott's avatar
Graeme Gott committed
149 150

	// Create favorites
Graeme Gott's avatar
Graeme Gott committed
151
	m_favorites = new FavoritesPage(this);
Graeme Gott's avatar
Graeme Gott committed
152

Graeme Gott's avatar
Graeme Gott committed
153
	GIcon* icon = g_themed_icon_new("user-bookmarks");
Graeme Gott's avatar
Graeme Gott committed
154
	m_favorites_button = new CategoryButton(icon, _("Favorites"));
Graeme Gott's avatar
Graeme Gott committed
155
	g_object_unref(icon);
156
	g_signal_connect_slot<GtkToggleButton*>(m_favorites_button->get_widget(), "toggled", &Window::favorites_toggled, this);
Graeme Gott's avatar
Graeme Gott committed
157 158

	// Create recent
Graeme Gott's avatar
Graeme Gott committed
159
	m_recent = new RecentPage(this);
Graeme Gott's avatar
Graeme Gott committed
160

Graeme Gott's avatar
Graeme Gott committed
161
	icon = g_themed_icon_new("document-open-recent");
Graeme Gott's avatar
Graeme Gott committed
162
	m_recent_button = new CategoryButton(icon, _("Recently Used"));
Graeme Gott's avatar
Graeme Gott committed
163
	g_object_unref(icon);
164 165
	m_recent_button->join_group(m_favorites_button);
	g_signal_connect_slot<GtkToggleButton*>(m_recent_button->get_widget(), "toggled", &Window::recent_toggled, this);
Graeme Gott's avatar
Graeme Gott committed
166 167 168 169 170 171 172

	// Create applications
	m_applications = new ApplicationsPage(this);

	// Create search results
	m_search_results = new SearchPage(this);

173
	// Handle default page
Graeme Gott's avatar
Graeme Gott committed
174
	if (!wm_settings->display_recent)
175 176 177 178 179 180 181 182 183 184
	{
		m_default_button = m_favorites_button;
		m_default_page = m_favorites;
	}
	else
	{
		m_default_button = m_recent_button;
		m_default_page = m_recent;
	}

Graeme Gott's avatar
Graeme Gott committed
185
	// Create box for packing children
Graeme Gott's avatar
Graeme Gott committed
186
	m_vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 6));
Graeme Gott's avatar
Graeme Gott committed
187
	gtk_container_set_border_width(GTK_CONTAINER(m_vbox), 2);
188
	gtk_stack_add_named(m_window_stack, GTK_WIDGET(m_vbox), "contents");
Graeme Gott's avatar
Graeme Gott committed
189

190
	// Create box for packing commands
Graeme Gott's avatar
Graeme Gott committed
191
	m_commands_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
Graeme Gott's avatar
Graeme Gott committed
192
	m_commands_spacer = gtk_label_new(nullptr);
Graeme Gott's avatar
Graeme Gott committed
193
	gtk_box_pack_start(m_commands_box, m_commands_spacer, true, true, 0);
Graeme Gott's avatar
Graeme Gott committed
194
	for (auto command : m_commands_button)
Graeme Gott's avatar
Graeme Gott committed
195
	{
Graeme Gott's avatar
Graeme Gott committed
196
		gtk_box_pack_start(m_commands_box, command, false, false, 0);
Graeme Gott's avatar
Graeme Gott committed
197
	}
198 199

	// Create box for packing username, commands, and resize widget
Graeme Gott's avatar
Graeme Gott committed
200
	m_title_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
Graeme Gott's avatar
Graeme Gott committed
201
	gtk_box_pack_start(m_vbox, GTK_WIDGET(m_title_box), false, false, 0);
202
	gtk_box_pack_start(m_title_box, GTK_WIDGET(m_profilepic->get_widget()), false, false, 0);
Graeme Gott's avatar
Graeme Gott committed
203
	gtk_box_pack_start(m_title_box, GTK_WIDGET(m_username), true, true, 0);
Graeme Gott's avatar
Graeme Gott committed
204
	gtk_box_pack_start(m_title_box, GTK_WIDGET(m_commands_box), false, false, 0);
Graeme Gott's avatar
Graeme Gott committed
205 206 207
	gtk_box_pack_start(m_title_box, GTK_WIDGET(m_resizer->get_widget()), false, false, 0);

	// Add search to layout
Graeme Gott's avatar
Graeme Gott committed
208
	m_search_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
209 210
	gtk_box_pack_start(m_vbox, GTK_WIDGET(m_search_box), false, true, 0);
	gtk_box_pack_start(m_search_box, GTK_WIDGET(m_search_entry), true, true, 0);
Graeme Gott's avatar
Graeme Gott committed
211 212

	// Create box for packing launcher pages and sidebar
Graeme Gott's avatar
Graeme Gott committed
213
	m_contents_stack = GTK_STACK(gtk_stack_new());
Graeme Gott's avatar
Graeme Gott committed
214
	m_contents_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
Graeme Gott's avatar
Graeme Gott committed
215 216 217
	gtk_stack_add_named(m_contents_stack, GTK_WIDGET(m_contents_box), "contents");
	gtk_stack_add_named(m_contents_stack, m_search_results->get_widget(), "search");
	gtk_box_pack_start(m_vbox, GTK_WIDGET(m_contents_stack), true, true, 0);
Graeme Gott's avatar
Graeme Gott committed
218 219

	// Create box for packing launcher pages
Graeme Gott's avatar
Graeme Gott committed
220 221 222 223 224
	m_panels_stack = GTK_STACK(gtk_stack_new());
	gtk_box_pack_start(m_contents_box, GTK_WIDGET(m_panels_stack), true, true, 0);
	gtk_stack_add_named(m_panels_stack, m_favorites->get_widget(), "favorites");
	gtk_stack_add_named(m_panels_stack, m_recent->get_widget(), "recent");
	gtk_stack_add_named(m_panels_stack, m_applications->get_widget(), "applications");
Graeme Gott's avatar
Graeme Gott committed
225 226

	// Create box for packing sidebar
Graeme Gott's avatar
Graeme Gott committed
227
	m_sidebar_buttons = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
228 229
	gtk_box_pack_start(m_sidebar_buttons, m_favorites_button->get_widget(), false, false, 0);
	gtk_box_pack_start(m_sidebar_buttons, m_recent_button->get_widget(), false, false, 0);
230
	gtk_box_pack_start(m_sidebar_buttons, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), false, false, 4);
Graeme Gott's avatar
Graeme Gott committed
231

Graeme Gott's avatar
Graeme Gott committed
232
	m_sidebar = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(nullptr, nullptr));
Graeme Gott's avatar
Graeme Gott committed
233 234 235
	gtk_box_pack_start(m_contents_box, GTK_WIDGET(m_sidebar), false, false, 0);
	gtk_scrolled_window_set_shadow_type(m_sidebar, GTK_SHADOW_NONE);
	gtk_scrolled_window_set_policy(m_sidebar, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
236
	gtk_container_add(GTK_CONTAINER(m_sidebar), GTK_WIDGET(m_sidebar_buttons));
Graeme Gott's avatar
Graeme Gott committed
237

Graeme Gott's avatar
Graeme Gott committed
238
	// Show widgets
239
	gtk_widget_show_all(frame);
240
	m_default_button->set_active(true);
Graeme Gott's avatar
Graeme Gott committed
241 242 243 244

	// Resize to last known size
	gtk_window_set_default_size(m_window, m_geometry.width, m_geometry.height);

245 246
	// Handle transparency
	gtk_widget_set_app_paintable(GTK_WIDGET(m_window), true);
Graeme Gott's avatar
Graeme Gott committed
247
	g_signal_connect_slot(m_window, "draw", &Window::on_draw_event, this);
248
	g_signal_connect_slot(m_window, "screen-changed", &Window::on_screen_changed_event, this);
Graeme Gott's avatar
Graeme Gott committed
249
	on_screen_changed_event(GTK_WIDGET(m_window), nullptr);
250

Graeme Gott's avatar
Graeme Gott committed
251
	// Load applications
252
	m_applications->load();
Graeme Gott's avatar
Graeme Gott committed
253

Graeme Gott's avatar
Graeme Gott committed
254 255 256 257 258
	g_object_ref_sink(m_window);
}

//-----------------------------------------------------------------------------

259
WhiskerMenu::Window::~Window()
Graeme Gott's avatar
Graeme Gott committed
260
{
261
	for (int i = 0; i < 9; ++i)
262 263 264 265 266
	{
		g_signal_handler_disconnect(m_commands_button[i], m_command_slots[i]);
		gtk_container_remove(GTK_CONTAINER(m_commands_box), m_commands_button[i]);
	}

Graeme Gott's avatar
Graeme Gott committed
267 268 269 270 271
	delete m_applications;
	delete m_search_results;
	delete m_recent;
	delete m_favorites;

272
	delete m_profilepic;
Graeme Gott's avatar
Graeme Gott committed
273
	delete m_resizer;
274

275 276 277
	delete m_favorites_button;
	delete m_recent_button;

278
	gtk_widget_destroy(GTK_WIDGET(m_window));
Graeme Gott's avatar
Graeme Gott committed
279 280 281 282 283
	g_object_unref(m_window);
}

//-----------------------------------------------------------------------------

284
void WhiskerMenu::Window::hide()
Graeme Gott's avatar
Graeme Gott committed
285
{
Graeme Gott's avatar
Graeme Gott committed
286
	ungrab_pointer();
Graeme Gott's avatar
Graeme Gott committed
287

288 289 290 291
	// Scroll categories to top
	GtkAdjustment* adjustment = gtk_scrolled_window_get_vadjustment(m_sidebar);
	gtk_adjustment_set_value(adjustment, gtk_adjustment_get_lower(adjustment));

292 293 294
	// Reset any pressed category buttons
	unset_pressed_category();

295
	// Hide command buttons to remove active border
Graeme Gott's avatar
Graeme Gott committed
296
	for (auto command : m_commands_button)
297
	{
Graeme Gott's avatar
Graeme Gott committed
298
		gtk_widget_set_visible(command, false);
299 300
	}

Graeme Gott's avatar
Graeme Gott committed
301 302 303
	// Hide window
	gtk_widget_hide(GTK_WIDGET(m_window));

304 305
	// Switch back to default page
	show_default_page();
Graeme Gott's avatar
Graeme Gott committed
306 307 308 309
}

//-----------------------------------------------------------------------------

310
void WhiskerMenu::Window::show(const Position position)
Graeme Gott's avatar
Graeme Gott committed
311
{
312 313 314 315 316 317
	// Handle switching view types
	m_search_results->update_view();
	m_favorites->update_view();
	m_recent->update_view();
	m_applications->update_view();

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	// Handle showing tooltips
	if (wm_settings->launcher_show_tooltip)
	{
		m_search_results->get_view()->show_tooltips();
		m_favorites->get_view()->show_tooltips();
		m_recent->get_view()->show_tooltips();
		m_applications->get_view()->show_tooltips();
	}
	else
	{
		m_search_results->get_view()->hide_tooltips();
		m_favorites->get_view()->hide_tooltips();
		m_recent->get_view()->hide_tooltips();
		m_applications->get_view()->hide_tooltips();
	}
333
	m_profilepic->reset_tooltip();
334

Graeme Gott's avatar
Graeme Gott committed
335
	// Make sure commands are valid and visible
Graeme Gott's avatar
Graeme Gott committed
336
	for (auto command : wm_settings->command)
Graeme Gott's avatar
Graeme Gott committed
337
	{
Graeme Gott's avatar
Graeme Gott committed
338
		command->check();
Graeme Gott's avatar
Graeme Gott committed
339
	}
Graeme Gott's avatar
Graeme Gott committed
340

341 342 343
	// Make sure recent item count is within max
	m_recent->enforce_item_count();

Graeme Gott's avatar
Graeme Gott committed
344 345 346
	// Make sure recent button is only visible when tracked
	gtk_widget_set_visible(m_recent_button->get_widget(), wm_settings->recent_items_max);

347
	// Make sure applications list is current; does nothing unless list has changed
348
	if (m_applications->load())
349 350 351 352 353
	{
		set_loaded();
	}
	else
	{
354
		m_plugin->set_loaded(false);
355 356 357
		gtk_stack_set_visible_child_name(m_window_stack, "load");
		gtk_spinner_start(m_window_load_spinner);
	}
358

359
	// Update default page
Graeme Gott's avatar
Graeme Gott committed
360
	if (wm_settings->display_recent)
361 362 363 364
	{
		m_default_button = m_recent_button;
		m_default_page = m_recent;
	}
Graeme Gott's avatar
Graeme Gott committed
365
	else
366 367 368 369 370 371
	{
		m_default_button = m_favorites_button;
		m_default_page = m_favorites;
	}
	show_default_page();

372 373 374 375 376 377 378 379 380 381
	// Make sure icon sizes are correct
	m_favorites_button->reload_icon_size();
	m_recent_button->reload_icon_size();
	m_applications->reload_category_icon_size();

	m_search_results->get_view()->reload_icon_size();
	m_favorites->get_view()->reload_icon_size();
	m_recent->get_view()->reload_icon_size();
	m_applications->get_view()->reload_icon_size();

Graeme Gott's avatar
Graeme Gott committed
382
	GdkScreen* screen = nullptr;
383
	int parent_x = 0, parent_y = 0, parent_w = 0, parent_h = 0;
384
	if (position != PositionAtCursor)
385
	{
386 387
		// Wait up to half a second for auto-hidden panels to be shown
		clock_t end = clock() + (CLOCKS_PER_SEC / 2);
388
		GtkWidget* parent = m_plugin->get_button();
389 390 391
		GtkWindow* parent_window = GTK_WINDOW(gtk_widget_get_toplevel(parent));
		gtk_window_get_position(parent_window, &parent_x, &parent_y);
		while ((parent_x == -9999) && (parent_y == -9999) && (clock() < end))
392
		{
393 394 395 396 397
			while (gtk_events_pending())
			{
				gtk_main_iteration();
			}
			gtk_window_get_position(parent_window, &parent_x, &parent_y);
398 399
		}

400 401 402 403 404 405 406 407 408 409 410 411
		// Fetch parent geometry
		if (!gtk_widget_get_realized(parent))
		{
			gtk_widget_realize(parent);
		}
		GdkWindow* window = gtk_widget_get_window(parent);
		gdk_window_get_origin(window, &parent_x, &parent_y);
		screen = gdk_window_get_screen(window);
		parent_w = gdk_window_get_width(window);
		parent_h = gdk_window_get_height(window);
	}
	else
412
	{
413
		GdkDisplay* display = gdk_display_get_default();
414 415 416
		GdkSeat* seat = gdk_display_get_default_seat(display);
		GdkDevice* device = gdk_seat_get_pointer(seat);
		gdk_device_get_position(device, &screen, &parent_x, &parent_y);
417
	}
Graeme Gott's avatar
Graeme Gott committed
418 419

	// Fetch screen geomtry
Graeme Gott's avatar
Graeme Gott committed
420
	GdkRectangle monitor;
Graeme Gott's avatar
Graeme Gott committed
421 422
	GdkMonitor* monitor_gdk = gdk_display_get_monitor_at_point(gdk_display_get_default(), parent_x, parent_y);
	gdk_monitor_get_geometry(monitor_gdk, &monitor);
Graeme Gott's avatar
Graeme Gott committed
423

424 425
	// Prevent window from being larger than screen
	if (m_geometry.width > monitor.width)
Graeme Gott's avatar
Graeme Gott committed
426
	{
427 428
		m_geometry.width = monitor.width;
		gtk_window_resize(m_window, m_geometry.width, m_geometry.height);
Graeme Gott's avatar
Graeme Gott committed
429
	}
430
	if (m_geometry.height > monitor.height)
Graeme Gott's avatar
Graeme Gott committed
431
	{
432 433
		m_geometry.height = monitor.height;
		gtk_window_resize(m_window, m_geometry.width, m_geometry.height);
Graeme Gott's avatar
Graeme Gott committed
434 435
	}

436 437
	// Find window position
	bool layout_left = ((2 * (parent_x - monitor.x)) + parent_w) < monitor.width;
Graeme Gott's avatar
Graeme Gott committed
438
	bool layout_bottom = ((2 * (parent_y - monitor.y)) + (parent_h / 2)) > monitor.height;
439
	if (position != PositionVertical)
Graeme Gott's avatar
Graeme Gott committed
440
	{
441
		m_geometry.x = layout_left ? parent_x : (parent_x + parent_w - m_geometry.width);
Graeme Gott's avatar
Graeme Gott committed
442 443 444 445
		m_geometry.y = layout_bottom ? (parent_y - m_geometry.height) : (parent_y + parent_h);
	}
	else
	{
446
		m_geometry.x = layout_left ? (parent_x + parent_w) : (parent_x - m_geometry.width);
Graeme Gott's avatar
Graeme Gott committed
447 448 449 450
		m_geometry.y = layout_bottom ? (parent_y + parent_h - m_geometry.height) : parent_y;
	}

	// Prevent window from leaving screen
451 452
	m_geometry.x = CLAMP(m_geometry.x, monitor.x, monitor.x + monitor.width - m_geometry.width);
	m_geometry.y = CLAMP(m_geometry.y, monitor.y, monitor.y + monitor.height - m_geometry.height);
Graeme Gott's avatar
Graeme Gott committed
453 454 455 456 457 458 459 460 461

	// Move window
	gtk_window_move(m_window, m_geometry.x, m_geometry.y);

	// Set corner for resizer
	if (layout_left)
	{
		if (layout_bottom)
		{
Graeme Gott's avatar
Graeme Gott committed
462
			m_resizer->set_corner(ResizeGrip::TopRight);
Graeme Gott's avatar
Graeme Gott committed
463 464 465
		}
		else
		{
Graeme Gott's avatar
Graeme Gott committed
466
			m_resizer->set_corner(ResizeGrip::BottomRight);
Graeme Gott's avatar
Graeme Gott committed
467 468 469 470 471 472
		}
	}
	else
	{
		if (layout_bottom)
		{
Graeme Gott's avatar
Graeme Gott committed
473
			m_resizer->set_corner(ResizeGrip::TopLeft);
Graeme Gott's avatar
Graeme Gott committed
474 475 476
		}
		else
		{
Graeme Gott's avatar
Graeme Gott committed
477
			m_resizer->set_corner(ResizeGrip::BottomLeft);
Graeme Gott's avatar
Graeme Gott committed
478 479 480 481 482 483 484 485
		}
	}

	// Relayout window if necessary
	if (gtk_widget_get_direction(GTK_WIDGET(m_window)) == GTK_TEXT_DIR_RTL)
	{
		layout_left = !layout_left;
	}
486

487 488 489 490 491
	if ((m_layout_left != layout_left)
			|| (m_layout_bottom != layout_bottom)
			|| (m_layout_categories_alternate != wm_settings->position_categories_alternate)
			|| (m_layout_search_alternate != wm_settings->position_search_alternate)
			|| (m_layout_commands_alternate != wm_settings->position_commands_alternate))
492
	{
493 494 495 496 497 498
		m_layout_left = layout_left;
		m_layout_bottom = layout_bottom;
		m_layout_categories_alternate = wm_settings->position_categories_alternate;
		m_layout_search_alternate = wm_settings->position_search_alternate;
		m_layout_commands_alternate = wm_settings->position_commands_alternate;
		update_layout();
499 500
	}

501
	if (!m_sidebar_size_group && m_layout_commands_alternate && wm_settings->category_show_name)
502
	{
503 504 505
		m_sidebar_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
		gtk_size_group_add_widget(m_sidebar_size_group, GTK_WIDGET(m_sidebar));
		gtk_size_group_add_widget(m_sidebar_size_group, GTK_WIDGET(m_commands_box));
506
	}
507
	else if (m_sidebar_size_group && (!m_layout_commands_alternate || !wm_settings->category_show_name))
508
	{
509 510 511
		gtk_size_group_remove_widget(m_sidebar_size_group, GTK_WIDGET(m_sidebar));
		gtk_size_group_remove_widget(m_sidebar_size_group, GTK_WIDGET(m_commands_box));
		g_object_unref(m_sidebar_size_group);
Graeme Gott's avatar
Graeme Gott committed
512
		m_sidebar_size_group = nullptr;
Graeme Gott's avatar
Graeme Gott committed
513 514 515 516 517 518 519 520 521
	}

	// Show window
	gtk_widget_show(GTK_WIDGET(m_window));
	gtk_window_move(m_window, m_geometry.x, m_geometry.y);
}

//-----------------------------------------------------------------------------

522
void WhiskerMenu::Window::save()
Graeme Gott's avatar
Graeme Gott committed
523
{
Graeme Gott's avatar
Graeme Gott committed
524 525 526 527 528 529 530 531 532 533
	if (wm_settings->menu_width != m_geometry.width)
	{
		wm_settings->menu_width = m_geometry.width;
		wm_settings->set_modified();
	}
	if (wm_settings->menu_height != m_geometry.height)
	{
		wm_settings->menu_height = m_geometry.height;
		wm_settings->set_modified();
	}
Graeme Gott's avatar
Graeme Gott committed
534 535 536 537
}

//-----------------------------------------------------------------------------

538
void WhiskerMenu::Window::on_context_menu_destroyed()
539
{
Graeme Gott's avatar
Graeme Gott committed
540
	grab_pointer(GTK_WIDGET(m_window));
541 542 543 544
}

//-----------------------------------------------------------------------------

Graeme Gott's avatar
Graeme Gott committed
545
void WhiskerMenu::Window::set_categories(const std::vector<CategoryButton*>& categories)
Graeme Gott's avatar
Graeme Gott committed
546
{
Graeme Gott's avatar
Graeme Gott committed
547
	CategoryButton* last_button = m_recent_button;
Graeme Gott's avatar
Graeme Gott committed
548
	for (auto button : categories)
Graeme Gott's avatar
Graeme Gott committed
549
	{
Graeme Gott's avatar
Graeme Gott committed
550 551
		button->join_group(last_button);
		last_button = button;
552 553
		gtk_box_pack_start(m_sidebar_buttons, button->get_widget(), false, false, 0);
		g_signal_connect_slot<GtkToggleButton*>(button->get_widget(), "toggled", &Window::category_toggled, this);
Graeme Gott's avatar
Graeme Gott committed
554 555
	}

556 557 558
	// Position "All Applications" above divider
	if (!categories.empty())
	{
559
		gtk_box_reorder_child(m_sidebar_buttons, categories.front()->get_widget(), 2);
560 561
	}

562
	show_default_page();
Graeme Gott's avatar
Graeme Gott committed
563 564 565 566
}

//-----------------------------------------------------------------------------

567
void WhiskerMenu::Window::set_items()
Graeme Gott's avatar
Graeme Gott committed
568
{
569
	m_search_results->set_menu_items();
570 571
	m_favorites->set_menu_items();
	m_recent->set_menu_items();
Graeme Gott's avatar
Graeme Gott committed
572 573 574

	// Handle switching to favorites are added
	GtkTreeModel* favorites_model = m_favorites->get_view()->get_model();
575
	g_signal_connect_slot<GtkTreeModel*, GtkTreePath*, GtkTreeIter*>(favorites_model, "row-inserted", &Window::show_favorites, this);
Graeme Gott's avatar
Graeme Gott committed
576 577 578
}

//-----------------------------------------------------------------------------
579

580 581 582 583
void WhiskerMenu::Window::set_loaded()
{
	// Hide loading spinner
	gtk_spinner_stop(m_window_load_spinner);
584
	gtk_stack_set_visible_child_name(m_window_stack, "contents");
585 586 587

	// Focus search entry
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));
588 589 590

	// Show panel button
	m_plugin->set_loaded(true);
591 592 593

	// Check in case of plugin reload
	check_scrollbar_needed();
594 595 596 597
}

//-----------------------------------------------------------------------------

598
void WhiskerMenu::Window::unset_items()
Graeme Gott's avatar
Graeme Gott committed
599 600 601 602 603 604 605 606
{
	m_search_results->unset_menu_items();
	m_favorites->unset_menu_items();
	m_recent->unset_menu_items();
}

//-----------------------------------------------------------------------------

607
gboolean WhiskerMenu::Window::on_enter_notify_event(GtkWidget*, GdkEvent* event)
Graeme Gott's avatar
Graeme Gott committed
608
{
609 610 611 612
	GdkEventCrossing* crossing_event = reinterpret_cast<GdkEventCrossing*>(event);
	if ((crossing_event->detail == GDK_NOTIFY_INFERIOR)
			|| (crossing_event->mode == GDK_CROSSING_GRAB)
			|| (crossing_event->mode == GDK_CROSSING_GTK_GRAB))
613 614 615 616
	{
		return false;
	}

Graeme Gott's avatar
Graeme Gott committed
617
	grab_pointer(GTK_WIDGET(m_window));
Graeme Gott's avatar
Graeme Gott committed
618 619 620 621 622 623

	return false;
}

//-----------------------------------------------------------------------------

624
gboolean WhiskerMenu::Window::on_leave_notify_event(GtkWidget*, GdkEvent* event)
Graeme Gott's avatar
Graeme Gott committed
625
{
626 627 628
	GdkEventCrossing* crossing_event = reinterpret_cast<GdkEventCrossing*>(event);
	if ((crossing_event->detail == GDK_NOTIFY_INFERIOR)
			|| (crossing_event->mode != GDK_CROSSING_NORMAL))
Graeme Gott's avatar
Graeme Gott committed
629 630 631 632
	{
		return false;
	}

Graeme Gott's avatar
Graeme Gott committed
633
	grab_pointer(GTK_WIDGET(m_window));
Graeme Gott's avatar
Graeme Gott committed
634 635 636 637 638 639

	return false;
}

//-----------------------------------------------------------------------------

640
gboolean WhiskerMenu::Window::on_button_press_event(GtkWidget*, GdkEvent* event)
Graeme Gott's avatar
Graeme Gott committed
641
{
642 643 644 645 646
	if (wm_settings->stay_on_focus_out)
	{
		return false;
	}

Graeme Gott's avatar
Graeme Gott committed
647
	// Hide menu if user clicks outside
648 649 650 651 652
	GdkEventButton* button_event = reinterpret_cast<GdkEventButton*>(event);
	if ((button_event->x_root <= m_geometry.x)
			|| (button_event->x_root >= m_geometry.x + m_geometry.width)
			|| (button_event->y_root <= m_geometry.y)
			|| (button_event->y_root >= m_geometry.y + m_geometry.height))
Graeme Gott's avatar
Graeme Gott committed
653 654 655 656 657 658 659 660
	{
		hide();
	}
	return false;
}

//-----------------------------------------------------------------------------

661 662 663 664 665 666 667 668
gboolean WhiskerMenu::Window::on_button_release_event(GtkWidget*, GdkEvent*)
{
	unset_pressed_category();
	return false;
}

//-----------------------------------------------------------------------------

669
gboolean WhiskerMenu::Window::on_key_press_event(GtkWidget* widget, GdkEvent* event)
Graeme Gott's avatar
Graeme Gott committed
670
{
671 672
	GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event);

Graeme Gott's avatar
Graeme Gott committed
673
	// Hide if escape is pressed and there is no text in search entry
Graeme Gott's avatar
Graeme Gott committed
674
	if ( (key_event->keyval == GDK_KEY_Escape) && exo_str_is_empty(gtk_entry_get_text(m_search_entry)) )
Graeme Gott's avatar
Graeme Gott committed
675 676 677 678 679
	{
		hide();
		return true;
	}

Graeme Gott's avatar
Graeme Gott committed
680
	Page* page = nullptr;
Graeme Gott's avatar
Graeme Gott committed
681
	if (gtk_stack_get_visible_child(m_contents_stack) == m_search_results->get_widget())
Graeme Gott's avatar
Graeme Gott committed
682
	{
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
		page = m_search_results;
	}
	else if (m_favorites_button->get_active())
	{
		page = m_favorites;
	}
	else if (m_recent_button->get_active())
	{
		page = m_recent;
	}
	else
	{
		page = m_applications;
	}
	GtkWidget* view = page->get_view()->get_widget();

	// Allow keyboard navigation out of treeview
	if ((key_event->keyval == GDK_KEY_Left) || (key_event->keyval == GDK_KEY_Right))
	{
702
		if (GTK_IS_TREE_VIEW(view) && ((widget == view) || (gtk_window_get_focus(m_window) == view)))
Graeme Gott's avatar
Graeme Gott committed
703
		{
704
			gtk_widget_grab_focus(m_favorites_button->get_widget());
705
			page->reset_selection();
706
		}
707
	}
708

709 710 711 712 713
	// Make up and down keys scroll current list of applications from search
	if ((key_event->keyval == GDK_KEY_Up) || (key_event->keyval == GDK_KEY_Down))
	{
		GtkWidget* search = GTK_WIDGET(m_search_entry);
		if ((widget == search) || (gtk_window_get_focus(m_window) == search))
714
		{
715 716 717 718 719
			GtkTreePath* path = page->get_view()->get_cursor();
			if (path)
			{
				page->get_view()->select_path(path);
			}
720
			gtk_widget_grab_focus(view);
721
			return true;
Graeme Gott's avatar
Graeme Gott committed
722 723 724
		}
	}

725 726 727 728 729
	return false;
}

//-----------------------------------------------------------------------------

730
gboolean WhiskerMenu::Window::on_key_press_event_after(GtkWidget* widget, GdkEvent* event)
731 732
{
	// Pass unhandled key presses to search entry
Graeme Gott's avatar
Graeme Gott committed
733
	GtkWidget* search_entry = GTK_WIDGET(m_search_entry);
734
	if ((widget != search_entry) && (gtk_window_get_focus(m_window) != search_entry))
Graeme Gott's avatar
Graeme Gott committed
735
	{
736 737 738 739 740
		GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event);
		if (key_event->is_modifier)
		{
			return false;
		}
Graeme Gott's avatar
Graeme Gott committed
741
		gtk_widget_grab_focus(search_entry);
742
		gtk_window_propagate_key_event(m_window, key_event);
743
		return true;
Graeme Gott's avatar
Graeme Gott committed
744 745 746 747 748 749
	}
	return false;
}

//-----------------------------------------------------------------------------

750
gboolean WhiskerMenu::Window::on_map_event(GtkWidget*, GdkEvent*)
Graeme Gott's avatar
Graeme Gott committed
751
{
752 753
	m_favorites->reset_selection();

Graeme Gott's avatar
Graeme Gott committed
754 755 756
	gtk_window_set_keep_above(m_window, true);

	// Track mouse clicks outside of menu
Graeme Gott's avatar
Graeme Gott committed
757
	grab_pointer(GTK_WIDGET(m_window));
Graeme Gott's avatar
Graeme Gott committed
758 759 760 761 762 763 764 765 766

	// Focus search entry
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));

	return false;
}

//-----------------------------------------------------------------------------

767 768 769 770 771 772 773 774 775 776 777 778 779
gboolean WhiskerMenu::Window::on_state_flags_changed_event(GtkWidget* widget, GtkStateFlags)
{
	// Refocus and raise window if visible
	if (gtk_widget_get_visible(widget))
	{
		gtk_window_present(m_window);
	}

	return false;
}

//-----------------------------------------------------------------------------

780
gboolean WhiskerMenu::Window::on_configure_event(GtkWidget*, GdkEvent* event)
Graeme Gott's avatar
Graeme Gott committed
781
{
782 783
	GdkEventConfigure* configure_event = reinterpret_cast<GdkEventConfigure*>(event);
	if (configure_event->width && configure_event->height)
Graeme Gott's avatar
Graeme Gott committed
784
	{
785 786 787 788
		m_geometry.x = configure_event->x;
		m_geometry.y = configure_event->y;
		m_geometry.width = configure_event->width;
		m_geometry.height = configure_event->height;
Graeme Gott's avatar
Graeme Gott committed
789
	}
790 791 792

	check_scrollbar_needed();

Graeme Gott's avatar
Graeme Gott committed
793 794 795 796 797
	return false;
}

//-----------------------------------------------------------------------------

798
void WhiskerMenu::Window::on_screen_changed_event(GtkWidget* widget, GdkScreen*)
799 800
{
	GdkScreen* screen = gtk_widget_get_screen(widget);
Graeme Gott's avatar
Graeme Gott committed
801 802
	GdkVisual* visual = gdk_screen_get_rgba_visual(screen);
	if (!visual || (wm_settings->menu_opacity == 100))
803
	{
Graeme Gott's avatar
Graeme Gott committed
804
		visual = gdk_screen_get_system_visual(screen);
805
		m_supports_alpha = false;
806 807 808 809 810
	}
	else
	{
		m_supports_alpha = true;
	}
Graeme Gott's avatar
Graeme Gott committed
811
	gtk_widget_set_visual(widget, visual);
812 813 814 815
}

//-----------------------------------------------------------------------------

Graeme Gott's avatar
Graeme Gott committed
816
gboolean WhiskerMenu::Window::on_draw_event(GtkWidget* widget, cairo_t* cr)
817 818 819 820 821 822
{
	if (!gtk_widget_get_realized(widget))
	{
		gtk_widget_realize(widget);
	}

823 824 825
	GtkStyleContext* context = gtk_widget_get_style_context(widget);
	const double width = gtk_widget_get_allocated_width(widget);
	const double height = gtk_widget_get_allocated_height(widget);
826 827 828

	if (m_supports_alpha)
	{
829 830 831 832 833 834 835 836 837 838 839
		cairo_surface_t* background = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
		cairo_t* cr_background = cairo_create(background);
		cairo_set_operator(cr_background, CAIRO_OPERATOR_SOURCE);
		gtk_render_background(context, cr_background, 0.0, 0.0, width, height);
		cairo_destroy(cr_background);

		cairo_set_source_surface(cr, background, 0.0, 0.0);
		cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
		cairo_paint_with_alpha(cr, wm_settings->menu_opacity / 100.0);

		cairo_surface_destroy(background);
840 841 842
	}
	else
	{
843
		gtk_render_background(context, cr, 0.0, 0.0, width, height);
844 845 846 847 848 849 850
	}

	return false;
}

//-----------------------------------------------------------------------------

851 852
void WhiskerMenu::Window::check_scrollbar_needed()
{
853
	// Find height of sidebar buttons
854
	int height = 0;
Graeme Gott's avatar
Graeme Gott committed
855
	gtk_widget_get_preferred_height(GTK_WIDGET(m_sidebar_buttons), nullptr, &height);
856

857 858 859
	// Always show scrollbar if sidebar is shorter than buttons
	int allocated = gtk_widget_get_allocated_height(GTK_WIDGET(m_sidebar));
	if ((allocated > height) || (allocated == 1))
860 861 862 863 864 865 866 867 868 869 870
	{
		gtk_scrolled_window_set_policy(m_sidebar, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	}
	else
	{
		gtk_scrolled_window_set_policy(m_sidebar, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
	}
}

//-----------------------------------------------------------------------------

871
void WhiskerMenu::Window::favorites_toggled()
Graeme Gott's avatar
Graeme Gott committed
872
{
873
	m_favorites->reset_selection();
Graeme Gott's avatar
Graeme Gott committed
874
	gtk_stack_set_visible_child_name(m_panels_stack, "favorites");
875
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));
Graeme Gott's avatar
Graeme Gott committed
876 877 878 879
}

//-----------------------------------------------------------------------------

880
void WhiskerMenu::Window::recent_toggled()
Graeme Gott's avatar
Graeme Gott committed
881
{
882
	m_recent->reset_selection();
Graeme Gott's avatar
Graeme Gott committed
883
	gtk_stack_set_visible_child_name(m_panels_stack, "recent");
884
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));
Graeme Gott's avatar
Graeme Gott committed
885 886 887 888
}

//-----------------------------------------------------------------------------

889
void WhiskerMenu::Window::category_toggled()
Graeme Gott's avatar
Graeme Gott committed
890
{
891
	m_applications->reset_selection();
Graeme Gott's avatar
Graeme Gott committed
892
	gtk_stack_set_visible_child_name(m_panels_stack, "applications");
893
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));
Graeme Gott's avatar
Graeme Gott committed
894 895 896 897
}

//-----------------------------------------------------------------------------

898
void WhiskerMenu::Window::show_favorites()
Graeme Gott's avatar
Graeme Gott committed
899 900
{
	// Switch to favorites panel
Graeme Gott's avatar
Graeme Gott committed
901
	m_favorites_button->set_active(true);
Graeme Gott's avatar
Graeme Gott committed
902 903 904 905 906 907 908 909

	// Clear search entry
	gtk_entry_set_text(m_search_entry, "");
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));
}

//-----------------------------------------------------------------------------

910
void WhiskerMenu::Window::show_default_page()
911 912 913 914 915 916 917 918 919 920 921
{
	// Switch to favorites panel
	m_default_button->set_active(true);

	// Clear search entry
	gtk_entry_set_text(m_search_entry, "");
	gtk_widget_grab_focus(GTK_WIDGET(m_search_entry));
}

//-----------------------------------------------------------------------------

922
void WhiskerMenu::Window::search()
Graeme Gott's avatar
Graeme Gott committed
923 924 925
{
	// Fetch search string
	const gchar* text = gtk_entry_get_text(m_search_entry);
926
	if (exo_str_is_empty(text))
Graeme Gott's avatar
Graeme Gott committed
927
	{
Graeme Gott's avatar
Graeme Gott committed
928
		text = nullptr;
Graeme Gott's avatar
Graeme Gott committed
929 930
	}

Graeme Gott's avatar
Graeme Gott committed
931
	if (text)
Graeme Gott's avatar
Graeme Gott committed
932 933
	{
		// Show search results
Graeme Gott's avatar
Graeme Gott committed
934
		gtk_stack_set_visible_child_full(m_contents_stack, "search", m_search_cover);
Graeme Gott's avatar
Graeme Gott committed
935 936 937 938
	}
	else
	{
		// Show active panel
Graeme Gott's avatar
Graeme Gott committed
939
		gtk_stack_set_visible_child_full(m_contents_stack, "contents", m_search_uncover);
Graeme Gott's avatar
Graeme Gott committed
940 941 942
	}

	// Apply filter
943
	m_search_results->set_filter(text);
Graeme Gott's avatar
Graeme Gott committed
944 945 946
}

//-----------------------------------------------------------------------------
947 948 949 950 951 952 953 954 955

void WhiskerMenu::Window::unset_pressed_category()
{
	// Force a state change on sidebar buttons
	gtk_widget_set_sensitive(GTK_WIDGET(m_sidebar_buttons), false);
	gtk_widget_set_sensitive(GTK_WIDGET(m_sidebar_buttons), true);
}

//-----------------------------------------------------------------------------
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064