solvespace/src/platform/gtkmain.cpp

1467 lines
42 KiB
C++
Raw Normal View History

//-----------------------------------------------------------------------------
// Our main() function, and GTK2/3-specific stuff to set up our windows and
// otherwise handle our interface to the operating system. Everything
// outside platform/... should be standard C++ and OpenGL.
//
// Copyright 2015 <whitequark@whitequark.org>
//-----------------------------------------------------------------------------
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <json-c/json_object.h>
#include <json-c/json_util.h>
#include <glibmm/main.h>
2015-12-30 22:59:04 +08:00
#include <glibmm/convert.h>
#include <giomm/file.h>
#include <gdkmm/cursor.h>
#include <gtkmm/drawingarea.h>
#include <gtkmm/glarea.h>
#include <gtkmm/scrollbar.h>
#include <gtkmm/entry.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/hvbox.h>
#include <gtkmm/fixed.h>
#include <gtkmm/adjustment.h>
#include <gtkmm/separatormenuitem.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/checkmenuitem.h>
#include <gtkmm/radiomenuitem.h>
#include <gtkmm/radiobuttongroup.h>
#include <gtkmm/menu.h>
#include <gtkmm/menubar.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/main.h>
#include <cairomm/xlib_surface.h>
#include <pangomm/fontdescription.h>
#include <gdk/gdkx.h>
#include <fontconfig/fontconfig.h>
#include <GL/glx.h>
#include "solvespace.h"
#include "config.h"
#ifdef HAVE_SPACEWARE
#include <spnav.h>
#endif
namespace SolveSpace {
/* Utility functions */
std::string Title(const std::string &s) {
return "SolveSpace - " + s;
}
/* Settings */
/* Why not just use GSettings? Two reasons. It doesn't allow to easily see
whether the setting had the default value, and it requires to install
a schema globally. */
static json_object *settings = NULL;
static std::string CnfPrepare() {
// Refer to http://standards.freedesktop.org/basedir-spec/latest/
std::string dir;
if(getenv("XDG_CONFIG_HOME")) {
dir = std::string(getenv("XDG_CONFIG_HOME")) + "/solvespace";
} else if(getenv("HOME")) {
dir = std::string(getenv("HOME")) + "/.config/solvespace";
} else {
dbp("neither XDG_CONFIG_HOME nor HOME are set");
return "";
}
struct stat st;
if(stat(dir.c_str(), &st)) {
if(errno == ENOENT) {
if(mkdir(dir.c_str(), 0777)) {
dbp("cannot mkdir %s: %s", dir.c_str(), strerror(errno));
return "";
}
} else {
dbp("cannot stat %s: %s", dir.c_str(), strerror(errno));
return "";
}
} else if(!S_ISDIR(st.st_mode)) {
dbp("%s is not a directory", dir.c_str());
return "";
}
return dir + "/settings.json";
}
static void CnfLoad() {
std::string path = CnfPrepare();
if(path.empty())
return;
if(settings)
json_object_put(settings); // deallocate
settings = json_object_from_file(path.c_str());
if(!settings) {
if(errno != ENOENT)
dbp("cannot load settings: %s", strerror(errno));
settings = json_object_new_object();
}
}
static void CnfSave() {
std::string path = CnfPrepare();
if(path.empty())
return;
/* json-c <0.12 has the first argument non-const here */
if(json_object_to_file_ext((char*) path.c_str(), settings, JSON_C_TO_STRING_PRETTY))
dbp("cannot save settings: %s", strerror(errno));
}
void CnfFreezeInt(uint32_t val, const std::string &key) {
struct json_object *jval = json_object_new_int(val);
json_object_object_add(settings, key.c_str(), jval);
CnfSave();
}
uint32_t CnfThawInt(uint32_t val, const std::string &key) {
struct json_object *jval;
if(json_object_object_get_ex(settings, key.c_str(), &jval))
return json_object_get_int(jval);
else return val;
}
void CnfFreezeFloat(float val, const std::string &key) {
struct json_object *jval = json_object_new_double(val);
json_object_object_add(settings, key.c_str(), jval);
CnfSave();
}
float CnfThawFloat(float val, const std::string &key) {
struct json_object *jval;
if(json_object_object_get_ex(settings, key.c_str(), &jval))
return json_object_get_double(jval);
else return val;
}
void CnfFreezeString(const std::string &val, const std::string &key) {
struct json_object *jval = json_object_new_string(val.c_str());
json_object_object_add(settings, key.c_str(), jval);
CnfSave();
}
std::string CnfThawString(const std::string &val, const std::string &key) {
struct json_object *jval;
if(json_object_object_get_ex(settings, key.c_str(), &jval))
return json_object_get_string(jval);
return val;
}
static void CnfFreezeWindowPos(Gtk::Window *win, const std::string &key) {
int x, y, w, h;
win->get_position(x, y);
win->get_size(w, h);
CnfFreezeInt(x, key + "_left");
CnfFreezeInt(y, key + "_top");
CnfFreezeInt(w, key + "_width");
CnfFreezeInt(h, key + "_height");
}
static void CnfThawWindowPos(Gtk::Window *win, const std::string &key) {
int x, y, w, h;
win->get_position(x, y);
win->get_size(w, h);
x = CnfThawInt(x, key + "_left");
y = CnfThawInt(y, key + "_top");
w = CnfThawInt(w, key + "_width");
h = CnfThawInt(h, key + "_height");
win->move(x, y);
win->resize(w, h);
}
/* Timers */
static bool TimerCallback() {
SS.GW.TimerCallback();
SS.TW.TimerCallback();
return false;
}
void SetTimerFor(int milliseconds) {
Glib::signal_timeout().connect(&TimerCallback, milliseconds);
}
static bool AutosaveTimerCallback() {
SS.Autosave();
return false;
}
void SetAutosaveTimerFor(int minutes) {
Glib::signal_timeout().connect(&AutosaveTimerCallback, minutes * 60 * 1000);
}
static bool LaterCallback() {
SS.DoLater();
return false;
}
void ScheduleLater() {
Glib::signal_idle().connect(&LaterCallback);
}
/* Editor overlay */
class EditorOverlay : public Gtk::Fixed {
public:
EditorOverlay(Gtk::Widget &underlay) : _underlay(underlay) {
set_size_request(0, 0);
add(_underlay);
_entry.set_no_show_all(true);
_entry.set_has_frame(false);
add(_entry);
_entry.signal_activate().
connect(sigc::mem_fun(this, &EditorOverlay::on_activate));
}
void start_editing(int x, int y, int font_height, bool is_monospace, int minWidthChars,
const std::string &val) {
Pango::FontDescription font_desc;
font_desc.set_family(is_monospace ? "monospace" : "normal");
font_desc.set_absolute_size(font_height * Pango::SCALE);
_entry.override_font(font_desc);
/* y coordinate denotes baseline */
Pango::FontMetrics font_metrics = get_pango_context()->get_metrics(font_desc);
y -= font_metrics.get_ascent() / Pango::SCALE;
Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
layout->set_font_description(font_desc);
layout->set_text(val + " "); /* avoid scrolling */
int width = layout->get_logical_extents().get_width();
Gtk::Border border = _entry.get_style_context()->get_padding();
move(_entry, x - border.get_left(), y - border.get_top());
_entry.set_width_chars(minWidthChars);
_entry.set_size_request(width / Pango::SCALE, -1);
_entry.set_text(val);
if(!_entry.is_visible()) {
_entry.show();
_entry.grab_focus();
_entry.add_modal_grab();
}
}
void stop_editing() {
if(_entry.is_visible())
_entry.remove_modal_grab();
_entry.hide();
}
bool is_editing() const {
return _entry.is_visible();
}
sigc::signal<void, Glib::ustring> signal_editing_done() {
return _signal_editing_done;
}
Gtk::Entry &get_entry() {
return _entry;
}
protected:
bool on_key_press_event(GdkEventKey *event) override {
if(event->keyval == GDK_KEY_Escape) {
stop_editing();
return true;
}
return false;
}
void on_size_allocate(Gtk::Allocation& allocation) override {
Gtk::Fixed::on_size_allocate(allocation);
_underlay.size_allocate(allocation);
}
void on_activate() {
_signal_editing_done(_entry.get_text());
}
private:
Gtk::Widget &_underlay;
Gtk::Entry _entry;
sigc::signal<void, Glib::ustring> _signal_editing_done;
};
/* Graphics window */
double DeltaYOfScrollEvent(GdkEventScroll *event) {
double delta_y = event->delta_y;
if(delta_y == 0) {
switch(event->direction) {
case GDK_SCROLL_UP:
delta_y = -1;
break;
case GDK_SCROLL_DOWN:
delta_y = 1;
break;
default:
/* do nothing */
return false;
}
}
return delta_y;
}
class GraphicsWidget : public Gtk::GLArea {
public:
GraphicsWidget() {
set_events(Gdk::POINTER_MOTION_MASK |
Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON_MOTION_MASK |
Gdk::SCROLL_MASK |
Gdk::LEAVE_NOTIFY_MASK);
set_has_alpha(true);
set_has_depth_buffer(true);
}
protected:
void on_resize(int width, int height) override {
_w = width;
_h = height;
}
bool on_render(const Glib::RefPtr<Gdk::GLContext> &context) override {
SS.GW.Paint();
return true;
}
bool on_motion_notify_event(GdkEventMotion *event) override {
int x, y;
ij_to_xy(event->x, event->y, x, y);
SS.GW.MouseMoved(x, y,
event->state & GDK_BUTTON1_MASK,
event->state & GDK_BUTTON2_MASK,
event->state & GDK_BUTTON3_MASK,
event->state & GDK_SHIFT_MASK,
event->state & GDK_CONTROL_MASK);
return true;
}
bool on_button_press_event(GdkEventButton *event) override {
int x, y;
ij_to_xy(event->x, event->y, x, y);
switch(event->button) {
case 1:
if(event->type == GDK_BUTTON_PRESS)
SS.GW.MouseLeftDown(x, y);
else if(event->type == GDK_2BUTTON_PRESS)
SS.GW.MouseLeftDoubleClick(x, y);
break;
case 2:
case 3:
SS.GW.MouseMiddleOrRightDown(x, y);
break;
}
return true;
}
bool on_button_release_event(GdkEventButton *event) override {
int x, y;
ij_to_xy(event->x, event->y, x, y);
switch(event->button) {
case 1:
SS.GW.MouseLeftUp(x, y);
break;
case 3:
SS.GW.MouseRightUp(x, y);
break;
}
return true;
}
bool on_scroll_event(GdkEventScroll *event) override {
int x, y;
ij_to_xy(event->x, event->y, x, y);
SS.GW.MouseScroll(x, y, (int)-DeltaYOfScrollEvent(event));
return true;
}
bool on_leave_notify_event (GdkEventCrossing *) override {
SS.GW.MouseLeave();
return true;
}
private:
int _w, _h;
void ij_to_xy(double i, double j, int &x, int &y) {
// Convert to xy (vs. ij) style coordinates,
// with (0, 0) at center
x = (int)(i * get_scale_factor()) - _w / 2;
y = _h / 2 - (int)(j * get_scale_factor());
}
};
class GraphicsWindowGtk : public Gtk::Window {
public:
2016-04-24 08:19:04 +08:00
GraphicsWindowGtk() : _overlay(_widget), _is_fullscreen(false) {
set_default_size(900, 600);
_box.pack_start(_menubar, false, true);
_box.pack_start(_overlay, true, true);
add(_box);
_overlay.signal_editing_done().
connect(sigc::mem_fun(this, &GraphicsWindowGtk::on_editing_done));
}
GraphicsWidget &get_widget() {
return _widget;
}
EditorOverlay &get_overlay() {
return _overlay;
}
Gtk::MenuBar &get_menubar() {
return _menubar;
}
bool is_fullscreen() const {
return _is_fullscreen;
}
bool emulate_key_press(GdkEventKey *event) {
return on_key_press_event(event);
}
protected:
void on_show() override {
Gtk::Window::on_show();
CnfThawWindowPos(this, "GraphicsWindow");
}
void on_hide() override {
CnfFreezeWindowPos(this, "GraphicsWindow");
Gtk::Window::on_hide();
}
bool on_delete_event(GdkEventAny *) override {
if(!SS.OkayToStartNewFile()) return true;
SS.Exit();
return true;
}
bool on_window_state_event(GdkEventWindowState *event) override {
_is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
/* The event arrives too late for the caller of ToggleFullScreen
to notice state change; and it's possible that the WM will
refuse our request, so we can't just toggle the saved state */
SS.GW.EnsureValidActives();
return Gtk::Window::on_window_state_event(event);
}
bool on_key_press_event(GdkEventKey *event) override {
int chr;
switch(event->keyval) {
case GDK_KEY_Escape:
chr = GraphicsWindow::ESCAPE_KEY;
break;
case GDK_KEY_Delete:
chr = GraphicsWindow::DELETE_KEY;
break;
case GDK_KEY_Tab:
chr = '\t';
break;
case GDK_KEY_BackSpace:
case GDK_KEY_Back:
chr = '\b';
break;
case GDK_KEY_KP_Decimal:
chr = '.';
break;
default:
if(event->keyval >= GDK_KEY_F1 && event->keyval <= GDK_KEY_F12) {
chr = GraphicsWindow::FUNCTION_KEY_BASE + (event->keyval - GDK_KEY_F1);
} else {
chr = gdk_keyval_to_unicode(event->keyval);
}
}
if(event->state & GDK_SHIFT_MASK){
chr |= GraphicsWindow::SHIFT_MASK;
}
if(event->state & GDK_CONTROL_MASK) {
chr |= GraphicsWindow::CTRL_MASK;
}
if(chr && SS.GW.KeyDown(chr)) {
return true;
}
if(chr == '\t') {
// Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=123994.
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
GraphicsWindow::MenuView(Command::SHOW_TEXT_WND);
return true;
}
return Gtk::Window::on_key_press_event(event);
}
void on_editing_done(Glib::ustring value) {
SS.GW.EditControlDone(value.c_str());
}
private:
GraphicsWidget _widget;
EditorOverlay _overlay;
Gtk::MenuBar _menubar;
Gtk::VBox _box;
bool _is_fullscreen;
};
std::unique_ptr<GraphicsWindowGtk> GW;
void GetGraphicsWindowSize(int *w, int *h) {
Gdk::Rectangle allocation = GW->get_widget().get_allocation();
*w = allocation.get_width() * GW->get_scale_factor();
*h = allocation.get_height() * GW->get_scale_factor();
}
void InvalidateGraphics(void) {
GW->get_widget().queue_draw();
}
void PaintGraphics(void) {
GW->get_widget().queue_draw();
/* Process animation */
Glib::MainContext::get_default()->iteration(false);
}
void SetCurrentFilename(const std::string &filename) {
GW->set_title(Title(filename.empty() ? C_("title", "(new sketch)") : filename.c_str()));
}
void ToggleFullScreen(void) {
if(GW->is_fullscreen())
GW->unfullscreen();
else
GW->fullscreen();
}
bool FullScreenIsActive(void) {
return GW->is_fullscreen();
}
void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars,
const std::string &val) {
Gdk::Rectangle rect = GW->get_widget().get_allocation();
// Convert to ij (vs. xy) style coordinates,
// and compensate for the input widget height due to inverse coord
int i, j;
i = x + rect.get_width() / 2;
j = -y + rect.get_height() / 2;
GW->get_overlay().start_editing(i, j, fontHeight, /*is_monospace=*/false, minWidthChars, val);
}
void HideGraphicsEditControl(void) {
GW->get_overlay().stop_editing();
}
bool GraphicsEditControlIsVisible(void) {
return GW->get_overlay().is_editing();
}
/* TODO: removing menubar breaks accelerators. */
void ToggleMenuBar(void) {
GW->get_menubar().set_visible(!GW->get_menubar().is_visible());
}
bool MenuBarIsVisible(void) {
return GW->get_menubar().is_visible();
}
/* Context menus */
class ContextMenuItem : public Gtk::MenuItem {
public:
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
static ContextCommand choice;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
ContextMenuItem(const Glib::ustring &label, ContextCommand cmd, bool mnemonic=false) :
Gtk::MenuItem(label, mnemonic), _cmd(cmd) {
}
protected:
void on_activate() override {
Gtk::MenuItem::on_activate();
if(has_submenu())
return;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
choice = _cmd;
}
/* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=695488.
This is used in addition to on_activate() to catch mouse events.
Without on_activate(), it would be impossible to select a menu item
via keyboard.
This selects the item twice in some cases, but we are idempotent.
*/
bool on_button_press_event(GdkEventButton *event) override {
if(event->button == 1 && event->type == GDK_BUTTON_PRESS) {
on_activate();
return true;
}
return Gtk::MenuItem::on_button_press_event(event);
}
private:
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
ContextCommand _cmd;
};
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
ContextCommand ContextMenuItem::choice = ContextCommand::CANCELLED;
static Gtk::Menu *context_menu = NULL, *context_submenu = NULL;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void AddContextMenuItem(const char *label, ContextCommand cmd) {
Gtk::MenuItem *menu_item;
if(label)
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
menu_item = new ContextMenuItem(label, cmd);
else
menu_item = new Gtk::SeparatorMenuItem();
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
if(cmd == ContextCommand::SUBMENU) {
menu_item->set_submenu(*context_submenu);
context_submenu = NULL;
}
if(context_submenu) {
context_submenu->append(*menu_item);
} else {
if(!context_menu)
context_menu = new Gtk::Menu;
context_menu->append(*menu_item);
}
}
void CreateContextSubmenu(void) {
ssassert(!context_submenu, "Unexpected nested submenu");
context_submenu = new Gtk::Menu;
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
ContextCommand ShowContextMenu(void) {
if(!context_menu)
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
return ContextCommand::CANCELLED;
Glib::RefPtr<Glib::MainLoop> loop = Glib::MainLoop::create();
context_menu->signal_deactivate().
connect(sigc::mem_fun(loop.operator->(), &Glib::MainLoop::quit));
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
ContextMenuItem::choice = ContextCommand::CANCELLED;
context_menu->show_all();
context_menu->popup(3, GDK_CURRENT_TIME);
loop->run();
delete context_menu;
context_menu = NULL;
return ContextMenuItem::choice;
}
/* Main menu */
template<class MenuItem> class MainMenuItem : public MenuItem {
public:
MainMenuItem(const GraphicsWindow::MenuEntry &entry) :
MenuItem(), _entry(entry), _synthetic(false) {
Glib::ustring label(_entry.label);
for(size_t i = 0; i < label.length(); i++) {
if(label[i] == '&')
label.replace(i, 1, "_");
}
guint accel_key = 0;
Gdk::ModifierType accel_mods = Gdk::ModifierType();
switch(_entry.accel) {
case GraphicsWindow::DELETE_KEY:
accel_key = GDK_KEY_Delete;
break;
case GraphicsWindow::ESCAPE_KEY:
accel_key = GDK_KEY_Escape;
break;
case '\t':
accel_key = GDK_KEY_Tab;
break;
default:
accel_key = _entry.accel & ~(GraphicsWindow::SHIFT_MASK | GraphicsWindow::CTRL_MASK);
if(accel_key > GraphicsWindow::FUNCTION_KEY_BASE &&
accel_key <= GraphicsWindow::FUNCTION_KEY_BASE + 12)
accel_key = GDK_KEY_F1 + (accel_key - GraphicsWindow::FUNCTION_KEY_BASE - 1);
else
accel_key = gdk_unicode_to_keyval(accel_key);
if(_entry.accel & GraphicsWindow::SHIFT_MASK)
accel_mods |= Gdk::SHIFT_MASK;
if(_entry.accel & GraphicsWindow::CTRL_MASK)
accel_mods |= Gdk::CONTROL_MASK;
}
MenuItem::set_label(label);
MenuItem::set_use_underline(true);
if(!(accel_key & 0x01000000))
MenuItem::set_accel_key(Gtk::AccelKey(accel_key, accel_mods));
}
void set_active(bool checked) {
if(MenuItem::get_active() == checked)
return;
_synthetic = true;
MenuItem::set_active(checked);
}
protected:
void on_activate() override {
MenuItem::on_activate();
if(_synthetic)
_synthetic = false;
else if(!MenuItem::has_submenu() && _entry.fn)
_entry.fn(_entry.id);
}
private:
2017-01-05 18:39:08 +08:00
const GraphicsWindow::MenuEntry _entry;
bool _synthetic;
};
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
static std::map<uint32_t, Gtk::MenuItem *> main_menu_items;
static void InitMainMenu(Gtk::MenuShell *menu_shell) {
Gtk::MenuItem *menu_item = NULL;
Gtk::MenuShell *levels[5] = {menu_shell, 0};
const GraphicsWindow::MenuEntry *entry = &GraphicsWindow::menu[0];
int current_level = 0;
while(entry->level >= 0) {
if(entry->level > current_level) {
Gtk::Menu *menu = new Gtk::Menu;
menu_item->set_submenu(*menu);
ssassert((unsigned)entry->level < sizeof(levels) / sizeof(levels[0]),
"Unexpected depth of menu nesting");
levels[entry->level] = menu;
}
current_level = entry->level;
if(entry->label) {
2017-01-05 18:39:08 +08:00
GraphicsWindow::MenuEntry localizedEntry = *entry;
localizedEntry.label = Translate(entry->label).c_str();
switch(entry->kind) {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
case GraphicsWindow::MenuKind::NORMAL:
2017-01-05 18:39:08 +08:00
menu_item = new MainMenuItem<Gtk::MenuItem>(localizedEntry);
break;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
case GraphicsWindow::MenuKind::CHECK:
2017-01-05 18:39:08 +08:00
menu_item = new MainMenuItem<Gtk::CheckMenuItem>(localizedEntry);
break;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
case GraphicsWindow::MenuKind::RADIO:
MainMenuItem<Gtk::CheckMenuItem> *radio_item =
2017-01-05 18:39:08 +08:00
new MainMenuItem<Gtk::CheckMenuItem>(localizedEntry);
radio_item->set_draw_as_radio(true);
menu_item = radio_item;
break;
}
} else {
menu_item = new Gtk::SeparatorMenuItem();
}
2017-01-05 18:39:08 +08:00
if(entry->id == Command::LOCALE) {
Gtk::Menu *menu = new Gtk::Menu;
menu_item->set_submenu(*menu);
size_t i = 0;
for(auto locale : Locales()) {
GraphicsWindow::MenuEntry localeEntry = {};
localeEntry.label = locale.displayName.c_str();
localeEntry.id = (Command)((uint32_t)Command::LOCALE + i++);
localeEntry.fn = entry->fn;
menu->append(*new MainMenuItem<Gtk::MenuItem>(localeEntry));
}
}
levels[entry->level]->append(*menu_item);
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
main_menu_items[(uint32_t)entry->id] = menu_item;
++entry;
}
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void EnableMenuByCmd(Command cmd, bool enabled) {
main_menu_items[(uint32_t)cmd]->set_sensitive(enabled);
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void CheckMenuByCmd(Command cmd, bool checked) {
((MainMenuItem<Gtk::CheckMenuItem>*)main_menu_items[(uint32_t)cmd])->set_active(checked);
}
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
void RadioMenuByCmd(Command cmd, bool selected) {
SolveSpace::CheckMenuByCmd(cmd, selected);
}
class RecentMenuItem : public Gtk::MenuItem {
public:
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
RecentMenuItem(const Glib::ustring& label, uint32_t cmd) :
MenuItem(label), _cmd(cmd) {
}
protected:
void on_activate() override {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
if(_cmd >= (uint32_t)Command::RECENT_OPEN &&
_cmd < ((uint32_t)Command::RECENT_OPEN + MAX_RECENT)) {
SolveSpaceUI::MenuFile((Command)_cmd);
} else if(_cmd >= (uint32_t)Command::RECENT_LINK &&
_cmd < ((uint32_t)Command::RECENT_LINK + MAX_RECENT)) {
Group::MenuGroup((Command)_cmd);
}
}
private:
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
uint32_t _cmd;
};
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
static void RefreshRecentMenu(Command cmd, Command base) {
Gtk::MenuItem *recent = static_cast<Gtk::MenuItem*>(main_menu_items[(uint32_t)cmd]);
recent->unset_submenu();
Gtk::Menu *menu = new Gtk::Menu;
recent->set_submenu(*menu);
if(RecentFile[0].empty()) {
Gtk::MenuItem *placeholder = new Gtk::MenuItem(_("(no recent files)"));
placeholder->set_sensitive(false);
menu->append(*placeholder);
} else {
for(size_t i = 0; i < MAX_RECENT; i++) {
if(RecentFile[i].empty())
break;
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
RecentMenuItem *item = new RecentMenuItem(RecentFile[i], (uint32_t)base + i);
menu->append(*item);
}
}
menu->show_all();
}
void RefreshRecentMenus(void) {
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
RefreshRecentMenu(Command::OPEN_RECENT, Command::RECENT_OPEN);
RefreshRecentMenu(Command::GROUP_RECENT, Command::RECENT_LINK);
}
/* Save/load */
static std::string ConvertFilters(std::string active, const FileFilter ssFilters[],
Gtk::FileChooser *chooser) {
for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) {
Glib::RefPtr<Gtk::FileFilter> filter = Gtk::FileFilter::create();
filter->set_name(Translate(ssFilter->name));
bool is_active = false;
std::string desc = "";
for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) {
std::string pattern = "*." + std::string(*ssPattern);
filter->add_pattern(pattern);
filter->add_pattern(Glib::ustring(pattern).uppercase());
if(active == "")
active = pattern.substr(2);
if("*." + active == pattern)
is_active = true;
if(desc == "")
desc = pattern;
else
desc += ", " + pattern;
}
filter->set_name(filter->get_name() + " (" + desc + ")");
2016-05-10 21:10:41 +08:00
chooser->add_filter(filter);
if(is_active)
chooser->set_filter(filter);
}
return active;
}
bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty,
const FileFilter filters[]) {
Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Open File")));
chooser.set_filename(*filename);
chooser.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
chooser.add_button(_("_Open"), Gtk::RESPONSE_OK);
chooser.set_current_folder(CnfThawString("", "FileChooserPath"));
ConvertFilters(activeOrEmpty, filters, &chooser);
if(chooser.run() == Gtk::RESPONSE_OK) {
CnfFreezeString(chooser.get_current_folder(), "FileChooserPath");
*filename = chooser.get_filename();
return true;
} else {
return false;
}
}
static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser)
{
/* Extract the pattern from the filter. GtkFileFilter doesn't provide
any way to list the patterns, so we extract it from the filter name.
Gross. */
std::string filter_name = chooser->get_filter()->get_name();
int lparen = filter_name.rfind('(') + 1;
int rdelim = filter_name.find(',', lparen);
if(rdelim < 0)
rdelim = filter_name.find(')', lparen);
ssassert(lparen > 0 && rdelim > 0, "Expected to find a parenthesized extension");
std::string extension = filter_name.substr(lparen, rdelim - lparen);
if(extension == "*")
return;
if(extension.length() > 2 && extension.substr(0, 2) == "*.")
extension = extension.substr(2, extension.length() - 2);
std::string basename = Basename(chooser->get_filename());
int dot = basename.rfind('.');
if(dot >= 0) {
basename.replace(dot + 1, basename.length() - dot - 1, extension);
chooser->set_current_name(basename);
} else {
chooser->set_current_name(basename + "." + extension);
}
}
bool GetSaveFile(std::string *filename, const std::string &defExtension,
const FileFilter filters[]) {
Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Save File")),
Gtk::FILE_CHOOSER_ACTION_SAVE);
chooser.set_do_overwrite_confirmation(true);
chooser.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
chooser.add_button(C_("button", "_Save"), Gtk::RESPONSE_OK);
std::string activeExtension = ConvertFilters(defExtension, filters, &chooser);
if(filename->empty()) {
chooser.set_current_folder(CnfThawString("", "FileChooserPath"));
chooser.set_current_name(std::string(_("untitled")) + "." + activeExtension);
} else {
chooser.set_current_folder(Dirname(*filename));
chooser.set_current_name(Basename(*filename, /*stripExtension=*/true) +
"." + activeExtension);
}
/* Gtk's dialog doesn't change the extension when you change the filter,
and makes it extremely hard to do so. Gtk is garbage. */
chooser.property_filter().signal_changed().
connect(sigc::bind(sigc::ptr_fun(&ChooserFilterChanged), &chooser));
if(chooser.run() == Gtk::RESPONSE_OK) {
CnfFreezeString(chooser.get_current_folder(), "FileChooserPath");
*filename = chooser.get_filename();
return true;
} else {
return false;
}
}
DialogChoice SaveFileYesNoCancel(void) {
Glib::ustring message =
_("The file has changed since it was last saved.\n\n"
"Do you want to save the changes?");
Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION,
Gtk::BUTTONS_NONE, /*is_modal*/ true);
dialog.set_title(Title(C_("title", "Modified File")));
dialog.add_button(C_("button", "_Save"), Gtk::RESPONSE_YES);
dialog.add_button(C_("button", "Do_n't Save"), Gtk::RESPONSE_NO);
dialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
switch(dialog.run()) {
case Gtk::RESPONSE_YES:
return DIALOG_YES;
case Gtk::RESPONSE_NO:
return DIALOG_NO;
case Gtk::RESPONSE_CANCEL:
default:
return DIALOG_CANCEL;
}
}
DialogChoice LoadAutosaveYesNo(void) {
Glib::ustring message =
_("An autosave file is availible for this project.\n\n"
"Do you want to load the autosave file instead?");
Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION,
Gtk::BUTTONS_NONE, /*is_modal*/ true);
dialog.set_title(Title(C_("title", "Autosave Available")));
dialog.add_button(C_("button", "_Load autosave"), Gtk::RESPONSE_YES);
dialog.add_button(C_("button", "Do_n't Load"), Gtk::RESPONSE_NO);
switch(dialog.run()) {
case Gtk::RESPONSE_YES:
return DIALOG_YES;
case Gtk::RESPONSE_NO:
default:
return DIALOG_NO;
}
}
DialogChoice LocateImportedFileYesNoCancel(const std::string &filename,
bool canCancel) {
Glib::ustring message =
"The linked file " + filename + " is not present.\n\n"
"Do you want to locate it manually?\n\n"
"If you select \"No\", any geometry that depends on "
"the missing file will be removed.";
Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION,
Gtk::BUTTONS_NONE, /*is_modal*/ true);
dialog.set_title(Title(C_("title", "Missing File")));
dialog.add_button(C_("button", "_Yes"), Gtk::RESPONSE_YES);
dialog.add_button(C_("button", "_No"), Gtk::RESPONSE_NO);
if(canCancel)
dialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
switch(dialog.run()) {
case Gtk::RESPONSE_YES:
return DIALOG_YES;
case Gtk::RESPONSE_NO:
return DIALOG_NO;
case Gtk::RESPONSE_CANCEL:
default:
return DIALOG_CANCEL;
}
}
/* Text window */
class TextWidget : public Gtk::GLArea {
public:
TextWidget(Glib::RefPtr<Gtk::Adjustment> adjustment) : _adjustment(adjustment) {
set_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK |
Gdk::LEAVE_NOTIFY_MASK);
set_has_alpha(true);
set_has_depth_buffer(true);
}
void set_cursor_hand(bool is_hand) {
Glib::RefPtr<Gdk::Window> gdkwin = get_window();
if(gdkwin) { // returns NULL if not realized
Gdk::CursorType type = is_hand ? Gdk::HAND1 : Gdk::ARROW;
gdkwin->set_cursor(Gdk::Cursor::create(type));
}
}
protected:
bool on_render(const Glib::RefPtr<Gdk::GLContext> &context) override {
SS.TW.Paint();
return true;
}
bool on_motion_notify_event(GdkEventMotion *event) override {
SS.TW.MouseEvent(/*leftClick*/ false,
/*leftDown*/ event->state & GDK_BUTTON1_MASK,
event->x * get_scale_factor(),
event->y * get_scale_factor());
return true;
}
bool on_button_press_event(GdkEventButton *event) override {
SS.TW.MouseEvent(/*leftClick*/ event->type == GDK_BUTTON_PRESS,
/*leftDown*/ event->state & GDK_BUTTON1_MASK,
event->x * get_scale_factor(),
event->y * get_scale_factor());
return true;
}
bool on_scroll_event(GdkEventScroll *event) override {
_adjustment->set_value(_adjustment->get_value() +
DeltaYOfScrollEvent(event) * _adjustment->get_page_increment());
return true;
}
bool on_leave_notify_event (GdkEventCrossing *) override {
SS.TW.MouseLeave();
return true;
}
private:
Glib::RefPtr<Gtk::Adjustment> _adjustment;
};
class TextWindowGtk : public Gtk::Window {
public:
TextWindowGtk() : _scrollbar(), _widget(_scrollbar.get_adjustment()),
2015-12-28 21:36:31 +08:00
_overlay(_widget), _box() {
set_type_hint(Gdk::WINDOW_TYPE_HINT_UTILITY);
set_skip_taskbar_hint(true);
set_skip_pager_hint(true);
set_default_size(420, 300);
_box.pack_start(_overlay, true, true);
_box.pack_start(_scrollbar, false, true);
add(_box);
_scrollbar.get_adjustment()->signal_value_changed().
connect(sigc::mem_fun(this, &TextWindowGtk::on_scrollbar_value_changed));
_overlay.signal_editing_done().
connect(sigc::mem_fun(this, &TextWindowGtk::on_editing_done));
_overlay.get_entry().signal_motion_notify_event().
connect(sigc::mem_fun(this, &TextWindowGtk::on_editor_motion_notify_event));
_overlay.get_entry().signal_button_press_event().
connect(sigc::mem_fun(this, &TextWindowGtk::on_editor_button_press_event));
}
Gtk::VScrollbar &get_scrollbar() {
return _scrollbar;
}
TextWidget &get_widget() {
return _widget;
}
EditorOverlay &get_overlay() {
return _overlay;
}
protected:
void on_show() override {
Gtk::Window::on_show();
CnfThawWindowPos(this, "TextWindow");
}
void on_hide() override {
CnfFreezeWindowPos(this, "TextWindow");
Gtk::Window::on_hide();
}
bool on_key_press_event(GdkEventKey *event) override {
if(GW->emulate_key_press(event)) {
return true;
}
return Gtk::Window::on_key_press_event(event);
}
bool on_delete_event(GdkEventAny *) override {
/* trigger the action and ignore the request */
Convert all enumerations to use `enum class`. Specifically, take the old code that looks like this: class Foo { enum { X = 1, Y = 2 }; int kind; } ... foo.kind = Foo::X; ... and convert it to this: class Foo { enum class Kind : uint32_t { X = 1, Y = 2 }; Kind kind; } ... foo.kind = Foo::Kind::X; (In some cases the enumeration would not be in the class namespace, such as when it is generally useful.) The benefits are as follows: * The type of the field gives a clear indication of intent, both to humans and tools (such as binding generators). * The compiler is able to automatically warn when a switch is not exhaustive; but this is currently suppressed by the default: ssassert(false, ...) idiom. * Integers and plain enums are weakly type checked: they implicitly convert into each other. This can hide bugs where type conversion is performed but not intended. Enum classes are strongly type checked. * Plain enums pollute parent namespaces; enum classes do not. Almost every defined enum we have already has a kind of ad-hoc namespacing via `NAMESPACE_`, which is now explicit. * Plain enums do not have a well-defined ABI size, which is important for bindings. Enum classes can have it, if specified. We specify the base type for all enums as uint32_t, which is a safe choice and allows us to not change the numeric values of any variants. This commit introduces absolutely no functional change to the code, just renaming and change of types. It handles almost all cases, except GraphicsWindow::pending.operation, which needs minor functional change.
2016-05-20 16:31:20 +08:00
GraphicsWindow::MenuView(Command::SHOW_TEXT_WND);
return false;
}
void on_scrollbar_value_changed() {
SS.TW.ScrollbarEvent((int)_scrollbar.get_adjustment()->get_value());
}
void on_editing_done(Glib::ustring value) {
SS.TW.EditControlDone(value.c_str());
}
bool on_editor_motion_notify_event(GdkEventMotion *event) {
return _widget.event((GdkEvent*) event);
}
bool on_editor_button_press_event(GdkEventButton *event) {
return _widget.event((GdkEvent*) event);
}
private:
Gtk::VScrollbar _scrollbar;
TextWidget _widget;
EditorOverlay _overlay;
Gtk::HBox _box;
};
std::unique_ptr<TextWindowGtk> TW;
void ShowTextWindow(bool visible) {
if(visible)
TW->show();
else
TW->hide();
}
void GetTextWindowSize(int *w, int *h) {
Gdk::Rectangle allocation = TW->get_widget().get_allocation();
*w = allocation.get_width() * TW->get_scale_factor();
*h = allocation.get_height() * TW->get_scale_factor();
}
double GetScreenDpi() {
return Gdk::Screen::get_default()->get_resolution();
}
void InvalidateText(void) {
TW->get_widget().queue_draw();
}
void MoveTextScrollbarTo(int pos, int maxPos, int page) {
TW->get_scrollbar().get_adjustment()->configure(pos, 0, maxPos, 1, 10, page);
}
void SetMousePointerToHand(bool is_hand) {
TW->get_widget().set_cursor_hand(is_hand);
}
void ShowTextEditControl(int x, int y, const std::string &val) {
TW->get_overlay().start_editing(x, y, TextWindow::CHAR_HEIGHT, /*is_monospace=*/true, 30, val);
}
void HideTextEditControl(void) {
TW->get_overlay().stop_editing();
}
bool TextEditControlIsVisible(void) {
return TW->get_overlay().is_editing();
}
/* Miscellanea */
void DoMessageBox(const char *message, int rows, int cols, bool error) {
Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true,
error ? Gtk::MESSAGE_ERROR : Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK,
/*is_modal*/ true);
dialog.set_title(error ?
Title(C_("title", "Error")) : Title(C_("title", "Message")));
dialog.run();
}
void OpenWebsite(const char *url) {
gtk_show_uri(Gdk::Screen::get_default()->gobj(), url, GDK_CURRENT_TIME, NULL);
}
/* fontconfig is already initialized by GTK */
std::vector<std::string> GetFontFiles() {
std::vector<std::string> fonts;
FcPattern *pat = FcPatternCreate();
FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0);
FcFontSet *fs = FcFontList(0, pat, os);
for(int i = 0; i < fs->nfont; i++) {
FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}");
std::string filename = (char*) filenameFC;
fonts.push_back(filename);
FcStrFree(filenameFC);
}
FcFontSetDestroy(fs);
FcObjectSetDestroy(os);
FcPatternDestroy(pat);
return fonts;
}
Implement a resource system. Currently, icons, fonts, etc are converted to C structures at compile time and are hardcoded to the binary. This presents several problems: * Cross-compilation is complicated. Right now, it is necessary to be able to run executables for the target platform; this happens to work with wine-binfmt installed, but is rather ugly. * Icons can only have one resolution. On OS X, modern software is expected to take advantage of high-DPI ("Retina") screens and use so-called @2x assets when ran in high-DPI mode. * Localization is complicated. Win32 and OS X provide built-in support for loading the resource appropriate for the user's locale. * Embedding strings can only be done as raw strings, using C++'s R"(...)" literals. This precludes embedding sizable strings, e.g. JavaScript libraries as used in Three.js export, and makes git history less useful. Not embedding the libraries means we have to rely on external CDNs, which requires an Internet connection and adds a glaring point of failure. * Linux distribution guidelines are violated. All architecture- independent data, especially large data such as fonts, is expected to be in /usr/share, not in the binary. * Customization is impossible without recompilation. Minor modifications like adding a few missing vector font characters or adjusting localization require a complete development environment, which is unreasonable to expect from users of a mechanical CAD. As such, this commit adds a resource system that bundles (and sometimes builds) resources with the executable. Where they go is platform-dependent: * on Win32: into resources of the executable, which allows us to keep distributing one file; * on OS X: into the app bundle; * on other *nix: into /usr/share/solvespace/ or ../res/ (relative to the executable path), the latter allowing us to run freshly built executables without installation. It also subsides the platform-specific resources that are in src/. The resource system is not yet used for anything; this will be added in later commits.
2016-04-21 23:54:18 +08:00
/* Space Navigator support */
#ifdef HAVE_SPACEWARE
static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gxevent, GdkEvent *, gpointer) {
XEvent *xevent = (XEvent*) gxevent;
spnav_event sev;
if(!spnav_x11_event(xevent, &sev))
return GDK_FILTER_CONTINUE;
switch(sev.type) {
case SPNAV_EVENT_MOTION:
SS.GW.SpaceNavigatorMoved(
(double)sev.motion.x,
(double)sev.motion.y,
(double)sev.motion.z * -1.0,
(double)sev.motion.rx * 0.001,
(double)sev.motion.ry * 0.001,
(double)sev.motion.rz * -0.001,
xevent->xmotion.state & ShiftMask);
break;
case SPNAV_EVENT_BUTTON:
if(!sev.button.press && sev.button.bnum == 0) {
SS.GW.SpaceNavigatorButtonUp();
}
break;
}
return GDK_FILTER_REMOVE;
}
#endif
/* Application lifecycle */
2017-01-05 18:39:08 +08:00
void RefreshLocale() {
SS.UpdateWindowTitle();
2017-01-05 18:39:08 +08:00
for(auto menu : GW->get_menubar().get_children()) {
GW->get_menubar().remove(*menu);
}
InitMainMenu(&GW->get_menubar());
RefreshRecentMenus();
2017-01-05 18:39:08 +08:00
GW->get_menubar().show_all();
GW->get_menubar().accelerate(*GW);
GW->get_menubar().accelerate(*TW);
TW->set_title(Title(C_("title", "Property Browser")));
2017-01-05 18:39:08 +08:00
}
void ExitNow() {
GW->hide();
TW->hide();
}
};
int main(int argc, char** argv) {
/* It would in principle be possible to judiciously use
Glib::filename_{from,to}_utf8, but it's not really worth
the effort.
The setlocale() call is necessary for Glib::get_charset()
to detect the system character set; otherwise it thinks
it is always ANSI_X3.4-1968.
We set it back to C after all. */
setlocale(LC_ALL, "");
if(!Glib::get_charset()) {
dbp("Sorry, only UTF-8 locales are supported.");
return 1;
}
setlocale(LC_ALL, "C");
/* If we don't do this, gtk_init will set the C standard library
locale, and printf will format floats using ",". We will then
fail to parse these. Also, many text window lines will become
ambiguous. */
gtk_disable_setlocale();
Gtk::Main main(argc, argv);
#ifdef HAVE_SPACEWARE
gdk_window_add_filter(NULL, GdkSpnavFilter, NULL);
#endif
CnfLoad();
Abstract all (ex-OpenGL) drawing operations into a Canvas interface. This has several desirable consequences: * It is now possible to port SolveSpace to a later version of OpenGL, such as OpenGLES 2, so that it runs on platforms that only have that OpenGL version; * The majority of geometry is now rendered without references to the camera in C++ code, so a renderer can now submit it to the video card once and re-rasterize with a different projection matrix every time the projection is changed, avoiding expensive reuploads; * The DOGD (draw or get distance) interface is now a straightforward Canvas implementation; * There are no more direct references to SS.GW.(projection) in sketch rendering code, which allows rendering to multiple viewports; * There are no more unnecessary framebuffer flips on CPU on Cocoa and GTK; * The platform-dependent GL code is now confined to rendergl1.cpp. * The Microsoft and Apple headers required by it that are prone to identifier conflicts are no longer included globally; * The rendergl1.cpp implementation can now be omitted from compilation to run SolveSpace headless or with a different OpenGL version. Note these implementation details of Canvas: * GetCamera currently always returns a reference to the field `Camera camera;`. This is so that a future renderer that caches geometry in the video memory can define it as asserting, which would provide assurance against code that could accidentally put something projection-dependent in the cache; * Line and triangle rendering is specified through a level of indirection, hStroke and hFill. This is so that a future renderer that batches geometry could cheaply group identical styles. * DrawPixmap and DrawVectorText accept a (o,u,v) and not a matrix. This is so that a future renderer into an output format that uses 2d transforms (e.g. SVG) could easily derive those. Some additional internal changes were required to enable this: * Pixmap is now always passed as std::shared_ptr<{const ,}Pixmap>. This is so that the renderer could cache uploaded textures between API calls, which requires it to capture a (weak) reference. * The PlatformPathEqual function was properly extracted into platform-specific code. This is so that the <windows.h> header could be included only where needed (in platform/w32* as well as rendergl1.cpp). * The SBsp{2,3}::DebugDraw functions were removed. They can be rewritten using the Canvas API if they are ever needed. While no visual changes were originally intended, some minor fixes happened anyway: * The "emphasis" yellow line from top-left corner is now correctly rendered much wider. * The marquee rectangle is now pixel grid aligned. * The hidden entities now do not clobber the depth buffer, removing some minor artifacts. * The workplane "tab" now scales with the font used to render the workplane name. * The workplane name font is now taken from the normals style. * Workplane and constraint line stipple is insignificantly different. This is so that it can reuse the existing stipple codepaths; rendering of workplanes and constraints predates those. Some debug functionality was added: * In graphics window, an fps counter that becomes red when rendering under 60fps is drawn.
2016-05-31 08:55:13 +08:00
auto icon = LoadPng("freedesktop/solvespace-48x48.png");
auto icon_gdk =
Gdk::Pixbuf::create_from_data(&icon->data[0], Gdk::COLORSPACE_RGB,
icon->format == SolveSpace::Pixmap::Format::RGBA, 8,
icon->width, icon->height, icon->stride);
2016-05-19 01:23:14 +08:00
TW.reset(new TextWindowGtk);
GW.reset(new GraphicsWindowGtk);
TW->set_transient_for(*GW);
2016-05-19 01:23:14 +08:00
GW->set_icon(icon_gdk);
TW->set_icon(icon_gdk);
TW->show_all();
GW->show_all();
2017-01-05 18:39:08 +08:00
const char* const* langNames = g_get_language_names();
while(*langNames) {
if(SetLocale(*langNames++)) break;
}
if(!*langNames) {
SetLocale("en_US");
}
2017-01-05 18:39:08 +08:00
#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11)
if(GDK_IS_X11_DISPLAY(Gdk::Display::get_default()->gobj())) {
// We don't care if it can't be opened; just continue without.
spnav_x11_open(gdk_x11_get_default_xdisplay(),
gdk_x11_window_get_xid(GW->get_window()->gobj()));
}
#endif
2015-03-24 14:45:53 +08:00
SS.Init();
if(argc >= 2) {
if(argc > 2) {
dbp("Only the first file passed on command line will be opened.");
}
/* Make sure the argument is valid UTF-8. */
SS.OpenFile(PathFromCurrentDirectory(Glib::ustring(argv[1])));
}
main.run(*GW);
TW.reset();
GW.reset();
SK.Clear();
SS.Clear();
return 0;
}